volviewer

Volumetric Data Toy Viewer
git clone anongit@rnpnr.xyz:volviewer.git
Log | Files | Refs | Feed | LICENSE

common.c (26287B)


      1 /* See LICENSE for license details. */
      2 #include "opengl.h"
      3 #include "GLFW/glfw3.h"
      4 
      5 #include <stdio.h>
      6 
      7 #include "options.h"
      8 
      9 #define RENDER_TARGET_SIZE   RENDER_TARGET_WIDTH, RENDER_TARGET_HEIGHT
     10 #define TOTAL_OUTPUT_FRAMES (OUTPUT_FRAME_RATE * OUTPUT_TIME_SECONDS - 1)
     11 
     12 #define MODEL_RENDER_MODEL_MATRIX_LOC   0
     13 #define MODEL_RENDER_VIEW_MATRIX_LOC    1
     14 #define MODEL_RENDER_PROJ_MATRIX_LOC    2
     15 #define MODEL_RENDER_CLIP_FRACTION_LOC  3
     16 #define MODEL_RENDER_SWIZZLE_LOC        4
     17 #define MODEL_RENDER_LOG_SCALE_LOC      5
     18 #define MODEL_RENDER_DYNAMIC_RANGE_LOC  6
     19 #define MODEL_RENDER_THRESHOLD_LOC      7
     20 #define MODEL_RENDER_GAMMA_LOC          8
     21 #define MODEL_RENDER_BB_COLOUR_LOC      9
     22 #define MODEL_RENDER_BB_FRACTION_LOC   10
     23 #define MODEL_RENDER_GAIN_LOC          11
     24 
     25 #define CYCLE_T_UPDATE_SPEED 0.25f
     26 #define BG_CLEAR_COLOUR      (v4){{0.12, 0.1, 0.1, 1}}
     27 
     28 struct gl_debug_ctx {
     29 	Stream  stream;
     30 	OS     *os;
     31 };
     32 
     33 function f32
     34 get_frame_time_step(ViewerContext *ctx)
     35 {
     36 	f32 result = 0;
     37 	/* NOTE(rnp): if we are outputting frames do a constant time step */
     38 	if (ctx->output_frames_count > 0) {
     39 		result = 1.0f / (OUTPUT_FRAME_RATE * OUTPUT_TIME_SECONDS * CYCLE_T_UPDATE_SPEED);
     40 	} else {
     41 		f64 now = glfwGetTime();
     42 		result = ctx->demo_mode * (now - ctx->last_time);
     43 		ctx->last_time = now;
     44 	}
     45 	ctx->do_update |= result != 0;
     46 	return result;
     47 }
     48 
     49 function void
     50 gl_debug_logger(u32 src, u32 type, u32 id, u32 lvl, s32 len, const char *msg, const void *userctx)
     51 {
     52 	(void)src; (void)type; (void)id;
     53 
     54 	struct gl_debug_ctx *ctx = (struct gl_debug_ctx *)userctx;
     55 	Stream *e = &ctx->stream;
     56 	stream_append_str8s(e, str8("[OpenGL] "), (str8){.len = len, .data = (u8 *)msg}, str8("\n"));
     57 	os_write_file(ctx->os->error_handle, stream_to_str8(e));
     58 	stream_reset(e, 0);
     59 }
     60 
     61 function u32
     62 compile_shader(OS *os, Arena a, u32 type, str8 shader, str8 name)
     63 {
     64 	u32 sid = glCreateShader(type);
     65 	glShaderSource(sid, 1, (const char **)&shader.data, (int *)&shader.len);
     66 	glCompileShader(sid);
     67 
     68 	s32 res = 0;
     69 	glGetShaderiv(sid, GL_COMPILE_STATUS, &res);
     70 
     71 	if (res == GL_FALSE) {
     72 		Stream buf = arena_stream(a);
     73 		stream_append_str8s(&buf, name, str8(": failed to compile\n"));
     74 
     75 		s32 len = 0, out_len = 0;
     76 		glGetShaderiv(sid, GL_INFO_LOG_LENGTH, &len);
     77 		glGetShaderInfoLog(sid, len, &out_len, (char *)(buf.data + buf.widx));
     78 		stream_commit(&buf, out_len);
     79 		glDeleteShader(sid);
     80 		os_write_file(os->error_handle, stream_to_str8(&buf));
     81 
     82 		sid = 0;
     83 	}
     84 
     85 	return sid;
     86 }
     87 
     88 function u32
     89 link_program(OS *os, Arena a, u32 *shader_ids, u32 shader_id_count)
     90 {
     91 	s32 success = 0;
     92 	u32 result  = glCreateProgram();
     93 	for (u32 i = 0; i < shader_id_count; i++)
     94 		glAttachShader(result, shader_ids[i]);
     95 	glLinkProgram(result);
     96 	glGetProgramiv(result, GL_LINK_STATUS, &success);
     97 	if (success == GL_FALSE) {
     98 		s32 len    = 0;
     99 		Stream buf = arena_stream(a);
    100 		stream_append_str8(&buf, str8("shader link error: "));
    101 		glGetProgramInfoLog(result, buf.cap - buf.widx, &len, (c8 *)(buf.data + buf.widx));
    102 		stream_reset(&buf, len);
    103 		stream_append_byte(&buf, '\n');
    104 		os_write_file(os->error_handle, stream_to_str8(&buf));
    105 		glDeleteProgram(result);
    106 		result = 0;
    107 	}
    108 	return result;
    109 }
    110 
    111 function u32
    112 load_shader(OS *os, Arena arena, str8 vs_text, str8 fs_text, str8 info_name, str8 label)
    113 {
    114 	u32 result = 0;
    115 	u32 fs_id = compile_shader(os, arena, GL_FRAGMENT_SHADER, fs_text, info_name);
    116 	u32 vs_id = compile_shader(os, arena, GL_VERTEX_SHADER,   vs_text, info_name);
    117 	if (fs_id && vs_id) result = link_program(os, arena, (u32 []){vs_id, fs_id}, 2);
    118 	glDeleteShader(fs_id);
    119 	glDeleteShader(vs_id);
    120 
    121 	if (result) {
    122 		Stream buf = arena_stream(arena);
    123 		stream_append_str8s(&buf, str8("loaded: "), info_name, str8("\n"));
    124 		os_write_file(os->error_handle, stream_to_str8(&buf));
    125 		LABEL_GL_OBJECT(GL_PROGRAM, result, label);
    126 	}
    127 
    128 	return result;
    129 }
    130 
    131 typedef struct {
    132 	RenderContext *render_context;
    133 	str8 vertex_text;
    134 	str8 fragment_header;
    135 } ShaderReloadContext;
    136 
    137 function FILE_WATCH_CALLBACK_FN(reload_shader)
    138 {
    139 	ShaderReloadContext *ctx = (typeof(ctx))user_data;
    140 	str8 header    = push_str8(&tmp, ctx->fragment_header);
    141 	str8 fragment  = os_read_whole_file(&tmp, (c8 *)path.data);
    142 	fragment.data -= header.len;
    143 	fragment.len  += header.len;
    144 	assert(fragment.data == header.data);
    145 	u32 new_program = load_shader(os, tmp, ctx->vertex_text, fragment, path, path);
    146 	if (new_program) {
    147 		glDeleteProgram(ctx->render_context->shader);
    148 		ctx->render_context->shader = new_program;
    149 	}
    150 	return 1;
    151 }
    152 
    153 function u32
    154 load_complex_texture(Arena arena, c8 *file_path, u32 width, u32 height, u32 depth)
    155 {
    156 	u32 result = 0;
    157 	glCreateTextures(GL_TEXTURE_3D, 1, &result);
    158 	glTextureStorage3D(result, 1, GL_RG32F, width, height, depth);
    159 	glTextureParameteri(result, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
    160 	glTextureParameteri(result, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
    161 	glTextureParameteri(result, GL_TEXTURE_WRAP_R, GL_MIRRORED_REPEAT);
    162 	glTextureParameteri(result, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    163 	glTextureParameteri(result, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    164 
    165 	str8 raw = os_read_whole_file(&arena, file_path);
    166 	if (raw.len) glTextureSubImage3D(result, 0, 0, 0, 0, width, height, depth, GL_RG, GL_FLOAT, raw.data);
    167 
    168 	return result;
    169 }
    170 
    171 function RenderModel
    172 render_model_from_arrays(f32 *vertices, f32 *normals, u16 *indices, u32 index_count)
    173 {
    174 	RenderModel result = {0};
    175 
    176 	s32 buffer_size    = index_count * (6 * sizeof(f32) + sizeof(u16));
    177 	s32 indices_offset = index_count * (6 * sizeof(f32));
    178 	s32 vert_size      = index_count * 3 * sizeof(f32);
    179 	s32 ind_size       = index_count * sizeof(u16);
    180 
    181 	result.elements        = index_count;
    182 	result.elements_offset = indices_offset;
    183 
    184 	glCreateBuffers(1, &result.buffer);
    185 	glNamedBufferStorage(result.buffer, buffer_size, 0, GL_DYNAMIC_STORAGE_BIT);
    186 	glNamedBufferSubData(result.buffer, 0,              vert_size, vertices);
    187 	glNamedBufferSubData(result.buffer, vert_size,      vert_size, normals);
    188 	glNamedBufferSubData(result.buffer, indices_offset, ind_size,  indices);
    189 
    190 	glCreateVertexArrays(1, &result.vao);
    191 	glVertexArrayVertexBuffer(result.vao, 0, result.buffer, 0,         3 * sizeof(f32));
    192 	glVertexArrayVertexBuffer(result.vao, 1, result.buffer, vert_size, 3 * sizeof(f32));
    193 	glVertexArrayElementBuffer(result.vao, result.buffer);
    194 
    195 	glEnableVertexArrayAttrib(result.vao, 0);
    196 	glEnableVertexArrayAttrib(result.vao, 1);
    197 
    198 	glVertexArrayAttribFormat(result.vao, 0, 3, GL_FLOAT, 0, 0);
    199 	glVertexArrayAttribFormat(result.vao, 1, 3, GL_FLOAT, 0, vert_size);
    200 
    201 	glVertexArrayAttribBinding(result.vao, 0, 0);
    202 	glVertexArrayAttribBinding(result.vao, 1, 0);
    203 
    204 	return result;
    205 }
    206 
    207 function RenderModel
    208 load_render_model(Arena arena, c8 *positions_file_name, c8 *indices_file_name, c8 *normals_file_name)
    209 {
    210 	str8 positions = os_read_whole_file(&arena, positions_file_name);
    211 	str8 normals   = os_read_whole_file(&arena, normals_file_name);
    212 	str8 indices   = os_read_whole_file(&arena, indices_file_name);
    213 
    214 	RenderModel result = render_model_from_arrays((f32 *)positions.data, (f32 *)normals.data,
    215 	                                              (u16 *)indices.data, indices.len / sizeof(u16));
    216 	return result;
    217 }
    218 
    219 function b32
    220 export_bitmap(ViewerContext *ctx)
    221 {
    222 	#pragma pack(push, 1)
    223 	struct bmp_header {
    224 		u8  file_type[2];
    225 		u32 file_size;
    226 		u16 reserved_1;
    227 		u16 reserved_2;
    228 		u32 bitmap_offset;
    229 
    230 		u32 size;
    231 		s32 width;
    232 		s32 height;
    233 		u16 planes;
    234 		u16 bits_per_pixel;
    235 
    236 		u32 compression;
    237 		u32 size_of_bitmap;
    238 
    239 		s32 horizontal_resolution;
    240 		s32 vertical_resolution;
    241 		u32 colours_used;
    242 		u32 colours_important;
    243 
    244 		u32 red_mask;
    245 		u32 green_mask;
    246 		u32 blue_mask;
    247 		u32 alpha_mask;
    248 
    249 		u32 colour_space_type;
    250 		u32 cie_xyz_triples[9];
    251 		u32 gamma_red;
    252 		u32 gamma_green;
    253 		u32 gamma_blue;
    254 	};
    255 	#pragma pack(pop)
    256 
    257 	s32 texture_size = ctx->output_target.size.w * ctx->output_target.size.h * sizeof(u32);
    258 	s32 out_size     = texture_size + round_up_power_of_2(sizeof(struct bmp_header));
    259 
    260 	Arena arena   = ctx->video_arena;
    261 	void *out_buf = arena_alloc(&arena, out_size, 64, 1);
    262 
    263 	struct bmp_header *header = (struct bmp_header *)out_buf;
    264 	header->bitmap_offset  = out_size - texture_size;
    265 	header->file_type[0]   = 'B';
    266 	header->file_type[1]   = 'M';
    267 	header->file_size      = out_size;
    268 	header->size           = 108;
    269 	header->width          =  ctx->output_target.size.w;
    270 	header->height         = -ctx->output_target.size.h;
    271 	header->planes         = 1;
    272 	header->bits_per_pixel = 32;
    273 	header->compression    = 3;
    274 	header->size_of_bitmap = texture_size;
    275 	header->red_mask       = 0xFF000000;
    276 	header->green_mask     = 0x00FF0000;
    277 	header->blue_mask      = 0x0000FF00;
    278 	header->alpha_mask     = 0x000000FF;
    279 
    280 	glGetTextureImage(ctx->output_target.textures[0], 0, GL_RGBA,
    281 	                  GL_UNSIGNED_INT_8_8_8_8, texture_size,
    282 	                  (u8 *)out_buf + header->bitmap_offset);
    283 
    284 	return os_write_new_file("view.bmp", (str8){.len  = out_size, .data = out_buf});
    285 }
    286 
    287 function void
    288 scroll_callback(GLFWwindow *window, f64 x, f64 y)
    289 {
    290 	ViewerContext *ctx  = glfwGetWindowUserPointer(window);
    291 	ctx->camera_fov += y;
    292 	ctx->do_update   = 1;
    293 }
    294 
    295 function void
    296 key_callback(GLFWwindow *window, s32 key, s32 scancode, s32 action, s32 modifiers)
    297 {
    298 	ViewerContext *ctx = glfwGetWindowUserPointer(window);
    299 	if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
    300 		ctx->should_exit = 1;
    301 
    302 	if (key == GLFW_KEY_SPACE && action == GLFW_PRESS)
    303 		ctx->demo_mode = !ctx->demo_mode;
    304 
    305 	if (key == GLFW_KEY_F11 || key == GLFW_KEY_F12) {
    306 		sz frames       = TOTAL_OUTPUT_FRAMES;
    307 		sz needed_bytes = sizeof(u32) * RENDER_TARGET_HEIGHT * RENDER_TARGET_WIDTH * frames;
    308 		if (!ctx->video_arena.beg) {
    309 			ctx->video_arena = os_alloc_arena(needed_bytes);
    310 			if (!ctx->video_arena.beg)
    311 				fputs("failed to allocate space for output data\n", stderr);
    312 		}
    313 	}
    314 
    315 	if (key == GLFW_KEY_F11) {
    316 		if (ctx->video_arena.beg) {
    317 			if (export_bitmap(ctx)) fputs("exported bitmap\n", stderr);
    318 			else                    fputs("failed to export bitmap\n", stderr);
    319 		}
    320 	}
    321 
    322 	if (key == GLFW_KEY_F12 && action == GLFW_PRESS && ctx->output_frames_count == 0) {
    323 		if (ctx->video_arena.beg) {
    324 			ctx->output_frames_count = TOTAL_OUTPUT_FRAMES;
    325 			ctx->cycle_t = 0;
    326 		}
    327 	}
    328 
    329 	if (key == GLFW_KEY_A && action != GLFW_RELEASE)
    330 		ctx->cycle_t += 4.0f / (OUTPUT_TIME_SECONDS * OUTPUT_FRAME_RATE);
    331 	if (key == GLFW_KEY_D && action != GLFW_RELEASE)
    332 		ctx->cycle_t -= 4.0f / (OUTPUT_TIME_SECONDS * OUTPUT_FRAME_RATE);
    333 	if (key == GLFW_KEY_W && action != GLFW_RELEASE)
    334 		ctx->camera_angle += 5 * PI / 180.0f;
    335 	if (key == GLFW_KEY_S && action != GLFW_RELEASE)
    336 		ctx->camera_angle -= 5 * PI / 180.0f;
    337 
    338 	ctx->do_update = 1;
    339 }
    340 
    341 function void
    342 fb_callback(GLFWwindow *window, s32 w, s32 h)
    343 {
    344 	ViewerContext *ctx = glfwGetWindowUserPointer(window);
    345 	ctx->window_size   = (sv2){.w = w, .h = h};
    346 }
    347 
    348 function void
    349 init_viewer(ViewerContext *ctx)
    350 {
    351 	ctx->demo_mode     = 1;
    352 	ctx->window_size   = (sv2){.w = 640, .h = 640};
    353 	ctx->camera_radius = CAMERA_RADIUS;
    354 	ctx->camera_angle  = -CAMERA_ELEVATION_ANGLE * PI / 180.0f;
    355 	ctx->camera_fov    = 60.0f;
    356 
    357 	if (!glfwInit()) os_fatal(str8("failed to start glfw\n"));
    358 
    359 	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    360 	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
    361 	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    362 	ctx->window = glfwCreateWindow(ctx->window_size.w, ctx->window_size.h, "3D Viewer", 0, 0);
    363 	if (!ctx->window) os_fatal(str8("failed to open window\n"));
    364 	glfwMakeContextCurrent(ctx->window);
    365 	glfwSetWindowUserPointer(ctx->window, ctx);
    366 	glfwSwapInterval(1);
    367 
    368 	glfwSetKeyCallback(ctx->window, key_callback);
    369 	glfwSetScrollCallback(ctx->window, scroll_callback);
    370 	glfwSetFramebufferSizeCallback(ctx->window, fb_callback);
    371 
    372 	#define X(name, ret, params) name = (name##_fn *)glfwGetProcAddress(#name);
    373 	OGLProcedureList
    374 	#undef X
    375 
    376 	/* NOTE: set up OpenGL debug logging */
    377 	struct gl_debug_ctx *gl_debug_ctx = push_struct(&ctx->arena, typeof(*gl_debug_ctx));
    378 	gl_debug_ctx->stream = stream_alloc(&ctx->arena, KB(4));
    379 	gl_debug_ctx->os     = &ctx->os;
    380 	glDebugMessageCallback(gl_debug_logger, gl_debug_ctx);
    381 #ifdef _DEBUG
    382 	glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
    383 #endif
    384 
    385 	glEnable(GL_MULTISAMPLE);
    386 	glEnable(GL_DEPTH_TEST);
    387 	glEnable(GL_BLEND);
    388 	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    389 
    390 	RenderContext *rc = &ctx->model_render_context;
    391 
    392 	RenderTarget *rt = &ctx->multisample_target;
    393 	rt->size = (sv2){{RENDER_TARGET_SIZE}};
    394 	glCreateRenderbuffers(countof(rt->textures), rt->textures);
    395 	glNamedRenderbufferStorageMultisample(rt->textures[0], RENDER_MSAA_SAMPLES,
    396 	                                      GL_RGBA8, RENDER_TARGET_SIZE);
    397 	glNamedRenderbufferStorageMultisample(rt->textures[1], RENDER_MSAA_SAMPLES,
    398 	                                      GL_DEPTH_COMPONENT24, RENDER_TARGET_SIZE);
    399 	glCreateFramebuffers(1, &rt->fb);
    400 	glNamedFramebufferRenderbuffer(rt->fb, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rt->textures[0]);
    401 	glNamedFramebufferRenderbuffer(rt->fb, GL_DEPTH_ATTACHMENT,  GL_RENDERBUFFER, rt->textures[1]);
    402 
    403 	rt = &ctx->output_target;
    404 	glCreateTextures(GL_TEXTURE_2D, countof(rt->textures), rt->textures);
    405 	rt->size = (sv2){{RENDER_TARGET_SIZE}};
    406 	glTextureStorage2D(rt->textures[0], 8, GL_RGBA8,             RENDER_TARGET_SIZE);
    407 	glTextureStorage2D(rt->textures[1], 1, GL_DEPTH_COMPONENT24, RENDER_TARGET_SIZE);
    408 
    409 	glCreateFramebuffers(1, &rt->fb);
    410 	glNamedFramebufferTexture(rt->fb, GL_COLOR_ATTACHMENT0, rt->textures[0], 0);
    411 	glNamedFramebufferTexture(rt->fb, GL_DEPTH_ATTACHMENT,  rt->textures[1], 0);
    412 
    413 	glTextureParameteri(rt->textures[0], GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    414 	glTextureParameteri(rt->textures[0], GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    415 
    416 	ShaderReloadContext *model_rc = push_struct(&ctx->arena, ShaderReloadContext);
    417 	model_rc->render_context = rc;
    418 	model_rc->vertex_text = str8(""
    419 	"#version 460 core\n"
    420 	"\n"
    421 	"layout(location = 0) in vec3 v_position;\n"
    422 	"layout(location = 1) in vec3 v_normal;\n"
    423 	"\n"
    424 	"layout(location = 0) out vec3 f_normal;\n"
    425 	"layout(location = 1) out vec3 f_texture_coordinate;\n"
    426 	"layout(location = 2) out vec3 f_orig_texture_coordinate;\n"
    427 	"\n"
    428 	"layout(location = " str(MODEL_RENDER_MODEL_MATRIX_LOC)  ") uniform mat4  u_model;\n"
    429 	"layout(location = " str(MODEL_RENDER_VIEW_MATRIX_LOC)   ") uniform mat4  u_view;\n"
    430 	"layout(location = " str(MODEL_RENDER_PROJ_MATRIX_LOC)   ") uniform mat4  u_projection;\n"
    431 	"layout(location = " str(MODEL_RENDER_CLIP_FRACTION_LOC) ") uniform float u_clip_fraction = 1;\n"
    432 	"layout(location = " str(MODEL_RENDER_SWIZZLE_LOC)       ") uniform bool  u_swizzle;\n"
    433 	"\n"
    434 	"\n"
    435 	"void main()\n"
    436 	"{\n"
    437 	"\tvec3 pos = v_position;\n"
    438 	"\tf_orig_texture_coordinate = (v_position + 1) / 2;\n"
    439 	"\tif (v_position.y == -1) pos.x = clamp(v_position.x, -u_clip_fraction, u_clip_fraction);\n"
    440 	"\tvec3 tex_coord = (pos + 1) / 2;\n"
    441 	"\tf_texture_coordinate = u_swizzle? tex_coord.xzy : tex_coord;\n"
    442 	//"\tf_normal    = normalize(mat3(u_model) * v_normal);\n"
    443 	"\tf_normal    = v_normal;\n"
    444 	"\tgl_Position = u_projection * u_view * u_model * vec4(pos, 1);\n"
    445 	"}\n");
    446 
    447 	model_rc->fragment_header = str8(""
    448 	"#version 460 core\n\n"
    449 	"layout(location = 0) in  vec3 normal;\n"
    450 	"layout(location = 1) in  vec3 texture_coordinate;\n\n"
    451 	"layout(location = 2) in  vec3 test_texture_coordinate;\n\n"
    452 	"layout(location = 0) out vec4 out_colour;\n\n"
    453 	"layout(location = " str(MODEL_RENDER_DYNAMIC_RANGE_LOC) ") uniform float u_db_cutoff = 60;\n"
    454 	"layout(location = " str(MODEL_RENDER_THRESHOLD_LOC)     ") uniform float u_threshold = 40;\n"
    455 	"layout(location = " str(MODEL_RENDER_GAMMA_LOC)         ") uniform float u_gamma     = 1;\n"
    456 	"layout(location = " str(MODEL_RENDER_LOG_SCALE_LOC)     ") uniform bool  u_log_scale;\n"
    457 	"layout(location = " str(MODEL_RENDER_BB_COLOUR_LOC)     ") uniform vec4  u_bb_colour   = vec4(" str(BOUNDING_BOX_COLOUR) ");\n"
    458 	"layout(location = " str(MODEL_RENDER_BB_FRACTION_LOC)   ") uniform float u_bb_fraction = " str(BOUNDING_BOX_FRACTION) ";\n"
    459 	"layout(location = " str(MODEL_RENDER_GAIN_LOC)          ") uniform float u_gain        = 1.0f;\n"
    460 	"\n"
    461 	"layout(binding = 0) uniform sampler3D u_texture;\n"
    462 	"\n#line 1\n");
    463 
    464 	str8 render_model = str8("render_model.frag.glsl");
    465 	reload_shader(&ctx->os, render_model, (sptr)model_rc, ctx->arena);
    466 	os_add_file_watch(&ctx->os, &ctx->arena, render_model, reload_shader, (sptr)model_rc);
    467 
    468 	rc = &ctx->overlay_render_context;
    469 	ShaderReloadContext *overlay_rc = push_struct(&ctx->arena, ShaderReloadContext);
    470 	overlay_rc->render_context = rc;
    471 	overlay_rc->vertex_text = str8(""
    472 	"#version 460 core\n"
    473 	"\n"
    474 	"layout(location = 0) in vec2 v_position;\n"
    475 	"layout(location = 1) in vec2 v_texture_coordinate;\n"
    476 	"layout(location = 2) in vec3 v_colour;\n"
    477 	"layout(location = 3) in uint v_flags;\n"
    478 	"\n"
    479 	"layout(location = 0) out vec2 f_texture_coordinate;\n"
    480 	"layout(location = 1) out vec3 f_colour;\n"
    481 	"layout(location = 2) out uint f_flags;\n"
    482 	"\n"
    483 	"layout(location = 0) uniform ivec2 u_screen_size;\n"
    484 	"\n"
    485 	"void main()\n"
    486 	"{\n"
    487 	"\tf_texture_coordinate = v_texture_coordinate;\n"
    488 	"\tf_colour             = v_colour;\n"
    489 	"\tf_flags              = v_flags;\n"
    490 	"\tgl_Position = vec4(v_position, 0, 1);\n"
    491 	//"\tgl_Position = vec4(2 * (v_position / vec2(u_screen_size)) - 1, 0, 1);\n"
    492 	"}\n");
    493 
    494 	overlay_rc->fragment_header = str8(""
    495 	"#version 460 core\n\n"
    496 	"layout(location = 0) in  vec2 texture_coordinate;\n"
    497 	"layout(location = 1) in  vec3 colour;\n"
    498 	"layout(location = 0) out vec4 out_colour;\n"
    499 	"\n#line 1\n");
    500 
    501 	f32 overlay_vertices[] = {
    502 		-1,  1, 0, 0,
    503 		-1, -1, 0, 1,
    504 		 1, -1, 1, 1,
    505 		-1,  1, 0, 0,
    506 		 1, -1, 1, 1,
    507 		 1,  1, 1, 0,
    508 	};
    509 	glCreateVertexArrays(1, &rc->vao);
    510 	glCreateBuffers(1, &rc->vbo);
    511 
    512 	glNamedBufferData(rc->vbo, sizeof(overlay_vertices), overlay_vertices, GL_STATIC_DRAW);
    513 
    514 	glEnableVertexArrayAttrib(rc->vao, 0);
    515 	glEnableVertexArrayAttrib(rc->vao, 1);
    516 	glVertexArrayVertexBuffer(rc->vao, 0, rc->vbo, 0,               4 * sizeof(f32));
    517 	glVertexArrayVertexBuffer(rc->vao, 1, rc->vbo, 2 * sizeof(f32), 4 * sizeof(f32));
    518 	glVertexArrayAttribFormat(rc->vao, 0, 2, GL_FLOAT, 0, 0);
    519 	glVertexArrayAttribFormat(rc->vao, 1, 2, GL_FLOAT, 0, 2 * sizeof(f32));
    520 	glVertexArrayAttribBinding(rc->vao, 0, 0);
    521 	glVertexArrayAttribBinding(rc->vao, 1, 0);
    522 
    523 	str8 render_overlay = str8("render_overlay.frag.glsl");
    524 	reload_shader(&ctx->os, render_overlay, (sptr)overlay_rc, ctx->arena);
    525 	os_add_file_watch(&ctx->os, &ctx->arena, render_overlay, reload_shader, (sptr)overlay_rc);
    526 
    527 	f32 unit_cube_vertices[] = {
    528 		 1.0f,  1.0f, -1.0f,
    529 		 1.0f,  1.0f, -1.0f,
    530 		 1.0f,  1.0f, -1.0f,
    531 		 1.0f, -1.0f, -1.0f,
    532 		 1.0f, -1.0f, -1.0f,
    533 		 1.0f, -1.0f, -1.0f,
    534 		 1.0f,  1.0f,  1.0f,
    535 		 1.0f,  1.0f,  1.0f,
    536 		 1.0f,  1.0f,  1.0f,
    537 		 1.0f, -1.0f,  1.0f,
    538 		 1.0f, -1.0f,  1.0f,
    539 		 1.0f, -1.0f,  1.0f,
    540 		-1.0f,  1.0f, -1.0f,
    541 		-1.0f,  1.0f, -1.0f,
    542 		-1.0f,  1.0f, -1.0f,
    543 		-1.0f, -1.0f, -1.0f,
    544 		-1.0f, -1.0f, -1.0f,
    545 		-1.0f, -1.0f, -1.0f,
    546 		-1.0f,  1.0f,  1.0f,
    547 		-1.0f,  1.0f,  1.0f,
    548 		-1.0f,  1.0f,  1.0f,
    549 		-1.0f, -1.0f,  1.0f,
    550 		-1.0f, -1.0f,  1.0f,
    551 		-1.0f, -1.0f,  1.0f
    552 	};
    553 	f32 unit_cube_normals[] = {
    554 		 0.0f,  0.0f, -1.0f,
    555 		 0.0f,  1.0f,  0.0f,
    556 		 1.0f,  0.0f,  0.0f,
    557 		 0.0f,  0.0f, -1.0f,
    558 		 0.0f, -1.0f,  0.0f,
    559 		 1.0f,  0.0f,  0.0f,
    560 		 0.0f,  0.0f,  1.0f,
    561 		 0.0f,  1.0f,  0.0f,
    562 		 1.0f,  0.0f,  0.0f,
    563 		 0.0f,  0.0f,  1.0f,
    564 		 0.0f, -1.0f,  0.0f,
    565 		 1.0f,  0.0f,  0.0f,
    566 		 0.0f,  0.0f, -1.0f,
    567 		 0.0f,  1.0f,  0.0f,
    568 		-1.0f,  0.0f,  0.0f,
    569 		 0.0f,  0.0f, -1.0f,
    570 		 0.0f, -1.0f,  0.0f,
    571 		-1.0f,  0.0f,  0.0f,
    572 		 0.0f,  0.0f,  1.0f,
    573 		 0.0f,  1.0f,  0.0f,
    574 		-1.0f,  0.0f,  0.0f,
    575 		 0.0f,  0.0f,  1.0f,
    576 		 0.0f, -1.0f,  0.0f,
    577 		-1.0f,  0.0f,  0.0f
    578 	};
    579 	u16 unit_cube_indices[] = {
    580 		1,  13, 19,
    581 		1,  19, 7,
    582 		9,  6,  18,
    583 		9,  18, 21,
    584 		23, 20, 14,
    585 		23, 14, 17,
    586 		16, 4,  10,
    587 		16, 10, 22,
    588 		5,  2,  8,
    589 		5,  8,  11,
    590 		15, 12, 0,
    591 		15, 0,  3
    592 	};
    593 
    594 	ctx->unit_cube = render_model_from_arrays(unit_cube_vertices, unit_cube_normals,
    595 	                                          unit_cube_indices, countof(unit_cube_indices));
    596 }
    597 
    598 function void
    599 set_camera(u32 program, u32 location, v3 position, v3 normal, v3 orthogonal)
    600 {
    601 	v3 right = cross(orthogonal, normal);
    602 	v3 up    = cross(normal,     right);
    603 
    604 	v3 translate;
    605 	position    = v3_sub((v3){0}, position);
    606 	translate.x = v3_dot(position, right);
    607 	translate.y = v3_dot(position, up);
    608 	translate.z = v3_dot(position, normal);
    609 
    610 	m4 transform;
    611 	transform.c[0] = (v4){{right.x,     up.x,        normal.x,    0}};
    612 	transform.c[1] = (v4){{right.y,     up.y,        normal.y,    0}};
    613 	transform.c[2] = (v4){{right.z,     up.z,        normal.z,    0}};
    614 	transform.c[3] = (v4){{translate.x, translate.y, translate.z, 1}};
    615 	glProgramUniformMatrix4fv(program, location, 1, 0, transform.E);
    616 }
    617 
    618 function void
    619 draw_volume_item(ViewerContext *ctx, VolumeDisplayItem *v, f32 rotation, f32 translate_x)
    620 {
    621 	if (!v->texture) {
    622 		v->texture = load_complex_texture(ctx->arena, v->file_path,
    623 		                                  v->width, v->height, v->depth);
    624 	}
    625 
    626 	u32 program = ctx->model_render_context.shader;
    627 	v3 scale = v3_sub(v->max_coord_mm, v->min_coord_mm);
    628 	m4 S;
    629 	S.c[0] = (v4){{scale.x, 0,       0,       0}};
    630 	S.c[1] = (v4){{0,       scale.z, 0,       0}};
    631 	S.c[2] = (v4){{0,       0,       scale.y, 0}};
    632 	S.c[3] = (v4){{0,       0,       0,       1}};
    633 
    634 	m4 T;
    635 	T.c[0] = (v4){{1, 0, 0, translate_x}};
    636 	T.c[1] = (v4){{0, 1, 0, 0}};
    637 	T.c[2] = (v4){{0, 0, 1, 0}};
    638 	T.c[3] = (v4){{0, 0, 0, 1}};
    639 
    640 	f32 sa = sin_f32(rotation);
    641 	f32 ca = cos_f32(rotation);
    642 	m4 R;
    643 	R.c[0] = (v4){{ ca, 0, sa, 0}};
    644 	R.c[1] = (v4){{ 0,  1, 0,  0}};
    645 	R.c[2] = (v4){{-sa, 0, ca, 0}};
    646 	R.c[3] = (v4){{ 0,  0, 0,  1}};
    647 
    648 	m4 model_transform = m4_mul(m4_mul(R, S), T);
    649 	glProgramUniformMatrix4fv(program, MODEL_RENDER_MODEL_MATRIX_LOC, 1, 0, model_transform.E);
    650 
    651 	glProgramUniform1f(program,  MODEL_RENDER_CLIP_FRACTION_LOC, 1 - v->clip_fraction);
    652 	glProgramUniform1f(program,  MODEL_RENDER_THRESHOLD_LOC,     v->threshold);
    653 	glProgramUniform1f(program,  MODEL_RENDER_GAIN_LOC,          v->gain);
    654 	glProgramUniform1ui(program, MODEL_RENDER_SWIZZLE_LOC,       v->swizzle);
    655 
    656 	glBindTextureUnit(0, v->texture);
    657 	glBindVertexArray(ctx->unit_cube.vao);
    658 	glDrawElements(GL_TRIANGLES, ctx->unit_cube.elements, GL_UNSIGNED_SHORT,
    659 	               (void *)ctx->unit_cube.elements_offset);
    660 }
    661 
    662 function void
    663 update_scene(ViewerContext *ctx, f32 dt)
    664 {
    665 	ctx->cycle_t += CYCLE_T_UPDATE_SPEED * dt;
    666 	if (ctx->cycle_t > 1) ctx->cycle_t -= 1;
    667 
    668 	f32 angle = ctx->cycle_t * 2 * PI;
    669 	ctx->camera_position.x =  0;
    670 	ctx->camera_position.z = -ctx->camera_radius;
    671 	ctx->camera_position.y =  ctx->camera_radius * tan_f32(ctx->camera_angle);
    672 
    673 	RenderTarget *rt = &ctx->multisample_target;
    674 	f32 one = 1;
    675 	glBindFramebuffer(GL_FRAMEBUFFER, rt->fb);
    676 	glClearNamedFramebufferfv(rt->fb, GL_COLOR, 0, OUTPUT_BG_CLEAR_COLOUR.E);
    677 	glClearNamedFramebufferfv(rt->fb, GL_DEPTH, 0, &one);
    678 	glViewport(0, 0, rt->size.w, rt->size.h);
    679 
    680 	u32 program = ctx->model_render_context.shader;
    681 	glUseProgram(program);
    682 
    683 	/* TODO(rnp): set this on hot reload instead of every frame */
    684 	v2 points = {{RENDER_TARGET_SIZE}};
    685 	f32 n = 0.1f;
    686 	f32 f = 400.0f;
    687 	f32 r = n * tan_f32(ctx->camera_fov / 2 * PI / 180.0f);
    688 	f32 t = r * points.h / points.w;
    689 	f32 a = -(f + n) / (f - n);
    690 	f32 b = -2 * f * n / (f - n);
    691 
    692 	m4 projection;
    693 	projection.c[0] = (v4){{n / r, 0,     0,  0}};
    694 	projection.c[1] = (v4){{0,     n / t, 0,  0}};
    695 	projection.c[2] = (v4){{0,     0,     a, -1}};
    696 	projection.c[3] = (v4){{0,     0,     b,  0}};
    697 	glProgramUniformMatrix4fv(program, MODEL_RENDER_PROJ_MATRIX_LOC, 1, 0, projection.E);
    698 
    699 	v3 camera = ctx->camera_position;
    700 	set_camera(program, MODEL_RENDER_VIEW_MATRIX_LOC, camera,
    701 	           v3_normalize(v3_sub(camera, (v3){0})), (v3){{0, 1, 0}});
    702 
    703 	glProgramUniform1ui(program, MODEL_RENDER_LOG_SCALE_LOC,     LOG_SCALE);
    704 	glProgramUniform1f(program,  MODEL_RENDER_DYNAMIC_RANGE_LOC, DYNAMIC_RANGE);
    705 
    706 	#if DRAW_ALL_VOLUMES
    707 	for (u32 i = 0; i < countof(volumes); i++)
    708 		draw_volume_item(ctx, volumes + i, angle, volumes[i].translate_x);
    709 	#else
    710 	draw_volume_item(ctx, volumes + single_volume_index, angle, 0);
    711 	#endif
    712 
    713 	/* NOTE(rnp): resolve multisampled scene */
    714 	glBlitNamedFramebuffer(rt->fb, ctx->output_target.fb, 0, 0, rt->size.w, rt->size.h,
    715 	                       0, 0, rt->size.w, rt->size.h, GL_COLOR_BUFFER_BIT, GL_NEAREST);
    716 
    717 	glGenerateTextureMipmap(ctx->output_target.textures[0]);
    718 }
    719 
    720 function void
    721 viewer_frame_step(ViewerContext *ctx, f32 dt)
    722 {
    723 	if (ctx->do_update) {
    724 		update_scene(ctx, dt);
    725 		if (ctx->output_frames_count) {
    726 			u32 frame_index  = TOTAL_OUTPUT_FRAMES - ctx->output_frames_count--;
    727 			printf("Reading Frame: [%u/%u]\n", frame_index, (u32)TOTAL_OUTPUT_FRAMES - 1);
    728 			sz needed = ctx->output_target.size.w * ctx->output_target.size.h * sizeof(u32);
    729 			glGetTextureImage(ctx->output_target.textures[0], 0, GL_RGBA,
    730 			                  GL_UNSIGNED_INT_8_8_8_8, needed,
    731 			                  ctx->video_arena.beg + ctx->video_arena_offset);
    732 			ctx->video_arena_offset += needed;
    733 			if (!ctx->output_frames_count) {
    734 				str8 raw = {.len  = TOTAL_OUTPUT_FRAMES * needed,
    735 				            .data = ctx->video_arena.beg};
    736 				ctx->video_arena_offset = 0;
    737 				os_write_new_file(RAW_OUTPUT_PATH, raw);
    738 			}
    739 		}
    740 		ctx->do_update = 0;
    741 	}
    742 
    743 	////////////////
    744 	// UI Overlay
    745 	f32 one = 1;
    746 	glBindFramebuffer(GL_FRAMEBUFFER, 0);
    747 	glClearNamedFramebufferfv(0, GL_COLOR, 0, BG_CLEAR_COLOUR.E);
    748 	glClearNamedFramebufferfv(0, GL_DEPTH, 0, &one);
    749 
    750 	f32 aspect_ratio = (f32)ctx->output_target.size.w / (f32)ctx->output_target.size.h;
    751 	sv2 target_size  = ctx->window_size;
    752 	if (aspect_ratio > 1) target_size.h = target_size.w / aspect_ratio;
    753 	else                  target_size.w = target_size.h * aspect_ratio;
    754 
    755 	if (target_size.w > ctx->window_size.w) {
    756 		target_size.w = ctx->window_size.w;
    757 		target_size.h = ctx->window_size.w / aspect_ratio;
    758 	} else if (target_size.h > ctx->window_size.h) {
    759 		target_size.h = ctx->window_size.h;
    760 		target_size.w = target_size.h * aspect_ratio;
    761 	}
    762 
    763 	sv2 size_delta = sv2_sub(ctx->window_size, target_size);
    764 
    765 	glViewport(size_delta.x / 2 + 0.5, size_delta.y / 2 + 0.5, target_size.w, target_size.h);
    766 
    767 	glUseProgram(ctx->overlay_render_context.shader);
    768 	glBindTextureUnit(0, ctx->output_target.textures[0]);
    769 	glBindVertexArray(ctx->overlay_render_context.vao);
    770 	glDrawArrays(GL_TRIANGLES, 0, 6);
    771 
    772 	ctx->should_exit |= glfwWindowShouldClose(ctx->window);
    773 }