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