vtgl

terminal emulator implemented in OpenGL
git clone anongit@rnpnr.xyz:vtgl.git
Log | Files | Refs | Feed | LICENSE

vtgl.c (41239B)


      1 /* See LICENSE for copyright details */
      2 /* TODO: define this ourselves since we should be loading it at runtime */
      3 #define GL_GLEXT_PROTOTYPES
      4 #include <GL/glcorearb.h>
      5 
      6 #include "vtgl.h"
      7 
      8 #include "config.h"
      9 
     10 #include "font.c"
     11 #include "terminal.c"
     12 
     13 #define LABEL_GL_OBJECT(type, id, s) {s8 _s = (s); glObjectLabel(type, id, _s.len, (c8 *)_s.data);}
     14 
     15 #define REVERSE_VIDEO_MASK (Colour){.r = 0xff, .g = 0xff, .b = 0xff}.rgba
     16 
     17 #define VERTEX_SHADER_TEXT                                       \
     18 "#version 430 core\n"                                            \
     19 "\n"                                                             \
     20 "layout(location = 0) in  vec2 vertex_position;\n"               \
     21 "layout(location = 1) in  vec2 vertex_texture_coordinate;\n"     \
     22 "layout(location = 2) in  vec4 vertex_colour;\n"                 \
     23 "\n"                                                             \
     24 "layout(location = 0) out vec2 fragment_texture_coordinate;\n"   \
     25 "layout(location = 1) out vec4 fragment_colour;\n"               \
     26 "\n"                                                             \
     27 "layout(location = 0) uniform mat4 u_Pmat;\n"                    \
     28 "\n"                                                             \
     29 "void main()\n"                                                  \
     30 "{\n"                                                            \
     31 "    fragment_texture_coordinate = vertex_texture_coordinate;\n" \
     32 "    fragment_colour             = vertex_colour;\n"             \
     33 "\n"                                                             \
     34 "    gl_Position = u_Pmat * vec4(vertex_position, 0.0, 1.0);\n"  \
     35 "}\n"
     36 
     37 static void
     38 set_projection_matrix(GLCtx *gl, u32 stage)
     39 {
     40 	f32 w = gl->window_size.w;
     41 	f32 h = gl->window_size.h;
     42 
     43 	f32 pmat[4 * 4] = {
     44 		2.0/w,   0.0,      0.0,    -1.0,
     45 		0.0,     2.0/h,    0.0,    -1.0,
     46 		0.0,     0.0,     -1.0,     0.0,
     47 		0.0,     0.0,      0.0,     1.0,
     48 	};
     49 
     50 	glProgramUniformMatrix4fv(gl->programs[stage], SHADER_PMAT_LOC, 1, GL_TRUE, pmat);
     51 }
     52 
     53 static u32
     54 compile_shader(Arena a, u32 type, s8 shader)
     55 {
     56 	u32 sid = glCreateShader(type);
     57 
     58 	glShaderSource(sid, 1, (const char **)&shader.data, (int *)&shader.len);
     59 	glCompileShader(sid);
     60 
     61 	i32 res = 0;
     62 	glGetShaderiv(sid, GL_COMPILE_STATUS, &res);
     63 	if (res != GL_TRUE) {
     64 		i32 len;
     65 		glGetShaderiv(sid, GL_INFO_LOG_LENGTH, &len);
     66 		s8 err = s8alloc(&a, len);
     67 		glGetShaderInfoLog(sid, len, (int *)&err.len, (char *)err.data);
     68 		os_write_err_msg(s8("compile_shader: "));
     69 		os_write_err_msg(err);
     70 		glDeleteShader(sid);
     71 		return 0;
     72 	}
     73 
     74 	return sid;
     75 }
     76 
     77 static u32
     78 program_from_shader_text(s8 vertex, s8 fragment, Arena a)
     79 {
     80 	u32 pid = glCreateProgram();
     81 
     82 	u32 vid = compile_shader(a, GL_VERTEX_SHADER, vertex);
     83 	if (vid == 0) {
     84 		glDeleteProgram(pid);
     85 		return 0;
     86 	}
     87 
     88 	u32 fid = compile_shader(a, GL_FRAGMENT_SHADER, fragment);
     89 	if (fid == 0) {
     90 		glDeleteShader(vid);
     91 		glDeleteProgram(pid);
     92 		return 0;
     93 	}
     94 
     95 	glAttachShader(pid, vid);
     96 	glAttachShader(pid, fid);
     97 	glLinkProgram(pid);
     98 	glValidateProgram(pid);
     99 	glUseProgram(pid);
    100 	glDeleteShader(vid);
    101 	glDeleteShader(fid);
    102 
    103 	return pid;
    104 }
    105 
    106 typedef struct {
    107 	Term *t;
    108 	u8   *path;
    109 	s8    info;
    110 	u32   stage;
    111 } queue_shader_reload_ctx;
    112 
    113 static void
    114 update_uniforms(GLCtx *gl, enum shader_stages stage)
    115 {
    116 	switch (stage) {
    117 	case SHADER_RENDER:
    118 	case SHADER_RECTS:
    119 		break;
    120 	case SHADER_POST:
    121 		#define X(name) gl->post.name = glGetUniformLocation(gl->programs[stage], "u_" #name);
    122 		GL_POST_UNIFORMS
    123 		#undef X
    124 		break;
    125 	case SHADER_COUNT: ASSERT(0); break;
    126 	}
    127 }
    128 
    129 static void
    130 reload_shader(GLCtx *gl, PlatformAPI *platform, u8 *path, u32 stage, s8 info, Arena a)
    131 {
    132 	s8 fs_text = platform->read_file(path, &a);
    133 	if (fs_text.len) {
    134 		u32 program = program_from_shader_text(s8(VERTEX_SHADER_TEXT), fs_text, a);
    135 		if (program) {
    136 			glDeleteProgram(gl->programs[stage]);
    137 			gl->programs[stage] = program;
    138 			update_uniforms(gl, stage);
    139 			set_projection_matrix(gl, stage);
    140 		}
    141 	}
    142 	if (info.len) os_write_err_msg(info);
    143 }
    144 
    145 static s8 fs_name[SHADER_COUNT] = {
    146 	[SHADER_RENDER] = s8("frag_render.glsl"),
    147 	[SHADER_RECTS]  = s8("frag_for_rects.glsl"),
    148 	[SHADER_POST]   = s8("frag_post.glsl"),
    149 };
    150 
    151 static void
    152 reload_all_shaders(GLCtx *gl, PlatformAPI *platform, Arena a)
    153 {
    154 	Stream fs_path = stream_alloc(&a, KB(4));
    155 	stream_push_s8(&fs_path, g_shader_path_prefix);
    156 	if (fs_path.widx && fs_path.buf[fs_path.widx - 1] != platform->path_separator)
    157 		stream_push_byte(&fs_path, platform->path_separator);
    158 
    159 	i32 sidx = fs_path.widx;
    160 	for (u32 i = 0; i < SHADER_COUNT; i++) {
    161 		stream_push_s8(&fs_path, fs_name[i]);
    162 		stream_push_byte(&fs_path, 0);
    163 		reload_shader(gl, platform, fs_path.buf, i, (s8){0}, a);
    164 		fs_path.widx = sidx;
    165 	}
    166 
    167 	os_write_err_msg(s8("Reloaded Shaders\n"));
    168 }
    169 
    170 static PLATFORM_FILE_WATCH_CALLBACK_FN(queue_shader_reload)
    171 {
    172 	queue_shader_reload_ctx *ctx = user_ctx;
    173 	ctx->path = path;
    174 	work_queue_insert(ctx->t, WQ_RELOAD_SHADER, ctx);
    175 }
    176 
    177 static v4
    178 normalize_colour(Colour c)
    179 {
    180 	return (v4){.r = c.r / 255.0f, .g = c.g / 255.0f, .b = c.b / 255.0f, .a = c.a / 255.0f};
    181 }
    182 
    183 static void
    184 clear_colour(void)
    185 {
    186 	Colour c = g_colours.data[g_colours.bgidx];
    187 	v4 cc = normalize_colour(c);
    188 	glClearColor(cc.r, cc.g, cc.b, cc.a);
    189 	glClear(GL_COLOR_BUFFER_BIT);
    190 }
    191 
    192 static v2
    193 get_cell_size(FontAtlas *fa)
    194 {
    195 	v2 result = {.w = fa->info.w, .h = fa->info.h};
    196 	return result;
    197 }
    198 
    199 static v2
    200 get_occupied_size(Term *t)
    201 {
    202 	v2 cs     = get_cell_size(&t->fa);
    203 	v2 result = {.x = t->size.w * cs.w, .y = t->size.h * cs.h};
    204 	return result;
    205 }
    206 
    207 static v2
    208 get_terminal_top_left(Term *t)
    209 {
    210 	v2 os     = get_occupied_size(t);
    211 	v2 delta  = {.x = t->gl.window_size.w - os.w, .y = t->gl.window_size.h - os.h};
    212 	v2 result = {.x = delta.x / 2, .y = t->gl.window_size.h - delta.y / 2};
    213 	return result;
    214 }
    215 
    216 static void
    217 resize_terminal(Term *t, PlatformAPI *platform, iv2 window_size)
    218 {
    219 	v2 ws  = v2_from_iv2(window_size);
    220 	ws.w  -= 2 * g_term_margin.w;
    221 	ws.h  -= 2 * g_term_margin.h;
    222 
    223 	iv2 old_size = t->size;
    224 	v2 cs        = get_cell_size(&t->fa);
    225 	t->size.w    = (i32)(ws.w / cs.w);
    226 	t->size.h    = (i32)(ws.h / cs.h);
    227 
    228 	if (t->size.w > ARRAY_COUNT(t->tabs) * 32) {
    229 		t->size.w = ARRAY_COUNT(t->tabs) * 32u;
    230 		stream_push_s8(&t->error_stream, s8("resize: max terminal width is "));
    231 		stream_push_u64(&t->error_stream, t->size.w);
    232 		stream_push_s8(&t->error_stream, s8("; clamping\n"));
    233 		os_write_err_msg(stream_to_s8(&t->error_stream));
    234 		t->error_stream.widx = 0;
    235 	}
    236 
    237 	if (!equal_iv2(old_size, t->size)) {
    238 		t->size = initialize_framebuffer(&t->views[0].fb, t->size);
    239 		initialize_framebuffer(&t->views[1].fb, t->size);
    240 		t->gl.flags |= NEEDS_REFILL;
    241 	}
    242 
    243 	platform->set_terminal_size(t->child, t->size.h, t->size.w, ws.w, ws.h);
    244 
    245 	t->gl.flags |= RESIZE_RENDERER;
    246 	t->gl.flags &= ~NEEDS_RESIZE;
    247 }
    248 
    249 static void
    250 resize(Term *t, PlatformAPI *platform, iv2 window_size)
    251 {
    252 	GLCtx *gl = &t->gl;
    253 	gl->window_size = window_size;
    254 
    255 	glViewport(0, 0, window_size.w, window_size.h);
    256 
    257 	glActiveTexture(GL_TEXTURE0 + gl->fb_tex_unit);
    258 	glBindTexture(GL_TEXTURE_2D, gl->fb_tex);
    259 	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, window_size.w, window_size.h, 0,
    260 	             GL_RGBA, GL_UNSIGNED_BYTE, 0);
    261 
    262 	/* NOTE: reactive the glyph texture unit */
    263 	glActiveTexture(GL_TEXTURE0);
    264 
    265 	u32 buffer_size = t->size.w * t->size.h * sizeof(RenderCell);
    266 	glDeleteBuffers(1, &gl->render_shader_ssbo);
    267 	glGenBuffers(1, &gl->render_shader_ssbo);
    268 	glBindBuffer(GL_SHADER_STORAGE_BUFFER, gl->render_shader_ssbo);
    269 	glBufferData(GL_SHADER_STORAGE_BUFFER, buffer_size, 0, GL_DYNAMIC_DRAW);
    270 	glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, gl->render_shader_ssbo);
    271 	LABEL_GL_OBJECT(GL_BUFFER, gl->render_shader_ssbo, s8("RenderCells"));
    272 	gl->queued_render = 1;
    273 
    274 	v2 cs = get_cell_size(&t->fa);
    275 
    276 	ShaderParameters *sp = &gl->shader_parameters;
    277 	sp->cell_size        = (iv2){.w = cs.w, .h = cs.h};
    278 	sp->strike_min       = (t->fa.info.baseline + 0.40 * t->fa.info.h);
    279 	sp->strike_max       = (t->fa.info.baseline + 0.48 * t->fa.info.h);
    280 	sp->underline_min    = (0.89 * t->fa.info.h);
    281 	sp->underline_max    = (0.96 * t->fa.info.h);
    282 	sp->top_left_margin  = g_term_margin;
    283 	sp->margin_colour    = g_colours.data[g_colours.bgidx].rgba;
    284 	//sp->margin_colour    = 0x7f003f00;
    285 
    286 	sp->term_size_in_pixels = gl->window_size;
    287 	sp->term_size_in_cells  = t->size;
    288 
    289 	for (u32 i = 0; i < SHADER_COUNT; i++)
    290 		set_projection_matrix(gl, i);
    291 
    292 	gl->flags &= ~RESIZE_RENDERER;
    293 }
    294 
    295 static RenderCtx
    296 make_render_ctx(Arena *a, GLCtx *gl, FontAtlas *fa)
    297 {
    298 	RenderCtx result;
    299 	result.gl  = gl;
    300 	result.fa  = fa;
    301 	result.rpb = alloc(a, RenderPushBuffer, 1);
    302 	result.a   = sub_arena(a, MB(4));
    303 	return result;
    304 }
    305 
    306 static iv2
    307 get_gpu_texture_position(v2 cs, u32 gpu_tile_index)
    308 {
    309 	uv2 gpu_position = {.x = gpu_tile_index & 0xFFFF, .y = gpu_tile_index >> 16};
    310 	iv2 result = {.y = gpu_position.y * cs.y, .x = gpu_position.x * cs.x};
    311 	return result;
    312 }
    313 
    314 static u32
    315 get_gpu_glyph_index(Arena a, GLCtx *gl, FontAtlas *fa, u32 codepoint, u32 font_id,
    316                     enum face_style style, CachedGlyph **out)
    317 {
    318 	u32 *data = render_glyph(&a, fa, codepoint, font_id, style, out);
    319 	if (data) {
    320 		CachedGlyph *cg = *out;
    321 		cg->uploaded_to_gpu = 1;
    322 
    323 		v2  cell_size      = get_cell_size(fa);
    324 		iv2 glyph_position = get_gpu_texture_position(cell_size, cg->gpu_tile_index);
    325 		ASSERT(glyph_position.x + cell_size.w * cg->tile_count < gl->glyph_bitmap_dim.x);
    326 		ASSERT(glyph_position.y + cell_size.h < gl->glyph_bitmap_dim.y);
    327 
    328 		glTexSubImage2D(GL_TEXTURE_2D, 0, glyph_position.x, glyph_position.y,
    329 		                cell_size.w * cg->tile_count, cell_size.h,
    330 		                GL_RGBA, GL_UNSIGNED_BYTE, data);
    331 	}
    332 	ASSERT((*out)->uploaded_to_gpu);
    333 	return (*out)->gpu_tile_index;
    334 }
    335 
    336 /* NOTE: this function assumes we are drawing quads */
    337 static void
    338 flush_render_push_buffer(RenderCtx *rc)
    339 {
    340 	BEGIN_TIMED_BLOCK();
    341 	if (rc->rpb->count > 0) {
    342 		u32 n     = rc->rpb->count;
    343 		GLCtx *gl = rc->gl;
    344 		RenderPushBuffer *rpb = rc->rpb;
    345 		ASSERT((n % 4) == 0);
    346 
    347 		BEGIN_NAMED_BLOCK(upload);
    348 		glBindBuffer(GL_ARRAY_BUFFER, gl->vbos[0]);
    349 		glBufferSubData(GL_ARRAY_BUFFER, 0, n * sizeof(*rpb->positions), rpb->positions);
    350 		glBindBuffer(GL_ARRAY_BUFFER, gl->vbos[1]);
    351 		glBufferSubData(GL_ARRAY_BUFFER, 0, n * sizeof(*rpb->texture_coordinates), rpb->texture_coordinates);
    352 		glBindBuffer(GL_ARRAY_BUFFER, gl->vbos[2]);
    353 		glBufferSubData(GL_ARRAY_BUFFER, 0, n * sizeof(*rpb->colours), rpb->colours);
    354 		END_NAMED_BLOCK(upload);
    355 
    356 		glDrawElements(GL_TRIANGLES, 6 * n / 4, GL_UNSIGNED_INT, 0);
    357 	}
    358 	rc->rpb->count = 0;
    359 	END_TIMED_BLOCK();
    360 }
    361 
    362 static u32
    363 get_render_push_buffer_idx(RenderCtx *rc, u32 count)
    364 {
    365 	if (rc->rpb->count + count > RENDER_PUSH_BUFFER_CAP)
    366 		flush_render_push_buffer(rc);
    367 	u32 result  = rc->rpb->count;
    368 	rc->rpb->count += count;
    369 	return result;
    370 }
    371 
    372 static void
    373 push_rect_full(RenderCtx *rc, Rect r, v4 colour, v2 min_tex_coord, v2 max_tex_coord)
    374 {
    375 	BEGIN_TIMED_BLOCK();
    376 
    377 	u32 idx = get_render_push_buffer_idx(rc, 4);
    378 	v2 start = r.pos;
    379 	v2 end   = {.x = r.pos.x + r.size.w, .y = r.pos.y + r.size.h};
    380 
    381 	RenderPushBuffer *rpb = rc->rpb;
    382 	rpb->positions[idx + 0] = (v2){.x = end.x,   .y = end.y  };
    383 	rpb->positions[idx + 1] = (v2){.x = end.x,   .y = start.y};
    384 	rpb->positions[idx + 2] = (v2){.x = start.x, .y = start.y};
    385 	rpb->positions[idx + 3] = (v2){.x = start.x, .y = end.y  };
    386 
    387 	rpb->texture_coordinates[idx + 0] = (v2){.x = max_tex_coord.x, .y = max_tex_coord.y};
    388 	rpb->texture_coordinates[idx + 1] = (v2){.x = max_tex_coord.x, .y = min_tex_coord.y};
    389 	rpb->texture_coordinates[idx + 2] = (v2){.x = min_tex_coord.x, .y = min_tex_coord.y};
    390 	rpb->texture_coordinates[idx + 3] = (v2){.x = min_tex_coord.x, .y = max_tex_coord.y};
    391 
    392 	rpb->colours[idx + 0] = colour;
    393 	rpb->colours[idx + 1] = colour;
    394 	rpb->colours[idx + 2] = colour;
    395 	rpb->colours[idx + 3] = colour;
    396 
    397 	END_TIMED_BLOCK();
    398 }
    399 
    400 static void
    401 push_rect_textured(RenderCtx *rc, Rect r, v4 colour, b32 flip_texture)
    402 {
    403 	v2 min_tex_coord, max_tex_coord;
    404 	if (!flip_texture) {
    405 		max_tex_coord = (v2){.x = 1.0f, .y = 1.0f};
    406 		min_tex_coord = (v2){.x = 0.0f, .y = 0.0f};
    407 	} else {
    408 		max_tex_coord = (v2){.x = 1.0f, .y = 0.0f};
    409 		min_tex_coord = (v2){.x = 0.0f, .y = 1.0f};
    410 	}
    411 	push_rect_full(rc, r, colour, min_tex_coord, max_tex_coord);
    412 }
    413 
    414 static void
    415 push_rect(RenderCtx *rc, Rect r, v4 colour)
    416 {
    417 	f32 max_x = 1.0f / rc->gl->glyph_bitmap_dim.x;
    418 	f32 max_y = 1.0f / rc->gl->glyph_bitmap_dim.y;
    419 	push_rect_full(rc, r, colour, (v2){0}, (v2){.x = max_x, .y = max_y});
    420 }
    421 
    422 static v2
    423 push_s8(RenderCtx *rc, v2 pos, v4 colour, u32 font_id, s8 s)
    424 {
    425 	BEGIN_TIMED_BLOCK();
    426 	CachedGlyph *cg;
    427 	v2 start, end, text_size = {0};
    428 	v2 scale = {.x = 1.0f / rc->gl->glyph_bitmap_dim.x, .y = 1.0f / rc->gl->glyph_bitmap_dim.y};
    429 
    430 	/* TODO: implement GPU based glyph rasterizing */
    431 	pos.x = (u32)pos.x;
    432 	pos.y = (u32)pos.y;
    433 
    434 	while (s.len) {
    435 		u32 cp = get_utf8(&s);
    436 		if (cp == (u32)-1)
    437 			break;
    438 
    439 		get_gpu_glyph_index(rc->a, rc->gl, rc->fa, cp, font_id, FS_NORMAL, &cg);
    440 		cached_glyph_to_uv(rc->fa, cg, &start, &end, scale);
    441 
    442 		Rect r = {.pos = pos, .size = {.x = cg->width, .y = cg->height}};
    443 		r.pos.x += cg->x0;
    444 		r.pos.y -= (cg->height + cg->y0);
    445 
    446 		push_rect_full(rc, r, colour, start, end);
    447 
    448 		text_size.x += cg->advance;
    449 		pos.x       += cg->advance;
    450 		if (cg->height > text_size.y)
    451 			text_size.y = cg->height;
    452 	}
    453 	text_size.x -= cg->advance;
    454 	text_size.w += cg->width;
    455 
    456 	END_TIMED_BLOCK();
    457 
    458 	return text_size;
    459 }
    460 
    461 static v2
    462 measure_text(RenderCtx *rc, u32 font_id, s8 text)
    463 {
    464 	BEGIN_TIMED_BLOCK();
    465 
    466 	v2  result = {0};
    467 
    468 	CachedGlyph *cg;
    469 	while (text.len) {
    470 		u32 cp = get_utf8(&text);
    471 		if (cp == (u32)-1)
    472 			break;
    473 
    474 		get_gpu_glyph_index(rc->a, rc->gl, rc->fa, cp, font_id, FS_NORMAL, &cg);
    475 		if (cg->height > result.y)
    476 			result.y = cg->height;
    477 		result.x += cg->advance;
    478 	}
    479 	result.x -= cg->advance;
    480 	result.x += cg->width;
    481 
    482 	END_TIMED_BLOCK();
    483 
    484 	return result;
    485 }
    486 
    487 /* TODO: this is outdated */
    488 /* NOTE: In this program we render to an offscreen render target that is later drawn
    489  * to the screen as a full window quad. Therefore render_framebuffer must take care
    490  * to handle all necessary padding (window and cell). Outside of here everyone should
    491  * simply care about the terminal in terms of rows and columns (t->size). */
    492 static void
    493 render_framebuffer(Term *t, RenderCell *render_buf, TerminalInput *input, Arena arena)
    494 {
    495 	BEGIN_TIMED_BLOCK();
    496 
    497 	TermView *tv  = t->views + t->view_idx;
    498 	iv2 term_size = t->size;
    499 	/* NOTE: draw whole framebuffer */
    500 	for (u32 row = 0; row < term_size.h; row++) {
    501 		for (u32 col = 0; col < term_size.w; col++) {
    502 			Cell c         = tv->fb.rows[row][col];
    503 			RenderCell *rc = render_buf + (row * term_size.w + col);
    504 
    505 			CachedGlyph *cg;
    506 			rc->gpu_glyph = get_gpu_glyph_index(arena, &t->gl, &t->fa, c.cp, 0,
    507 			                                    c.bg & FS_MASK, &cg);
    508 			rc->fg = c.fg;
    509 			rc->bg = c.bg;
    510 
    511 			/* TODO: there is probably a better way to do this */
    512 			u32 tiles = cg->tile_count;
    513 			if (tiles > 1) {
    514 				ASSERT(tiles == 2);
    515 				rc[1].gpu_glyph = rc->gpu_glyph + 1;
    516 				rc[1].fg        = rc->fg;
    517 				rc[1].bg        = rc->bg;
    518 				col++;
    519 			}
    520 		}
    521 	}
    522 
    523 	/* NOTE: draw selection if active */
    524 	SelectionIterator si = selection_iterator(t->selection.range, tv->fb.rows, term_size.w);
    525 	for (Cell *c = selection_next(&si); c; c = selection_next(&si)) {
    526 		RenderCell *rc = render_buf + si.cursor.y * term_size.w + si.cursor.x;
    527 		rc->fg ^= SHADER_PACK_ATTR(ATTR_INVERSE);
    528 	}
    529 
    530 	END_TIMED_BLOCK();
    531 }
    532 
    533 static void
    534 render_cursor(Term *t, b32 focused, Arena a)
    535 {
    536 	BEGIN_TIMED_BLOCK();
    537 
    538 	iv2 curs = t->cursor.pos;
    539 	Cell *c  = &t->views[t->view_idx].fb.rows[curs.y][curs.x];
    540 	RenderCell *rc = alloc(&a, RenderCell, 3);
    541 
    542 	size rc_off = 1;
    543 	size length = sizeof(RenderCell);
    544 	size offset = sizeof(RenderCell) * (curs.y * t->size.w + curs.x);
    545 
    546 	CachedGlyph *cg;
    547 	rc[1].gpu_glyph = get_gpu_glyph_index(a, &t->gl, &t->fa, c->cp, 0, c->bg & FS_MASK, &cg);
    548 	rc[1].fg        = c->fg;
    549 	rc[1].bg        = c->bg;
    550 
    551 	/* NOTE: draw cursor */
    552 	if (focused && (!(t->mode.win & WM_HIDECURSOR) && t->scroll_offset == 0)) {
    553 		rc[1].fg ^= SHADER_PACK_ATTR(ATTR_INVERSE);
    554 		//if ((t->mode.term & TM_ALTSCREEN) == 0)
    555 		//	rc[1].fg |= SHADER_PACK_ATTR(ATTR_BLINK);
    556 
    557 		if (c->bg & ATTR_WIDE) {
    558 			length   *= 2;
    559 			rc[2].fg ^= SHADER_PACK_ATTR(ATTR_INVERSE);
    560 			//if ((t->mode.term & TM_ALTSCREEN) == 0)
    561 			//	rc[2].fg |= SHADER_PACK_ATTR(ATTR_BLINK);
    562 		} else if (c->bg & ATTR_WDUMMY) {
    563 			rc_off    = 0;
    564 			length   *= 2;
    565 			offset   -= sizeof(RenderCell);
    566 			rc[0].fg ^= SHADER_PACK_ATTR(ATTR_INVERSE);
    567 			//if ((t->mode.term & TM_ALTSCREEN) == 0)
    568 			//	rc[0].fg |= SHADER_PACK_ATTR(ATTR_BLINK);
    569 		}
    570 	}
    571 
    572 	glBufferSubData(GL_SHADER_STORAGE_BUFFER, offset, length, rc + rc_off);
    573 
    574 	END_TIMED_BLOCK();
    575 }
    576 
    577 static iv2
    578 mouse_to_cell_space(Term *t, v2 mouse)
    579 {
    580 	v2 cell_size = get_cell_size(&t->fa);
    581 	v2 top_left  = get_terminal_top_left(t);
    582 
    583 	iv2 result;
    584 	result.x = (i32)((mouse.x - top_left.x) / cell_size.w + 0.5);
    585 	result.y = (i32)((top_left.y - mouse.y) / cell_size.h + 0.5);
    586 
    587 	CLAMP(result.x, 0, t->size.w - 1);
    588 	CLAMP(result.y, 0, t->size.h - 1);
    589 
    590 	return result;
    591 }
    592 
    593 static void
    594 stream_push_selection(Stream *s, Row *rows, Range sel, u32 term_width)
    595 {
    596 	s->errors |= !is_valid_range(sel);
    597 	if (s->errors) return;
    598 
    599 	SelectionIterator si = selection_iterator(sel, rows, term_width);
    600 	u32 last_non_space_idx = 0;
    601 	for (Cell *c = selection_next(&si); c; c = selection_next(&si)) {
    602 		if (c->bg & ATTR_WDUMMY)
    603 			continue;
    604 
    605 		stream_push_s8(s, utf8_encode(c->cp));
    606 		if (!ISSPACE(c->cp))
    607 			last_non_space_idx = s->widx;
    608 
    609 		if (si.next.y != si.cursor.y) {
    610 			s->widx = last_non_space_idx;
    611 			stream_push_byte(s, '\n');
    612 		}
    613 	}
    614 
    615 	s->widx = last_non_space_idx;
    616 }
    617 
    618 static void
    619 begin_selection(Term *t, u32 click_count, v2 mouse)
    620 {
    621 	Selection *sel = &t->selection;
    622 	sel->state     = CLAMP(click_count, SS_NONE, SS_WORDS);
    623 	sel->range.end = INVALID_RANGE_END;
    624 
    625 	iv2 cell = mouse_to_cell_space(t, mouse);
    626 
    627 	switch (sel->state) {
    628 	case SS_WORDS: sel->anchor = sel->range = get_word_around_cell(t, cell); break;
    629 	case SS_CHAR:  sel->anchor = get_char_around_cell(t, cell);              break;
    630 	case SS_NONE:  break;
    631 	}
    632 
    633 	t->gl.queued_render = 1;
    634 }
    635 
    636 static void
    637 update_selection(Term *t, TerminalInput *input)
    638 {
    639 	if (!input->keys[MOUSE_LEFT].ended_down)
    640 		return;
    641 
    642 	if (input->mouse.x == input->last_mouse.x && input->mouse.y == input->last_mouse.y)
    643 		return;
    644 
    645 	Selection *sel = &t->selection;
    646 	iv2 new_p      = mouse_to_cell_space(t, input->mouse);
    647 	Range new, old_range = sel->range;
    648 	switch (sel->state) {
    649 	case SS_WORDS: new = get_word_around_cell(t, new_p); break;
    650 	case SS_CHAR:  new = get_char_around_cell(t, new_p); break;
    651 	case SS_NONE:  /*TODO: INVALID_CODE_PATH;*/ return;  break;
    652 	}
    653 
    654 	if (sel->anchor.start.y < new.start.y) {
    655 		sel->range.start = sel->anchor.start;
    656 		sel->range.end   = new.end;
    657 	} else if (sel->anchor.start.y > new.start.y) {
    658 		sel->range.start = new.start;
    659 		sel->range.end   = sel->anchor.end;
    660 	} else {
    661 		if (new.start.x < sel->anchor.start.x) {
    662 			sel->range.start = new.start;
    663 			sel->range.end   = sel->anchor.end;
    664 		} else {
    665 			sel->range.start = sel->anchor.start;
    666 			sel->range.end   = new.end;
    667 		}
    668 	}
    669 	sel->range = normalize_range(sel->range);
    670 
    671 	if (!equal_range(old_range, sel->range))
    672 		t->gl.queued_render = 1;
    673 }
    674 
    675 KEYBIND_FN(copy)
    676 {
    677 	Stream buf = arena_stream(t->arena_for_frame);
    678 	stream_push_selection(&buf, t->views[t->view_idx].fb.rows, t->selection.range, t->size.w);
    679 	platform->set_clipboard(&buf, a.i);
    680 	return 1;
    681 }
    682 
    683 KEYBIND_FN(paste)
    684 {
    685 	Stream buf    = arena_stream(t->arena_for_frame);
    686 	b32 bracketed = t->mode.win & WM_BRACKPASTE;
    687 
    688 	if (bracketed) stream_push_s8(&buf, s8("\033[200~"));
    689 	b32 success = platform->get_clipboard(&buf, a.i);
    690 	if (bracketed) stream_push_s8(&buf, s8("\033[201~"));
    691 
    692 	if (success)
    693 		platform->write(t->child, stream_to_s8(&buf));
    694 
    695 	return 1;
    696 }
    697 
    698 KEYBIND_FN(scroll)
    699 {
    700 	if (t->mode.term & TM_ALTSCREEN)
    701 		return 0;
    702 
    703 	TermView *tv = t->views + t->view_idx;
    704 
    705 	/* NOTE: do nothing if there aren't enough lines to scrollback */
    706 	if (tv->lines.filled < t->size.h)
    707 		return 1;
    708 
    709 	t->scroll_offset += a.i;
    710 	CLAMP(t->scroll_offset, 0, tv->lines.filled - (t->size.h - 1));
    711 
    712 	t->gl.flags |= NEEDS_REFILL;
    713 
    714 	return 1;
    715 }
    716 
    717 KEYBIND_FN(zoom)
    718 {
    719 	shift_font_sizes(&t->fa, a.i);
    720 	font_atlas_update(&t->fa, t->gl.glyph_bitmap_dim);
    721 	t->gl.flags |= NEEDS_RESIZE;
    722 	return 1;
    723 }
    724 
    725 static void
    726 report_mouse(Term *t, TerminalInput *input, b32 released, b32 beginning)
    727 {
    728 	if ((t->mode.win & WM_MOUSE_X10) && released)
    729 		return;
    730 
    731 	iv2 pos = mouse_to_cell_space(t, input->mouse);
    732 	if ((pos.x > (255 - 32)) || (pos.y > (255 - 32)))
    733 		return;
    734 
    735 	/* TODO: pass the button into this function once they are given in order */
    736 	/* TODO: extended mouse buttons (up to button 11) should also be encoded */
    737 	i32 button = 0;
    738 	if      (input->keys[MOUSE_LEFT].ended_down)   button = 1;
    739 	else if (input->keys[MOUSE_MIDDLE].ended_down) button = 2;
    740 	else if (input->keys[MOUSE_RIGHT].ended_down)  button = 3;
    741 	else if (input->mouse_scroll.y > 0)            button = 4;
    742 	else if (input->mouse_scroll.y < 0)            button = 5;
    743 
    744 	i32 value;
    745 	if (t->mode.win & WM_MOUSE_TRK && !beginning && !released) {
    746 		if (equal_iv2(t->interaction.last_cell_report, pos))
    747 			return;
    748 		value = 32;
    749 	} else {
    750 		value = 0;
    751 	}
    752 	t->interaction.last_cell_report = pos;
    753 
    754 	if ((t->mode.win & WM_MOUSE_SGR) && !button)
    755 		value += 0;
    756 	else if (!button)
    757 		value += 3;
    758 	else if (button >= 4)
    759 		value += 64 + button - 4;
    760 	else
    761 		value += button - 1;
    762 
    763 	if (!(t->mode.win & WM_MOUSE_X10)) {
    764 		value += ((input->modifiers & MOD_SHIFT)   ?  4 : 0)
    765 		       + ((input->modifiers & MOD_ALT)     ?  8 : 0)
    766 		       + ((input->modifiers & MOD_CONTROL) ? 16 : 0);
    767 	}
    768 
    769 	Stream buf = arena_stream(t->arena_for_frame);
    770 	if (t->mode.win & WM_MOUSE_SGR) {
    771 		stream_push_s8(&buf, s8("\x1B[<"));
    772 		stream_push_i64(&buf, value);
    773 		stream_push_byte(&buf, ';');
    774 		stream_push_i64(&buf, pos.x + 1);
    775 		stream_push_byte(&buf, ';');
    776 		stream_push_i64(&buf, pos.y + 1);
    777 		stream_push_byte(&buf, released? 'm' : 'M');
    778 	} else if ((pos.x < (255 - 32)) && (pos.y < (255 - 32))) {
    779 		stream_push_s8(&buf, s8("\x1B[M"));
    780 		stream_push_byte(&buf, 32 + value);
    781 		stream_push_byte(&buf, 32 + pos.x + 1);
    782 		stream_push_byte(&buf, 32 + pos.y + 1);
    783 	} else {
    784 		INVALID_CODE_PATH;
    785 		return;
    786 	}
    787 
    788 	t->platform->write(t->child, stream_to_s8(&buf));
    789 }
    790 
    791 static void
    792 begin_terminal_interaction(Term *t, TerminalInput *input, u32 click_count)
    793 {
    794 	if (t->mode.win & WM_MOUSE_MASK) {
    795 		report_mouse(t, input, 0, 1);
    796 	} else {
    797 		if (pressed_last_frame(input->keys + MOUSE_LEFT))
    798 			begin_selection(t, click_count, input->mouse);
    799 	}
    800 }
    801 
    802 static b32
    803 terminal_interaction(Term *t, PlatformAPI *platform, TerminalInput *input, u32 click_count)
    804 {
    805 
    806 	b32 should_end_interaction = all_mouse_up(input);
    807 	if (t->mode.win & WM_MOUSE_MASK) {
    808 		if (t->mode.win & WM_MOUSE_TRK)
    809 			report_mouse(t, input, should_end_interaction, 0);
    810 	} else {
    811 		update_selection(t, input);
    812 		if (pressed_last_frame(input->keys + MOUSE_MIDDLE))
    813 			paste(t, platform, (Arg){.i = CLIPBOARD_1});
    814 
    815 		b32 shift_down = input->modifiers & MOD_SHIFT;
    816 		if (input->mouse_scroll.y) {
    817 			if (t->mode.term & TM_ALTSCREEN) {
    818 				iptr child = t->child;
    819 				if (input->mouse_scroll.y > 0) {
    820 					if (shift_down) platform->write(child, s8("\x1B[5;2~"));
    821 					else            platform->write(child, s8("\x19"));
    822 				} else {
    823 					if (shift_down) platform->write(child, s8("\x1B[6;2~"));
    824 					else            platform->write(child, s8("\x05"));
    825 				}
    826 			} else {
    827 				Arg a = {.i = (i32)input->mouse_scroll.y};
    828 				if (shift_down)
    829 				    a.i *= 5;
    830 				scroll(t, platform, a);
    831 			}
    832 		}
    833 	}
    834 
    835 	return should_end_interaction;
    836 }
    837 
    838 DEBUG_EXPORT VTGL_HANDLE_KEYS_FN(vtgl_handle_keys)
    839 {
    840 	Term *t               = memory->memory;
    841 	PlatformAPI *platform = &memory->platform_api;
    842 	iptr child            = t->child;
    843 
    844 	#ifdef _DEBUG
    845 	if (key == KEY_F1 && action == ACT_PRESS) {
    846 		dump_lines_to_file(t);
    847 		return;
    848 	}
    849 	if (key == KEY_F11 && action == ACT_PRESS) {
    850 		/* TODO: probably move this into the debug frame start */
    851 		DebugState *ds = memory->debug_memory;
    852 		ds->paused = !ds->paused;
    853 		return;
    854 	}
    855 	if (key == KEY_F12 && action == ACT_PRESS) {
    856 		t->gl.flags ^= DRAW_DEBUG_OVERLAY;
    857 		input->window_refreshed = 1;
    858 		return;
    859 	}
    860 	#endif
    861 
    862 	/* NOTE: handle mapped keybindings */
    863 	u32 enc = ENCODE_KEY(action, modifiers, key);
    864 	for (u32 i = 0; i < ARRAY_COUNT(g_hotkeys); i++) {
    865 		struct hotkey *hk = g_hotkeys + i;
    866 		if (hk->key == enc) {
    867 			b32 handled = hk->fn(t, &memory->platform_api, hk->arg);
    868 			if (handled)
    869 				return;
    870 		}
    871 	}
    872 
    873 	/* NOTE: send control sequences */
    874 	if (modifiers & MOD_CONTROL && action != ACT_RELEASE) {
    875 		/* TODO: this is wrong. look up where 8-bit modifiers should be sent */
    876 		if (0 && t->mode.win & WM_8BIT) {
    877 			if (key < 0x7F) {
    878 				platform->write(child, utf8_encode(key | 0x80));
    879 				return;
    880 			}
    881 		} else if (BETWEEN(key, 0x40, 0x5F)) {
    882 			platform->write(child, utf8_encode(key - 0x40));
    883 			return;
    884 		}
    885 	}
    886 
    887 	/* TODO: construct a hash table of bound keys */
    888 	switch (ENCODE_KEY(action, 0, key)) {
    889 	case ENCODE_KEY(ACT_PRESS,  0, KEY_ESCAPE):
    890 	case ENCODE_KEY(ACT_REPEAT, 0, KEY_ESCAPE):
    891 		platform->write(child, s8("\x1B"));
    892 		break;
    893 	case ENCODE_KEY(ACT_PRESS,  0, KEY_TAB):
    894 	case ENCODE_KEY(ACT_REPEAT, 0, KEY_TAB):
    895 		platform->write(child, s8("\t"));
    896 		break;
    897 	case ENCODE_KEY(ACT_PRESS,  0, KEY_ENTER):
    898 	case ENCODE_KEY(ACT_REPEAT, 0, KEY_ENTER):
    899 		platform->write(child, s8("\r"));
    900 		break;
    901 	case ENCODE_KEY(ACT_PRESS,  0, KEY_BACKSPACE):
    902 	case ENCODE_KEY(ACT_REPEAT, 0, KEY_BACKSPACE):
    903 		platform->write(child, s8("\x7F"));
    904 		break;
    905 	case ENCODE_KEY(ACT_PRESS,  0, KEY_UP):
    906 	case ENCODE_KEY(ACT_REPEAT, 0, KEY_UP):
    907 		if (t->mode.win & WM_APPCURSOR)
    908 			platform->write(child, s8("\x1BOA"));
    909 		else
    910 			platform->write(child, s8("\x1B[A"));
    911 		break;
    912 	case ENCODE_KEY(ACT_PRESS,  0, KEY_DOWN):
    913 	case ENCODE_KEY(ACT_REPEAT, 0, KEY_DOWN):
    914 		if (t->mode.win & WM_APPCURSOR)
    915 			platform->write(child, s8("\x1BOB"));
    916 		else
    917 			platform->write(child, s8("\x1B[B"));
    918 		break;
    919 	case ENCODE_KEY(ACT_PRESS,  0, KEY_RIGHT):
    920 	case ENCODE_KEY(ACT_REPEAT, 0, KEY_RIGHT):
    921 		if (t->mode.win & WM_APPCURSOR)
    922 			platform->write(child, s8("\x1BOC"));
    923 		else
    924 			platform->write(child, s8("\x1B[C"));
    925 		break;
    926 	case ENCODE_KEY(ACT_PRESS,  0, KEY_LEFT):
    927 	case ENCODE_KEY(ACT_REPEAT, 0, KEY_LEFT):
    928 		if (t->mode.win & WM_APPCURSOR)
    929 			platform->write(child, s8("\x1BOD"));
    930 		else
    931 			platform->write(child, s8("\x1B[D"));
    932 		break;
    933 	case ENCODE_KEY(ACT_PRESS,  0, KEY_PAGE_UP):
    934 	case ENCODE_KEY(ACT_REPEAT, 0, KEY_PAGE_UP):
    935 		if      (modifiers & MOD_CONTROL) platform->write(child, s8("\x1B[5;5~"));
    936 		else if (modifiers & MOD_SHIFT)   platform->write(child, s8("\x1B[5;2~"));
    937 		else                              platform->write(child, s8("\x1B[5~"));
    938 		break;
    939 	case ENCODE_KEY(ACT_PRESS,  0, KEY_PAGE_DOWN):
    940 	case ENCODE_KEY(ACT_REPEAT, 0, KEY_PAGE_DOWN):
    941 		if      (modifiers & MOD_CONTROL) platform->write(child, s8("\x1B[6;5~"));
    942 		else if (modifiers & MOD_SHIFT)   platform->write(child, s8("\x1B[6;2~"));
    943 		else                              platform->write(child, s8("\x1B[6~"));
    944 		break;
    945 	}
    946 }
    947 
    948 static b32
    949 should_start_interaction(TerminalInput *input)
    950 {
    951 	b32 result = input->mouse_scroll.y || input->mouse_scroll.x ||
    952 	             pressed_last_frame(input->keys + MOUSE_LEFT)   ||
    953 	             pressed_last_frame(input->keys + MOUSE_MIDDLE) ||
    954 	             pressed_last_frame(input->keys + MOUSE_RIGHT);
    955 	return result;
    956 }
    957 
    958 static void
    959 begin_interaction(Term *t, InteractionState *is, TerminalInput *input)
    960 {
    961 	is->click_count++;
    962 	if (is->multi_click_t < 0)
    963 		is->multi_click_t = INTERACTION_MULTI_CLICK_TIME;
    964 
    965 	if (is->hot.type != IS_NONE) {
    966 		if (is->hot.type == IS_AUTO) {
    967 			//switch (is->hot.var.type) {
    968 			//	/* TODO: start the interaction */
    969 			//}
    970 		}
    971 
    972 		is->active = is->hot;
    973 
    974 		switch (is->active.type) {
    975 		case IS_TERM:
    976 			begin_terminal_interaction(t, input, is->click_count);
    977 			break;
    978 		default:
    979 			break;
    980 		}
    981 	} else {
    982 		is->active = (Interaction){.type = IS_NOP};
    983 	}
    984 }
    985 
    986 static void
    987 end_interaction(InteractionState *is, TerminalInput *input)
    988 {
    989 	is->active = (Interaction){.type = IS_NONE};
    990 }
    991 
    992 static void
    993 handle_interactions(Term *t, TerminalInput *input, PlatformAPI *platform)
    994 {
    995 	InteractionState *is = &t->interaction;
    996 
    997 	ButtonState *mouse_left = input->keys + MOUSE_LEFT;
    998 
    999 	is->multi_click_t -= dt_for_frame;
   1000 	if (!mouse_left->ended_down && is->multi_click_t < 0) {
   1001 		is->click_count = 0;
   1002 	}
   1003 
   1004 	if (is->hot.type != IS_NONE) {
   1005 		if (should_start_interaction(input)) {
   1006 			end_interaction(is, input);
   1007 			begin_interaction(t, is, input);
   1008 		}
   1009 	}
   1010 
   1011 	switch (is->active.type) {
   1012 	case IS_NONE: break;
   1013 	case IS_NOP:  break;
   1014 	case IS_SET:  end_interaction(is, input); break;
   1015 	case IS_AUTO:  /* TODO */ break;
   1016 	case IS_DRAG:  /* TODO */ break;
   1017 	case IS_DEBUG: /* TODO */ break;
   1018 	case IS_TERM: {
   1019 		if (terminal_interaction(t, platform, input, is->click_count))
   1020 			end_interaction(is, input);
   1021 	} break;
   1022 	}
   1023 }
   1024 
   1025 static void
   1026 gl_debug_logger(u32 src, u32 type, u32 id, u32 lvl, i32 len, const char *msg, const void *data)
   1027 {
   1028 	(void)src; (void)type; (void)id;
   1029 	Stream *err = (Stream *)data;
   1030 	stream_push_s8(err, s8("[GL Error "));
   1031 	switch (lvl) {
   1032 	case GL_DEBUG_SEVERITY_HIGH:         stream_push_s8(err, s8("HIGH]: "));         break;
   1033 	case GL_DEBUG_SEVERITY_MEDIUM:       stream_push_s8(err, s8("MEDIUM]: "));       break;
   1034 	case GL_DEBUG_SEVERITY_LOW:          stream_push_s8(err, s8("LOW]: "));          break;
   1035 	case GL_DEBUG_SEVERITY_NOTIFICATION: stream_push_s8(err, s8("NOTIFICATION]: ")); break;
   1036 	default:                             stream_push_s8(err, s8("INVALID]: "));      break;
   1037 	}
   1038 	stream_push_s8(err, (s8){.len = len, .data = (u8 *)msg});
   1039 	stream_push_byte(err, '\n');
   1040 	os_write_err_msg(stream_to_s8(err));
   1041 	err->widx = 0;
   1042 }
   1043 
   1044 static u32
   1045 gen_2D_texture(iv2 size, u32 format, u32 filter, u32 *rgba)
   1046 {
   1047 	/* TODO: logging */
   1048 	u32 result;
   1049 	glGenTextures(1, &result);
   1050 	glBindTexture(GL_TEXTURE_2D, result);
   1051 	glTexImage2D(GL_TEXTURE_2D, 0, format, size.w, size.h, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgba);
   1052 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
   1053 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
   1054 	return result;
   1055 }
   1056 
   1057 DEBUG_EXPORT VTGL_INITIALIZE_FN(vtgl_initialize)
   1058 {
   1059 	Term *t = (Term *)memory->memory;
   1060 	Arena a = {.beg = (u8 *)(t + 1), .end = memory->memory + memory->memory_size};
   1061 
   1062 	t->platform = &memory->platform_api;
   1063 
   1064 	t->cursor.state = CURSOR_NORMAL;
   1065 	cursor_reset(t);
   1066 
   1067 	memory->platform_api.allocate_ring_buffer(&t->views[0].log, BACKLOG_SIZE);
   1068 	line_buf_alloc(&t->views[0].lines, &a, t->views[0].log.buf, t->cursor.style, BACKLOG_LINES);
   1069 	memory->platform_api.allocate_ring_buffer(&t->views[1].log, ALT_BACKLOG_SIZE);
   1070 	line_buf_alloc(&t->views[1].lines, &a, t->views[1].log.buf, t->cursor.style, ALT_BACKLOG_LINES);
   1071 
   1072 	t->views[0].fb.backing_store = memory_block_from_arena(&a, MB(2));
   1073 	t->views[1].fb.backing_store = memory_block_from_arena(&a, MB(2));
   1074 
   1075 	t->gl.glyph_bitmap_dim = monitor_size;
   1076 	init_fonts(&t->fa, &a, t->gl.glyph_bitmap_dim);
   1077 	selection_clear(&t->selection);
   1078 	v2 cs = get_cell_size(&t->fa);
   1079 	iv2 requested_size = {
   1080 		.x = cs.x * requested_cells.x + 2 * g_term_margin.x,
   1081 		.y = cs.y * requested_cells.y + 2 * g_term_margin.y,
   1082 	};
   1083 	if (requested_cells.x < 0 || requested_cells.y)
   1084 		requested_size = (iv2){.x = -1, .y = -1};
   1085 
   1086 	t->size = (iv2){.x = 1, .y = 1};
   1087 	initialize_framebuffer(&t->views[0].fb, t->size);
   1088 	initialize_framebuffer(&t->views[1].fb, t->size);
   1089 
   1090 	t->work_queue_items    = alloc(&a, typeof(*t->work_queue_items), 1 << 6);
   1091 	t->work_queue_capacity = 1 << 6;
   1092 
   1093 	queue_shader_reload_ctx *reload_ctxs = alloc(&a, typeof(*reload_ctxs), SHADER_COUNT);
   1094 
   1095 	s8 shader_infos[SHADER_COUNT] = {
   1096 		[SHADER_POST]   = s8("Post Processing Shader Reloaded!\n"),
   1097 		[SHADER_RECTS]  = s8("UI Shader Reloaded!\n"),
   1098 		[SHADER_RENDER] = s8("Render Shader Reloaded!\n"),
   1099 	};
   1100 
   1101 	for (u32 i = 0; i < SHADER_COUNT; i++) {
   1102 		Stream path = arena_stream(a);
   1103 		stream_push_s8(&path, g_shader_path_prefix);
   1104 		if (path.widx && path.buf[path.widx - 1] != memory->platform_api.path_separator)
   1105 			stream_push_byte(&path, memory->platform_api.path_separator);
   1106 
   1107 		queue_shader_reload_ctx *src = reload_ctxs + i;
   1108 		src->info  = shader_infos[i];
   1109 		src->stage = i;
   1110 		src->t     = t;
   1111 		stream_push_s8(&path, fs_name[i]);
   1112 		stream_push_byte(&path, 0);
   1113 		memory->platform_api.add_file_watch(path.buf, queue_shader_reload, src);
   1114 		a.beg = path.buf + path.widx;
   1115 	}
   1116 
   1117 	t->error_stream    = stream_alloc(&a, KB(256));
   1118 	t->saved_title     = stream_alloc(&a, KB(16));
   1119 	t->arena_for_frame = a;
   1120 	t->child           = child;
   1121 
   1122 	/* NOTE: Set up OpenGL Render Pipeline */
   1123 	glDebugMessageCallback(gl_debug_logger, &t->error_stream);
   1124 	glEnable(GL_DEBUG_OUTPUT);
   1125 	/* NOTE: shut up useless shader compilation statistics */
   1126 	glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE,
   1127 	                      GL_DEBUG_SEVERITY_NOTIFICATION,
   1128 	                      0, 0, GL_FALSE);
   1129 
   1130 	glGenVertexArrays(1, &t->gl.vao);
   1131 	glBindVertexArray(t->gl.vao);
   1132 
   1133 	glGenBuffers(ARRAY_COUNT(t->gl.vbos), t->gl.vbos);
   1134 
   1135 	RenderPushBuffer *rpb = NULL;
   1136 	/* NOTE: vertex position buffer */
   1137 	glBindBuffer(GL_ARRAY_BUFFER, t->gl.vbos[0]);
   1138 	glBufferData(GL_ARRAY_BUFFER, sizeof(rpb->positions), 0, GL_DYNAMIC_DRAW);
   1139 	glEnableVertexAttribArray(0);
   1140 	glVertexAttribPointer(0, 2, GL_FLOAT, 0, 0, 0);
   1141 
   1142 	/* NOTE: vertex texture coordinate buffer */
   1143 	glBindBuffer(GL_ARRAY_BUFFER, t->gl.vbos[1]);
   1144 	glBufferData(GL_ARRAY_BUFFER, sizeof(rpb->texture_coordinates), 0, GL_DYNAMIC_DRAW);
   1145 	glEnableVertexAttribArray(1);
   1146 	glVertexAttribPointer(1, 2, GL_FLOAT, 0, 0, 0);
   1147 
   1148 	/* NOTE: vertex colour buffer */
   1149 	glBindBuffer(GL_ARRAY_BUFFER, t->gl.vbos[2]);
   1150 	glBufferData(GL_ARRAY_BUFFER, sizeof(rpb->colours), 0, GL_DYNAMIC_DRAW);
   1151 	glEnableVertexAttribArray(2);
   1152 	glVertexAttribPointer(2, 4, GL_FLOAT, 0, 0, 0);
   1153 
   1154 	/* NOTE: fill in element index buffer */
   1155 	i32 *element_indices = alloc(&a, i32, 6 * ARRAY_COUNT(rpb->positions));
   1156 	for (i32 i = 0, j = 0; i < 6 * ARRAY_COUNT(rpb->positions); i += 6, j++) {
   1157 		element_indices[i + 0] = 4 * j;
   1158 		element_indices[i + 1] = 4 * j + 1;
   1159 		element_indices[i + 2] = 4 * j + 2;
   1160 		element_indices[i + 3] = 4 * j;
   1161 		element_indices[i + 4] = 4 * j + 2;
   1162 		element_indices[i + 5] = 4 * j + 3;
   1163 	}
   1164 	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, t->gl.vbos[4]);
   1165 	glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * ARRAY_COUNT(rpb->positions) * sizeof(i32),
   1166 	             element_indices, GL_STATIC_DRAW);
   1167 
   1168 	t->gl.glyph_bitmap_tex = gen_2D_texture(t->gl.glyph_bitmap_dim, GL_RGBA, GL_NEAREST, 0);
   1169 	/* NOTE: set pixel 0,0 to white (tile 0,0 is reserved). We can use this texture for
   1170 	 * drawing glyphs from the font cache or for drawing plain rectangles */
   1171 	u32 white = 0xFFFFFFFF;
   1172 	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &white);
   1173 	LABEL_GL_OBJECT(GL_TEXTURE, t->gl.glyph_bitmap_tex, s8("Glyph_Bitmap"));
   1174 
   1175 	glEnable(GL_BLEND);
   1176 	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
   1177 
   1178         glDisable(GL_DEPTH_TEST);
   1179         glDisable(GL_CULL_FACE);
   1180 
   1181 	/* NOTE: Generate an intermediate framebuffer for rendering to. This
   1182 	 * allows for additional post processing via a second shader stage */
   1183 	glGenFramebuffers(1, &t->gl.fb);
   1184 	glBindFramebuffer(GL_FRAMEBUFFER, t->gl.fb);
   1185 
   1186 	t->gl.fb_tex_unit = 1;
   1187 	glActiveTexture(GL_TEXTURE0 + t->gl.fb_tex_unit);
   1188 	iv2 ws = monitor_size;
   1189 	t->gl.fb_tex = gen_2D_texture(ws, GL_RGBA, GL_NEAREST, 0);
   1190 	glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, t->gl.fb_tex, 0);
   1191 	LABEL_GL_OBJECT(GL_TEXTURE, t->gl.fb_tex, s8("Framebuffer_Texture"));
   1192 
   1193 	glGenBuffers(1, &t->gl.render_shader_ubo);
   1194 	glBindBuffer(GL_UNIFORM_BUFFER, t->gl.render_shader_ubo);
   1195 	glBufferData(GL_UNIFORM_BUFFER, sizeof(ShaderParameters), 0, GL_DYNAMIC_DRAW);
   1196 	glBindBufferBase(GL_UNIFORM_BUFFER, 0, t->gl.render_shader_ubo);
   1197 	LABEL_GL_OBJECT(GL_BUFFER, t->gl.render_shader_ubo, s8("ShaderParameters"));
   1198 
   1199 	glActiveTexture(GL_TEXTURE0);
   1200 
   1201 	reload_all_shaders(&t->gl, &memory->platform_api, a);
   1202 
   1203 	return requested_size;
   1204 }
   1205 
   1206 DEBUG_EXPORT VTGL_ACTIVE_SELECTION_FN(vtgl_active_selection)
   1207 {
   1208 	Term *t      = memory->memory;
   1209 	Range result = t->selection.range;
   1210 	if (out) stream_push_selection(out, t->views[t->view_idx].fb.rows, result, t->size.w);
   1211 	return result;
   1212 }
   1213 
   1214 DEBUG_EXPORT VTGL_RENDER_FRAME_FN(vtgl_render_frame)
   1215 {
   1216 	BEGIN_TIMED_BLOCK();
   1217 
   1218 	Term *t = memory->memory;
   1219 
   1220 	dt_for_frame = input->dt;
   1221 
   1222 	TempArena temp_arena = begin_temp_arena(&arena);
   1223 
   1224 	i32 queue_item;
   1225 	while ((queue_item = work_queue_pop(&t->work_queue, t->work_queue_capacity)) != -1) {
   1226 		work_queue_pop_commit(&t->work_queue);
   1227 		work_queue_entry *entry = t->work_queue_items + queue_item;
   1228 		switch (entry->type) {
   1229 		case WQ_RELOAD_SHADER: {
   1230 			queue_shader_reload_ctx *ctx = entry->ctx;
   1231 			reload_shader(&t->gl, &memory->platform_api, ctx->path, ctx->stage,
   1232 			              ctx->info, arena);
   1233 		} break;
   1234 		case WQ_RELOAD_ALL_SHADERS: {
   1235 			reload_all_shaders(&t->gl, &memory->platform_api, arena);
   1236 		} break;
   1237 		default: INVALID_CODE_PATH;
   1238 		}
   1239 	}
   1240 
   1241 	/* NOTE: default state which can be overwritten later in the frame */
   1242 	/* TODO: if (!t->ui_active) */
   1243 	{
   1244 		t->interaction.hot.type = IS_TERM;
   1245 	}
   1246 
   1247 	glBindTexture(GL_TEXTURE_2D, t->gl.glyph_bitmap_tex);
   1248 
   1249 	BEGIN_NAMED_BLOCK(update_render);
   1250 
   1251 	RenderCtx rc = make_render_ctx(&arena, &t->gl, &t->fa);
   1252 
   1253 	glUseProgram(t->gl.programs[SHADER_RENDER]);
   1254 	glBindFramebuffer(GL_FRAMEBUFFER, t->gl.fb);
   1255 	clear_colour();
   1256 
   1257 	/* NOTE(rnp): spin lock here so that we don't have to deal with rescheduling
   1258 	 * a redraw. Remember that this failing is already unlikely. */
   1259 	while (atomic_exchange_n(&t->resize_lock, 1) != 0);
   1260 
   1261 	if (t->gl.flags & RESIZE_RENDERER)
   1262 		resize(t, &memory->platform_api, input->window_size);
   1263 
   1264 	if (t->gl.queued_render) {
   1265 		t->gl.queued_render = 0;
   1266 		u32 cell_count = t->size.h * t->size.w;
   1267 		RenderCell *render_buf = alloc(&arena, RenderCell, cell_count);
   1268 		render_framebuffer(t, render_buf, input, arena);
   1269 		glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0,
   1270 		                cell_count * sizeof(*render_buf), render_buf);
   1271 	}
   1272 	render_cursor(t, input->window_focused, arena);
   1273 
   1274 	t->resize_lock = 0;
   1275 
   1276 	ShaderParameters *sp = &t->gl.shader_parameters;
   1277 	sp->blink_parameter += 2 * PI * g_blink_speed * dt_for_frame;
   1278 	if (sp->blink_parameter > 2 * PI) sp->blink_parameter -= 2 * PI;
   1279 	sp->reverse_video_mask = REVERSE_VIDEO_MASK * !!(t->mode.win & WM_REVERSE);
   1280 
   1281 	glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(*sp), sp);
   1282 
   1283 	push_rect_textured(&rc, (Rect){.size = v2_from_iv2(t->gl.window_size)}, (v4){0}, 0);
   1284 	flush_render_push_buffer(&rc);
   1285 
   1286 	static f32 param = 0;
   1287 	static f32 p_scale = 1;
   1288 	param += p_scale * 0.005 * dt_for_frame;
   1289 	if (param > 1.0f || param < 0)
   1290 		p_scale *= -1.0f;
   1291 
   1292 	glBindFramebuffer(GL_FRAMEBUFFER, 0);
   1293 
   1294 	clear_colour();
   1295 	glUseProgram(t->gl.programs[SHADER_POST]);
   1296 	glUniform1i(t->gl.post.texslot, t->gl.fb_tex_unit);
   1297 	glUniform1f(t->gl.post.param, param);
   1298 
   1299 	push_rect_textured(&rc, (Rect){.size = v2_from_iv2(t->gl.window_size)}, (v4){0}, 1);
   1300 	flush_render_push_buffer(&rc);
   1301 	END_NAMED_BLOCK(update_render);
   1302 
   1303 	/* NOTE: this happens at the end so that ui stuff doesn't go through the post
   1304 	 * processing/effects shader */
   1305 	glUseProgram(t->gl.programs[SHADER_RECTS]);
   1306 
   1307 	BEGIN_NAMED_BLOCK(debug_overlay);
   1308 	draw_debug_overlay(memory, input, &rc);
   1309 	END_NAMED_BLOCK(debug_overlay);
   1310 
   1311 	end_temp_arena(temp_arena);
   1312 
   1313 	END_TIMED_BLOCK();
   1314 }
   1315 
   1316 DEBUG_EXPORT VTGL_FRAME_STEP_FN(vtgl_frame_step)
   1317 {
   1318 	BEGIN_NAMED_BLOCK(debug_end_frame);
   1319 	debug_frame_end(memory, input);
   1320 	END_NAMED_BLOCK(debug_end_frame);
   1321 
   1322 	BEGIN_TIMED_BLOCK();
   1323 
   1324 	Term *t = memory->memory;
   1325 
   1326 	dt_for_frame = input->dt;
   1327 
   1328 	t->temp_arena = begin_temp_arena(&t->arena_for_frame);
   1329 
   1330 	if (t->gl.flags & NEEDS_RESIZE || !equal_iv2(input->window_size, t->gl.window_size)) {
   1331 		/* NOTE(rnp): we skip the resize this time through so that we don't add
   1332 		 * input latency waiting for the render thread to release this lock */
   1333 		if (atomic_exchange_n(&t->resize_lock, 1) == 0) {
   1334 			resize_terminal(t, &memory->platform_api, input->window_size);
   1335 			t->resize_lock = 0;
   1336 		} else {
   1337 			input->pending_updates = 1;
   1338 		}
   1339 	}
   1340 
   1341 	if (input->executable_reloaded) {
   1342 		work_queue_insert(t, WQ_RELOAD_ALL_SHADERS, 0);
   1343 	}
   1344 
   1345 	BEGIN_NAMED_BLOCK(mouse_and_keyboard_input);
   1346 	if (input->character_input.len) {
   1347 		if (t->scroll_offset) {
   1348 			t->scroll_offset = 0;
   1349 			t->gl.flags |= NEEDS_REFILL;
   1350 		}
   1351 		memory->platform_api.write(t->child, input->character_input);
   1352 	}
   1353 
   1354 	handle_interactions(t, input, &memory->platform_api);
   1355 	END_NAMED_BLOCK(mouse_and_keyboard_input);
   1356 
   1357 	if (t->gl.flags & NEEDS_REFILL) {
   1358 		blit_lines(t, t->arena_for_frame);
   1359 		t->gl.queued_render = 1;
   1360 	}
   1361 
   1362 	BEGIN_NAMED_BLOCK(input_from_child);
   1363 	if (input->data_available) {
   1364 		RingBuf *rb = &t->views[t->view_idx].log;
   1365 		s8 buffer = {.len = rb->cap - t->unprocessed_bytes, .data = rb->buf + rb->widx};
   1366 
   1367 		size bytes_read = memory->platform_api.read(t->child, buffer);
   1368 		ASSERT(bytes_read <= rb->cap);
   1369 		commit_to_rb(t->views + t->view_idx, bytes_read);
   1370 
   1371 		t->unprocessed_bytes += bytes_read;
   1372 		s8 raw = {
   1373 			.len  = t->unprocessed_bytes,
   1374 			.data = rb->buf + (rb->widx - t->unprocessed_bytes)
   1375 		};
   1376 		handle_input(t, t->arena_for_frame, raw);
   1377 		t->gl.queued_render = 1;
   1378 	}
   1379 	END_NAMED_BLOCK(input_from_child);
   1380 
   1381 	end_temp_arena(t->temp_arena);
   1382 
   1383 	END_TIMED_BLOCK();
   1384 
   1385 	return t->gl.queued_render || input->window_refreshed || t->gl.flags & DRAW_DEBUG_OVERLAY
   1386 	       || !work_queue_empty(&t->work_queue, t->work_queue_capacity);
   1387 }
   1388 
   1389 #ifdef _DEBUG
   1390 #include "debug.c"
   1391 #endif