volviewer

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

common.c (24310B)


      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 void
    220 scroll_callback(GLFWwindow *window, f64 x, f64 y)
    221 {
    222 	ViewerContext *ctx  = glfwGetWindowUserPointer(window);
    223 	ctx->camera_fov += y;
    224 	ctx->do_update   = 1;
    225 }
    226 
    227 function void
    228 key_callback(GLFWwindow *window, s32 key, s32 scancode, s32 action, s32 modifiers)
    229 {
    230 	ViewerContext *ctx = glfwGetWindowUserPointer(window);
    231 	if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
    232 		ctx->should_exit = 1;
    233 
    234 	if (key == GLFW_KEY_SPACE && action == GLFW_PRESS)
    235 		ctx->demo_mode = !ctx->demo_mode;
    236 
    237 	if (key == GLFW_KEY_F12 && action == GLFW_PRESS && ctx->output_frames_count == 0) {
    238 		sz frames       = TOTAL_OUTPUT_FRAMES;
    239 		sz needed_bytes = sizeof(u32) * RENDER_TARGET_HEIGHT * RENDER_TARGET_WIDTH * frames;
    240 		if (!ctx->video_arena.beg) {
    241 			ctx->video_arena = os_alloc_arena(needed_bytes);
    242 			if (!ctx->video_arena.beg) {
    243 				fputs("failed to allocate space for output video, video "
    244 				      "won't be saved\n", stderr);
    245 			}
    246 		}
    247 		if (ctx->video_arena.beg) {
    248 			ctx->output_frames_count = TOTAL_OUTPUT_FRAMES;
    249 			ctx->cycle_t = 0;
    250 		}
    251 	}
    252 
    253 	if (key == GLFW_KEY_A && action != GLFW_RELEASE)
    254 		ctx->cycle_t += 4.0f / (OUTPUT_TIME_SECONDS * OUTPUT_FRAME_RATE);
    255 	if (key == GLFW_KEY_D && action != GLFW_RELEASE)
    256 		ctx->cycle_t -= 4.0f / (OUTPUT_TIME_SECONDS * OUTPUT_FRAME_RATE);
    257 	if (key == GLFW_KEY_W && action != GLFW_RELEASE)
    258 		ctx->camera_angle += 5 * PI / 180.0f;
    259 	if (key == GLFW_KEY_S && action != GLFW_RELEASE)
    260 		ctx->camera_angle -= 5 * PI / 180.0f;
    261 
    262 	ctx->do_update = 1;
    263 }
    264 
    265 function void
    266 fb_callback(GLFWwindow *window, s32 w, s32 h)
    267 {
    268 	ViewerContext *ctx = glfwGetWindowUserPointer(window);
    269 	ctx->window_size   = (sv2){.w = w, .h = h};
    270 }
    271 
    272 function void
    273 init_viewer(ViewerContext *ctx)
    274 {
    275 	ctx->demo_mode     = 1;
    276 	ctx->window_size   = (sv2){.w = 640, .h = 640};
    277 	ctx->camera_radius = CAMERA_RADIUS;
    278 	ctx->camera_angle  = -CAMERA_ELEVATION_ANGLE * PI / 180.0f;
    279 	ctx->camera_fov    = 60.0f;
    280 
    281 	if (!glfwInit()) os_fatal(str8("failed to start glfw\n"));
    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 	ctx->window = glfwCreateWindow(ctx->window_size.w, ctx->window_size.h, "3D Viewer", 0, 0);
    287 	if (!ctx->window) os_fatal(str8("failed to open window\n"));
    288 	glfwMakeContextCurrent(ctx->window);
    289 	glfwSetWindowUserPointer(ctx->window, ctx);
    290 	glfwSwapInterval(1);
    291 
    292 	glfwSetKeyCallback(ctx->window, key_callback);
    293 	glfwSetScrollCallback(ctx->window, scroll_callback);
    294 	glfwSetFramebufferSizeCallback(ctx->window, fb_callback);
    295 
    296 	#define X(name, ret, params) name = (name##_fn *)glfwGetProcAddress(#name);
    297 	OGLProcedureList
    298 	#undef X
    299 
    300 	/* NOTE: set up OpenGL debug logging */
    301 	struct gl_debug_ctx *gl_debug_ctx = push_struct(&ctx->arena, typeof(*gl_debug_ctx));
    302 	gl_debug_ctx->stream = stream_alloc(&ctx->arena, KB(4));
    303 	gl_debug_ctx->os     = &ctx->os;
    304 	glDebugMessageCallback(gl_debug_logger, gl_debug_ctx);
    305 #ifdef _DEBUG
    306 	glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
    307 #endif
    308 
    309 	glEnable(GL_MULTISAMPLE);
    310 	glEnable(GL_DEPTH_TEST);
    311 	glEnable(GL_BLEND);
    312 	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    313 
    314 	RenderContext *rc = &ctx->model_render_context;
    315 
    316 	RenderTarget *rt = &ctx->multisample_target;
    317 	rt->size = (sv2){{RENDER_TARGET_SIZE}};
    318 	glCreateRenderbuffers(countof(rt->textures), rt->textures);
    319 	glNamedRenderbufferStorageMultisample(rt->textures[0], RENDER_MSAA_SAMPLES,
    320 	                                      GL_RGBA8, RENDER_TARGET_SIZE);
    321 	glNamedRenderbufferStorageMultisample(rt->textures[1], RENDER_MSAA_SAMPLES,
    322 	                                      GL_DEPTH_COMPONENT24, RENDER_TARGET_SIZE);
    323 	glCreateFramebuffers(1, &rt->fb);
    324 	glNamedFramebufferRenderbuffer(rt->fb, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rt->textures[0]);
    325 	glNamedFramebufferRenderbuffer(rt->fb, GL_DEPTH_ATTACHMENT,  GL_RENDERBUFFER, rt->textures[1]);
    326 
    327 	rt = &ctx->output_target;
    328 	glCreateTextures(GL_TEXTURE_2D, countof(rt->textures), rt->textures);
    329 	rt->size = (sv2){{RENDER_TARGET_SIZE}};
    330 	glTextureStorage2D(rt->textures[0], 8, GL_RGBA8,             RENDER_TARGET_SIZE);
    331 	glTextureStorage2D(rt->textures[1], 1, GL_DEPTH_COMPONENT24, RENDER_TARGET_SIZE);
    332 
    333 	glCreateFramebuffers(1, &rt->fb);
    334 	glNamedFramebufferTexture(rt->fb, GL_COLOR_ATTACHMENT0, rt->textures[0], 0);
    335 	glNamedFramebufferTexture(rt->fb, GL_DEPTH_ATTACHMENT,  rt->textures[1], 0);
    336 
    337 	glTextureParameteri(rt->textures[0], GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    338 	glTextureParameteri(rt->textures[0], GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    339 
    340 	ShaderReloadContext *model_rc = push_struct(&ctx->arena, ShaderReloadContext);
    341 	model_rc->render_context = rc;
    342 	model_rc->vertex_text = str8(""
    343 	"#version 460 core\n"
    344 	"\n"
    345 	"layout(location = 0) in vec3 v_position;\n"
    346 	"layout(location = 1) in vec3 v_normal;\n"
    347 	"\n"
    348 	"layout(location = 0) out vec3 f_normal;\n"
    349 	"layout(location = 1) out vec3 f_texture_coordinate;\n"
    350 	"layout(location = 2) out vec3 f_orig_texture_coordinate;\n"
    351 	"\n"
    352 	"layout(location = " str(MODEL_RENDER_MODEL_MATRIX_LOC)  ") uniform mat4  u_model;\n"
    353 	"layout(location = " str(MODEL_RENDER_VIEW_MATRIX_LOC)   ") uniform mat4  u_view;\n"
    354 	"layout(location = " str(MODEL_RENDER_PROJ_MATRIX_LOC)   ") uniform mat4  u_projection;\n"
    355 	"layout(location = " str(MODEL_RENDER_CLIP_FRACTION_LOC) ") uniform float u_clip_fraction = 1;\n"
    356 	"layout(location = " str(MODEL_RENDER_SWIZZLE_LOC)       ") uniform bool  u_swizzle;\n"
    357 	"\n"
    358 	"\n"
    359 	"void main()\n"
    360 	"{\n"
    361 	"\tvec3 pos = v_position;\n"
    362 	"\tf_orig_texture_coordinate = (v_position + 1) / 2;\n"
    363 	"\tif (v_position.y == -1) pos.x = clamp(v_position.x, -u_clip_fraction, u_clip_fraction);\n"
    364 	"\tvec3 tex_coord = (pos + 1) / 2;\n"
    365 	"\tf_texture_coordinate = u_swizzle? tex_coord.xzy : tex_coord;\n"
    366 	//"\tf_normal    = normalize(mat3(u_model) * v_normal);\n"
    367 	"\tf_normal    = v_normal;\n"
    368 	"\tgl_Position = u_projection * u_view * u_model * vec4(pos, 1);\n"
    369 	"}\n");
    370 
    371 	model_rc->fragment_header = str8(""
    372 	"#version 460 core\n\n"
    373 	"layout(location = 0) in  vec3 normal;\n"
    374 	"layout(location = 1) in  vec3 texture_coordinate;\n\n"
    375 	"layout(location = 2) in  vec3 test_texture_coordinate;\n\n"
    376 	"layout(location = 0) out vec4 out_colour;\n\n"
    377 	"layout(location = " str(MODEL_RENDER_DYNAMIC_RANGE_LOC) ") uniform float u_db_cutoff = 60;\n"
    378 	"layout(location = " str(MODEL_RENDER_THRESHOLD_LOC)     ") uniform float u_threshold = 40;\n"
    379 	"layout(location = " str(MODEL_RENDER_GAMMA_LOC)         ") uniform float u_gamma     = 1;\n"
    380 	"layout(location = " str(MODEL_RENDER_LOG_SCALE_LOC)     ") uniform bool  u_log_scale;\n"
    381 	"layout(location = " str(MODEL_RENDER_BB_COLOUR_LOC)     ") uniform vec4  u_bb_colour   = vec4(" str(BOUNDING_BOX_COLOUR) ");\n"
    382 	"layout(location = " str(MODEL_RENDER_BB_FRACTION_LOC)   ") uniform float u_bb_fraction = " str(BOUNDING_BOX_FRACTION) ";\n"
    383 	"layout(location = " str(MODEL_RENDER_GAIN_LOC)          ") uniform float u_gain        = 1.0f;\n"
    384 	"\n"
    385 	"layout(binding = 0) uniform sampler3D u_texture;\n"
    386 	"\n#line 1\n");
    387 
    388 	str8 render_model = str8("render_model.frag.glsl");
    389 	reload_shader(&ctx->os, render_model, (sptr)model_rc, ctx->arena);
    390 	os_add_file_watch(&ctx->os, &ctx->arena, render_model, reload_shader, (sptr)model_rc);
    391 
    392 	rc = &ctx->overlay_render_context;
    393 	ShaderReloadContext *overlay_rc = push_struct(&ctx->arena, ShaderReloadContext);
    394 	overlay_rc->render_context = rc;
    395 	overlay_rc->vertex_text = str8(""
    396 	"#version 460 core\n"
    397 	"\n"
    398 	"layout(location = 0) in vec2 v_position;\n"
    399 	"layout(location = 1) in vec2 v_texture_coordinate;\n"
    400 	"layout(location = 2) in vec3 v_colour;\n"
    401 	"layout(location = 3) in uint v_flags;\n"
    402 	"\n"
    403 	"layout(location = 0) out vec2 f_texture_coordinate;\n"
    404 	"layout(location = 1) out vec3 f_colour;\n"
    405 	"layout(location = 2) out uint f_flags;\n"
    406 	"\n"
    407 	"layout(location = 0) uniform ivec2 u_screen_size;\n"
    408 	"\n"
    409 	"void main()\n"
    410 	"{\n"
    411 	"\tf_texture_coordinate = v_texture_coordinate;\n"
    412 	"\tf_colour             = v_colour;\n"
    413 	"\tf_flags              = v_flags;\n"
    414 	"\tgl_Position = vec4(v_position, 0, 1);\n"
    415 	//"\tgl_Position = vec4(2 * (v_position / vec2(u_screen_size)) - 1, 0, 1);\n"
    416 	"}\n");
    417 
    418 	overlay_rc->fragment_header = str8(""
    419 	"#version 460 core\n\n"
    420 	"layout(location = 0) in  vec2 texture_coordinate;\n"
    421 	"layout(location = 1) in  vec3 colour;\n"
    422 	"layout(location = 0) out vec4 out_colour;\n"
    423 	"\n#line 1\n");
    424 
    425 	f32 overlay_vertices[] = {
    426 		-1,  1, 0, 0,
    427 		-1, -1, 0, 1,
    428 		 1, -1, 1, 1,
    429 		-1,  1, 0, 0,
    430 		 1, -1, 1, 1,
    431 		 1,  1, 1, 0,
    432 	};
    433 	glCreateVertexArrays(1, &rc->vao);
    434 	glCreateBuffers(1, &rc->vbo);
    435 
    436 	glNamedBufferData(rc->vbo, sizeof(overlay_vertices), overlay_vertices, GL_STATIC_DRAW);
    437 
    438 	glEnableVertexArrayAttrib(rc->vao, 0);
    439 	glEnableVertexArrayAttrib(rc->vao, 1);
    440 	glVertexArrayVertexBuffer(rc->vao, 0, rc->vbo, 0,               4 * sizeof(f32));
    441 	glVertexArrayVertexBuffer(rc->vao, 1, rc->vbo, 2 * sizeof(f32), 4 * sizeof(f32));
    442 	glVertexArrayAttribFormat(rc->vao, 0, 2, GL_FLOAT, 0, 0);
    443 	glVertexArrayAttribFormat(rc->vao, 1, 2, GL_FLOAT, 0, 2 * sizeof(f32));
    444 	glVertexArrayAttribBinding(rc->vao, 0, 0);
    445 	glVertexArrayAttribBinding(rc->vao, 1, 0);
    446 
    447 	str8 render_overlay = str8("render_overlay.frag.glsl");
    448 	reload_shader(&ctx->os, render_overlay, (sptr)overlay_rc, ctx->arena);
    449 	os_add_file_watch(&ctx->os, &ctx->arena, render_overlay, reload_shader, (sptr)overlay_rc);
    450 
    451 	f32 unit_cube_vertices[] = {
    452 		 1.0f,  1.0f, -1.0f,
    453 		 1.0f,  1.0f, -1.0f,
    454 		 1.0f,  1.0f, -1.0f,
    455 		 1.0f, -1.0f, -1.0f,
    456 		 1.0f, -1.0f, -1.0f,
    457 		 1.0f, -1.0f, -1.0f,
    458 		 1.0f,  1.0f,  1.0f,
    459 		 1.0f,  1.0f,  1.0f,
    460 		 1.0f,  1.0f,  1.0f,
    461 		 1.0f, -1.0f,  1.0f,
    462 		 1.0f, -1.0f,  1.0f,
    463 		 1.0f, -1.0f,  1.0f,
    464 		-1.0f,  1.0f, -1.0f,
    465 		-1.0f,  1.0f, -1.0f,
    466 		-1.0f,  1.0f, -1.0f,
    467 		-1.0f, -1.0f, -1.0f,
    468 		-1.0f, -1.0f, -1.0f,
    469 		-1.0f, -1.0f, -1.0f,
    470 		-1.0f,  1.0f,  1.0f,
    471 		-1.0f,  1.0f,  1.0f,
    472 		-1.0f,  1.0f,  1.0f,
    473 		-1.0f, -1.0f,  1.0f,
    474 		-1.0f, -1.0f,  1.0f,
    475 		-1.0f, -1.0f,  1.0f
    476 	};
    477 	f32 unit_cube_normals[] = {
    478 		 0.0f,  0.0f, -1.0f,
    479 		 0.0f,  1.0f,  0.0f,
    480 		 1.0f,  0.0f,  0.0f,
    481 		 0.0f,  0.0f, -1.0f,
    482 		 0.0f, -1.0f,  0.0f,
    483 		 1.0f,  0.0f,  0.0f,
    484 		 0.0f,  0.0f,  1.0f,
    485 		 0.0f,  1.0f,  0.0f,
    486 		 1.0f,  0.0f,  0.0f,
    487 		 0.0f,  0.0f,  1.0f,
    488 		 0.0f, -1.0f,  0.0f,
    489 		 1.0f,  0.0f,  0.0f,
    490 		 0.0f,  0.0f, -1.0f,
    491 		 0.0f,  1.0f,  0.0f,
    492 		-1.0f,  0.0f,  0.0f,
    493 		 0.0f,  0.0f, -1.0f,
    494 		 0.0f, -1.0f,  0.0f,
    495 		-1.0f,  0.0f,  0.0f,
    496 		 0.0f,  0.0f,  1.0f,
    497 		 0.0f,  1.0f,  0.0f,
    498 		-1.0f,  0.0f,  0.0f,
    499 		 0.0f,  0.0f,  1.0f,
    500 		 0.0f, -1.0f,  0.0f,
    501 		-1.0f,  0.0f,  0.0f
    502 	};
    503 	u16 unit_cube_indices[] = {
    504 		1,  13, 19,
    505 		1,  19, 7,
    506 		9,  6,  18,
    507 		9,  18, 21,
    508 		23, 20, 14,
    509 		23, 14, 17,
    510 		16, 4,  10,
    511 		16, 10, 22,
    512 		5,  2,  8,
    513 		5,  8,  11,
    514 		15, 12, 0,
    515 		15, 0,  3
    516 	};
    517 
    518 	ctx->unit_cube = render_model_from_arrays(unit_cube_vertices, unit_cube_normals,
    519 	                                          unit_cube_indices, countof(unit_cube_indices));
    520 }
    521 
    522 function void
    523 set_camera(u32 program, u32 location, v3 position, v3 normal, v3 orthogonal)
    524 {
    525 	v3 right = cross(orthogonal, normal);
    526 	v3 up    = cross(normal,     right);
    527 
    528 	v3 translate;
    529 	position    = v3_sub((v3){0}, position);
    530 	translate.x = v3_dot(position, right);
    531 	translate.y = v3_dot(position, up);
    532 	translate.z = v3_dot(position, normal);
    533 
    534 	m4 transform;
    535 	transform.c[0] = (v4){{right.x,     up.x,        normal.x,    0}};
    536 	transform.c[1] = (v4){{right.y,     up.y,        normal.y,    0}};
    537 	transform.c[2] = (v4){{right.z,     up.z,        normal.z,    0}};
    538 	transform.c[3] = (v4){{translate.x, translate.y, translate.z, 1}};
    539 	glProgramUniformMatrix4fv(program, location, 1, 0, transform.E);
    540 }
    541 
    542 function void
    543 draw_volume_item(ViewerContext *ctx, VolumeDisplayItem *v, f32 rotation, f32 translate_x)
    544 {
    545 	if (!v->texture) {
    546 		v->texture = load_complex_texture(ctx->arena, v->file_path,
    547 		                                  v->width, v->height, v->depth);
    548 	}
    549 
    550 	u32 program = ctx->model_render_context.shader;
    551 	v3 scale = v3_sub(v->max_coord_mm, v->min_coord_mm);
    552 	m4 S;
    553 	S.c[0] = (v4){{scale.x, 0,       0,       0}};
    554 	S.c[1] = (v4){{0,       scale.z, 0,       0}};
    555 	S.c[2] = (v4){{0,       0,       scale.y, 0}};
    556 	S.c[3] = (v4){{0,       0,       0,       1}};
    557 
    558 	m4 T;
    559 	T.c[0] = (v4){{1, 0, 0, translate_x}};
    560 	T.c[1] = (v4){{0, 1, 0, 0}};
    561 	T.c[2] = (v4){{0, 0, 1, 0}};
    562 	T.c[3] = (v4){{0, 0, 0, 1}};
    563 
    564 	f32 sa = sin_f32(rotation);
    565 	f32 ca = cos_f32(rotation);
    566 	m4 R;
    567 	R.c[0] = (v4){{ ca, 0, sa, 0}};
    568 	R.c[1] = (v4){{ 0,  1, 0,  0}};
    569 	R.c[2] = (v4){{-sa, 0, ca, 0}};
    570 	R.c[3] = (v4){{ 0,  0, 0,  1}};
    571 
    572 	m4 model_transform = m4_mul(m4_mul(R, S), T);
    573 	glProgramUniformMatrix4fv(program, MODEL_RENDER_MODEL_MATRIX_LOC, 1, 0, model_transform.E);
    574 
    575 	glProgramUniform1f(program,  MODEL_RENDER_CLIP_FRACTION_LOC, 1 - v->clip_fraction);
    576 	glProgramUniform1f(program,  MODEL_RENDER_THRESHOLD_LOC,     v->threshold);
    577 	glProgramUniform1f(program,  MODEL_RENDER_GAIN_LOC,          v->gain);
    578 	glProgramUniform1ui(program, MODEL_RENDER_SWIZZLE_LOC,       v->swizzle);
    579 
    580 	glBindTextureUnit(0, v->texture);
    581 	glBindVertexArray(ctx->unit_cube.vao);
    582 	glDrawElements(GL_TRIANGLES, ctx->unit_cube.elements, GL_UNSIGNED_SHORT,
    583 	               (void *)ctx->unit_cube.elements_offset);
    584 }
    585 
    586 function void
    587 update_scene(ViewerContext *ctx, f32 dt)
    588 {
    589 	ctx->cycle_t += CYCLE_T_UPDATE_SPEED * dt;
    590 	if (ctx->cycle_t > 1) ctx->cycle_t -= 1;
    591 
    592 	f32 angle = ctx->cycle_t * 2 * PI;
    593 	ctx->camera_position.x =  0;
    594 	ctx->camera_position.z = -ctx->camera_radius;
    595 	ctx->camera_position.y =  ctx->camera_radius * tan_f32(ctx->camera_angle);
    596 
    597 	RenderTarget *rt = &ctx->multisample_target;
    598 	f32 one = 1;
    599 	glBindFramebuffer(GL_FRAMEBUFFER, rt->fb);
    600 	glClearNamedFramebufferfv(rt->fb, GL_COLOR, 0, OUTPUT_BG_CLEAR_COLOUR.E);
    601 	glClearNamedFramebufferfv(rt->fb, GL_DEPTH, 0, &one);
    602 	glViewport(0, 0, rt->size.w, rt->size.h);
    603 
    604 	u32 program = ctx->model_render_context.shader;
    605 	glUseProgram(program);
    606 
    607 	/* TODO(rnp): set this on hot reload instead of every frame */
    608 	v2 points = {{RENDER_TARGET_SIZE}};
    609 	f32 n = 0.1f;
    610 	f32 f = 400.0f;
    611 	f32 r = n * tan_f32(ctx->camera_fov / 2 * PI / 180.0f);
    612 	f32 t = r * points.h / points.w;
    613 	f32 a = -(f + n) / (f - n);
    614 	f32 b = -2 * f * n / (f - n);
    615 
    616 	m4 projection;
    617 	projection.c[0] = (v4){{n / r, 0,     0,  0}};
    618 	projection.c[1] = (v4){{0,     n / t, 0,  0}};
    619 	projection.c[2] = (v4){{0,     0,     a, -1}};
    620 	projection.c[3] = (v4){{0,     0,     b,  0}};
    621 	glProgramUniformMatrix4fv(program, MODEL_RENDER_PROJ_MATRIX_LOC, 1, 0, projection.E);
    622 
    623 	v3 camera = ctx->camera_position;
    624 	set_camera(program, MODEL_RENDER_VIEW_MATRIX_LOC, camera,
    625 	           v3_normalize(v3_sub(camera, (v3){0})), (v3){{0, 1, 0}});
    626 
    627 	glProgramUniform1ui(program, MODEL_RENDER_LOG_SCALE_LOC,     LOG_SCALE);
    628 	glProgramUniform1f(program,  MODEL_RENDER_DYNAMIC_RANGE_LOC, DYNAMIC_RANGE);
    629 
    630 	#if DRAW_ALL_VOLUMES
    631 	for (u32 i = 0; i < countof(volumes); i++)
    632 		draw_volume_item(ctx, volumes + i, angle, volumes[i].translate_x);
    633 	#else
    634 	draw_volume_item(ctx, volumes + single_volume_index, angle, 0);
    635 	#endif
    636 
    637 	/* NOTE(rnp): resolve multisampled scene */
    638 	glBlitNamedFramebuffer(rt->fb, ctx->output_target.fb, 0, 0, rt->size.w, rt->size.h,
    639 	                       0, 0, rt->size.w, rt->size.h, GL_COLOR_BUFFER_BIT, GL_NEAREST);
    640 
    641 	glGenerateTextureMipmap(ctx->output_target.textures[0]);
    642 }
    643 
    644 function void
    645 viewer_frame_step(ViewerContext *ctx, f32 dt)
    646 {
    647 	if (ctx->do_update) {
    648 		update_scene(ctx, dt);
    649 		if (ctx->output_frames_count) {
    650 			u32 frame_index  = TOTAL_OUTPUT_FRAMES - ctx->output_frames_count--;
    651 			printf("Reading Frame: [%u/%u]\n", frame_index, (u32)TOTAL_OUTPUT_FRAMES - 1);
    652 			sz needed = ctx->output_target.size.w * ctx->output_target.size.h * sizeof(u32);
    653 			glGetTextureImage(ctx->output_target.textures[0], 0, GL_RGBA,
    654 			                  GL_UNSIGNED_INT_8_8_8_8, needed,
    655 			                  ctx->video_arena.beg + ctx->video_arena_offset);
    656 			ctx->video_arena_offset += needed;
    657 			if (!ctx->output_frames_count) {
    658 				str8 raw = {.len  = TOTAL_OUTPUT_FRAMES * needed,
    659 				            .data = ctx->video_arena.beg};
    660 				ctx->video_arena_offset = 0;
    661 				os_write_new_file(RAW_OUTPUT_PATH, raw);
    662 			}
    663 		}
    664 		ctx->do_update = 0;
    665 	}
    666 
    667 	////////////////
    668 	// UI Overlay
    669 	f32 one = 1;
    670 	glBindFramebuffer(GL_FRAMEBUFFER, 0);
    671 	glClearNamedFramebufferfv(0, GL_COLOR, 0, BG_CLEAR_COLOUR.E);
    672 	glClearNamedFramebufferfv(0, GL_DEPTH, 0, &one);
    673 
    674 	f32 aspect_ratio = (f32)ctx->output_target.size.w / (f32)ctx->output_target.size.h;
    675 	sv2 target_size  = ctx->window_size;
    676 	if (aspect_ratio > 1) target_size.h = target_size.w / aspect_ratio;
    677 	else                  target_size.w = target_size.h * aspect_ratio;
    678 
    679 	if (target_size.w > ctx->window_size.w) {
    680 		target_size.w = ctx->window_size.w;
    681 		target_size.h = ctx->window_size.w / aspect_ratio;
    682 	} else if (target_size.h > ctx->window_size.h) {
    683 		target_size.h = ctx->window_size.h;
    684 		target_size.w = target_size.h * aspect_ratio;
    685 	}
    686 
    687 	sv2 size_delta = sv2_sub(ctx->window_size, target_size);
    688 
    689 	glViewport(size_delta.x / 2 + 0.5, size_delta.y / 2 + 0.5, target_size.w, target_size.h);
    690 
    691 	glUseProgram(ctx->overlay_render_context.shader);
    692 	glBindTextureUnit(0, ctx->output_target.textures[0]);
    693 	glBindVertexArray(ctx->overlay_render_context.vao);
    694 	glDrawArrays(GL_TRIANGLES, 0, 6);
    695 
    696 	ctx->should_exit |= glfwWindowShouldClose(ctx->window);
    697 }