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 }