Migrate child-monitor.c to use OSWindow

This commit is contained in:
Kovid Goyal 2017-11-14 14:43:57 +05:30
parent f0003f223a
commit 141a08ec7c
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
6 changed files with 193 additions and 86 deletions

View File

@ -24,11 +24,9 @@ extern int pthread_setname_np(const char *name);
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <signal.h>
#include <GLFW/glfw3.h>
extern PyTypeObject Screen_Type;
#define EXTRA_FDS 2
#define wakeup_main_loop glfwPostEmptyEvent
static void (*parse_func)(Screen*, PyObject*);
@ -58,7 +56,6 @@ static bool signal_received = false;
static ChildMonitor *the_monitor = NULL;
static uint8_t drain_buf[1024];
static int signal_fds[2], wakeup_fds[2];
static void *glfw_window_id = NULL;
static inline void
@ -120,12 +117,11 @@ self_pipe(int fds[2]) {
static PyObject *
new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
ChildMonitor *self;
PyObject *dump_callback, *death_notify, *wid;
PyObject *dump_callback, *death_notify;
int ret;
if (the_monitor) { PyErr_SetString(PyExc_RuntimeError, "Can have only a single ChildMonitor instance"); return NULL; }
if (!PyArg_ParseTuple(args, "OOO", &wid, &death_notify, &dump_callback)) return NULL;
glfw_window_id = PyLong_AsVoidPtr(wid);
if (!PyArg_ParseTuple(args, "OO", &death_notify, &dump_callback)) return NULL;
if ((ret = pthread_mutex_init(&children_lock, NULL)) != 0) {
PyErr_Format(PyExc_RuntimeError, "Failed to create children_lock mutex: %s", strerror(ret));
return NULL;
@ -313,7 +309,7 @@ parse_input(ChildMonitor *self) {
}
if (UNLIKELY(signal_received)) {
glfwSetWindowShouldClose(glfw_window_id, true);
global_state.close_all_windows = true;
} else {
count = self->count;
for (size_t i = 0; i < count; i++) {
@ -436,24 +432,26 @@ pyset_iutf8(ChildMonitor *self, PyObject *args) {
static double last_render_at = -DBL_MAX;
static inline double
cursor_width(double w, bool vert) {
cursor_width(double w, bool vert, OSWindow *os_window) {
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);
double factor = 2.0 / (vert ? os_window->viewport_width : os_window->viewport_height);
return ans * factor;
}
extern void cocoa_update_title(PyObject*);
static inline void
collect_cursor_info(CursorRenderInfo *ans, Window *w, double now) {
collect_cursor_info(CursorRenderInfo *ans, Window *w, double now, OSWindow *os_window) {
ScreenRenderData *rd = &w->render_data;
Cursor *cursor = rd->screen->cursor;
ans->x = cursor->x; ans->y = cursor->y;
ans->is_visible = false;
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;
double time_since_start_blink = now - os_window->cursor_blink_zero_time;
bool cursor_blinking = OPT(cursor_blink_interval) > 0 && os_window->is_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);
@ -467,28 +465,29 @@ collect_cursor_info(CursorRenderInfo *ans, Window *w, double now) {
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 right = left + (ans->shape == CURSOR_BEAM ? cursor_width(1.5, true, os_window) : rd->dx * mult);
double bottom = top - rd->dy;
if (ans->shape == CURSOR_UNDERLINE) top = bottom + cursor_width(2.0, false);
if (ans->shape == CURSOR_UNDERLINE) top = bottom + cursor_width(2.0, false, os_window);
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));
static inline bool
update_window_title(Window *w, OSWindow *os_window) {
if (w->title && w->title != os_window->window_title) {
os_window->window_title = w->title;
set_os_window_title(os_window, PyUnicode_AsUTF8(w->title));
#ifdef __APPLE__
cocoa_update_title(w->title);
if (os_window == global_state.focussed_os_window) cocoa_update_title(w->title);
#endif
return true;
}
return false;
}
static PyObject*
@ -498,34 +497,23 @@ simple_render_screen(PyObject UNUSED *self, PyObject *args) {
ssize_t vao_idx, gvao_idx;
float xstart, ystart, dx, dy;
if (!PyArg_ParseTuple(args, "O!nnffff", &Screen_Type, &screen, &vao_idx, &gvao_idx, &xstart, &ystart, &dx, &dy)) return NULL;
static CursorRenderInfo cursor_info = {0};
draw_cells(vao_idx, gvao_idx, xstart, ystart, dx, dy, screen, &cursor_info);
Py_RETURN_NONE;
PyObject *ret = draw_cells(vao_idx, gvao_idx, xstart, ystart, dx, dy, screen, current_os_window()) ? Py_True : Py_False;
Py_INCREF(ret); return ret;
}
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, 0, 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;
static inline bool
render_os_window(OSWindow *os_window, double now, unsigned int *active_window_id) {
bool dirtied = false;
if (OPT(mouse_hide_wait) > 0 && now - os_window->last_mouse_activity_at > OPT(mouse_hide_wait)) hide_mouse(os_window);
if (os_window->num_tabs) {
Tab *tab = os_window->tabs + os_window->active_tab;
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) {
if (w->last_drag_scroll_at > 0) {
if (now - w->last_drag_scroll_at >= 0.02) {
if (drag_scroll(w)) {
if (drag_scroll(w, os_window)) {
w->last_drag_scroll_at = now;
set_maximum_wait(0.02);
} else w->last_drag_scroll_at = 0;
@ -533,24 +521,55 @@ render(double now) {
}
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.gvao_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);
*active_window_id = w->id;
collect_cursor_info(&WD.screen->current_cursor_render_info, w, now, os_window);
if (update_window_title(w, os_window)) dirtied = true;
} else WD.screen->current_cursor_render_info.is_visible = false;
if (draw_cells(WD.vao_idx, WD.gvao_idx, WD.xstart, WD.ystart, WD.dx, WD.dy, WD.screen, os_window)) {
if (is_active_window && WD.screen->current_cursor_render_info.is_visible && WD.screen->current_cursor_render_info.shape != CURSOR_BLOCK) {
draw_cursor(&WD.screen->current_cursor_render_info, os_window == global_state.focussed_os_window);
}
dirtied = true;
}
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);
}
}
}
#undef WD
}
glfwSwapBuffers(glfw_window_id);
last_render_at = now;
return dirtied;
}
static inline void
render(double now) {
double time_since_last_render = now - last_render_at;
if (time_since_last_render < OPT(repaint_delay)) {
set_maximum_wait(OPT(repaint_delay) - time_since_last_render);
return;
}
#define TD w->tab_bar_render_data
for (size_t i = 0; i < global_state.num_os_windows; i++) {
OSWindow *w = global_state.os_windows + i;
if (!should_os_window_be_rendered(w)) continue;
make_window_context_current(w);
unsigned int active_window_id = 0;
bool window_rendered = render_os_window(w, now, &active_window_id);
bool tab_bar_changed = w->num_tabs > 1 && (w->last_active_tab != w->active_tab || w->last_num_tabs != w->num_tabs);
if (window_rendered || tab_bar_changed || active_window_id != w->last_active_window_id) {
draw_borders();
if (TD.screen && w->num_tabs > 1) draw_cells(TD.vao_idx, 0, TD.xstart, TD.ystart, TD.dx, TD.dy, TD.screen, w);
swap_window_buffers(w);
w->last_active_tab = w->active_tab; w->last_num_tabs = w->num_tabs; w->last_active_window_id = active_window_id;
}
}
last_render_at = now;
#undef TD
}
typedef struct { int fd; uint8_t *buf; size_t sz; } ThreadWriteData;
static inline ThreadWriteData*
@ -599,18 +618,9 @@ cm_thread_write(PyObject UNUSED *self, PyObject *args) {
Py_RETURN_NONE;
}
static inline void
hide_mouse(double now) {
if (glfwGetInputMode(glfw_window_id, GLFW_CURSOR) == GLFW_CURSOR_NORMAL && OPT(mouse_hide_wait) > 0 && now - global_state.last_mouse_activity_at > OPT(mouse_hide_wait)) {
glfwSetInputMode(glfw_window_id, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
}
}
static inline void
wait_for_events() {
if (maximum_wait < 0) glfwWaitEvents();
else if (maximum_wait > 0) glfwWaitEventsTimeout(maximum_wait);
event_loop_wait(maximum_wait);
maximum_wait = -1;
}
@ -618,12 +628,24 @@ wait_for_events() {
static PyObject*
main_loop(ChildMonitor *self) {
#define main_loop_doc "The main thread loop"
while (!glfwWindowShouldClose(glfw_window_id)) {
bool has_open_windows = true;
while (has_open_windows) {
double now = monotonic();
render(now);
hide_mouse(now);
wait_for_events();
parse_input(self);
if (global_state.close_all_windows) {
for (size_t w = 0; w < global_state.num_os_windows; w++) mark_os_window_for_close(&global_state.os_windows[w], true);
global_state.close_all_windows = false;
}
has_open_windows = false;
for (size_t w = 0; w < global_state.num_os_windows; w++) {
if (!should_os_window_close(&global_state.os_windows[w])) {
has_open_windows = true;
break;
}
}
}
if (PyErr_Occurred()) return NULL;
Py_RETURN_NONE;

View File

@ -172,6 +172,14 @@ typedef struct {
} Cursor;
typedef struct {
bool is_visible;
CursorShape shape;
unsigned int x, y;
double left, right, top, bottom;
color_type color;
} CursorRenderInfo;
typedef struct {
color_type default_fg, default_bg, cursor_color, highlight_fg, highlight_bg;
} DynamicColor;

View File

@ -182,7 +182,7 @@ create_new_os_window(PyObject UNUSED *self, PyObject *args) {
glfwSetWindowUserPointer(glfw_window, w);
w->handle = glfw_window;
glfwSetCursor(glfw_window, standard_cursor);
global_state.callback_os_window->viewport_size_dirty = true;
w->viewport_size_dirty = true;
update_viewport(w);
glfwSetFramebufferSizeCallback(glfw_window, framebuffer_size_callback);
glfwSetCharModsCallback(glfw_window, char_mods_callback);
@ -394,6 +394,55 @@ request_window_attention(unsigned int kitty_window_id) {
}
}
void
set_os_window_title(OSWindow *w, const char *title) {
glfwSetWindowTitle(w->handle, title);
}
void
hide_mouse(OSWindow *w) {
glfwSetInputMode(w->handle, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
}
void
make_window_context_current(OSWindow *w) {
glfwMakeContextCurrent(w->handle);
if (w->viewport_size_dirty) update_viewport_size(w->viewport_width, w->viewport_height);
}
void
swap_window_buffers(OSWindow *w) {
glfwSwapBuffers(w->handle);
}
void
event_loop_wait(double timeout) {
if (timeout < 0) glfwWaitEvents();
else if (timeout > 0) glfwWaitEventsTimeout(timeout);
}
void
wakeup_main_loop() {
glfwPostEmptyEvent();
}
void
mark_os_window_for_close(OSWindow* w, bool yes) {
glfwSetWindowShouldClose(w->handle, yes);
}
bool
should_os_window_be_rendered(OSWindow* w) {
if (glfwGetWindowAttrib(w->handle, GLFW_ICONIFIED)) return false;
if (!glfwGetWindowAttrib(w->handle, GLFW_VISIBLE)) return false;
return true;
}
bool
should_os_window_close(OSWindow* w) {
return glfwWindowShouldClose(w->handle) ? true : false;
}
static PyObject*
primary_monitor_size(PyObject UNUSED *self) {
GLFWmonitor* monitor = glfwGetPrimaryMonitor();

View File

@ -55,6 +55,10 @@ typedef struct {
size_t read_buf_sz, write_buf_sz, write_buf_used;
pthread_mutex_t read_buf_lock, write_buf_lock;
CursorRenderInfo last_cursor_render_info, current_cursor_render_info;
bool colors_inverted_at_last_render;
float last_render_x_start, last_render_y_start;
} Screen;

View File

@ -203,7 +203,7 @@ create_graphics_vao() {
}
static inline void
cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, CursorRenderInfo *cursor) {
cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, CursorRenderInfo *cursor, bool inverted) {
struct CellRenderData {
GLfloat xstart, ystart, dx, dy, sprite_dx, sprite_dy;
@ -215,7 +215,6 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, G
};
static struct CellRenderData *rd;
bool inverted = screen_invert_colors(screen);
// Send the uniform data
rd = (struct CellRenderData*)map_vao_buffer(vao_idx, uniform_buffer, GL_WRITE_ONLY);
if (UNLIKELY(screen->color_profile->dirty)) {
@ -246,11 +245,12 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, G
unmap_vao_buffer(vao_idx, uniform_buffer); rd = NULL;
}
static inline void
cell_prepare_to_render(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, CursorRenderInfo *cursor) {
static inline bool
cell_prepare_to_render(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy) {
size_t sz;
CELL_BUFFERS;
void *address;
bool needs_render = false;
ensure_sprite_map();
@ -259,6 +259,7 @@ cell_prepare_to_render(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen, GLfloa
address = alloc_and_map_vao_buffer(vao_idx, sz, cell_data_buffer, GL_STREAM_DRAW, GL_WRITE_ONLY);
screen_update_cell_data(screen, address, sz);
unmap_vao_buffer(vao_idx, cell_data_buffer); address = NULL;
needs_render = true;
}
if (screen_is_selection_dirty(screen)) {
@ -266,6 +267,7 @@ cell_prepare_to_render(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen, GLfloa
address = alloc_and_map_vao_buffer(vao_idx, sz, selection_buffer, GL_STREAM_DRAW, GL_WRITE_ONLY);
screen_apply_selection(screen, address, sz);
unmap_vao_buffer(vao_idx, selection_buffer); address = NULL;
needs_render = true;
}
if (gvao_idx && grman_update_layers(screen->grman, screen->scrolled_by, xstart, ystart, dx, dy, screen->columns, screen->lines)) {
@ -273,12 +275,28 @@ cell_prepare_to_render(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen, GLfloa
GLfloat *a = alloc_and_map_vao_buffer(gvao_idx, sz, 0, GL_STREAM_DRAW, GL_WRITE_ONLY);
for (size_t i = 0; i < screen->grman->count; i++, a += 16) memcpy(a, screen->grman->render_data[i].vertices, sizeof(screen->grman->render_data[0].vertices));
unmap_vao_buffer(gvao_idx, 0); a = NULL;
needs_render = true;
}
bool inverted = screen_invert_colors(screen);
if (inverted != screen->colors_inverted_at_last_render) needs_render = true;
cell_update_uniform_block(vao_idx, screen, uniform_buffer, xstart, ystart, dx, dy, cursor);
if (!needs_render && (screen->last_render_x_start != xstart || screen->last_render_y_start != ystart)) needs_render = true;
#define CC(attr) (screen->last_cursor_render_info.attr != screen->current_cursor_render_info.attr)
if (!needs_render && (CC(is_visible) || CC(shape) || CC(x) || CC(y) || CC(color))) needs_render = true;
#undef CC
if (needs_render) {
cell_update_uniform_block(vao_idx, screen, uniform_buffer, xstart, ystart, dx, dy, &screen->current_cursor_render_info, inverted);
bind_vao_uniform_buffer(vao_idx, uniform_buffer, cell_program_layouts[CELL_PROGRAM].render_data.index);
bind_vertex_array(vao_idx);
screen->colors_inverted_at_last_render = inverted;
screen->last_render_x_start = xstart; screen->last_render_y_start = ystart;
#define CC(attr) screen->last_cursor_render_info.attr = screen->current_cursor_render_info.attr
CC(is_visible); CC(shape); CC(x); CC(y); CC(color);
#undef CC
}
return needs_render;
}
static void
@ -336,10 +354,12 @@ draw_cells_interleaved(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen) {
if (screen->grman->num_of_positive_refs) draw_graphics(vao_idx, gvao_idx, screen->grman->render_data, screen->grman->num_of_negative_refs, screen->grman->num_of_positive_refs);
}
void
draw_cells(ssize_t vao_idx, ssize_t gvao_idx, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, Screen *screen, CursorRenderInfo *cursor) {
bool
draw_cells(ssize_t vao_idx, ssize_t gvao_idx, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, Screen *screen, OSWindow *os_window) {
bool needs_render = cell_prepare_to_render(vao_idx, gvao_idx, screen, xstart, ystart, dx, dy);
if (!needs_render) return false;
GLfloat h = (GLfloat)screen->lines * dy;
#define SCALE(w, x) ((GLfloat)(global_state.viewport_##w) * (GLfloat)(x))
#define SCALE(w, x) ((GLfloat)(os_window->viewport_##w) * (GLfloat)(x))
glScissor(
(GLint)(SCALE(width, (xstart + 1.0f) / 2.0f)),
(GLint)(SCALE(height, ((ystart - h) + 1.0f) / 2.0f)),
@ -347,9 +367,9 @@ draw_cells(ssize_t vao_idx, ssize_t gvao_idx, GLfloat xstart, GLfloat ystart, GL
(GLsizei)(ceilf(SCALE(height, h / 2.0f)))
);
#undef SCALE
cell_prepare_to_render(vao_idx, gvao_idx, screen, xstart, ystart, dx, dy, cursor);
if (screen->grman->num_of_negative_refs) draw_cells_interleaved(vao_idx, gvao_idx, screen);
else draw_all_cells(vao_idx, gvao_idx, screen);
return true;
}
// }}}
@ -374,11 +394,11 @@ init_cursor_program() {
}
void
draw_cursor(CursorRenderInfo *cursor) {
draw_cursor(CursorRenderInfo *cursor, bool is_focused) {
bind_program(CURSOR_PROGRAM); bind_vertex_array(cursor_vertex_array);
glUniform3f(cursor_uniform_locations[CURSOR_color], ((cursor->color >> 16) & 0xff) / 255.0, ((cursor->color >> 8) & 0xff) / 255.0, (cursor->color & 0xff) / 255.0);
glUniform4f(cursor_uniform_locations[CURSOR_pos], cursor->left, cursor->top, cursor->right, cursor->bottom);
glDrawArrays(global_state.application_focused ? GL_TRIANGLE_FAN : GL_LINE_LOOP, 0, 4);
glDrawArrays(is_focused ? GL_TRIANGLE_FAN : GL_LINE_LOOP, 0, 4);
unbind_vertex_array(); unbind_program();
}
// }}}

View File

@ -76,7 +76,7 @@ typedef struct {
int viewport_width, viewport_height;
double viewport_x_ratio, viewport_y_ratio;
Tab tabs[MAX_CHILDREN];
unsigned int active_tab, num_tabs;
unsigned int active_tab, num_tabs, last_active_tab, last_num_tabs, last_active_window_id;
ScreenRenderData tab_bar_render_data;
bool is_focused;
double cursor_blink_zero_time, last_mouse_activity_at;
@ -85,6 +85,7 @@ typedef struct {
PyObject *window_title;
bool is_key_pressed[MAX_KEY_COUNT];
bool viewport_size_dirty;
bool needs_render;
} OSWindow;
@ -99,29 +100,32 @@ typedef struct {
OSWindow os_windows[MAX_CHILDREN];
size_t num_os_windows;
OSWindow *callback_os_window, *focussed_os_window;
bool close_all_windows;
} GlobalState;
extern GlobalState global_state;
typedef struct {
bool is_visible;
CursorShape shape;
double left, right, top, bottom;
color_type color;
} CursorRenderInfo;
#define call_boss(name, ...) { \
PyObject *cret_ = PyObject_CallMethod(global_state.boss, #name, __VA_ARGS__); \
if (cret_ == NULL) { PyErr_Print(); } \
else Py_DECREF(cret_); \
}
void mark_os_window_for_close(OSWindow* w, bool yes);
bool should_os_window_close(OSWindow* w);
bool should_os_window_be_rendered(OSWindow* w);
void wakeup_main_loop();
void event_loop_wait(double timeout);
void swap_window_buffers(OSWindow *w);
void make_window_context_current(OSWindow *w);
void hide_mouse(OSWindow *w);
void set_os_window_title(OSWindow *w, const char *title);
OSWindow* os_window_for_kitty_window(unsigned int);
OSWindow* current_os_window();
bool drag_scroll(Window *, OSWindow*);
void draw_borders();
void draw_cells(ssize_t, ssize_t, float, float, float, float, Screen *, CursorRenderInfo *);
void draw_cursor(CursorRenderInfo *);
bool draw_cells(ssize_t, ssize_t, float, float, float, float, Screen *, OSWindow *);
void draw_cursor(CursorRenderInfo *, bool);
void update_viewport_size(int, int);
void free_texture(uint32_t*);
void send_image_to_gpu(uint32_t*, const void*, int32_t, int32_t, bool, bool);