/* * ***** BEGIN GPL LICENSE BLOCK ***** * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * The Original Code is Copyright (C) 2012 Blender Foundation. * All rights reserved. * * * Contributor(s): Blender Foundation, * Campbell Barton, * Sergey Sharybin * * ***** END GPL LICENSE BLOCK ***** */ /** \file blender/editors/mask/mask_draw.c * \ingroup edmask */ #include "MEM_guardedalloc.h" #include "BLI_utildefines.h" #include "BLI_math.h" #include "BLI_rect.h" #include "BLI_task.h" #include "BLI_listbase.h" #include "BKE_context.h" #include "BKE_mask.h" #include "DNA_mask_types.h" #include "DNA_screen_types.h" #include "DNA_object_types.h" /* SELECT */ #include "DNA_space_types.h" #include "ED_clip.h" #include "ED_mask.h" /* own include */ #include "ED_space_api.h" #include "BIF_glutil.h" #include "GPU_immediate.h" #include "GPU_draw.h" #include "GPU_shader.h" #include "GPU_matrix.h" #include "GPU_state.h" #include "UI_resources.h" #include "UI_view2d.h" #include "mask_intern.h" /* own include */ static void mask_spline_color_get(MaskLayer *masklay, MaskSpline *spline, const bool is_sel, unsigned char r_rgb[4]) { if (is_sel) { if (masklay->act_spline == spline) { r_rgb[0] = r_rgb[1] = r_rgb[2] = 255; } else { r_rgb[0] = 255; r_rgb[1] = r_rgb[2] = 0; } } else { r_rgb[0] = 128; r_rgb[1] = r_rgb[2] = 0; } r_rgb[3] = 255; } static void mask_spline_feather_color_get(MaskLayer *UNUSED(masklay), MaskSpline *UNUSED(spline), const bool is_sel, unsigned char r_rgb[4]) { if (is_sel) { r_rgb[1] = 255; r_rgb[0] = r_rgb[2] = 0; } else { r_rgb[1] = 128; r_rgb[0] = r_rgb[2] = 0; } r_rgb[3] = 255; } static void mask_point_undistort_pos(SpaceClip *sc, float r_co[2], const float co[2]) { BKE_mask_coord_to_movieclip(sc->clip, &sc->user, r_co, co); ED_clip_point_undistorted_pos(sc, r_co, r_co); BKE_mask_coord_from_movieclip(sc->clip, &sc->user, r_co, r_co); } static void draw_single_handle(const MaskLayer *mask_layer, const MaskSplinePoint *point, const eMaskWhichHandle which_handle, const int draw_type, const float handle_size, const float point_pos[2], const float handle_pos[2]) { const BezTriple *bezt = &point->bezt; char handle_type; if (which_handle == MASK_WHICH_HANDLE_STICK || which_handle == MASK_WHICH_HANDLE_LEFT) { handle_type = bezt->h1; } else { handle_type = bezt->h2; } if (handle_type == HD_VECT) { return; } Gwn_VertFormat *format = immVertexFormat(); uint pos = GWN_vertformat_attr_add(format, "pos", GWN_COMP_F32, 2, GWN_FETCH_FLOAT); const unsigned char rgb_gray[4] = {0x60, 0x60, 0x60, 0xff}; immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); immUniformColor3ubv(rgb_gray); /* this could be split into its own loop */ if (draw_type == MASK_DT_OUTLINE) { GPU_line_width(3.0f); immBegin(GWN_PRIM_LINES, 2); immVertex2fv(pos, point_pos); immVertex2fv(pos, handle_pos); immEnd(); } switch (handle_type) { case HD_FREE: immUniformThemeColor(TH_HANDLE_FREE); break; case HD_AUTO: immUniformThemeColor(TH_HANDLE_AUTO); break; case HD_ALIGN: case HD_ALIGN_DOUBLESIDE: immUniformThemeColor(TH_HANDLE_ALIGN); break; } GPU_line_width(1.0f); immBegin(GWN_PRIM_LINES, 2); immVertex2fv(pos, point_pos); immVertex2fv(pos, handle_pos); immEnd(); immUnbindProgram(); /* draw handle points */ immBindBuiltinProgram(GPU_SHADER_2D_POINT_UNIFORM_SIZE_UNIFORM_COLOR_OUTLINE_AA); immUniform1f("size", handle_size); immUniform1f("outlineWidth", 1.5f); float point_color[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; /* active color by default */ if (MASKPOINT_ISSEL_HANDLE(point, which_handle)) { if (point != mask_layer->act_point) { UI_GetThemeColor3fv(TH_HANDLE_VERTEX_SELECT, point_color); } } else { UI_GetThemeColor3fv(TH_HANDLE_VERTEX, point_color); } immUniform4fv("outlineColor", point_color); immUniformColor3fvAlpha(point_color, 0.25f); immBegin(GWN_PRIM_POINTS, 1); immVertex2fv(pos, handle_pos); immEnd(); immUnbindProgram(); } /* return non-zero if spline is selected */ static void draw_spline_points(const bContext *C, MaskLayer *masklay, MaskSpline *spline, const char draw_flag, const char draw_type) { const bool is_spline_sel = (spline->flag & SELECT) && (masklay->restrictflag & MASK_RESTRICT_SELECT) == 0; const bool is_smooth = (draw_flag & MASK_DRAWFLAG_SMOOTH) != 0; unsigned char rgb_spline[4]; MaskSplinePoint *points_array = BKE_mask_spline_point_array(spline); SpaceClip *sc = CTX_wm_space_clip(C); bool undistort = false; int tot_feather_point; float (*feather_points)[2], (*fp)[2]; float min[2], max[2]; if (!spline->tot_point) return; if (sc) undistort = sc->clip && (sc->user.render_flag & MCLIP_PROXY_RENDER_UNDISTORT); /* TODO, add this to sequence editor */ float handle_size = 2.0f * UI_GetThemeValuef(TH_HANDLE_VERTEX_SIZE) * U.pixelsize; mask_spline_color_get(masklay, spline, is_spline_sel, rgb_spline); Gwn_VertFormat *format = immVertexFormat(); uint pos = GWN_vertformat_attr_add(format, "pos", GWN_COMP_F32, 2, GWN_FETCH_FLOAT); immBindBuiltinProgram(GPU_SHADER_2D_POINT_UNIFORM_SIZE_UNIFORM_COLOR_AA); immUniform1f("size", 0.7f * handle_size); /* feather points */ feather_points = fp = BKE_mask_spline_feather_points(spline, &tot_feather_point); for (int i = 0; i < spline->tot_point; i++) { /* watch it! this is intentionally not the deform array, only check for sel */ MaskSplinePoint *point = &spline->points[i]; for (int j = 0; j <= point->tot_uw; j++) { float feather_point[2]; bool sel = false; copy_v2_v2(feather_point, *fp); if (undistort) mask_point_undistort_pos(sc, feather_point, feather_point); if (j == 0) { sel = MASKPOINT_ISSEL_ANY(point); } else { sel = (point->uw[j - 1].flag & SELECT) != 0; } if (sel) { if (point == masklay->act_point) immUniformColor3f(1.0f, 1.0f, 1.0f); else immUniformThemeColor(TH_HANDLE_VERTEX_SELECT); } else { immUniformThemeColor(TH_HANDLE_VERTEX); } immBegin(GWN_PRIM_POINTS, 1); immVertex2fv(pos, feather_point); immEnd(); fp++; } } MEM_freeN(feather_points); immUnbindProgram(); if (is_smooth) { GPU_line_smooth(true); } /* control points */ INIT_MINMAX2(min, max); for (int i = 0; i < spline->tot_point; i++) { /* watch it! this is intentionally not the deform array, only check for sel */ MaskSplinePoint *point = &spline->points[i]; MaskSplinePoint *point_deform = &points_array[i]; BezTriple *bezt = &point_deform->bezt; float vert[2]; copy_v2_v2(vert, bezt->vec[1]); if (undistort) { mask_point_undistort_pos(sc, vert, vert); } /* draw handle segment */ if (BKE_mask_point_handles_mode_get(point) == MASK_HANDLE_MODE_STICK) { float handle[2]; BKE_mask_point_handle(point_deform, MASK_WHICH_HANDLE_STICK, handle); if (undistort) { mask_point_undistort_pos(sc, handle, handle); } draw_single_handle(masklay, point, MASK_WHICH_HANDLE_STICK, draw_type, handle_size, vert, handle); } else { float handle_left[2], handle_right[2]; BKE_mask_point_handle(point_deform, MASK_WHICH_HANDLE_LEFT, handle_left); BKE_mask_point_handle(point_deform, MASK_WHICH_HANDLE_RIGHT, handle_right); if (undistort) { mask_point_undistort_pos(sc, handle_left, handle_left); mask_point_undistort_pos(sc, handle_left, handle_left); } draw_single_handle(masklay, point, MASK_WHICH_HANDLE_LEFT, draw_type, handle_size, vert, handle_left); draw_single_handle(masklay, point, MASK_WHICH_HANDLE_RIGHT, draw_type, handle_size, vert, handle_right); } /* bind program in loop so it does not interfere with draw_single_handle */ immBindBuiltinProgram(GPU_SHADER_2D_POINT_UNIFORM_SIZE_UNIFORM_COLOR_AA); /* draw CV point */ if (MASKPOINT_ISSEL_KNOT(point)) { if (point == masklay->act_point) immUniformColor3f(1.0f, 1.0f, 1.0f); else immUniformThemeColor(TH_HANDLE_VERTEX_SELECT); } else immUniformThemeColor(TH_HANDLE_VERTEX); immBegin(GWN_PRIM_POINTS, 1); immVertex2fv(pos, vert); immEnd(); immUnbindProgram(); minmax_v2v2_v2(min, max, vert); } if (is_smooth) { GPU_line_smooth(false); } if (is_spline_sel) { float x = (min[0] + max[0]) * 0.5f; float y = (min[1] + max[1]) * 0.5f; immBindBuiltinProgram(GPU_SHADER_2D_POINT_UNIFORM_SIZE_UNIFORM_COLOR_OUTLINE_AA); immUniform1f("outlineWidth", 1.5f); if (masklay->act_spline == spline) { immUniformColor3f(1.0f, 1.0f, 1.0f); } else { immUniformColor3f(1.0f, 1.0f, 0.0f); } immUniform4f("outlineColor", 0.0f, 0.0f, 0.0f, 1.0f); immUniform1f("size", 12.0f); immBegin(GWN_PRIM_POINTS, 1); immVertex2f(pos, x, y); immEnd(); immUnbindProgram(); } } static void mask_color_active_tint(unsigned char r_rgb[4], const unsigned char rgb[4], const bool is_active) { if (!is_active) { r_rgb[0] = (unsigned char)((((int)(rgb[0])) + 128) / 2); r_rgb[1] = (unsigned char)((((int)(rgb[1])) + 128) / 2); r_rgb[2] = (unsigned char)((((int)(rgb[2])) + 128) / 2); r_rgb[3] = rgb[3]; } else { *(unsigned int *)r_rgb = *(const unsigned int *)rgb; } } static void mask_draw_array(unsigned int pos, Gwn_PrimType prim_type, const float (*points)[2], unsigned int vertex_len) { immBegin(prim_type, vertex_len); for (unsigned int i = 0; i < vertex_len; ++i) { immVertex2fv(pos, points[i]); } immEnd(); } static void mask_draw_curve_type(const bContext *C, MaskSpline *spline, float (*orig_points)[2], int tot_point, const bool is_feather, const bool is_active, const unsigned char rgb_spline[4], const char draw_type) { const Gwn_PrimType draw_method = (spline->flag & MASK_SPLINE_CYCLIC) ? GWN_PRIM_LINE_LOOP : GWN_PRIM_LINE_STRIP; const unsigned char rgb_black[4] = {0x00, 0x00, 0x00, 0xff}; unsigned char rgb_tmp[4]; SpaceClip *sc = CTX_wm_space_clip(C); float (*points)[2] = orig_points; if (sc) { const bool undistort = sc->clip && (sc->user.render_flag & MCLIP_PROXY_RENDER_UNDISTORT); if (undistort) { points = MEM_callocN(2 * tot_point * sizeof(float), "undistorthed mask curve"); for (int i = 0; i < tot_point; i++) { mask_point_undistort_pos(sc, points[i], orig_points[i]); } } } Gwn_VertFormat *format = immVertexFormat(); uint pos = GWN_vertformat_attr_add(format, "pos", GWN_COMP_F32, 2, GWN_FETCH_FLOAT); switch (draw_type) { case MASK_DT_OUTLINE: /* TODO(merwin): use fancy line shader here * probably better with geometry shader (after core profile switch) */ immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); GPU_line_width(3.0f); mask_color_active_tint(rgb_tmp, rgb_black, is_active); immUniformColor4ubv(rgb_tmp); mask_draw_array(pos, draw_method, points, tot_point); GPU_line_width(1.0f); mask_color_active_tint(rgb_tmp, rgb_spline, is_active); immUniformColor4ubv(rgb_tmp); mask_draw_array(pos, draw_method, points, tot_point); immUnbindProgram(); break; case MASK_DT_BLACK: case MASK_DT_WHITE: immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); GPU_line_width(1.0f); if (draw_type == MASK_DT_BLACK) { rgb_tmp[0] = rgb_tmp[1] = rgb_tmp[2] = 0; } else { rgb_tmp[0] = rgb_tmp[1] = rgb_tmp[2] = 255; } /* alpha values seem too low but gl draws many points that compensate for it */ if (is_feather) { rgb_tmp[3] = 64; } else { rgb_tmp[3] = 128; } if (is_feather) { rgb_tmp[0] = (unsigned char)(((short)rgb_tmp[0] + (short)rgb_spline[0]) / 2); rgb_tmp[1] = (unsigned char)(((short)rgb_tmp[1] + (short)rgb_spline[1]) / 2); rgb_tmp[2] = (unsigned char)(((short)rgb_tmp[2] + (short)rgb_spline[2]) / 2); } mask_color_active_tint(rgb_tmp, rgb_tmp, is_active); immUniformColor4ubv(rgb_tmp); mask_draw_array(pos, draw_method, points, tot_point); immUnbindProgram(); break; case MASK_DT_DASH: /* TODO(merwin): use dashed line shader here * probably better with geometry shader (after core profile switch) */ #if 0 GPU_line_width(1.0f); GPU_basic_shader_bind_enable(GPU_SHADER_LINE | GPU_SHADER_STIPPLE); GPU_basic_shader_line_stipple(3, 0xAAAA); mask_color_active_tint(rgb_tmp, rgb_spline, is_active); immUniformColor4ubv(rgb_tmp); mask_draw_array(pos, draw_method, points, tot_point); mask_color_active_tint(rgb_tmp, rgb_black, is_active); immUniformColor4ubv(rgb_tmp); GPU_basic_shader_line_stipple(3, 0x5555); mask_draw_array(pos, draw_method, points, tot_point); GPU_basic_shader_bind_disable(GPU_SHADER_LINE | GPU_SHADER_STIPPLE); #endif break; default: BLI_assert(false); } if (points != orig_points) MEM_freeN(points); } static void draw_spline_curve(const bContext *C, MaskLayer *masklay, MaskSpline *spline, const char draw_flag, const char draw_type, const bool is_active, const int width, const int height) { const unsigned int resol = max_ii(BKE_mask_spline_feather_resolution(spline, width, height), BKE_mask_spline_resolution(spline, width, height)); unsigned char rgb_tmp[4]; const bool is_spline_sel = (spline->flag & SELECT) && (masklay->restrictflag & MASK_RESTRICT_SELECT) == 0; const bool is_smooth = (draw_flag & MASK_DRAWFLAG_SMOOTH) != 0; const bool is_fill = (spline->flag & MASK_SPLINE_NOFILL) == 0; unsigned int tot_diff_point; float (*diff_points)[2]; unsigned int tot_feather_point; float (*feather_points)[2]; diff_points = BKE_mask_spline_differentiate_with_resolution(spline, &tot_diff_point, resol); if (!diff_points) return; if (is_smooth) { GPU_line_smooth(true); } feather_points = BKE_mask_spline_feather_differentiated_points_with_resolution(spline, &tot_feather_point, resol, (is_fill != false)); /* draw feather */ mask_spline_feather_color_get(masklay, spline, is_spline_sel, rgb_tmp); mask_draw_curve_type(C, spline, feather_points, tot_feather_point, true, is_active, rgb_tmp, draw_type); if (!is_fill) { const float *fp = &diff_points[0][0]; float *fp_feather = &feather_points[0][0]; BLI_assert(tot_diff_point == tot_feather_point); for (int i = 0; i < tot_diff_point; i++, fp += 2, fp_feather += 2) { float tvec[2]; sub_v2_v2v2(tvec, fp, fp_feather); add_v2_v2v2(fp_feather, fp, tvec); } /* same as above */ mask_draw_curve_type(C, spline, feather_points, tot_feather_point, true, is_active, rgb_tmp, draw_type); } MEM_freeN(feather_points); /* draw main curve */ mask_spline_color_get(masklay, spline, is_spline_sel, rgb_tmp); mask_draw_curve_type(C, spline, diff_points, tot_diff_point, false, is_active, rgb_tmp, draw_type); MEM_freeN(diff_points); if (is_smooth) { GPU_line_smooth(false); } } static void draw_masklays(const bContext *C, Mask *mask, const char draw_flag, const char draw_type, const int width, const int height) { GPU_blend(true); GPU_blend_set_func_separate(GPU_SRC_ALPHA, GPU_ONE_MINUS_SRC_ALPHA, GPU_ONE, GPU_ONE_MINUS_SRC_ALPHA); GPU_enable_program_point_size(); MaskLayer *masklay; int i; for (masklay = mask->masklayers.first, i = 0; masklay; masklay = masklay->next, i++) { MaskSpline *spline; const bool is_active = (i == mask->masklay_act); if (masklay->restrictflag & MASK_RESTRICT_VIEW) { continue; } for (spline = masklay->splines.first; spline; spline = spline->next) { /* draw curve itself first... */ draw_spline_curve(C, masklay, spline, draw_flag, draw_type, is_active, width, height); if (!(masklay->restrictflag & MASK_RESTRICT_SELECT)) { /* ...and then handles over the curve so they're nicely visible */ draw_spline_points(C, masklay, spline, draw_flag, draw_type); } /* show undeform for testing */ if (0) { void *back = spline->points_deform; spline->points_deform = NULL; draw_spline_curve(C, masklay, spline, draw_flag, draw_type, is_active, width, height); draw_spline_points(C, masklay, spline, draw_flag, draw_type); spline->points_deform = back; } } } GPU_disable_program_point_size(); GPU_blend(false); } void ED_mask_draw(const bContext *C, const char draw_flag, const char draw_type) { ScrArea *sa = CTX_wm_area(C); Mask *mask = CTX_data_edit_mask(C); int width, height; if (!mask) return; ED_mask_get_size(sa, &width, &height); draw_masklays(C, mask, draw_flag, draw_type, width, height); } static float *mask_rasterize(Mask *mask, const int width, const int height) { MaskRasterHandle *handle; float *buffer = MEM_mallocN(sizeof(float) * height * width, "rasterized mask buffer"); /* Initialize rasterization handle. */ handle = BKE_maskrasterize_handle_new(); BKE_maskrasterize_handle_init(handle, mask, width, height, true, true, true); BKE_maskrasterize_buffer(handle, width, height, buffer); /* Free memory. */ BKE_maskrasterize_handle_free(handle); return buffer; } /* sets up the opengl context. * width, height are to match the values from ED_mask_get_size() */ void ED_mask_draw_region(Mask *mask, ARegion *ar, const char draw_flag, const char draw_type, const char overlay_mode, const int width_i, const int height_i, /* convert directly into aspect corrected vars */ const float aspx, const float aspy, const bool do_scale_applied, const bool do_draw_cb, float stabmat[4][4], /* optional - only used by clip */ const bContext *C /* optional - only used when do_post_draw is set or called from clip editor */ ) { struct View2D *v2d = &ar->v2d; /* aspect always scales vertically in movie and image spaces */ const float width = width_i, height = (float)height_i * (aspy / aspx); int x, y; /* int w, h; */ float zoomx, zoomy; /* frame image */ float maxdim; float xofs, yofs; /* find window pixel coordinates of origin */ UI_view2d_view_to_region(&ar->v2d, 0.0f, 0.0f, &x, &y); /* w = BLI_rctf_size_x(&v2d->tot); */ /* h = BLI_rctf_size_y(&v2d->tot); */ zoomx = (float)(BLI_rcti_size_x(&ar->winrct) + 1) / BLI_rctf_size_x(&ar->v2d.cur); zoomy = (float)(BLI_rcti_size_y(&ar->winrct) + 1) / BLI_rctf_size_y(&ar->v2d.cur); if (do_scale_applied) { zoomx /= width; zoomy /= height; } x += v2d->tot.xmin * zoomx; y += v2d->tot.ymin * zoomy; /* frame the image */ maxdim = max_ff(width, height); if (width == height) { xofs = yofs = 0; } else if (width < height) { xofs = ((height - width) / -2.0f) * zoomx; yofs = 0.0f; } else { /* (width > height) */ xofs = 0.0f; yofs = ((width - height) / -2.0f) * zoomy; } if (draw_flag & MASK_DRAWFLAG_OVERLAY) { float red[4] = {1.0f, 0.0f, 0.0f, 0.0f}; float *buffer = mask_rasterize(mask, width, height); if (overlay_mode != MASK_OVERLAY_ALPHACHANNEL) { /* More blending types could be supported in the future. */ GPU_blend(true); GPU_blend_set_func(GPU_DST_COLOR, GPU_ZERO); } gpuPushMatrix(); gpuTranslate2f(x, y); gpuScale2f(zoomx, zoomy); if (stabmat) { gpuMultMatrix(stabmat); } IMMDrawPixelsTexState state = immDrawPixelsTexSetup(GPU_SHADER_2D_IMAGE_SHUFFLE_COLOR); GPU_shader_uniform_vector(state.shader, GPU_shader_get_uniform(state.shader, "shuffle"), 4, 1, red); immDrawPixelsTex(&state, 0.0f, 0.0f, width, height, GL_RED, GL_FLOAT, GL_NEAREST, buffer, 1.0f, 1.0f, NULL); gpuPopMatrix(); if (overlay_mode != MASK_OVERLAY_ALPHACHANNEL) { GPU_blend(false); } MEM_freeN(buffer); } /* apply transformation so mask editing tools will assume drawing from the origin in normalized space */ gpuPushMatrix(); gpuTranslate2f(x + xofs, y + yofs); gpuScale2f(zoomx, zoomy); if (stabmat) { gpuMultMatrix(stabmat); } gpuScale2f(maxdim, maxdim); if (do_draw_cb) { ED_region_draw_cb_draw(C, ar, REGION_DRAW_PRE_VIEW); } /* draw! */ draw_masklays(C, mask, draw_flag, draw_type, width, height); if (do_draw_cb) { ED_region_draw_cb_draw(C, ar, REGION_DRAW_POST_VIEW); } gpuPopMatrix(); } void ED_mask_draw_frames(Mask *mask, ARegion *ar, const int cfra, const int sfra, const int efra) { const float framelen = ar->winx / (float)(efra - sfra + 1); MaskLayer *masklay = BKE_mask_layer_active(mask); if (masklay) { unsigned int num_lines = BLI_listbase_count(&masklay->splines_shapes); if (num_lines > 0) { uint pos = GWN_vertformat_attr_add(immVertexFormat(), "pos", GWN_COMP_I32, 2, GWN_FETCH_INT_TO_FLOAT); immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); immUniformColor4ub(255, 175, 0, 255); immBegin(GWN_PRIM_LINES, 2 * num_lines); for (MaskLayerShape *masklay_shape = masklay->splines_shapes.first; masklay_shape; masklay_shape = masklay_shape->next) { int frame = masklay_shape->frame; /* draw_keyframe(i, CFRA, sfra, framelen, 1); */ int height = (frame == cfra) ? 22 : 10; int x = (frame - sfra) * framelen; immVertex2i(pos, x, 0); immVertex2i(pos, x, height); } immEnd(); immUnbindProgram(); } } }