Move cursor render call to C

This commit is contained in:
Kovid Goyal 2017-09-13 11:12:10 +05:30
parent 6e4b977128
commit 13ac050bf8
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
12 changed files with 126 additions and 104 deletions

View File

@ -3,7 +3,6 @@
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
from gettext import gettext as _
from time import monotonic
from weakref import WeakValueDictionary
from .char_grid import load_shader_programs
@ -16,7 +15,7 @@ from .fast_data_types import (
GLFW_CURSOR, GLFW_CURSOR_HIDDEN, GLFW_CURSOR_NORMAL, GLFW_MOUSE_BUTTON_1,
GLFW_PRESS, GLFW_REPEAT, ChildMonitor, Timers as _Timers,
destroy_global_data, destroy_sprite_map, glfw_post_empty_event,
layout_sprite_map, resize_gl_viewport
layout_sprite_map
)
from .fonts.render import render_cell_wrapper, set_font_family
from .keys import (
@ -91,7 +90,6 @@ class Boss:
def __init__(self, glfw_window, opts, args):
self.window_id_map = WeakValueDictionary()
startup_session = create_session(opts, args)
self.cursor_blink_zero_time = monotonic()
self.cursor_blinking = True
self.window_is_focused = True
self.glfw_window_title = None
@ -119,7 +117,6 @@ class Boss:
layout_sprite_map(cell_size.width, cell_size.height, render_cell_wrapper)
self.glfw_window.set_click_cursor(False)
self.show_mouse_cursor()
self.start_cursor_blink()
@property
def current_tab_bar_height(self):
@ -165,14 +162,8 @@ class Boss:
self.io_thread_started = True
def on_window_resize(self, window, w, h):
# debounce resize events
if w > 100 and h > 100:
viewport_size.width, viewport_size.height = w, h
self.tab_manager.resize()
resize_gl_viewport(w, h)
glfw_post_empty_event()
else:
safe_print('Ignoring resize request for sizes under 100x100')
viewport_size.width, viewport_size.height = w, h
self.tab_manager.resize()
def increase_font_size(self):
self.change_font_size(
@ -232,8 +223,6 @@ class Boss:
def on_key(self, window, key, scancode, action, mods):
is_key_pressed[key] = action == GLFW_PRESS
self.start_cursor_blink()
self.cursor_blink_zero_time = monotonic()
func = None
if action == GLFW_PRESS or action == GLFW_REPEAT:
func = get_shortcut(self.opts.keymap, mods, key, scancode)
@ -346,16 +335,6 @@ class Boss:
except AttributeError:
pass # needs glfw 3.3
def start_cursor_blink(self):
self.cursor_blinking = True
if self.opts.cursor_stop_blinking_after > 0:
self.ui_timers.add(
self.opts.cursor_stop_blinking_after,
self.stop_cursor_blinking)
def stop_cursor_blinking(self):
self.cursor_blinking = False
def render(self):
tab = self.active_tab
if tab is None:
@ -365,19 +344,6 @@ class Boss:
self.glfw_window.set_title(self.glfw_window_title)
if isosx:
cocoa_update_title(self.glfw_window_title)
active = self.active_window
if active is not None:
draw_cursor = True
if self.cursor_blinking and self.opts.cursor_blink_interval > 0 and self.window_is_focused:
now = monotonic() - self.cursor_blink_zero_time
t = int(now * 1000)
d = int(self.opts.cursor_blink_interval * 1000)
n = t // d
draw_cursor = n % 2 == 0
self.ui_timers.add_if_before(
((n + 1) * d / 1000) - now, None)
if draw_cursor:
active.char_grid.render_cursor(self.window_is_focused)
def gui_close_window(self, window):
window.destroy()

View File

@ -3,22 +3,16 @@
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import re
from collections import namedtuple
from enum import Enum
from .config import build_ansi_color_table
from .constants import ScreenGeometry, cell_size, viewport_size
from .fast_data_types import (
CELL_PROGRAM, CURSOR_BEAM, CURSOR_BLOCK, CURSOR_PROGRAM, CURSOR_UNDERLINE,
compile_program, draw_cursor, init_cell_program, init_cursor_program
CELL_PROGRAM, CURSOR_PROGRAM, compile_program, init_cell_program,
init_cursor_program
)
from .rgb import to_color
from .utils import (
color_as_int, get_logical_dpi, load_shaders, open_url,
set_primary_selection
)
Cursor = namedtuple('Cursor', 'x y shape blink')
from .utils import color_as_int, load_shaders, open_url, set_primary_selection
class DynamicColor(Enum):
@ -52,9 +46,7 @@ class CharGrid:
self.screen.color_profile.update_ansi_color_table(build_ansi_color_table(opts))
self.screen.color_profile.set_configured_colors(*map(color_as_int, (
opts.foreground, opts.background, opts.cursor, opts.selection_foreground, opts.selection_background)))
self.dpix, self.dpiy = get_logical_dpi()
self.opts = opts
self.default_cursor = Cursor(0, 0, opts.cursor_shape, opts.cursor_blink_interval > 0)
self.opts = opts
def update_position(self, window_geometry):
@ -158,28 +150,3 @@ class CharGrid:
def text_for_selection(self):
return ''.join(self.screen.text_for_selection())
def render_cursor(self, is_focused):
if not self.screen.cursor_visible or self.screen.scrolled_by:
return
cursor = self.screen.cursor
def width(w=2, vert=True):
dpi = self.dpix if vert else self.dpiy
w *= dpi / 72.0 # as pixels
factor = 2 / (viewport_size.width if vert else viewport_size.height)
return w * factor
sg = self.screen_geometry
left = sg.xstart + cursor.x * sg.dx
top = sg.ystart - cursor.y * sg.dy
col = self.screen.color_profile.cursor_color
shape = cursor.shape or self.default_cursor.shape
alpha = self.opts.cursor_opacity
mult = self.screen.current_char_width()
right = left + (width(1.5) if shape == CURSOR_BEAM else sg.dx * mult)
bottom = top - sg.dy
if shape == CURSOR_UNDERLINE:
top = bottom + width(vert=False)
semi_transparent = alpha < 1.0 and shape == CURSOR_BLOCK
draw_cursor(semi_transparent, is_focused, col, alpha, left, right, top, bottom)

View File

@ -16,6 +16,7 @@ extern int pthread_setname_np(const char *name);
#undef _GNU_SOURCE
#endif
#include "state.h"
#include "screen.h"
#include <termios.h>
#include <unistd.h>
#include <float.h>
@ -426,6 +427,46 @@ pyset_iutf8(ChildMonitor *self, PyObject *args) {
static double last_render_at = -DBL_MAX;
draw_borders_func draw_borders = NULL;
draw_cells_func draw_cells = NULL;
draw_cursor_func draw_cursor = NULL;
static inline double
cursor_width(unsigned int w, bool vert) {
double dpi = vert ? global_state.logical_dpi_x : global_state.logical_dpi_y;
double ans = w * dpi / 72.0; // as pixels
double factor = 2.0 / (vert ? global_state.viewport_width : global_state.viewport_height);
return ans * factor;
}
static inline void
render_cursor(ChildMonitor *self, Window *w, double now) {
ScreenRenderData *rd = &w->render_data;
if (rd->screen->scrolled_by || ! screen_is_cursor_visible(rd->screen)) 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;
if (cursor_blinking) {
int t = (int)(time_since_start_blink * 1000);
int d = (int)(OPT(cursor_blink_interval) * 1000);
int n = t / d;
do_draw_cursor = n % 2 == 0 ? true : false;
double delay = MAX(0, ((n + 1) * d / 1000) - time_since_start_blink);
timers_add_if_before(self->timers, delay, Py_None, NULL);
}
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 = 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);
}
}
static inline bool
render(ChildMonitor *self, double *timeout) {
@ -443,8 +484,10 @@ render(ChildMonitor *self, double *timeout) {
Window *w = tab->windows + i;
#define WD w->render_data
if (w->visible && WD.screen) draw_cells(WD.vao_idx, WD.xstart, WD.ystart, WD.dx, WD.dy, WD.screen);
#undef WD
}
Window *w = tab->windows + tab->active_window;
if (w->visible && WD.screen) render_cursor(self, w, now);
#undef WD
}
ret = PyObject_CallFunctionObjArgs(self->render_func, NULL);

View File

@ -27,12 +27,15 @@ static int __eq__(Cursor *a, Cursor *b) {
return EQ(bold) && EQ(italic) && EQ(strikethrough) && EQ(reverse) && EQ(decoration) && EQ(fg) && EQ(bg) && EQ(decoration_fg) && EQ(x) && EQ(y) && EQ(shape) && EQ(blink);
}
static const char* cursor_names[NUM_OF_CURSOR_SHAPES] = { "NO_SHAPE", "BLOCK", "BEAM", "UNDERLINE" };
#define BOOL(x) ((x) ? Py_True : Py_False)
static PyObject *
repr(Cursor *self) {
return PyUnicode_FromFormat(
"Cursor(x=%u, y=%u, shape=%d, blink=%R, fg=#%08x, bg=#%08x, bold=%R, italic=%R, reverse=%R, strikethrough=%R, decoration=%d, decoration_fg=#%08x)",
self->x, self->y, self->shape, BOOL(self->blink), self->fg, self->bg, BOOL(self->bold), BOOL(self->italic), BOOL(self->reverse), BOOL(self->strikethrough), self->decoration, self->decoration_fg
"Cursor(x=%u, y=%u, shape=%s, blink=%R, fg=#%08x, bg=#%08x, bold=%R, italic=%R, reverse=%R, strikethrough=%R, decoration=%d, decoration_fg=#%08x)",
self->x, self->y, (self->shape < NUM_OF_CURSOR_SHAPES && self->shape >= 0 ? cursor_names[self->shape] : "INVALID"),
BOOL(self->blink), self->fg, self->bg, BOOL(self->bold), BOOL(self->italic), BOOL(self->reverse), BOOL(self->strikethrough), self->decoration, self->decoration_fg
);
}
@ -51,7 +54,7 @@ reset_display_attrs(Cursor *self) {
void cursor_reset(Cursor *self) {
cursor_reset_display_attrs(self);
self->x = 0; self->y = 0;
self->shape = 0; self->blink = false;
self->shape = NO_CURSOR_SHAPE; self->blink = false;
}
void cursor_copy_to(Cursor *src, Cursor *dest) {
@ -75,7 +78,7 @@ BOOL_GETSET(Cursor, blink)
static PyMemberDef members[] = {
{"x", T_UINT, offsetof(Cursor, x), 0, "x"},
{"y", T_UINT, offsetof(Cursor, y), 0, "y"},
{"shape", T_UBYTE, offsetof(Cursor, shape), 0, "shape"},
{"shape", T_INT, offsetof(Cursor, shape), 0, "shape"},
{"decoration", T_UBYTE, offsetof(Cursor, decoration), 0, "decoration"},
{"fg", T_ULONG, offsetof(Cursor, fg), 0, "fg"},
{"bg", T_ULONG, offsetof(Cursor, bg), 0, "bg"},

View File

@ -29,6 +29,7 @@ typedef uint32_t color_type;
typedef uint32_t combining_type;
typedef unsigned int index_type;
typedef uint16_t sprite_index;
typedef enum CursorShapes { NO_CURSOR_SHAPE, CURSOR_BLOCK, CURSOR_BEAM, CURSOR_UNDERLINE, NUM_OF_CURSOR_SHAPES } CursorShape;
#define ERROR_PREFIX "[PARSE ERROR]"
#define ANY_MODE 3
@ -61,9 +62,6 @@ typedef uint16_t sprite_index;
#define DECORATION_FG_CODE 58
#define CHAR_IS_BLANK(ch) ((ch & CHAR_MASK) == 32 || (ch & CHAR_MASK) == 0)
#define CURSOR_BLOCK 1
#define CURSOR_BEAM 2
#define CURSOR_UNDERLINE 3
#define FG 1
#define BG 2
@ -172,7 +170,8 @@ typedef struct {
bool bold, italic, reverse, strikethrough, blink;
unsigned int x, y;
uint8_t decoration, shape;
uint8_t decoration;
CursorShape shape;
unsigned long fg, bg, decoration_fg;
} Cursor;

View File

@ -39,19 +39,27 @@ typedef struct {
// callbacks {{{
static WindowWrapper* the_window = NULL;
update_viewport_size_func update_viewport_size = NULL;
static void
framebuffer_size_callback(GLFWwindow UNUSED *w, int width, int height) {
WINDOW_CALLBACK(framebuffer_size_callback, "ii", width, height);
if (width > 100 && height > 100) {
update_viewport_size(width, height);
global_state.viewport_width = width; global_state.viewport_height = height;
WINDOW_CALLBACK(framebuffer_size_callback, "ii", width, height);
glfwPostEmptyEvent();
} else fprintf(stderr, "Ignoring resize request for tiny size: %dx%d\n", width, height);
}
static void
char_mods_callback(GLFWwindow UNUSED *w, unsigned int codepoint, int mods) {
global_state.cursor_blink_zero_time = monotonic();
WINDOW_CALLBACK(char_mods_callback, "Ii", codepoint, mods);
}
static void
key_callback(GLFWwindow UNUSED *w, int key, int scancode, int action, int mods) {
global_state.cursor_blink_zero_time = monotonic();
WINDOW_CALLBACK(key_callback, "iiii", key, scancode, action, mods);
}
@ -67,12 +75,14 @@ scroll_callback(GLFWwindow UNUSED *w, double xoffset, double yoffset) {
static void
cursor_pos_callback(GLFWwindow UNUSED *w, double x, double y) {
global_state.cursor_blink_zero_time = monotonic();
WINDOW_CALLBACK(cursor_pos_callback, "dd", x, y);
}
static void
window_focus_callback(GLFWwindow UNUSED *w, int focused) {
global_state.application_focused = focused ? true : false;
global_state.cursor_blink_zero_time = monotonic();
WINDOW_CALLBACK(window_focus_callback, "O", focused ? Py_True : Py_False);
}
// }}}
@ -90,6 +100,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
the_window = self;
self->window = glfwCreateWindow(width, height, title, NULL, NULL);
if (self->window == NULL) { Py_CLEAR(self); the_window = NULL; PyErr_SetString(PyExc_ValueError, "Failed to create GLFWwindow"); return NULL; }
global_state.viewport_width = width; global_state.viewport_height = height;
self->standard_cursor = glfwCreateStandardCursor(GLFW_IBEAM_CURSOR);
self->click_cursor = glfwCreateStandardCursor(GLFW_HAND_CURSOR);
if (self->standard_cursor == NULL || self->click_cursor == NULL) { Py_CLEAR(self); PyErr_SetString(PyExc_ValueError, "Failed to create standard mouse cursors"); return NULL; }

View File

@ -23,10 +23,10 @@ from .fast_data_types import (
GLFW_STENCIL_BITS, GLFWWindow, change_wcwidth, check_for_extensions,
clear_buffers, glewInit, glfw_init, glfw_init_hint_string,
glfw_set_error_callback, glfw_swap_interval, glfw_terminate,
glfw_window_hint, set_options
glfw_window_hint, set_logical_dpi, set_options
)
from .layout import all_layouts
from .utils import color_as_int, detach, safe_print
from .utils import color_as_int, detach, get_logical_dpi, safe_print
try:
from .fast_data_types import GLFW_X11_WM_CLASS_NAME, GLFW_X11_WM_CLASS_CLASS
@ -186,6 +186,7 @@ def run_app(opts, args):
else:
with open(logo_data_file, 'rb') as f:
window.set_icon(f.read(), 256, 256)
set_logical_dpi(*get_logical_dpi())
viewport_size.width, viewport_size.height = window.get_framebuffer_size()
w, h = window.get_window_size()
viewport_size.x_ratio = viewport_size.width / float(w)

View File

@ -511,6 +511,20 @@ void screen_reset_mode(Screen *self, unsigned int mode) {
// Cursor {{{
unsigned long
screen_current_char_width(Screen *self) {
unsigned long ans = 1;
if (self->cursor->x < self->columns - 1 && self->cursor->y < self->lines) {
ans = linebuf_char_width_at(self->linebuf, self->cursor->x, self->cursor->y);
}
return ans;
}
bool
screen_is_cursor_visible(Screen *self) {
return self->modes.mDECTCEM;
}
void
screen_backspace(Screen *self) {
screen_cursor_back(self, 1, -1);
@ -1443,11 +1457,7 @@ mark_as_dirty(Screen *self) {
static PyObject*
current_char_width(Screen *self) {
#define current_char_width_doc "The width of the character under the cursor"
unsigned long ans = 1;
if (self->cursor->x < self->columns - 1 && self->cursor->y < self->lines) {
ans = linebuf_char_width_at(self->linebuf, self->cursor->x, self->cursor->y);
}
return PyLong_FromUnsignedLong(ans);
return PyLong_FromUnsignedLong(screen_current_char_width(self));
}
static PyObject*

View File

@ -63,6 +63,8 @@ void screen_apply_selection(Screen *self, void *address, size_t size);
bool screen_is_selection_dirty(Screen *self);
bool screen_invert_colors(Screen *self);
void screen_update_cell_data(Screen *self, void *address, size_t sz);
bool screen_is_cursor_visible(Screen *self);
unsigned long screen_current_char_width(Screen *self);
#define DECLARE_CH_SCREEN_HANDLER(name) void screen_##name(Screen *screen);
DECLARE_CH_SCREEN_HANDLER(bell)
DECLARE_CH_SCREEN_HANDLER(backspace)

View File

@ -84,6 +84,11 @@ glew_init(PyObject UNUSED *self) {
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Py_RETURN_NONE;
}
static void
update_viewport_size_impl(int w, int h) {
glViewport(0, 0, w, h); check_gl();
}
// }}}
// Programs {{{
@ -668,7 +673,7 @@ init_cursor_program() {
}
static void
draw_cursor(bool semi_transparent, bool is_focused, color_type color, float alpha, float left, float right, float top, float bottom) {
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);
@ -852,12 +857,6 @@ PYWRAP1(layout_sprite_map) {
Py_RETURN_NONE;
}
PYWRAP1(resize_gl_viewport) {
unsigned int w, h; PA("II", &w, &h);
glViewport(0, 0, w, h);
Py_RETURN_NONE;
}
PYWRAP1(clear_buffers) {
PyObject *swap_buffers;
unsigned int bg;
@ -918,7 +917,6 @@ static PyMethodDef module_methods[] = {
MW(create_cell_vao, METH_NOARGS),
MW(layout_sprite_map, METH_VARARGS),
MW(destroy_sprite_map, METH_NOARGS),
MW(resize_gl_viewport, METH_VARARGS),
MW(clear_buffers, METH_VARARGS),
{NULL, NULL, 0, NULL} /* Sentinel */
@ -962,8 +960,10 @@ init_shaders(PyObject *module) {
#undef C
PyModule_AddObject(module, "GL_VERSION_REQUIRED", Py_BuildValue("II", REQUIRED_VERSION_MAJOR, REQUIRED_VERSION_MINOR));
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
update_viewport_size = &update_viewport_size_impl;
draw_borders = &draw_borders_impl;
draw_cells = &draw_cells_impl;
draw_cursor = &draw_cursor_impl;
return true;
}
// }}}

View File

@ -103,9 +103,13 @@ swap_windows(unsigned int tab_id, unsigned int a, unsigned int b) {
#define THREE_UINT(name) PYWRAP1(name) { unsigned int a, b, c; PA("III", &a, &b, &c); name(a, b, c); Py_RETURN_NONE; }
PYWRAP1(set_options) {
#define S(name, convert) { PyObject *ret = PyObject_GetAttrString(args, #name); if (ret == NULL) return NULL; global_state.opts.name = convert(ret); Py_DECREF(ret); }
#define S(name, convert) { PyObject *ret = PyObject_GetAttrString(args, #name); if (ret == NULL) return NULL; global_state.opts.name = convert(ret); Py_DECREF(ret); if (PyErr_Occurred()) return NULL; }
S(visual_bell_duration, PyFloat_AsDouble);
S(enable_audio_bell, PyObject_IsTrue);
S(cursor_blink_interval, PyFloat_AsDouble);
S(cursor_stop_blinking_after, PyFloat_AsDouble);
S(cursor_shape, PyLong_AsLong);
S(cursor_opacity, PyFloat_AsDouble);
#undef S
Py_RETURN_NONE;
}
@ -144,6 +148,11 @@ PYWRAP1(update_window_visibility) {
Py_RETURN_NONE;
}
PYWRAP1(set_logical_dpi) {
PA("dd", &global_state.logical_dpi_x, &global_state.logical_dpi_y);
Py_RETURN_NONE;
}
PYWRAP0(destroy_global_data) {
Py_CLEAR(global_state.tab_bar_render_data.screen);
Py_RETURN_NONE;
@ -163,6 +172,7 @@ THREE_UINT(swap_windows)
static PyMethodDef module_methods[] = {
MW(set_options, METH_O),
MW(set_logical_dpi, METH_VARARGS),
MW(add_tab, METH_O),
MW(add_window, METH_VARARGS),
MW(remove_tab, METH_O),
@ -183,6 +193,7 @@ static PyMethodDef module_methods[] = {
bool
init_state(PyObject *module) {
global_state.application_focused = true;
global_state.cursor_blink_zero_time = monotonic();
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
return true;
}

View File

@ -7,9 +7,13 @@
#pragma once
#include "data-types.h"
#define OPT(name) global_state.opts.name
typedef struct {
double visual_bell_duration;
double visual_bell_duration, cursor_blink_interval, cursor_stop_blinking_after;
bool enable_audio_bell;
CursorShape cursor_shape;
double cursor_opacity;
} Options;
typedef struct {
@ -36,9 +40,14 @@ typedef struct {
unsigned int active_tab, num_tabs;
ScreenRenderData tab_bar_render_data;
bool application_focused;
double cursor_blink_zero_time;
double logical_dpi_x, logical_dpi_y;
int viewport_width, viewport_height;
} GlobalState;
typedef void (*draw_borders_func)();
extern draw_borders_func draw_borders;
typedef void (*draw_cells_func)(ssize_t, float, float, float, float, Screen *);
extern draw_cells_func draw_cells;
#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(update_viewport_size, void, int, int);