vtgl

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

main.c (10388B)


      1 /* See LICENSE for copyright details */
      2 #define GL_GLEXT_PROTOTYPES 1
      3 #include <GL/glcorearb.h>
      4 #include <GL/glext.h>
      5 #include <GLFW/glfw3.h>
      6 
      7 #include "util.h"
      8 
      9 #ifndef VERSION
     10 #define VERSION "unknown"
     11 #endif
     12 
     13 #ifndef _DEBUG
     14 #define do_debug(...)
     15 #include "vtgl.c"
     16 #else
     17 #include <dlfcn.h>
     18 #include <time.h>
     19 
     20 static char *libname = "./vtgl.so";
     21 static void *libhandle;
     22 
     23 typedef void do_terminal_fn(Term *);
     24 static do_terminal_fn *do_terminal;
     25 
     26 typedef void init_callbacks_fn(GLCtx *);
     27 static init_callbacks_fn *init_callbacks;
     28 
     29 typedef iv2 init_term_fn(Term *, Arena *, iv2);
     30 static init_term_fn *init_term;
     31 
     32 static void
     33 load_library(const char *lib)
     34 {
     35 	/* NOTE: glibc sucks and will crash if this is NULL */
     36 	if (libhandle)
     37 		dlclose(libhandle);
     38 	libhandle = dlopen(lib, RTLD_NOW|RTLD_LOCAL);
     39 	if (!libhandle)
     40 		fprintf(stderr, "do_debug: dlopen: %s\n", dlerror());
     41 	do_terminal = dlsym(libhandle, "do_terminal");
     42 	if (!do_terminal)
     43 		fprintf(stderr, "do_debug: dlsym: %s\n", dlerror());
     44 	init_callbacks = dlsym(libhandle, "init_callbacks");
     45 	if (!init_callbacks)
     46 		fprintf(stderr, "do_debug: dlsym: %s\n", dlerror());
     47 	init_term = dlsym(libhandle, "init_term");
     48 	if (!init_term)
     49 		fprintf(stderr, "do_debug: dlsym: %s\n", dlerror());
     50 }
     51 
     52 static void
     53 do_debug(GLCtx *gl)
     54 {
     55 	static os_file_stats updated;
     56 	os_file_stats test = os_get_file_stats(libname);
     57 
     58 	if (os_filetime_changed(test.timestamp, updated.timestamp)) {
     59 		updated = test;
     60 		/* NOTE: sucks but seems to be easiest reliable way to make sure lib is written */
     61 		struct timespec sleep_time = { .tv_nsec = 100e6 };
     62 		nanosleep(&sleep_time, &sleep_time);
     63 		load_library(libname);
     64 		if (gl) {
     65 			init_callbacks(gl);
     66 			gl->flags |= NEEDS_BLIT;
     67 		}
     68 		os_write_err_msg(s8("Reloaded Main Program\n"));
     69 	}
     70 }
     71 
     72 #endif /* _DEBUG */
     73 
     74 static void
     75 gl_debug_logger(u32 src, u32 type, u32 id, u32 lvl, i32 len, const char *msg, const void *data)
     76 {
     77 	(void)src; (void)type; (void)id; (void)data;
     78 	os_write_err_msg(s8("[GL Error "));
     79 	switch (lvl) {
     80 	case GL_DEBUG_SEVERITY_HIGH:         os_write_err_msg(s8("HIGH]: "));         break;
     81 	case GL_DEBUG_SEVERITY_MEDIUM:       os_write_err_msg(s8("MEDIUM]: "));       break;
     82 	case GL_DEBUG_SEVERITY_LOW:          os_write_err_msg(s8("LOW]: "));          break;
     83 	case GL_DEBUG_SEVERITY_NOTIFICATION: os_write_err_msg(s8("NOTIFICATION]: ")); break;
     84 	default:                             os_write_err_msg(s8("INVALID]: "));      break;
     85 	}
     86 	os_write_err_msg((s8){.len = len, .data = (u8 *)msg});
     87 	os_write_err_msg(s8("\n"));
     88 }
     89 
     90 
     91 static void
     92 error_callback(int code, const char *desc)
     93 {
     94 	fprintf(stderr, "GLFW Error (0x%04X): %s\n", code, desc);
     95 }
     96 
     97 static void
     98 init_window(Term *t, Arena arena, iv2 requested_size)
     99 {
    100 	if (!glfwInit())
    101 		os_die("Failed to init GLFW\n");
    102 	glfwSetErrorCallback(error_callback);
    103 
    104 	GLFWmonitor *mon = glfwGetPrimaryMonitor();
    105 	if (!mon) {
    106 		glfwTerminate();
    107 		os_die("Failed to get GLFW monitor\n");
    108 	}
    109 
    110 	iv2 ws;
    111 	glfwGetMonitorWorkarea(mon, NULL, NULL, &ws.w, &ws.h);
    112 
    113 	if (requested_size.w > 0 && requested_size.h > 0) {
    114 		t->gl.window_size = (v2){.w = requested_size.w, .h = requested_size.h};
    115 		glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
    116 		glfwWindowHint(GLFW_FLOATING, GLFW_TRUE);
    117 	} else {
    118 		t->gl.window_size = (v2){.w = ws.w, .h = ws.h};
    119 	}
    120 
    121 	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    122 	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
    123 	glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
    124 	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    125 
    126 	t->gl.window = glfwCreateWindow(t->gl.window_size.w, t->gl.window_size.h, "vtgl", NULL, NULL);
    127 	if (!t->gl.window) {
    128 		glfwTerminate();
    129 		os_die("Failed to spawn GLFW window\n");
    130 	}
    131 	glfwMakeContextCurrent(t->gl.window);
    132 	glfwSetWindowUserPointer(t->gl.window, t);
    133 	glfwSetWindowAttrib(t->gl.window, GLFW_RESIZABLE, GLFW_TRUE);
    134 
    135 	/* NOTE: make sure the terminal performs a resize after initialization. The
    136 	 * terminal code handles this because it also needs to communicate the size
    137 	 * to the child process */
    138 	t->gl.flags |= NEEDS_RESIZE;
    139 
    140 	/* TODO: swap interval is not needed because we will sleep on waiting for terminal input */
    141 	glfwSwapInterval(1);
    142 
    143 	/* NOTE: Set up OpenGL Render Pipeline */
    144 	glDebugMessageCallback(gl_debug_logger, NULL);
    145 	glEnable(GL_DEBUG_OUTPUT);
    146 	/* NOTE: shut up useless shader compilation statistics */
    147 	glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE,
    148 	                      GL_DEBUG_SEVERITY_NOTIFICATION,
    149 	                      0, 0, GL_FALSE);
    150 
    151 	glGenVertexArrays(1, &t->gl.vao);
    152 	glBindVertexArray(t->gl.vao);
    153 
    154 	glGenBuffers(1, &t->gl.vbo);
    155 	glBindBuffer(GL_ARRAY_BUFFER, t->gl.vbo);
    156 
    157 	f32 verts[] = {
    158 		0.0f, 1.0f,
    159 		0.0f, 0.0f,
    160 		1.0f, 1.0f,
    161 		1.0f, 0.0f,
    162 	};
    163 	glEnableVertexAttribArray(0);
    164 	glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
    165 	glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW);
    166 
    167 	glGenTextures(1, &t->gl.glyph_tex);
    168 	glBindTexture(GL_TEXTURE_2D_ARRAY, t->gl.glyph_tex);
    169 	glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    170 	glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    171 	glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    172 	glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    173 
    174 	glEnable(GL_BLEND);
    175 	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    176 
    177 	/* NOTE: Generate an intermediate framebuffer for rendering to. This
    178 	 * allows for additional post processing via a second shader stage */
    179 	glGenFramebuffers(1, &t->gl.fb);
    180 	glBindFramebuffer(GL_FRAMEBUFFER, t->gl.fb);
    181 
    182 	t->gl.fb_tex_unit = 1;
    183 	glActiveTexture(GL_TEXTURE0 + t->gl.fb_tex_unit);
    184 	glGenTextures(1, &t->gl.fb_tex);
    185 	glBindTexture(GL_TEXTURE_2D, t->gl.fb_tex);
    186 	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
    187 	             t->gl.window_size.w, t->gl.window_size.h,
    188 	             0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
    189 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    190 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    191 
    192 	glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, t->gl.fb_tex, 0);
    193 }
    194 
    195 static u32
    196 compile_shader(Arena a, u32 type, s8 shader)
    197 {
    198 	u32 sid = glCreateShader(type);
    199 
    200 	glShaderSource(sid, 1, (const char **)&shader.data, (int *)&shader.len);
    201 	glCompileShader(sid);
    202 
    203 	i32 res = 0;
    204 	glGetShaderiv(sid, GL_COMPILE_STATUS, &res);
    205 	if (res != GL_TRUE) {
    206 		i32 len;
    207 		glGetShaderiv(sid, GL_INFO_LOG_LENGTH, &len);
    208 		s8 err = s8alloc(&a, len);
    209 		glGetShaderInfoLog(sid, len, (int *)&err.len, (char *)err.data);
    210 		os_write_err_msg(s8("compile_shader: "));
    211 		os_write_err_msg(err);
    212 		glDeleteShader(sid);
    213 		return 0;
    214 	}
    215 
    216 	return sid;
    217 }
    218 
    219 static u32
    220 program_from_shader_text(s8 vertex, s8 fragment, Arena a)
    221 {
    222 	u32 pid = glCreateProgram();
    223 
    224 	u32 vid = compile_shader(a, GL_VERTEX_SHADER, vertex);
    225 	if (vid == 0) {
    226 		glDeleteProgram(pid);
    227 		return 0;
    228 	}
    229 
    230 	u32 fid = compile_shader(a, GL_FRAGMENT_SHADER, fragment);
    231 	if (fid == 0) {
    232 		glDeleteProgram(pid);
    233 		return 0;
    234 	}
    235 
    236 	glAttachShader(pid, vid);
    237 	glAttachShader(pid, fid);
    238 	glLinkProgram(pid);
    239 	glValidateProgram(pid);
    240 	glUseProgram(pid);
    241 	glDeleteShader(vid);
    242 	glDeleteShader(fid);
    243 
    244 	return pid;
    245 }
    246 
    247 static void
    248 check_shaders(GLCtx *gl, Arena a)
    249 {
    250 	static char *fs_name[SHADER_LAST] = {"/home/rnp/src/c/vtgl/frag_render.glsl", "/home/rnp/src/c/vtgl/frag_post.glsl"};
    251 	static char *vs_name[SHADER_LAST] = {"/home/rnp/src/c/vtgl/vert_render.glsl", "/home/rnp/src/c/vtgl/vert_post.glsl"};
    252 	static os_file_stats fs_stats[SHADER_LAST], vs_stats[SHADER_LAST];
    253 	static struct {
    254 		char *name;
    255 		u32   flag;
    256 	} map[SHADER_LAST] = {
    257 		[SHADER_RENDER] = {.name = "Render", .flag = UPDATE_RENDER_UNIFORMS},
    258 		[SHADER_POST]   = {.name = "Post",   .flag = UPDATE_POST_UNIFORMS},
    259 	};
    260 
    261 	for (u32 i = 0; i < SHADER_LAST; i++) {
    262 		os_file_stats fs_test = os_get_file_stats(fs_name[i]);
    263 		os_file_stats vs_test = os_get_file_stats(vs_name[i]);
    264 
    265 		if (!os_filetime_changed(fs_test.timestamp, fs_stats[i].timestamp) &&
    266 		    !os_filetime_changed(vs_test.timestamp, vs_stats[i].timestamp))
    267 			continue;
    268 
    269 		fprintf(stderr, "Reloading %s Shader!\n", map[i].name);
    270 		fs_stats[i] = fs_test;
    271 		vs_stats[i] = vs_test;
    272 
    273 		s8 vs_text = os_read_file(&a, vs_name[i], vs_stats[i].filesize);
    274 		s8 fs_text = os_read_file(&a, fs_name[i], fs_stats[i].filesize);
    275 		ASSERT(vs_text.len > 0 && fs_text.len > 0);
    276 
    277 		u32 program = program_from_shader_text(vs_text, fs_text, a);
    278 		if (!program)
    279 			continue;
    280 		glDeleteProgram(gl->programs[i]);
    281 		gl->programs[i]  = program;
    282 		gl->flags       |= map[i].flag;
    283 		fprintf(stderr, "%s Program Updated!\n", map[i].name);
    284 	}
    285 }
    286 
    287 static void
    288 usage(char *argv0)
    289 {
    290 	os_die("usage: %s [-v] [-g COLxROW]\n", argv0);
    291 }
    292 
    293 i32
    294 main(i32 argc, char *argv[])
    295 {
    296 	Arena memory = os_new_arena(16 * MEGABYTE);
    297 	Term  term   = {0};
    298 
    299 	iv2 cells = {.x = -1, .y = -1};
    300 
    301 	char *argv0 = *argv++;
    302 	argc--;
    303 	struct conversion_result cres;
    304 	for (i32 i = 0; i < argc; i++) {
    305 		char *arg = argv[i];
    306 		if (!arg || !arg[0])
    307 			usage(argv0);
    308 		if (arg[0] != '-')
    309 			break;
    310 		arg++;
    311 		switch (arg[0]) {
    312 		case 'g':
    313 			if (!argv[i + 1])
    314 				usage(argv0);
    315 			cres = i32_from_cstr(argv[i + 1], 'x');
    316 			if (cres.status == CR_SUCCESS)
    317 				cells.w = cres.i;
    318 			cres = i32_from_cstr(cres.unparsed, 0);
    319 			if (cres.status == CR_SUCCESS)
    320 				cells.h = cres.i;
    321 			if (cells.w <= 0 || cells.h <= 0)
    322 				fprintf(stderr, "ignoring malformed geometry: %s\n", argv[i + 1]);
    323 			argv++;
    324 			argc--;
    325 			break;
    326 		case 'v':
    327 			os_die("%s " VERSION "\n", argv0);
    328 		default:
    329 			usage(argv0);
    330 		}
    331 	}
    332 
    333 	do_debug(NULL);
    334 	iv2 requested_size = init_term(&term, &memory, cells);
    335 	init_window(&term, memory, requested_size);
    336 	init_callbacks(&term.gl);
    337 
    338 	os_alloc_ring_buffer(&term.views[0].log, BACKLOG_SIZE);
    339 	line_buf_alloc(&term.views[0].lines, &memory, term.views[0].log.buf, term.cursor.style,
    340 	               BACKLOG_LINES);
    341 	os_alloc_ring_buffer(&term.views[1].log, ALT_BACKLOG_SIZE);
    342 	line_buf_alloc(&term.views[1].lines, &memory, term.views[1].log.buf, term.cursor.style,
    343 	               ALT_BACKLOG_LINES);
    344 
    345 	term.child = os_fork_child("/bin/sh");
    346 
    347 	f32 last_time = 0;
    348 	while (!glfwWindowShouldClose(term.gl.window)) {
    349 		do_debug(&term.gl);
    350 		check_shaders(&term.gl, memory);
    351 
    352 		f32 current_time = (f32)glfwGetTime();
    353 		term.gl.dt       = current_time - last_time;
    354 		last_time        = current_time;
    355 
    356 		term.arena_for_frame = memory;
    357 
    358 		glfwPollEvents();
    359 		do_terminal(&term);
    360 		glfwSwapBuffers(term.gl.window);
    361 	}
    362 
    363 	return 0;
    364 }