diff --git a/kitty/cell_vertex.glsl b/kitty/cell_vertex.glsl index 2d8b72ffa..1483a0908 100644 --- a/kitty/cell_vertex.glsl +++ b/kitty/cell_vertex.glsl @@ -1,10 +1,9 @@ #version GLSL_VERSION -uniform uvec2 dimensions; // xnum, ynum +uniform uvec4 dimensions; // xnum, ynum, cursor.x, cursor.y uniform vec4 steps; // xstart, ystart, dx, dy uniform vec2 sprite_layout; // dx, dy uniform ivec2 color_indices; // which color to use as fg and which as bg -uniform uvec4 default_colors; // The default colors -uniform uint url_color; // The color to use for the URL highlight +uniform uint default_colors[6]; // The default colors uniform uvec4 url_range; // The range for the currently highlighted URL (start_x, end_x, start_y, end_y) uniform ColorTable { uint color_table[256]; // The color table @@ -83,10 +82,18 @@ float in_range(uvec4 range, uint x, uint y) { return 0.0; } +float is_cursor(uint x, uint y) { + if (x == dimensions[2] && y == dimensions[3]) return 1.0; + return 0.0; +} + void main() { uint instance_id = uint(gl_InstanceID); + // The current cell being rendered uint r = instance_id / dimensions.x; uint c = instance_id - r * dimensions.x; + + // The position of this vertex, at a corner of the cell float left = steps[0] + c * steps[2]; float top = steps[1] - r * steps[3]; vec2 xpos = vec2(left, left + steps[2]); @@ -94,15 +101,23 @@ void main() { uvec2 pos = pos_map[gl_VertexID]; gl_Position = vec4(xpos[pos.x], ypos[pos.y], 0, 1); + // The character sprite being rendered sprite_pos = to_sprite_pos(pos, sprite_coords.x, sprite_coords.y, sprite_coords.z & SHORT_MASK); + + // Foreground and background colors uint text_attrs = sprite_coords[3]; int fg_index = color_indices[(text_attrs >> 6) & REVERSE_MASK]; int bg_index = color_indices[1 - fg_index]; uint resolved_fg = as_color(colors[fg_index], default_colors[fg_index]); foreground = apply_selection(color_to_vec(resolved_fg), default_colors[2]); background = apply_selection(to_color(colors[bg_index], default_colors[bg_index]), default_colors[3]); + float cursor = is_cursor(c, r); + foreground = cursor * background + (1.0 - cursor) * foreground; + background = cursor * color_to_vec(default_colors[4]) + (1.0 - cursor) * background; + + // Underline and strike through (rendered via sprites) float in_url = in_range(url_range, c, r); - decoration_fg = mix_vecs(in_url, color_to_vec(url_color), to_color(colors[2], resolved_fg)); + decoration_fg = mix_vecs(in_url, color_to_vec(default_colors[5]), to_color(colors[2], resolved_fg)); underline_pos = mix_vecs(in_url, to_sprite_pos(pos, TWO, ZERO, ZERO), to_sprite_pos(pos, (text_attrs >> 2) & DECORATION_MASK, ZERO, ZERO)); strike_pos = to_sprite_pos(pos, ((text_attrs >> 7) & STRIKE_MASK) * THREE, ZERO, ZERO); } diff --git a/kitty/child-monitor.c b/kitty/child-monitor.c index 30a8184f9..8cf2f51a7 100644 --- a/kitty/child-monitor.c +++ b/kitty/child-monitor.c @@ -451,9 +451,12 @@ cursor_width(double w, bool vert) { extern void cocoa_update_title(PyObject*); static inline void -render_cursor(Window *w, double now) { +collect_cursor_info(CursorRenderInfo *ans, Window *w, double now) { ScreenRenderData *rd = &w->render_data; - if (rd->screen->scrolled_by || !screen_is_cursor_visible(rd->screen)) return; + if (rd->screen->scrolled_by || !screen_is_cursor_visible(rd->screen)) { + ans->is_visible = false; + return; + } double time_since_start_blink = now - global_state.cursor_blink_zero_time; bool cursor_blinking = OPT(cursor_blink_interval) > 0 && global_state.application_focused && time_since_start_blink <= OPT(cursor_stop_blinking_after) ? true : false; bool do_draw_cursor = true; @@ -466,37 +469,50 @@ render_cursor(Window *w, double now) { double delay = (bucket / 1000.0) - time_since_start_blink; set_maximum_wait(delay); } - if (do_draw_cursor) { - Cursor *cursor = rd->screen->cursor; - double left = rd->xstart + cursor->x * rd->dx; - double top = rd->ystart - cursor->y * rd->dy; - int shape = cursor->shape ? cursor->shape : OPT(cursor_shape); - unsigned long mult = MAX(1, screen_current_char_width(rd->screen)); - double right = left + (shape == CURSOR_BEAM ? cursor_width(1.5, true) : rd->dx * mult); - double bottom = top - rd->dy; - if (shape == CURSOR_UNDERLINE) top = bottom + cursor_width(2.0, false); - bool semi_transparent = OPT(cursor_opacity) < 1.0 && shape == CURSOR_BLOCK ? true : false; - ColorProfile *cp = rd->screen->color_profile; - color_type col = colorprofile_to_color(cp, cp->overridden.cursor_color, cp->configured.cursor_color); - draw_cursor(semi_transparent, global_state.application_focused, col, OPT(cursor_opacity), left, right, top, bottom); + if (!do_draw_cursor) { ans->is_visible = false; return; } + ans->is_visible = true; + ColorProfile *cp = rd->screen->color_profile; + Cursor *cursor = rd->screen->cursor; + ans->shape = cursor->shape ? cursor->shape : OPT(cursor_shape); + ans->color = colorprofile_to_color(cp, cp->overridden.cursor_color, cp->configured.cursor_color); + if (ans->shape == CURSOR_BLOCK) return; + double left = rd->xstart + cursor->x * rd->dx; + double top = rd->ystart - cursor->y * rd->dy; + unsigned long mult = MAX(1, screen_current_char_width(rd->screen)); + double right = left + (ans->shape == CURSOR_BEAM ? cursor_width(1.5, true) : rd->dx * mult); + double bottom = top - rd->dy; + if (ans->shape == CURSOR_UNDERLINE) top = bottom + cursor_width(2.0, false); + ans->left = left; ans->right = right; ans->top = top; ans->bottom = bottom; +} + +static inline void +update_window_title(Window *w) { + if (w->title && w->title != global_state.application_title) { + global_state.application_title = w->title; + glfwSetWindowTitle(glfw_window_id, PyUnicode_AsUTF8(w->title)); +#ifdef __APPLE__ + cocoa_update_title(w->title); +#endif } } static inline void render(double now) { double time_since_last_render = now - last_render_at; + static CursorRenderInfo cursor_info; if (time_since_last_render < OPT(repaint_delay)) { set_maximum_wait(OPT(repaint_delay) - time_since_last_render); return; } draw_borders(); + cursor_info.is_visible = false; #define TD global_state.tab_bar_render_data - if (TD.screen && global_state.num_tabs > 1) draw_cells(TD.vao_idx, TD.xstart, TD.ystart, TD.dx, TD.dy, TD.screen); + if (TD.screen && global_state.num_tabs > 1) draw_cells(TD.vao_idx, TD.xstart, TD.ystart, TD.dx, TD.dy, TD.screen, &cursor_info); #undef TD if (global_state.num_tabs) { Tab *tab = global_state.tabs + global_state.active_tab; - for (size_t i = 0; i < tab->num_windows; i++) { + for (unsigned int i = 0; i < tab->num_windows; i++) { Window *w = tab->windows + i; #define WD w->render_data if (w->visible && WD.screen) { @@ -508,22 +524,20 @@ render(double now) { } else w->last_drag_scroll_at = 0; } else set_maximum_wait(now - w->last_drag_scroll_at); } - draw_cells(WD.vao_idx, WD.xstart, WD.ystart, WD.dx, WD.dy, WD.screen); + bool is_active_window = i == tab->active_window; + if (is_active_window) { + collect_cursor_info(&cursor_info, w, now); + update_window_title(w); + } else cursor_info.is_visible = false; + draw_cells(WD.vao_idx, WD.xstart, WD.ystart, WD.dx, WD.dy, WD.screen, &cursor_info); + if (is_active_window && cursor_info.is_visible && cursor_info.shape != CURSOR_BLOCK) draw_cursor(&cursor_info); if (WD.screen->start_visual_bell_at != 0) { double bell_left = global_state.opts.visual_bell_duration - (now - WD.screen->start_visual_bell_at); set_maximum_wait(bell_left); } } } - Window *w = tab->windows + tab->active_window; - if (w->visible && WD.screen) render_cursor(w, now); - if (w->title && w->title != global_state.application_title) { - global_state.application_title = w->title; - glfwSetWindowTitle(glfw_window_id, PyUnicode_AsUTF8(w->title)); -#ifdef __APPLE__ - cocoa_update_title(w->title); -#endif - } + #undef WD } glfwSwapBuffers(glfw_window_id); diff --git a/kitty/config.py b/kitty/config.py index d34c7a266..3feb6b093 100644 --- a/kitty/config.py +++ b/kitty/config.py @@ -47,10 +47,6 @@ def to_bool(x): return x.lower() in 'y yes true'.split() -def to_opacity(x): - return max(0.3, min(float(x), 1)) - - def parse_mods(parts): def map_mod(m): @@ -212,7 +208,6 @@ type_map = { 'font_size': to_font_size, 'font_size_delta': positive_float, 'cursor_shape': to_cursor_shape, - 'cursor_opacity': to_opacity, 'open_url_modifiers': to_open_url_modifiers, 'repaint_delay': positive_int, 'input_delay': positive_int, diff --git a/kitty/cursor_fragment.glsl b/kitty/cursor_fragment.glsl index 45e832a81..a07f9f498 100644 --- a/kitty/cursor_fragment.glsl +++ b/kitty/cursor_fragment.glsl @@ -1,7 +1,7 @@ #version GLSL_VERSION -uniform vec4 color; +uniform vec3 color; out vec4 final_color; void main() { - final_color = color; + final_color = vec4(color, 1); } diff --git a/kitty/cursor_vertex.glsl b/kitty/cursor_vertex.glsl index bc33d1b60..3329f2063 100644 --- a/kitty/cursor_vertex.glsl +++ b/kitty/cursor_vertex.glsl @@ -1,6 +1,5 @@ #version GLSL_VERSION -uniform vec2 xpos; -uniform vec2 ypos; +uniform vec4 pos; const uvec2 pos_map[] = uvec2[4]( uvec2(1, 0), // right, top @@ -10,6 +9,8 @@ const uvec2 pos_map[] = uvec2[4]( ); void main() { + vec2 xpos = vec2(pos[0], pos[2]); + vec2 ypos = vec2(pos[1], pos[3]); uvec2 pos = pos_map[gl_VertexID]; gl_Position = vec4(xpos[pos[0]], ypos[pos[1]], 0, 1); } diff --git a/kitty/kitty.conf b/kitty/kitty.conf index 8f44334c5..5059db8b8 100644 --- a/kitty/kitty.conf +++ b/kitty/kitty.conf @@ -49,10 +49,7 @@ selection_background #FFFACD url_color #0087BD # The cursor color -cursor #ffffff - -# The cursor opacity -cursor_opacity 0.7 +cursor #cccccc # The cursor shape can be one of (block, beam, underline) cursor_shape block diff --git a/kitty/shaders.c b/kitty/shaders.c index 9a35ce25d..7411d5b44 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -552,33 +552,32 @@ destroy_sprite_map() { // Cell {{{ -enum CellUniforms { CELL_dimensions, CELL_default_colors, CELL_color_indices, CELL_steps, CELL_sprites, CELL_sprite_layout, CELL_url_color, CELL_url_range, CELL_color_table, NUM_CELL_UNIFORMS }; +enum CellUniforms { CELL_dimensions, CELL_default_colors, CELL_color_indices, CELL_steps, CELL_sprites, CELL_sprite_layout, CELL_url_range, CELL_color_table, NUM_CELL_UNIFORMS }; static GLint cell_uniform_locations[NUM_CELL_UNIFORMS] = {0}; +static GLint cell_uniform_indices[NUM_CELL_UNIFORMS] = {0}; static GLint cell_color_table_stride = 0, cell_color_table_offset = 0, cell_color_table_size = 0, cell_color_table_block_index = 0; static void init_cell_program() { Program *p = programs + CELL_PROGRAM; int left = NUM_CELL_UNIFORMS; - GLint ctable_idx = 0; for (int i = 0; i < p->num_of_uniforms; i++, left--) { -#define SET_LOC(which) if (strcmp(p->uniforms[i].name, #which) == 0) cell_uniform_locations[CELL_##which] = p->uniforms[i].location - SET_LOC(dimensions); - else SET_LOC(default_colors); - else SET_LOC(color_indices); - else SET_LOC(steps); - else SET_LOC(sprites); - else SET_LOC(sprite_layout); - else SET_LOC(url_range); - else SET_LOC(url_color); - else if (strcmp(p->uniforms[i].name, "color_table[0]") == 0) { ctable_idx = i; cell_uniform_locations[CELL_color_table] = p->uniforms[i].location; } +#define SET_LOC(which) if (strcmp(p->uniforms[i].name, #which) == 0 || strcmp(p->uniforms[i].name, #which "[0]") == 0) { cell_uniform_locations[CELL_##which] = p->uniforms[i].location; cell_uniform_indices[CELL_##which] = i; } + SET_LOC(dimensions) + else SET_LOC(color_table) + else SET_LOC(default_colors) + else SET_LOC(color_indices) + else SET_LOC(steps) + else SET_LOC(sprites) + else SET_LOC(sprite_layout) + else SET_LOC(url_range) else { fatal("Unknown uniform in cell program: %s", p->uniforms[i].name); } } if (left) { fatal("Left over uniforms in cell program"); } cell_color_table_block_index = block_index(CELL_PROGRAM, "ColorTable"); cell_color_table_size = block_size(CELL_PROGRAM, cell_color_table_block_index); cell_color_table_stride = cell_color_table_size / (256 * sizeof(GLuint)); - cell_color_table_offset = block_offset(CELL_PROGRAM, ctable_idx); + cell_color_table_offset = block_offset(CELL_PROGRAM, cell_uniform_indices[CELL_color_table]); #undef SET_LOC } @@ -601,10 +600,9 @@ create_cell_vao() { #undef A1 } -static bool cell_constants_sent = false; static void -draw_cells_impl(ssize_t vao_idx, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, Screen *screen) { +draw_cells_impl(ssize_t vao_idx, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, Screen *screen, CursorRenderInfo *cursor) { size_t sz; void *address; bool inverted = screen_invert_colors(screen); @@ -625,16 +623,20 @@ draw_cells_impl(ssize_t vao_idx, GLfloat xstart, GLfloat ystart, GLfloat dx, GLf copy_color_table_to_buffer(screen->color_profile, address, cell_color_table_offset, cell_color_table_stride); unmap_vao_buffer(vao_idx, 2); } + index_type cx = screen->columns, cy = screen->lines; + if (cursor->is_visible && cursor->shape == CURSOR_BLOCK) { cx = screen->cursor->x, cy = screen->cursor->y; } int sprite_map_unit = bind_sprite_map(); render_dirty_sprites(render_and_send_dirty_sprites); #define UL(name) cell_uniform_locations[CELL_##name] bind_program(CELL_PROGRAM); bind_vao_uniform_buffer(vao_idx, 2, cell_color_table_block_index); - glUniform2ui(UL(dimensions), screen->columns, screen->lines); check_gl(); + glUniform4ui(UL(dimensions), screen->columns, screen->lines, cx, cy); check_gl(); glUniform4f(UL(steps), xstart, ystart, dx, dy); check_gl(); glUniform2i(UL(color_indices), inverted & 1, 1 - (inverted & 1)); check_gl(); #define COLOR(name) colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.name, screen->color_profile->configured.name) - glUniform4ui(UL(default_colors), COLOR(default_fg), COLOR(default_bg), COLOR(highlight_fg), COLOR(highlight_bg)); check_gl(); + static GLuint colors[6]; + colors[0] = COLOR(default_fg); colors[1] = COLOR(default_bg); colors[2] = COLOR(highlight_fg); colors[3] = COLOR(highlight_bg); colors[4] = cursor->color; colors[5] = OPT(url_color); + glUniform1uiv(UL(default_colors), sizeof(colors)/sizeof(colors[0]), colors); check_gl(); #undef COLOR GLuint start_x, start_y, end_x, end_y; screen_url_range(screen, &start_x, &start_y, &end_x, &end_y); @@ -643,10 +645,6 @@ draw_cells_impl(ssize_t vao_idx, GLfloat xstart, GLfloat ystart, GLfloat dx, GLf unsigned int x, y, z; sprite_map_current_layout(&x, &y, &z); glUniform2f(UL(sprite_layout), 1.0 / (float)x, 1.0 / (float)y); check_gl(); - if (!cell_constants_sent) { - glUniform1ui(UL(url_color), OPT(url_color)); check_gl(); - cell_constants_sent = true; - } bind_vertex_array(vao_idx); glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); check_gl(); unbind_vertex_array(); @@ -657,7 +655,7 @@ draw_cells_impl(ssize_t vao_idx, GLfloat xstart, GLfloat ystart, GLfloat dx, GLf // }}} // Cursor {{{ -enum CursorUniforms { CURSOR_color, CURSOR_xpos, CURSOR_ypos, NUM_CURSOR_UNIFORMS }; +enum CursorUniforms { CURSOR_color, CURSOR_pos, NUM_CURSOR_UNIFORMS }; static GLint cursor_uniform_locations[NUM_CURSOR_UNIFORMS] = {0}; static ssize_t cursor_vertex_array; @@ -669,8 +667,7 @@ init_cursor_program() { for (int i = 0; i < p->num_of_uniforms; i++, left--) { #define SET_LOC(which) if (strcmp(p->uniforms[i].name, #which) == 0) cursor_uniform_locations[CURSOR_##which] = p->uniforms[i].location SET_LOC(color); - else SET_LOC(xpos); - else SET_LOC(ypos); + else SET_LOC(pos); else { fatal("Unknown uniform in cursor program"); } } if (left) { fatal("Left over uniforms in cursor program"); } @@ -678,19 +675,12 @@ init_cursor_program() { } static void -draw_cursor_impl(bool semi_transparent, bool is_focused, color_type color, float alpha, float left, float right, float top, float bottom) { - if (semi_transparent) { glEnable(GL_BLEND); check_gl(); } - bind_program(CURSOR_PROGRAM); bind_vertex_array(cursor_vertex_array); - glUniform4f(cursor_uniform_locations[CURSOR_color], ((color >> 16) & 0xff) / 255.0, ((color >> 8) & 0xff) / 255.0, (color & 0xff) / 255.0, alpha); - check_gl(); - glUniform2f(cursor_uniform_locations[CURSOR_xpos], left, right); - check_gl(); - glUniform2f(cursor_uniform_locations[CURSOR_ypos], top, bottom); - check_gl(); - glDrawArrays(is_focused ? GL_TRIANGLE_FAN : GL_LINE_LOOP, 0, 4); - check_gl(); +draw_cursor_impl(CursorRenderInfo *cursor) { + bind_program(CURSOR_PROGRAM); bind_vertex_array(cursor_vertex_array); check_gl(); + glUniform3f(cursor_uniform_locations[CURSOR_color], ((cursor->color >> 16) & 0xff) / 255.0, ((cursor->color >> 8) & 0xff) / 255.0, (cursor->color & 0xff) / 255.0); check_gl(); + glUniform4f(cursor_uniform_locations[CURSOR_pos], cursor->left, cursor->top, cursor->right, cursor->bottom); check_gl(); + glDrawArrays(global_state.application_focused ? GL_TRIANGLE_FAN : GL_LINE_LOOP, 0, 4); check_gl(); unbind_vertex_array(); unbind_program(); - if (semi_transparent) { glDisable(GL_BLEND); check_gl(); } } // }}} @@ -826,14 +816,6 @@ PYWRAP1(map_vao_buffer) { } NO_ARG(init_cursor_program) -PYWRAP1(draw_cursor) { - int semi_transparent, is_focused; - unsigned int color; - float alpha, left, right, top, bottom; - PA("ppIfffff", &semi_transparent, &is_focused, &color, &alpha, &left, &right, &top, &bottom); - draw_cursor(semi_transparent, is_focused, color, alpha, left, right, top, bottom); - Py_RETURN_NONE; -} NO_ARG(init_borders_program) PYWRAP1(add_borders_rect) { unsigned int a, b, c, d, e; PA("IIIII", &a, &b, &c, &d, &e); add_borders_rect(a, b, c, d, e); Py_RETURN_NONE; } @@ -841,15 +823,6 @@ TWO_INT(send_borders_rects) NO_ARG(init_cell_program) NO_ARG_INT(create_cell_vao) -PYWRAP1(draw_cells) { - float xstart, ystart, dx, dy; - int vao_idx; - Screen *screen; - - PA("iffffO", &vao_idx, &xstart, &ystart, &dx, &dy, &screen); - draw_cells_impl(vao_idx, xstart, ystart, dx, dy, screen); - Py_RETURN_NONE; -} NO_ARG(destroy_sprite_map) PYWRAP1(layout_sprite_map) { unsigned int cell_width, cell_height; @@ -899,7 +872,6 @@ PYWRAP0(check_for_extensions) { #define MW(name, arg_type) {#name, (PyCFunction)py##name, arg_type, NULL} static PyMethodDef module_methods[] = { {"glewInit", (PyCFunction)glew_init, METH_NOARGS, NULL}, - {"draw_cells", (PyCFunction)pydraw_cells, METH_VARARGS, NULL}, M(compile_program, METH_VARARGS), MW(check_for_extensions, METH_NOARGS), MW(create_vao, METH_NOARGS), @@ -911,7 +883,6 @@ static PyMethodDef module_methods[] = { MW(bind_program, METH_O), MW(unbind_program, METH_NOARGS), MW(init_cursor_program, METH_NOARGS), - MW(draw_cursor, METH_VARARGS), MW(init_borders_program, METH_NOARGS), MW(add_borders_rect, METH_VARARGS), MW(send_borders_rects, METH_VARARGS), diff --git a/kitty/state.c b/kitty/state.c index ab4915070..9ed6e9335 100644 --- a/kitty/state.c +++ b/kitty/state.c @@ -154,7 +154,6 @@ PYWRAP1(set_options) { S(cursor_blink_interval, PyFloat_AsDouble); S(cursor_stop_blinking_after, PyFloat_AsDouble); S(cursor_shape, PyLong_AsLong); - S(cursor_opacity, PyFloat_AsDouble); S(mouse_hide_wait, PyFloat_AsDouble); S(wheel_scroll_multiplier, PyFloat_AsDouble); S(open_url_modifiers, PyLong_AsUnsignedLong); diff --git a/kitty/state.h b/kitty/state.h index 6c5d9961e..36dfa9ce3 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -10,7 +10,7 @@ #define OPT(name) global_state.opts.name typedef struct { - double visual_bell_duration, cursor_blink_interval, cursor_stop_blinking_after, mouse_hide_wait, click_interval, cursor_opacity, wheel_scroll_multiplier; + double visual_bell_duration, cursor_blink_interval, cursor_stop_blinking_after, mouse_hide_wait, click_interval, wheel_scroll_multiplier; bool enable_audio_bell; CursorShape cursor_shape; unsigned int open_url_modifiers; @@ -77,6 +77,14 @@ typedef struct { } GlobalState; extern GlobalState global_state; + +typedef struct { + bool is_visible; + CursorShape shape; + double left, right, top, bottom; + color_type color; +} CursorRenderInfo; + bool drag_scroll(Window *); #define call_boss(name, ...) { \ @@ -88,6 +96,6 @@ bool drag_scroll(Window *); #define EXTERNAL_FUNC(name, ret, ...) typedef ret (*name##_func)(__VA_ARGS__); extern name##_func name #define EXTERNAL_FUNC0(name, ret) typedef ret (*name##_func)(); extern name##_func name EXTERNAL_FUNC0(draw_borders, void); -EXTERNAL_FUNC(draw_cells, void, ssize_t, float, float, float, float, Screen *); -EXTERNAL_FUNC(draw_cursor, void, bool, bool, color_type, float, float, float, float, float); +EXTERNAL_FUNC(draw_cells, void, ssize_t, float, float, float, float, Screen *, CursorRenderInfo *); +EXTERNAL_FUNC(draw_cursor, void, CursorRenderInfo *); EXTERNAL_FUNC(update_viewport_size, void, int, int);