Commit: 1a87fc8b28e9232f27f13573cfe07b5fd6168f58
Parent: 9b9d12a749b6d314cc435b108e39e693370dc01d
Author: Randy Palamar
Date:   Sun, 25 May 2025 11:46:42 -0600
add multisampling, camera fov, multiple volume support
Diffstat:
5 files changed, 165 insertions(+), 85 deletions(-)
diff --git a/common.c b/common.c
@@ -6,30 +6,52 @@
 #include <stdio.h>
 #include <stdarg.h>
 
-global v3 min_coord_mm = {.x = -18.5, .y = -9.6, .z = 5};
-global v3 max_coord_mm = {.x =  18.5, .y =  9.6, .z = 42};
-
 /* NOTE(rnp): for video output we will render a full rotation in this much time at the
  * the specified frame rate */
 #define OUTPUT_TIME_SECONDS     8.0f
 #define OUTPUT_FRAME_RATE      30.0f
 #define OUTPUT_BG_CLEAR_COLOUR (v4){{0.05, 0.05, 0.05, 1}}
 
-#define RENDER_TARGET_SIZE     1024, 1024
-#define CAMERA_ELEVATION_ANGLE 30.0f
+#define RENDER_MSAA_SAMPLES    8
+#define RENDER_TARGET_SIZE     1920, 1080
+#define CAMERA_ELEVATION_ANGLE 25.0f
+#define CAMERA_RADIUS          200.0f
 
 #define BOUNDING_BOX_COLOUR    0.78, 0.07, 0.20, 1
-#define BOUNDING_BOX_FRACTION  0.005f
+#define BOUNDING_BOX_FRACTION  0.007f
+
+typedef struct {
+	c8  *file_path;
+	u32  width;         /* number of points in data */
+	u32  height;
+	u32  depth;
+	v3   min_coord_mm;
+	v3   max_coord_mm;
+	f32  clip_fraction; /* fraction of half volume used to create pyramidal shape (0 for cube) */
+	f32  threshold;
+	f32  translate_x;   /* mm to translate by when multi display is active */
+	b32  swizzle;       /* 1 -> swap y-z coordinates when sampling texture */
+	b32  multi_file;    /* 1 -> depth == N-frames, file_path == fmt string */
+	u32  texture;
+} VolumeDisplayItem;
+
+#define DRAW_ALL_VOLUMES 0
+global u32 single_volume_index = 0;
+global VolumeDisplayItem volumes[] = {
+	/* WALKING FORCES */
+	{"./data/test/frame_%02u.bin", 512, 1024, 64, {{-18.5, -9.6, 5}}, {{18.5, 9.6, 42}}, 0.58, 62, 0, 0, 1},
+};
 
 #define MODEL_RENDER_MODEL_MATRIX_LOC  (0)
 #define MODEL_RENDER_VIEW_MATRIX_LOC   (1)
 #define MODEL_RENDER_PROJ_MATRIX_LOC   (2)
-#define MODEL_RENDER_LOG_SCALE_LOC     (3)
-#define MODEL_RENDER_DYNAMIC_RANGE_LOC (4)
-#define MODEL_RENDER_THRESHOLD_LOC     (5)
-#define MODEL_RENDER_GAMMA_LOC         (6)
-#define MODEL_RENDER_BB_COLOUR_LOC     (7)
-#define MODEL_RENDER_BB_FRACTION_LOC   (8)
+#define MODEL_RENDER_CLIP_FRACTION_LOC (3)
+#define MODEL_RENDER_LOG_SCALE_LOC     (4)
+#define MODEL_RENDER_DYNAMIC_RANGE_LOC (5)
+#define MODEL_RENDER_THRESHOLD_LOC     (6)
+#define MODEL_RENDER_GAMMA_LOC         (7)
+#define MODEL_RENDER_BB_COLOUR_LOC     (8)
+#define MODEL_RENDER_BB_FRACTION_LOC   (9)
 
 #define BG_CLEAR_COLOUR    (v4){{0.12, 0.1, 0.1, 1}}
 
@@ -166,14 +188,14 @@ function FILE_WATCH_CALLBACK_FN(reload_shader)
 	return 1;
 }
 
-function Texture
+function u32
 load_complex_texture(Arena arena, c8 *file_path, b32 multi_file, u32 width, u32 height, u32 depth)
 {
-	Texture result = {0};
-	glCreateTextures(GL_TEXTURE_3D, 1, &result.texture);
-	glTextureStorage3D(result.texture, 1, GL_RG32F, width, height, depth);
-	glTextureParameteri(result.texture, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
-	glTextureParameteri(result.texture, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
+	u32 result = 0;
+	glCreateTextures(GL_TEXTURE_3D, 1, &result);
+	glTextureStorage3D(result, 1, GL_RG32F, width, height, depth);
+	glTextureParameteri(result, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
+	glTextureParameteri(result, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
 
 	if (multi_file) {
 		/* NOTE(rnp): assumes single plane */
@@ -182,13 +204,11 @@ load_complex_texture(Arena arena, c8 *file_path, b32 multi_file, u32 width, u32 
 			stream_printf(&spath, file_path, i);
 			str8 path = arena_stream_commit_zero(&arena, &spath);
 			str8 raw  = os_read_whole_file(&arena, (char *)path.data);
-			glTextureSubImage3D(result.texture, 0, 0, 0, i, width, height, 1, GL_RG,
-			                    GL_FLOAT, raw.data);
+			glTextureSubImage3D(result, 0, 0, 0, i, width, height, 1, GL_RG, GL_FLOAT, raw.data);
 		}
 	} else {
 		str8 raw = os_read_whole_file(&arena, file_path);
-		glTextureSubImage3D(result.texture, 0, 0, 0, 0, width, height, depth, GL_RG,
-		                    GL_FLOAT, raw.data);
+		glTextureSubImage3D(result, 0, 0, 0, 0, width, height, depth, GL_RG, GL_FLOAT, raw.data);
 	}
 	return result;
 }
@@ -236,7 +256,7 @@ function void
 scroll_callback(GLFWwindow *window, f64 x, f64 y)
 {
 	ViewerContext *ctx  = glfwGetWindowUserPointer(window);
-	ctx->camera_radius += y;
+	ctx->camera_fov += y;
 }
 
 function void
@@ -246,6 +266,14 @@ key_callback(GLFWwindow *window, s32 key, s32 scancode, s32 action, s32 modifier
 	if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
 		ctx->should_exit = 1;
 
+	if (key == GLFW_KEY_SPACE && action == GLFW_PRESS)
+		ctx->demo_mode = !ctx->demo_mode;
+
+	if (key == GLFW_KEY_RIGHT && (action == GLFW_PRESS || action == GLFW_REPEAT))
+		ctx->input_dt += 4.0f / (OUTPUT_TIME_SECONDS * OUTPUT_FRAME_RATE);
+	if (key == GLFW_KEY_LEFT && (action == GLFW_PRESS || action == GLFW_REPEAT))
+		ctx->input_dt -= 4.0f / (OUTPUT_TIME_SECONDS * OUTPUT_FRAME_RATE);
+
 	ctx->camera_angle += (key == GLFW_KEY_W && action != GLFW_RELEASE) * 5 * PI / 180.0f;
 	ctx->camera_angle -= (key == GLFW_KEY_S && action != GLFW_RELEASE) * 5 * PI / 180.0f;
 }
@@ -260,12 +288,15 @@ fb_callback(GLFWwindow *window, s32 w, s32 h)
 function void
 init_viewer(ViewerContext *ctx)
 {
+	ctx->demo_mode     = 1;
 	ctx->window_size   = (sv2){.w = 640, .h = 640};
-	ctx->camera_radius = 80;
+	ctx->camera_radius = CAMERA_RADIUS;
 	ctx->camera_angle  = -CAMERA_ELEVATION_ANGLE * PI / 180.0f;
+	ctx->camera_fov    = 60.0f;
 
 	if (!glfwInit()) os_fatal(str8("failed to start glfw\n"));
 
+
 	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
 	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
 	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
@@ -290,22 +321,37 @@ init_viewer(ViewerContext *ctx)
 	glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
 #endif
 
+	glEnable(GL_MULTISAMPLE);
 	glEnable(GL_DEPTH_TEST);
 	glEnable(GL_BLEND);
 	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 
 	RenderContext *rc = &ctx->model_render_context;
 
-	RenderTarget *rt = &ctx->output_target;
+	RenderTarget *rt = &ctx->multisample_target;
+	rt->size = (sv2){{RENDER_TARGET_SIZE}};
+	glCreateRenderbuffers(countof(rt->textures), rt->textures);
+	glNamedRenderbufferStorageMultisample(rt->textures[0], RENDER_MSAA_SAMPLES,
+	                                      GL_RGBA8, RENDER_TARGET_SIZE);
+	glNamedRenderbufferStorageMultisample(rt->textures[1], RENDER_MSAA_SAMPLES,
+	                                      GL_DEPTH_COMPONENT24, RENDER_TARGET_SIZE);
+	glCreateFramebuffers(1, &rt->fb);
+	glNamedFramebufferRenderbuffer(rt->fb, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rt->textures[0]);
+	glNamedFramebufferRenderbuffer(rt->fb, GL_DEPTH_ATTACHMENT,  GL_RENDERBUFFER, rt->textures[1]);
+
+	rt = &ctx->output_target;
 	glCreateTextures(GL_TEXTURE_2D, countof(rt->textures), rt->textures);
 	rt->size = (sv2){{RENDER_TARGET_SIZE}};
-	glTextureStorage2D(rt->textures[0], 1, GL_RGBA8,             RENDER_TARGET_SIZE);
+	glTextureStorage2D(rt->textures[0], 8, GL_RGBA8,             RENDER_TARGET_SIZE);
 	glTextureStorage2D(rt->textures[1], 1, GL_DEPTH_COMPONENT24, RENDER_TARGET_SIZE);
 
 	glCreateFramebuffers(1, &rt->fb);
 	glNamedFramebufferTexture(rt->fb, GL_COLOR_ATTACHMENT0, rt->textures[0], 0);
 	glNamedFramebufferTexture(rt->fb, GL_DEPTH_ATTACHMENT,  rt->textures[1], 0);
 
+	glTextureParameteri(rt->textures[0], GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+	glTextureParameteri(rt->textures[0], GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+
 	ShaderReloadContext *model_rc = push_struct(&ctx->arena, ShaderReloadContext);
 	model_rc->render_context = rc;
 	model_rc->vertex_text = str8(""
@@ -318,16 +364,17 @@ init_viewer(ViewerContext *ctx)
 	"layout(location = 1) out vec3 f_texture_coordinate;\n"
 	"layout(location = 2) out vec3 f_orig_texture_coordinate;\n"
 	"\n"
-	"layout(location = " str(MODEL_RENDER_MODEL_MATRIX_LOC) ") uniform mat4 u_model;\n"
-	"layout(location = " str(MODEL_RENDER_VIEW_MATRIX_LOC)  ") uniform mat4 u_view;\n"
-	"layout(location = " str(MODEL_RENDER_PROJ_MATRIX_LOC)  ") uniform mat4 u_projection;\n"
+	"layout(location = " str(MODEL_RENDER_MODEL_MATRIX_LOC)  ") uniform mat4  u_model;\n"
+	"layout(location = " str(MODEL_RENDER_VIEW_MATRIX_LOC)   ") uniform mat4  u_view;\n"
+	"layout(location = " str(MODEL_RENDER_PROJ_MATRIX_LOC)   ") uniform mat4  u_projection;\n"
+	"layout(location = " str(MODEL_RENDER_CLIP_FRACTION_LOC) ") uniform float u_clip_fraction = 1;\n"
 	"\n"
 	"\n"
 	"void main()\n"
 	"{\n"
 	"\tvec3 pos = v_position;\n"
 	"\tf_orig_texture_coordinate = (v_position + 1) / 2;\n"
-	"\tif (v_position.y == -1) pos.x = clamp(v_position.x, -0.42, 0.42);\n"
+	"\tif (v_position.y == -1) pos.x = clamp(v_position.x, -u_clip_fraction, u_clip_fraction);\n"
 	"\tf_texture_coordinate = (pos + 1) / 2;\n"
 	//"\tf_normal    = normalize(mat3(u_model) * v_normal);\n"
 	"\tf_normal    = v_normal;\n"
@@ -411,9 +458,6 @@ init_viewer(ViewerContext *ctx)
 
 	ctx->unit_cube = load_render_model(ctx->arena, "unit_cube_positions.bin",
 	                                   "unit_cube_indices.bin", "unit_cube_normals.bin");
-
-	c8 *walking_cysts = "./data/test/frame_%02u.bin";
-	ctx->view_texture = load_complex_texture(ctx->arena, walking_cysts, 1, 512, 1024, 64);
 }
 
 function void
@@ -437,7 +481,33 @@ set_camera(u32 program, u32 location, v3 position, v3 normal, v3 orthogonal)
 }
 
 function void
-viewer_frame_step(ViewerContext *ctx, f32 dt)
+draw_volume_item(ViewerContext *ctx, VolumeDisplayItem *v)
+{
+	if (!v->texture) {
+		v->texture = load_complex_texture(ctx->arena, v->file_path, v->multi_file,
+		                                  v->width, v->height, v->depth);
+	}
+
+	u32 program = ctx->model_render_context.shader;
+	v3 scale = v3_sub(v->max_coord_mm, v->min_coord_mm);
+	m4 model_transform;
+	model_transform.c[0] = (v4){{scale.x,        0,       0,       0}};
+	model_transform.c[1] = (v4){{0,              scale.z, 0,       0}};
+	model_transform.c[2] = (v4){{0,              0,       scale.y, 0}};
+	model_transform.c[3] = (v4){{v->translate_x, 0,       0,       1}};
+	glProgramUniformMatrix4fv(program, MODEL_RENDER_MODEL_MATRIX_LOC, 1, 0, model_transform.E);
+
+	glProgramUniform1f(program, MODEL_RENDER_CLIP_FRACTION_LOC, 1 - v->clip_fraction);
+	glProgramUniform1f(program, MODEL_RENDER_THRESHOLD_LOC,     v->threshold);
+
+	glBindTextureUnit(0, v->texture);
+	glBindVertexArray(ctx->unit_cube.vao);
+	glDrawElements(GL_TRIANGLES, ctx->unit_cube.elements, GL_UNSIGNED_SHORT,
+	               (void *)ctx->unit_cube.elements_offset);
+}
+
+function void
+update_scene(ViewerContext *ctx, f32 dt)
 {
 	ctx->cycle_t += 0.25 * dt;
 	if (ctx->cycle_t > 1) ctx->cycle_t -= 1;
@@ -447,50 +517,58 @@ viewer_frame_step(ViewerContext *ctx, f32 dt)
 	ctx->camera_position.z = -ctx->camera_radius * sin_f32(angle);
 	ctx->camera_position.y =  ctx->camera_radius * tan_f32(ctx->camera_angle);
 
-	//////////////
-	// 3D Models
+	RenderTarget *rt = &ctx->multisample_target;
 	f32 one = 1;
-	glBindFramebuffer(GL_FRAMEBUFFER, ctx->output_target.fb);
-	glClearNamedFramebufferfv(ctx->output_target.fb, GL_COLOR, 0, OUTPUT_BG_CLEAR_COLOUR.E);
-	glClearNamedFramebufferfv(ctx->output_target.fb, GL_DEPTH, 0, &one);
-	glViewport(0, 0, ctx->output_target.size.w, ctx->output_target.size.h);
-
-	glUseProgram(ctx->model_render_context.shader);
-
-	/* TODO(rnp): this needs to be set on hot reload */
-	/* TODO(rnp): think about a reasonable region (probably min_coord -> max_coord + 10%) */
-	f32 n = 1;
-	f32 f = 200;
-	f32 a = -f / (f - n);
-	f32 b = -f * n / (f - n);
+	glBindFramebuffer(GL_FRAMEBUFFER, rt->fb);
+	glClearNamedFramebufferfv(rt->fb, GL_COLOR, 0, OUTPUT_BG_CLEAR_COLOUR.E);
+	glClearNamedFramebufferfv(rt->fb, GL_DEPTH, 0, &one);
+	glViewport(0, 0, rt->size.w, rt->size.h);
+
+	u32 program = ctx->model_render_context.shader;
+	glUseProgram(program);
+
+	/* TODO(rnp): set this on hot reload instead of every frame */
+	v2 points = {{RENDER_TARGET_SIZE}};
+	f32 n = 0.1f;
+	f32 f = 400.0f;
+	f32 r = n * tan_f32(ctx->camera_fov / 2 * PI / 180.0f);
+	f32 t = r * points.h / points.w;
+	f32 a = -(f + n) / (f - n);
+	f32 b = -2 * f * n / (f - n);
+
 	m4 projection;
-	projection.c[0] = (v4){{1, 0, 0,  0}};
-	projection.c[1] = (v4){{0, 1, 0,  0}};
-	projection.c[2] = (v4){{0, 0, a, -1}};
-	projection.c[3] = (v4){{0, 0, b,  0}};
-	glProgramUniformMatrix4fv(ctx->model_render_context.shader, MODEL_RENDER_PROJ_MATRIX_LOC,
-	                          1, 0, projection.E);
+	projection.c[0] = (v4){{n / r, 0,     0,  0}};
+	projection.c[1] = (v4){{0,     n / t, 0,  0}};
+	projection.c[2] = (v4){{0,     0,     a, -1}};
+	projection.c[3] = (v4){{0,     0,     b,  0}};
+	glProgramUniformMatrix4fv(program, MODEL_RENDER_PROJ_MATRIX_LOC, 1, 0, projection.E);
 
 	v3 camera = ctx->camera_position;
-	set_camera(ctx->model_render_context.shader, MODEL_RENDER_VIEW_MATRIX_LOC,
-	           camera, v3_normalize(v3_sub(camera, (v3){0})), (v3){{0, 1, 0}});
+	set_camera(program, MODEL_RENDER_VIEW_MATRIX_LOC, camera,
+	           v3_normalize(v3_sub(camera, (v3){0})), (v3){{0, 1, 0}});
 
-	v3 scale = v3_sub(max_coord_mm, min_coord_mm);
-	m4 model_transform;
-	model_transform.c[0] = (v4){{scale.x, 0,       0,       0}};
-	model_transform.c[1] = (v4){{0,       scale.z, 0,       0}};
-	model_transform.c[2] = (v4){{0,       0,       scale.y, 0}};
-	model_transform.c[3] = (v4){{0,       0,       0,       1}};
-	glProgramUniformMatrix4fv(ctx->model_render_context.shader, MODEL_RENDER_MODEL_MATRIX_LOC,
-	                          1, 0, model_transform.E);
-
-	glBindTextureUnit(0, ctx->view_texture.texture);
-	glBindVertexArray(ctx->unit_cube.vao);
-	glDrawElements(GL_TRIANGLES, ctx->unit_cube.elements, GL_UNSIGNED_SHORT,
-	               (void *)ctx->unit_cube.elements_offset);
+	#if DRAW_ALL_VOLUMES
+	for (u32 i = 0; i < countof(volumes); i++)
+		draw_volume_item(ctx, volumes + i);
+	#else
+	draw_volume_item(ctx, volumes + single_volume_index);
+	#endif
+
+	/* NOTE(rnp): resolve multisampled scene */
+	glBlitNamedFramebuffer(rt->fb, ctx->output_target.fb, 0, 0, rt->size.w, rt->size.h,
+	                       0, 0, rt->size.w, rt->size.h, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+
+	glGenerateTextureMipmap(ctx->output_target.textures[0]);
+}
+
+function void
+viewer_frame_step(ViewerContext *ctx, f32 dt)
+{
+	if (dt != 0) update_scene(ctx, dt);
 
 	////////////////
 	// UI Overlay
+	f32 one = 1;
 	glBindFramebuffer(GL_FRAMEBUFFER, 0);
 	glClearNamedFramebufferfv(0, GL_COLOR, 0, BG_CLEAR_COLOUR.E);
 	glClearNamedFramebufferfv(0, GL_DEPTH, 0, &one);
@@ -516,7 +594,4 @@ viewer_frame_step(ViewerContext *ctx, f32 dt)
 	glBindTextureUnit(0, ctx->output_target.textures[0]);
 	glBindVertexArray(ctx->overlay_render_context.vao);
 	glDrawArrays(GL_TRIANGLES, 0, 6);
-
-	glfwSwapBuffers(ctx->window);
-	glfwPollEvents();
 }
diff --git a/main_linux.c b/main_linux.c
@@ -66,7 +66,10 @@ main(void)
 		if (fds[0].revents & POLLIN)
 			dispatch_file_watch_events(&ctx->os, ctx->arena);
 		f64 now = glfwGetTime();
-		viewer_frame_step(ctx, now - last_time);
+		viewer_frame_step(ctx, ctx->demo_mode * (now - last_time) + ctx->input_dt);
 		last_time = now;
+		ctx->input_dt = 0;
+		glfwSwapBuffers(ctx->window);
+		glfwPollEvents();
 	}
 }
diff --git a/main_w32.c b/main_w32.c
@@ -89,8 +89,14 @@ main(void)
 
 	init_viewer(ctx);
 
+	f64 last_time = 0;
 	while (!ctx->should_exit) {
-		clear_io_queue(&ctx->os, &input, ctx->arena);
-		viewer_frame_step(ctx, &input);
+		clear_io_queue(&ctx->os, ctx->arena);
+		f64 now = glfwGetTime();
+		viewer_frame_step(ctx, ctx->demo_mode * (now - last_time) + ctx->input_dt);
+		last_time = now;
+		ctx->input_dt = 0;
+		glfwSwapBuffers(ctx->window);
+		glfwPollEvents();
 	}
 }
diff --git a/render_model.frag.glsl b/render_model.frag.glsl
@@ -21,7 +21,7 @@ bool bounding_box_test(vec3 coord, float p)
 void main()
 {
 	float smp = length(texture(u_texture, texture_coordinate).xy);
-	float threshold_val = pow(10.0f, (u_threshold + 13) / 20.0f);
+	float threshold_val = pow(10.0f, u_threshold / 20.0f);
 	smp = clamp(smp, 0.0f, threshold_val);
 	smp = smp / threshold_val;
 	smp = pow(smp, u_gamma);
diff --git a/util.h b/util.h
@@ -269,13 +269,6 @@ struct OS {
 };
 
 typedef struct {
-	u32 texture;
-	u32 width;
-	u32 height;
-	u32 depth;
-} Texture;
-
-typedef struct {
 	u32 shader;
 	u32 vao;
 	u32 vbo;
@@ -301,17 +294,20 @@ typedef struct {
 	RenderContext model_render_context;
 	RenderContext overlay_render_context;
 
+	RenderTarget multisample_target;
 	RenderTarget output_target;
 	RenderModel  unit_cube;
 
 	sv2 window_size;
 
+	b32 demo_mode;
 	f32 cycle_t;
 	f32 camera_angle;
+	f32 camera_fov;
 	f32 camera_radius;
 	v3  camera_position;
 
-	Texture view_texture;
+	f32 input_dt;
 
 	b32 should_exit;