mandelbrot

Mandelbrot viewer written in C and GLSL
git clone anongit@rnpnr.xyz:mandelbrot.git
Log | Files | Refs | Feed | LICENSE

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 }