vtgl

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

main.c (13742B)


      1 /* See LICENSE for copyright details */
      2 #define GL_GLEXT_PROTOTYPES 1
      3 #include <GLFW/glfw3.h>
      4 
      5 #include "util.h"
      6 
      7 #ifndef VERSION
      8 #define VERSION "unknown"
      9 #endif
     10 
     11 #ifndef _DEBUG
     12 #define do_debug(...)
     13 #include "vtgl.c"
     14 #else
     15 #include <dlfcn.h>
     16 #include <time.h>
     17 
     18 static char *libname = "./vtgl.so";
     19 static void *libhandle;
     20 
     21 typedef void do_terminal_fn(Term *, f32);
     22 typedef void init_callbacks_fn(GLCtx *);
     23 typedef iv2  init_term_fn(Term *, Arena *, iv2);
     24 
     25 #define LIB_FNS              \
     26 	X(debug_begin_frame) \
     27 	X(debug_end_frame)   \
     28 	X(do_terminal)       \
     29 	X(init_callbacks)    \
     30 	X(init_term)
     31 #define X(name) static name ## _fn *name;
     32 LIB_FNS
     33 #undef X
     34 
     35 static void
     36 load_library(const char *lib, Stream *err)
     37 {
     38 	s8 nl = s8("\n");
     39 	/* NOTE: glibc sucks and will crash if this is NULL */
     40 	if (libhandle)
     41 		dlclose(libhandle);
     42 	libhandle = dlopen(lib, RTLD_NOW|RTLD_LOCAL);
     43 	if (!libhandle)
     44 		stream_push_s8s(err, 3, (s8 []){s8("do_debug: dlopen: "), c_str_to_s8(dlerror()), nl});
     45 	#define X(name) \
     46 		name = dlsym(libhandle, #name); \
     47 		if (!name) stream_push_s8s(err, 3, (s8 []){s8("do_debug: dlsym: "), \
     48 			                                   c_str_to_s8(dlerror()), nl});
     49 	LIB_FNS
     50 	#undef X
     51 }
     52 
     53 static void
     54 do_debug(GLCtx *gl, Stream *err)
     55 {
     56 	static os_file_stats updated;
     57 	os_file_stats test = os_get_file_stats(libname);
     58 
     59 	if (os_filetime_changed(test.timestamp, updated.timestamp)) {
     60 		updated = test;
     61 		/* NOTE: sucks but seems to be easiest reliable way to make sure lib is written */
     62 		struct timespec sleep_time = { .tv_nsec = 100e6 };
     63 		nanosleep(&sleep_time, &sleep_time);
     64 		load_library(libname, err);
     65 		if (gl) {
     66 			init_callbacks(gl);
     67 		}
     68 		stream_push_s8(err, s8("Reloaded Main Program\n"));
     69 	}
     70 
     71 	/* TODO: clear debug memory? */
     72 
     73 	if (err->widx) {
     74 		os_write_err_msg(stream_to_s8(err));
     75 		err->widx = 0;
     76 	}
     77 }
     78 
     79 #endif /* _DEBUG */
     80 
     81 static void
     82 gl_debug_logger(u32 src, u32 type, u32 id, u32 lvl, i32 len, const char *msg, const void *data)
     83 {
     84 	(void)src; (void)type; (void)id;
     85 	Stream *err = (Stream *)data;
     86 	stream_push_s8(err, s8("[GL Error "));
     87 	switch (lvl) {
     88 	case GL_DEBUG_SEVERITY_HIGH:         stream_push_s8(err, s8("HIGH]: "));         break;
     89 	case GL_DEBUG_SEVERITY_MEDIUM:       stream_push_s8(err, s8("MEDIUM]: "));       break;
     90 	case GL_DEBUG_SEVERITY_LOW:          stream_push_s8(err, s8("LOW]: "));          break;
     91 	case GL_DEBUG_SEVERITY_NOTIFICATION: stream_push_s8(err, s8("NOTIFICATION]: ")); break;
     92 	default:                             stream_push_s8(err, s8("INVALID]: "));      break;
     93 	}
     94 	stream_push_s8(err, (s8){.len = len, .data = (u8 *)msg});
     95 	stream_push_byte(err, '\n');
     96 	os_write_err_msg(stream_to_s8(err));
     97 	err->widx = 0;
     98 }
     99 
    100 
    101 static void
    102 error_callback(int code, const char *desc)
    103 {
    104 	u8 buf[256];
    105 	Stream err = {.cap = sizeof(buf), .buf = buf};
    106 	stream_push_s8(&err, s8("GLFW Error (0x"));
    107 	stream_push_hex_u64(&err, code);
    108 	stream_push_s8(&err, s8("): "));
    109 	os_write_err_msg(stream_to_s8(&err));
    110 	os_write_err_msg(c_str_to_s8((char *)desc));
    111 	os_write_err_msg(s8("\n"));
    112 }
    113 
    114 static u32
    115 gen_2D_texture(iv2 size, u32 format, u32 filter, u32 *rgba)
    116 {
    117 	/* TODO: logging */
    118 	u32 result;
    119 	glGenTextures(1, &result);
    120 	glBindTexture(GL_TEXTURE_2D, result);
    121 	glTexImage2D(GL_TEXTURE_2D, 0, format, size.w, size.h, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgba);
    122 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
    123 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
    124 	return result;
    125 }
    126 
    127 static void
    128 init_window(Term *t, Arena arena, iv2 window_size)
    129 {
    130 	t->gl.window_size = (v2){.w = window_size.w, .h = window_size.h};
    131 
    132 	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    133 	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    134 	glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
    135 	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    136 
    137 	t->gl.window = glfwCreateWindow(t->gl.window_size.w, t->gl.window_size.h, "vtgl", NULL, NULL);
    138 	if (!t->gl.window) {
    139 		glfwTerminate();
    140 		os_fatal(s8("Failed to spawn GLFW window\n"));
    141 	}
    142 	glfwMakeContextCurrent(t->gl.window);
    143 	glfwSetWindowUserPointer(t->gl.window, t);
    144 	glfwSetWindowAttrib(t->gl.window, GLFW_RESIZABLE, GLFW_TRUE);
    145 
    146 	/* NOTE: make sure the terminal performs a resize after initialization. The
    147 	 * terminal code handles this because it also needs to communicate the size
    148 	 * to the child process */
    149 	t->gl.flags |= NEEDS_RESIZE;
    150 
    151 	/* TODO: swap interval is not needed because we will sleep on waiting for terminal input */
    152 	glfwSwapInterval(1);
    153 
    154 	/* NOTE: Set up OpenGL Render Pipeline */
    155 	glDebugMessageCallback(gl_debug_logger, &t->error_stream);
    156 	glEnable(GL_DEBUG_OUTPUT);
    157 	/* NOTE: shut up useless shader compilation statistics */
    158 	glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE,
    159 	                      GL_DEBUG_SEVERITY_NOTIFICATION,
    160 	                      0, 0, GL_FALSE);
    161 
    162 	glGenVertexArrays(1, &t->gl.vao);
    163 	glBindVertexArray(t->gl.vao);
    164 
    165 	glGenBuffers(ARRAY_COUNT(t->gl.vbos), t->gl.vbos);
    166 
    167 	RenderPushBuffer *rpb = NULL;
    168 	/* NOTE: vertex position buffer */
    169 	glBindBuffer(GL_ARRAY_BUFFER, t->gl.vbos[0]);
    170 	glBufferData(GL_ARRAY_BUFFER, sizeof(rpb->positions), 0, GL_DYNAMIC_DRAW);
    171 	glEnableVertexAttribArray(0);
    172 	glVertexAttribPointer(0, 2, GL_FLOAT, 0, 0, 0);
    173 
    174 	/* NOTE: vertex texture coordinate buffer */
    175 	glBindBuffer(GL_ARRAY_BUFFER, t->gl.vbos[1]);
    176 	glBufferData(GL_ARRAY_BUFFER, sizeof(rpb->texture_coordinates), 0, GL_DYNAMIC_DRAW);
    177 	glEnableVertexAttribArray(1);
    178 	glVertexAttribPointer(1, 2, GL_FLOAT, 0, 0, 0);
    179 
    180 	/* NOTE: vertex colour buffer */
    181 	glBindBuffer(GL_ARRAY_BUFFER, t->gl.vbos[2]);
    182 	glBufferData(GL_ARRAY_BUFFER, sizeof(rpb->colours), 0, GL_DYNAMIC_DRAW);
    183 	glEnableVertexAttribArray(2);
    184 	glVertexAttribPointer(2, 4, GL_FLOAT, 0, 0, 0);
    185 
    186 	/* NOTE: fill in element index buffer */
    187 	i32 *element_indices = alloc(&arena, i32, 6 * ARRAY_COUNT(rpb->positions));
    188 	for (i32 i = 0, j = 0; i < 6 * ARRAY_COUNT(rpb->positions); i += 6, j++) {
    189 		element_indices[i + 0] = 4 * j;
    190 		element_indices[i + 1] = 4 * j + 1;
    191 		element_indices[i + 2] = 4 * j + 2;
    192 		element_indices[i + 3] = 4 * j;
    193 		element_indices[i + 4] = 4 * j + 2;
    194 		element_indices[i + 5] = 4 * j + 3;
    195 	}
    196 	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, t->gl.vbos[4]);
    197 	glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * ARRAY_COUNT(rpb->positions) * sizeof(i32),
    198 	             element_indices, GL_STATIC_DRAW);
    199 
    200 	t->gl.glyph_bitmap_tex = gen_2D_texture(t->gl.glyph_bitmap_dim, GL_RGBA, GL_NEAREST, 0);
    201 	/* NOTE: set pixel 0,0 to white (tile 0,0 is reserved). We can use this texture for
    202 	 * drawing glyphs from the font cache or for drawing plain rectangles */
    203 	u32 white = 0xFFFFFFFF;
    204 	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &white);
    205 
    206 	glEnable(GL_BLEND);
    207 	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    208 
    209 	/* NOTE: Generate an intermediate framebuffer for rendering to. This
    210 	 * allows for additional post processing via a second shader stage */
    211 	glGenFramebuffers(1, &t->gl.fb);
    212 	glBindFramebuffer(GL_FRAMEBUFFER, t->gl.fb);
    213 
    214 	t->gl.fb_tex_unit = 1;
    215 	glActiveTexture(GL_TEXTURE0 + t->gl.fb_tex_unit);
    216 	iv2 ws = {.x = t->gl.window_size.w, .y = t->gl.window_size.h};
    217 	t->gl.fb_tex = gen_2D_texture(ws, GL_RGBA, GL_NEAREST, 0);
    218 	glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, t->gl.fb_tex, 0);
    219 
    220 	glGenBuffers(1, &t->gl.render_shader_ubo);
    221 	glBindBuffer(GL_UNIFORM_BUFFER, t->gl.render_shader_ubo);
    222 	glBufferData(GL_UNIFORM_BUFFER, sizeof(ShaderParameters), 0, GL_DYNAMIC_DRAW);
    223 	glBindBufferBase(GL_UNIFORM_BUFFER, 0, t->gl.render_shader_ubo);
    224 
    225 	glActiveTexture(GL_TEXTURE0);
    226 
    227 	t->gl.flags |= INIT_DEBUG;
    228 }
    229 
    230 static u32
    231 compile_shader(Arena a, u32 type, s8 shader)
    232 {
    233 	u32 sid = glCreateShader(type);
    234 
    235 	glShaderSource(sid, 1, (const char **)&shader.data, (int *)&shader.len);
    236 	glCompileShader(sid);
    237 
    238 	i32 res = 0;
    239 	glGetShaderiv(sid, GL_COMPILE_STATUS, &res);
    240 	if (res != GL_TRUE) {
    241 		i32 len;
    242 		glGetShaderiv(sid, GL_INFO_LOG_LENGTH, &len);
    243 		s8 err = s8alloc(&a, len);
    244 		glGetShaderInfoLog(sid, len, (int *)&err.len, (char *)err.data);
    245 		os_write_err_msg(s8("compile_shader: "));
    246 		os_write_err_msg(err);
    247 		glDeleteShader(sid);
    248 		return 0;
    249 	}
    250 
    251 	return sid;
    252 }
    253 
    254 static u32
    255 program_from_shader_text(s8 vertex, s8 fragment, Arena a)
    256 {
    257 	u32 pid = glCreateProgram();
    258 
    259 	u32 vid = compile_shader(a, GL_VERTEX_SHADER, vertex);
    260 	if (vid == 0) {
    261 		glDeleteProgram(pid);
    262 		return 0;
    263 	}
    264 
    265 	u32 fid = compile_shader(a, GL_FRAGMENT_SHADER, fragment);
    266 	if (fid == 0) {
    267 		glDeleteShader(vid);
    268 		glDeleteProgram(pid);
    269 		return 0;
    270 	}
    271 
    272 	glAttachShader(pid, vid);
    273 	glAttachShader(pid, fid);
    274 	glLinkProgram(pid);
    275 	glValidateProgram(pid);
    276 	glUseProgram(pid);
    277 	glDeleteShader(vid);
    278 	glDeleteShader(fid);
    279 
    280 	return pid;
    281 }
    282 
    283 static void
    284 check_shaders(GLCtx *gl, Arena a, Stream *err)
    285 {
    286 	static char *fs_name[SHADER_LAST] = {
    287 		"frag_render.glsl",
    288 		"frag_for_rects.glsl",
    289 		"frag_post.glsl",
    290 	};
    291 	static char *vs_name[SHADER_LAST] = {
    292 		"vert_for_rects.glsl",
    293 		"vert_for_rects.glsl",
    294 		"vert_for_rects.glsl",
    295 	};
    296 
    297 	static os_file_stats fs_stats[SHADER_LAST], vs_stats[SHADER_LAST];
    298 	static struct {
    299 		s8  name;
    300 		u32 flag;
    301 	} map[SHADER_LAST] = {
    302 		[SHADER_RENDER] = {.name = s8("Render"), .flag = 0},
    303 		[SHADER_RECTS]  = {.name = s8("Rects"),  .flag = 0},
    304 		[SHADER_POST]   = {.name = s8("Post"),   .flag = UPDATE_POST_UNIFORMS},
    305 	};
    306 
    307 	for (u32 i = 0; i < SHADER_LAST; i++) {
    308 		os_file_stats fs_test = os_get_file_stats(fs_name[i]);
    309 		os_file_stats vs_test = os_get_file_stats(vs_name[i]);
    310 
    311 		if (!os_filetime_changed(fs_test.timestamp, fs_stats[i].timestamp) &&
    312 		    !os_filetime_changed(vs_test.timestamp, vs_stats[i].timestamp))
    313 			continue;
    314 
    315 		stream_push_s8s(err, 3, (s8 []){s8("Reloading "), map[i].name, s8(" Shader!\n")});
    316 		fs_stats[i] = fs_test;
    317 		vs_stats[i] = vs_test;
    318 
    319 		s8 vs_text = os_read_file(&a, vs_name[i], vs_stats[i].filesize);
    320 		s8 fs_text = os_read_file(&a, fs_name[i], fs_stats[i].filesize);
    321 		ASSERT(vs_text.len > 0 && fs_text.len > 0);
    322 
    323 		u32 program = program_from_shader_text(vs_text, fs_text, a);
    324 		if (!program)
    325 			continue;
    326 		glDeleteProgram(gl->programs[i]);
    327 		gl->programs[i]  = program;
    328 		gl->flags       |= map[i].flag;
    329 		stream_push_s8s(err, 2, (s8 []){map[i].name, s8(" Program Updated!\n")});
    330 	}
    331 
    332 	if (err->widx) {
    333 		os_write_err_msg(stream_to_s8(err));
    334 		err->widx = 0;
    335 	}
    336 }
    337 
    338 static void
    339 usage(char *argv0, Stream *err)
    340 {
    341 	stream_push_s8(err, s8("usage: "));
    342 	stream_push_s8(err, c_str_to_s8(argv0));
    343 	stream_push_s8(err, s8(" [-v] [-g COLxROW]\n"));
    344 	os_fatal(stream_to_s8(err));
    345 }
    346 
    347 i32
    348 main(i32 argc, char *argv[], char *envp[])
    349 {
    350 	Arena memory = os_new_arena(16 * MEGABYTE);
    351 	Term  term   = {0};
    352 	#ifdef _DEBUG
    353 	{
    354 		Arena debug_memory       = os_new_arena(128 * MEGABYTE);
    355 		term.debug_state         = alloc_(&debug_memory, sizeof(*term.debug_state), 64, 1);
    356 		term.debug_state->memory = debug_memory;
    357 	}
    358 	#endif
    359 
    360 	term.error_stream = stream_alloc(&memory, MEGABYTE / 4);
    361 
    362 	iv2 cells = {.x = -1, .y = -1};
    363 
    364 	char *argv0 = *argv++;
    365 	argc--;
    366 	struct conversion_result cres;
    367 	for (i32 i = 0; i < argc; i++) {
    368 		char *arg = argv[i];
    369 		if (!arg || !arg[0])
    370 			usage(argv0, &term.error_stream);
    371 		if (arg[0] != '-')
    372 			break;
    373 		arg++;
    374 		switch (arg[0]) {
    375 		case 'g':
    376 			if (!argv[i + 1])
    377 				usage(argv0, &term.error_stream);
    378 			cres = i32_from_cstr(argv[i + 1], 'x');
    379 			if (cres.status == CR_SUCCESS)
    380 				cells.w = cres.i;
    381 			cres = i32_from_cstr(cres.unparsed, 0);
    382 			if (cres.status == CR_SUCCESS)
    383 				cells.h = cres.i;
    384 			if (cells.w <= 0 || cells.h <= 0) {
    385 				stream_push_s8(&term.error_stream, s8("ignoring malformed geometry: "));
    386 				stream_push_s8(&term.error_stream, c_str_to_s8(argv[i + 1]));
    387 				stream_push_byte(&term.error_stream, '\n');
    388 			}
    389 			argv++;
    390 			argc--;
    391 			break;
    392 		case 'v':
    393 			stream_push_s8s(&term.error_stream, 2,
    394 			                (s8 []){c_str_to_s8(argv0), s8(" " VERSION "\n")});
    395 			os_fatal(stream_to_s8(&term.error_stream));
    396 		default:
    397 			usage(argv0, &term.error_stream);
    398 		}
    399 	}
    400 	if (term.error_stream.widx) {
    401 		os_write_err_msg(stream_to_s8(&term.error_stream));
    402 		term.error_stream.widx = 0;
    403 	}
    404 
    405 	do_debug(NULL, &term.error_stream);
    406 
    407 	if (!glfwInit())
    408 		os_fatal(s8("Failed to init GLFW\n"));
    409 	glfwSetErrorCallback(error_callback);
    410 
    411 	GLFWmonitor *mon = glfwGetPrimaryMonitor();
    412 	if (!mon) {
    413 		glfwTerminate();
    414 		os_fatal(s8("Failed to get GLFW monitor\n"));
    415 	}
    416 
    417 	iv2 ws;
    418 	glfwGetMonitorWorkarea(mon, NULL, NULL, &ws.w, &ws.h);
    419 	term.gl.glyph_bitmap_dim = ws;
    420 
    421 	iv2 requested_size = init_term(&term, &memory, cells);
    422 	if (requested_size.w > 0 && requested_size.h > 0) {
    423 		glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
    424 		glfwWindowHint(GLFW_FLOATING, GLFW_TRUE);
    425 		ws = requested_size;
    426 	}
    427 
    428 	init_window(&term, memory, ws);
    429 	init_callbacks(&term.gl);
    430 
    431 	os_alloc_ring_buffer(&term.views[0].log, BACKLOG_SIZE);
    432 	line_buf_alloc(&term.views[0].lines, &memory, term.views[0].log.buf, term.cursor.style,
    433 	               BACKLOG_LINES);
    434 	os_alloc_ring_buffer(&term.views[1].log, ALT_BACKLOG_SIZE);
    435 	line_buf_alloc(&term.views[1].lines, &memory, term.views[1].log.buf, term.cursor.style,
    436 	               ALT_BACKLOG_LINES);
    437 
    438 	term.child = os_fork_child("/bin/sh");
    439 
    440 	f64 last_time = os_get_time();
    441 	while (!glfwWindowShouldClose(term.gl.window)) {
    442 		do_debug(&term.gl, &term.error_stream);
    443 
    444 		/* TODO: cpu time excluding waiting for the vblank */
    445 		f64 current_time = os_get_time();
    446 		f32 dt           = current_time - last_time;
    447 		last_time        = current_time;
    448 
    449 		debug_begin_frame(term.debug_state, dt);
    450 
    451 		check_shaders(&term.gl, memory, &term.error_stream);
    452 
    453 		term.arena_for_frame = memory;
    454 
    455 		glfwPollEvents();
    456 		do_terminal(&term, dt);
    457 
    458 		debug_end_frame(term.debug_state, term.gl.window_size);
    459 
    460 		glfwSwapBuffers(term.gl.window);
    461 	}
    462 
    463 	return 0;
    464 }
    465 
    466 #ifdef _DEBUG
    467 /* NOTE: shut up compiler warning that can't be disabled */
    468 DebugMetadata debug_metadata[__COUNTER__];
    469 #endif