main.c (12013B)
1 /* see LICENSE for licensing details */ 2 #include <stddef.h> 3 #include <stdio.h> 4 #include <stdint.h> 5 6 #include <GL/gl.h> 7 #include <GLES3/gl32.h> 8 #include <GLFW/glfw3.h> 9 10 typedef float f32; 11 typedef double f64; 12 typedef uint8_t u8; 13 typedef int32_t i32; 14 typedef uint32_t u32; 15 typedef ptrdiff_t size; 16 17 typedef struct { f32 x, y; } v2; 18 typedef struct { v2 top_left, bottom_right; } Rect; 19 20 typedef struct { size len; u8 *data; } s8; 21 22 typedef struct { u8 *beg, *end; } Arena; 23 24 #ifdef __clang__ 25 #define ASSERT(c) if (!(c)) __builtin_debugtrap() 26 #else 27 #define ASSERT(c) if (!(c)) asm("int3; nop"); 28 #endif 29 30 #define ABS(x) ((x) <= 0 ? -(x) : (x)) 31 #define BETWEEN(x, l, h) ((x) >= (l) && (x) <= (h)) 32 #define ARRAY_COUNT(a) (sizeof(a) / sizeof(*a)) 33 34 #define EPS 0.00001f 35 36 #define MAX_ITERATIONS 300 37 38 #include "util.c" 39 40 #ifdef __unix__ 41 #include "os_unix.c" 42 #else 43 #error Unsupported Platform! 44 #endif 45 46 typedef union { 47 struct { u8 a, b, g, r; }; 48 u32 rgba; 49 } Colour; 50 51 static struct { 52 GLFWwindow *window; 53 u32 vao, vbo; 54 i32 pid; 55 i32 height, width; 56 union { 57 struct { 58 i32 screen_dim, z_n; 59 i32 top_left, bottom_right; 60 i32 use_approx; 61 }; 62 i32 E[5]; 63 } uniforms; 64 v2 dP; 65 Rect boundary; 66 f32 zoom; 67 v2 *z_n; 68 Colour clear_colour; 69 } g_glctx; 70 71 static const char *uniform_names[] = { 72 "u_screen_dim", 73 "u_z_n", 74 "u_top_left", 75 "u_bottom_right", 76 "u_use_approx", 77 }; 78 79 static Rect default_boundary = { 80 .top_left = (v2){ .x = -2.5, .y = 1.5 }, 81 .bottom_right = (v2){ .x = 1.0, .y = -1.5 }, 82 }; 83 84 static Rect 85 move_rect(Rect r, v2 delta) 86 { 87 r.top_left.x += delta.x; 88 r.top_left.y += delta.y; 89 r.bottom_right.x += delta.x; 90 r.bottom_right.y += delta.y; 91 return r; 92 } 93 94 static v2 95 rect_center(Rect r) 96 { 97 return (v2) { 98 .x = r.bottom_right.x - r.top_left.x, 99 .y = r.top_left.y - r.bottom_right.y, 100 }; 101 } 102 103 static f32 104 magnitude_v2(v2 v) 105 { 106 return v.x * v.x + v.y * v.y; 107 } 108 109 static v2 110 sub_v2(v2 a, v2 b) 111 { 112 return (v2){ .x = a.x - b.x, .y = a.y - b.y }; 113 } 114 115 static void 116 debug_logger(u32 src, u32 type, u32 id, u32 lvl, i32 len, const char *msg, const void *data) 117 { 118 (void)src; (void)type; (void)id; (void)data; 119 fputs("[gl error ", stderr); 120 switch(lvl) { 121 case GL_DEBUG_SEVERITY_HIGH: fputs("HIGH]: ", stderr); break; 122 case GL_DEBUG_SEVERITY_MEDIUM: fputs("MEDIUM]: ", stderr); break; 123 case GL_DEBUG_SEVERITY_LOW: fputs("LOW]: ", stderr); break; 124 default: fputs("(default)]: ", stderr); break; 125 } 126 fwrite(msg, 1, len, stderr); 127 fputc('\n', stderr); 128 } 129 130 static void 131 error_callback(int code, const char *desc) 132 { 133 fprintf(stderr, "GLFW Error (0x%04X): %s\n", code, desc); 134 } 135 136 static void 137 fb_callback(GLFWwindow *win, i32 w, i32 h) 138 { 139 g_glctx.height = h; 140 g_glctx.width = w; 141 glViewport(0, 0, w, h); 142 } 143 144 static void 145 key_callback(GLFWwindow *win, i32 key, i32 sc, i32 action, i32 mod) 146 { 147 (void)sc; 148 149 f32 scale = (mod & GLFW_MOD_SHIFT) ? 1 : 0.5; 150 151 v2 dP = sub_v2(g_glctx.boundary.top_left, g_glctx.boundary.bottom_right); 152 dP.x *= -scale; //g_glctx.zoom; 153 dP.y *= scale; //g_glctx.zoom; 154 155 switch (key) { 156 case GLFW_KEY_ESCAPE: 157 if (action == GLFW_PRESS) 158 glfwSetWindowShouldClose(win, GL_TRUE); 159 break; 160 case GLFW_KEY_W: 161 if (action == GLFW_PRESS || action == GLFW_REPEAT) 162 g_glctx.dP.y = dP.y; 163 else if (action == GLFW_RELEASE) 164 g_glctx.dP.y = 0; 165 break; 166 case GLFW_KEY_A: 167 if (action == GLFW_PRESS || action == GLFW_REPEAT) 168 g_glctx.dP.x = -dP.x; 169 else if (action == GLFW_RELEASE) 170 g_glctx.dP.x = 0; 171 break; 172 case GLFW_KEY_S: 173 if (action == GLFW_PRESS || action == GLFW_REPEAT) 174 g_glctx.dP.y = -dP.y; 175 else if (action == GLFW_RELEASE) 176 g_glctx.dP.y = 0; 177 break; 178 case GLFW_KEY_D: 179 if (action == GLFW_PRESS || action == GLFW_REPEAT) 180 g_glctx.dP.x = dP.x; 181 else if (action == GLFW_RELEASE) 182 g_glctx.dP.x = 0; 183 break; 184 } 185 } 186 187 static void 188 scroll_callback(GLFWwindow *win, f64 xdelta, f64 ydelta) 189 { 190 v2 delta = sub_v2(g_glctx.boundary.top_left, g_glctx.boundary.bottom_right); 191 192 f32 scale = glfwGetKey(win, GLFW_KEY_LEFT_SHIFT) ? 0.2 : 0.05; 193 194 g_glctx.zoom += ydelta * 1/scale; 195 if (g_glctx.zoom < 1) g_glctx.zoom = 1; 196 197 delta.x = ABS(delta.x) * scale * 0.5 * ydelta; 198 delta.y = ABS(delta.y) * scale * 0.5 * ydelta; 199 g_glctx.boundary.top_left.x += delta.x; 200 g_glctx.boundary.top_left.y -= delta.y; 201 g_glctx.boundary.bottom_right.x -= delta.x; 202 g_glctx.boundary.bottom_right.y += delta.y; 203 } 204 205 static void 206 recalculate_z_n(void) 207 { 208 v2 *z_n = g_glctx.z_n; 209 z_n[0] = rect_center(g_glctx.boundary); 210 for (u32 i = 1; i < MAX_ITERATIONS; i++) { 211 f32 xx = z_n[i - 1].x * z_n[i - 1].x; 212 f32 yy = z_n[i - 1].y * z_n[i - 1].y; 213 z_n[i].x = xx - yy + z_n[0].x; 214 z_n[i].y = 2 * z_n[i - 1].x * z_n[i - 1].y + z_n[0].y; 215 } 216 } 217 218 static void 219 mouse_button_callback(GLFWwindow *win, i32 btn, i32 act, i32 mod) 220 { 221 #if 0 222 if (btn == GLFW_MOUSE_BUTTON_LEFT && act == GLFW_PRESS) { 223 f64 xpos, ypos; 224 glfwGetCursorPos(g_glctx.window, &xpos, &ypos); 225 f32 delta_x = 2 * xpos / g_glctx.width - 1; 226 f32 delta_y = 2 * ypos / g_glctx.height - 1; 227 f32 scale = 1; //g_glctx.zoom; 228 g_glctx.cursor.x -= scale * delta_x; 229 g_glctx.cursor.y -= scale * delta_y; 230 } 231 #endif 232 233 if (btn == GLFW_MOUSE_BUTTON_RIGHT && act == GLFW_PRESS) { 234 g_glctx.boundary = default_boundary; 235 g_glctx.dP = (v2){0}; 236 g_glctx.zoom = 1.0; 237 recalculate_z_n(); 238 glUniform2fv(g_glctx.uniforms.z_n, MAX_ITERATIONS, 239 (f32 *)g_glctx.z_n); 240 } 241 } 242 243 static void 244 clear_colour(Colour c) 245 { 246 glClearColor(c.r / 255.0f, c.b / 255.0f, c.g / 255.0f, c.a / 255.0f); 247 glClear(GL_COLOR_BUFFER_BIT); 248 } 249 250 static void 251 init_renderer(void) 252 { 253 glDebugMessageCallback(debug_logger, NULL); 254 glEnable(GL_DEBUG_OUTPUT); 255 glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, 256 GL_DEBUG_SEVERITY_NOTIFICATION, 257 0, 0, GL_FALSE); 258 259 glGenVertexArrays(1, &g_glctx.vao); 260 glBindVertexArray(g_glctx.vao); 261 262 f32 vertices[] = { 263 -1.0f, 1.0f, 264 -1.0f, -1.0f, 265 1.0f, 1.0f, 266 1.0f, -1.0f, 267 }; 268 glGenBuffers(1, &g_glctx.vbo); 269 glBindBuffer(GL_ARRAY_BUFFER, g_glctx.vbo); 270 glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), &vertices, GL_STATIC_DRAW); 271 glEnableVertexAttribArray(0); 272 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0); 273 } 274 275 static i32 276 spawn_window(void) 277 { 278 GLFWmonitor *mon = glfwGetPrimaryMonitor(); 279 if (!mon) 280 return -1; 281 glfwGetMonitorWorkarea(mon, NULL, NULL, &g_glctx.width, &g_glctx.height); 282 283 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); 284 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6); 285 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); 286 287 g_glctx.window = glfwCreateWindow(g_glctx.width, g_glctx.height, 288 "Mandelbrot Viewer", NULL, NULL); 289 if (g_glctx.window == NULL) 290 return -1; 291 292 glfwMakeContextCurrent(g_glctx.window); 293 294 /* disable vsync */ 295 //glfwSwapInterval(0); 296 297 glfwSetFramebufferSizeCallback(g_glctx.window, fb_callback); 298 glfwSetKeyCallback(g_glctx.window, key_callback); 299 glfwSetScrollCallback(g_glctx.window, scroll_callback); 300 glfwSetMouseButtonCallback(g_glctx.window, mouse_button_callback); 301 302 g_glctx.clear_colour = (Colour){ .r = 64, .b = 64, .g = 64, .a = 255 }; 303 clear_colour(g_glctx.clear_colour); 304 305 init_renderer(); 306 307 return 0; 308 } 309 310 static u32 311 compile_shader(Arena a, u32 type, s8 s) 312 { 313 u32 sid = glCreateShader(type); 314 glShaderSource(sid, 1, (const char **)&s.data, (int *)&s.len); 315 glCompileShader(sid); 316 317 i32 res = 0; 318 glGetShaderiv(sid, GL_COMPILE_STATUS, &res); 319 if (res != GL_TRUE) { 320 i32 len, len2; 321 glGetShaderiv(sid, GL_INFO_LOG_LENGTH, &len); 322 char *data = alloc(&a, char, len); 323 glGetShaderInfoLog(sid, len, &len2, data); 324 fputs("compile_shader: ", stderr); 325 fwrite(data, 1, len2, stderr); 326 fputc('\n', stderr); 327 glDeleteShader(sid); 328 return 0; 329 } 330 331 return sid; 332 } 333 334 static i32 335 program_from_files(Arena a, char *vert, size vfilesize, char *frag, size ffilesize) 336 { 337 s8 vertex = os_read_file(&a, vert, vfilesize); 338 s8 fragment = os_read_file(&a, frag, ffilesize); 339 if (vertex.len == 0 || fragment.len == 0) 340 return -1; 341 i32 pid = glCreateProgram(); 342 u32 vid = compile_shader(a, GL_VERTEX_SHADER, vertex); 343 u32 fid = compile_shader(a, GL_FRAGMENT_SHADER, fragment); 344 345 if (fid == 0 || vid == 0) 346 return -1; 347 348 glAttachShader(pid, vid); 349 glAttachShader(pid, fid); 350 glLinkProgram(pid); 351 glValidateProgram(pid); 352 glDeleteShader(vid); 353 glDeleteShader(fid); 354 355 return pid; 356 } 357 358 static Arena 359 get_arena(void) 360 { 361 static u8 *data[32 * 1024]; 362 Arena a = {0}; 363 a.beg = (u8 *)data; 364 /* cleanup up your dirty laundry boy!! */ 365 asm("" : "+r"(a.beg)); 366 a.end = a.beg + 32 * 1024; 367 return a; 368 } 369 370 static void 371 validate_uniforms(void) 372 { 373 for (u32 i = 0; i < ARRAY_COUNT(g_glctx.uniforms.E); i++) { 374 i32 uid = glGetUniformLocation(g_glctx.pid, uniform_names[i]); 375 g_glctx.uniforms.E[i] = uid; 376 } 377 } 378 379 #if 0 380 static v2 381 map_screen_to_rect(Rect bounds, v2 screen_pos) 382 { 383 ASSERT(BETWEEN(screen_pos.x, 0, 1.0) && BETWEEN(screen_pos.y, 0, 1.0)); 384 385 v2 size = { 386 .x = ABS(bounds.bottom_right.x - bounds.top_left.x), 387 .y = ABS(bounds.top_left.y - bounds.bottom_right.y), 388 }; 389 390 return (v2){ 391 .x = bounds.top_left.x + size.x * screen_pos.x, 392 .y = bounds.bottom_right.y + size.y * screen_pos.y, 393 }; 394 } 395 396 static void 397 print_motion(void) 398 { 399 v2 dP = g_glctx.dP; 400 v2 ddP = g_glctx.ddP; 401 printf("dP = { .x = %0.02f, .y = %0.02f }\n", dP.x, dP.y); 402 printf("ddP = { .x = %0.02f, .y = %0.02f }\n", ddP.x, ddP.y); 403 } 404 #endif 405 406 int 407 main(void) 408 { 409 Arena memory = get_arena(); 410 g_glctx.z_n = alloc(&memory, v2, MAX_ITERATIONS); 411 g_glctx.zoom = 1.0; 412 413 g_glctx.boundary = default_boundary; 414 415 if (!glfwInit()) 416 return -1; 417 glfwSetErrorCallback(error_callback); 418 419 spawn_window(); 420 421 if (g_glctx.window == NULL) { 422 glfwTerminate(); 423 return -1; 424 } 425 426 os_file_stats vert_stats = os_get_file_stats("vert.glsl"); 427 os_file_stats frag_stats = os_get_file_stats("frag.glsl"); 428 g_glctx.pid = program_from_files(memory, 429 "vert.glsl", vert_stats.filesize, 430 "frag.glsl", frag_stats.filesize); 431 if (g_glctx.pid == -1) { 432 glfwTerminate(); 433 return -1; 434 } 435 glUseProgram(g_glctx.pid); 436 validate_uniforms(); 437 438 u32 fcount = 0; 439 f32 last_time = 0; 440 while (!glfwWindowShouldClose(g_glctx.window)) { 441 glfwPollEvents(); 442 443 f32 current_time = glfwGetTime(); 444 f32 dt = current_time - last_time; 445 last_time = current_time; 446 447 i32 ua = 1.0e-8 > magnitude_v2(sub_v2(g_glctx.boundary.top_left, 448 g_glctx.boundary.bottom_right)); 449 450 if (++fcount > 300) { 451 v2 bound_cent = rect_center(g_glctx.boundary); 452 printf("FPS: %0.03f | dt = %0.03f [ms] | approx = %d\n" 453 "Center: <%f, %f>\n", 454 1 / dt, dt * 1e3, ua, bound_cent.x, bound_cent.y); 455 fcount = 0; 456 } 457 458 os_file_stats new_vert = os_get_file_stats("vert.glsl"); 459 os_file_stats new_frag = os_get_file_stats("frag.glsl"); 460 if (os_compare_filetime(vert_stats.timestamp, new_vert.timestamp) || 461 os_compare_filetime(frag_stats.timestamp, new_frag.timestamp)) { 462 i32 pid = program_from_files(memory, 463 "vert.glsl", new_vert.filesize, 464 "frag.glsl", new_frag.filesize); 465 if (pid > 0) { 466 frag_stats = new_frag; 467 vert_stats = new_vert; 468 glDeleteProgram(g_glctx.pid); 469 g_glctx.pid = pid; 470 glUseProgram(g_glctx.pid); 471 validate_uniforms(); 472 } 473 } 474 475 v2 v = g_glctx.dP; 476 v2 delta; 477 delta.x = v.x * dt; 478 delta.y = v.y * dt; 479 g_glctx.boundary = move_rect(g_glctx.boundary, delta); 480 481 if (ua) { 482 recalculate_z_n(); 483 glUniform2fv(g_glctx.uniforms.z_n, MAX_ITERATIONS, 484 (f32 *)g_glctx.z_n); 485 } 486 487 glUniform2fv(g_glctx.uniforms.top_left, 1, 488 (f32 *)&g_glctx.boundary.top_left); 489 glUniform2fv(g_glctx.uniforms.bottom_right, 1, 490 (f32 *)&g_glctx.boundary.bottom_right); 491 glUniform2ui(g_glctx.uniforms.screen_dim, 492 g_glctx.width, g_glctx.height); 493 494 glUniform1i(g_glctx.uniforms.use_approx, ua); 495 496 clear_colour(g_glctx.clear_colour); 497 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); 498 glfwSwapBuffers(g_glctx.window); 499 } 500 501 return 0; 502 }