Commit: 84a0929c638a45db0d27910d291a68bae52d2492
Parent: 6f70fb0962e6c9cd37882b12821159b434f61c2e
Author: Randy Palamar
Date:   Sun, 25 May 2025 15:50:24 -0600
finish constant update rate video output
Diffstat:
10 files changed, 168 insertions(+), 32 deletions(-)
diff --git a/build.c b/build.c
@@ -134,12 +134,6 @@ os_remove_file(char *name)
 	return result;
 }
 
-function void
-os_make_directory(char *name)
-{
-	mkdir(name, 0770);
-}
-
 function u64
 os_get_filetime(char *file)
 {
@@ -200,19 +194,12 @@ enum {
 	MOVEFILE_REPLACE_EXISTING = 0x01,
 };
 
-W32(b32) CreateDirectoryA(c8 *, void *);
 W32(b32) CreateProcessA(u8 *, u8 *, sptr, sptr, b32, u32, sptr, u8 *, sptr, sptr);
 W32(b32) GetExitCodeProcess(iptr handle, u32 *);
 W32(b32) GetFileTime(sptr, sptr, sptr, sptr);
 W32(b32) MoveFileExA(c8 *, c8 *, u32);
 W32(u32) WaitForSingleObject(sptr, u32);
 
-function void
-os_make_directory(char *name)
-{
-	CreateDirectoryA(name, 0);
-}
-
 function b32
 os_rename_file(char *name, char *new)
 {
diff --git a/common.c b/common.c
@@ -9,9 +9,11 @@
 /* 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_FRAME_RATE      60.0f
 #define OUTPUT_BG_CLEAR_COLOUR (v4){{0.05, 0.05, 0.05, 1}}
 
+#define OUTPUT_PATH "/tmp/out"
+
 #define RENDER_MSAA_SAMPLES    8
 #define RENDER_TARGET_SIZE     1920, 1080
 #define CAMERA_ELEVATION_ANGLE 25.0f
@@ -39,8 +41,10 @@ typedef struct {
 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, 3 * -18.5, 0, 1},
-	{"./data/test/frame_%02u.bin", 512, 1024, 64, {{-18.5, -9.6, 5}}, {{18.5, 9.6, 42}}, 0.58, 62, 3 *  18.5, 0, 1},
+	{"./data/test/frame_%02u.bin", 512, 1024, 64, {{-18.5, -9.6, 5}}, {{18.5, 9.6, 42}}, 0.58, 62, 0, 0, 1},
+	/* RCA */
+	{"./data/tpw.bin", 512, 64, 1024, {{-9.6, -9.6, 5}}, {{9.6, 9.6, 42}}, 0, 85, -5 *  18.5, 1, 0},
+	{"./data/vls.bin", 512, 64, 1024, {{-9.6, -9.6, 5}}, {{9.6, 9.6, 42}}, 0, 82,  5 *  18.5, 1, 0},
 };
 
 #define MODEL_RENDER_MODEL_MATRIX_LOC  (0)
@@ -53,14 +57,32 @@ global VolumeDisplayItem volumes[] = {
 #define MODEL_RENDER_GAMMA_LOC         (7)
 #define MODEL_RENDER_BB_COLOUR_LOC     (8)
 #define MODEL_RENDER_BB_FRACTION_LOC   (9)
+#define MODEL_RENDER_SWIZZLE_LOC       (10)
 
-#define BG_CLEAR_COLOUR    (v4){{0.12, 0.1, 0.1, 1}}
+#define CYCLE_T_UPDATE_SPEED 0.25f
+#define BG_CLEAR_COLOUR      (v4){{0.12, 0.1, 0.1, 1}}
 
 struct gl_debug_ctx {
 	Stream  stream;
 	OS     *os;
 };
 
+function f32
+get_frame_time_step(ViewerContext *ctx)
+{
+	f32 result = 0;
+	/* NOTE(rnp): if we are outputting frames do a constant time step */
+	if (ctx->output_frames_count > 0) {
+		result = 1.0f / (OUTPUT_FRAME_RATE * OUTPUT_TIME_SECONDS * CYCLE_T_UPDATE_SPEED);
+	} else {
+		f64 now = glfwGetTime();
+		result = ctx->demo_mode * (now - ctx->last_time) + ctx->input_dt;
+		ctx->last_time = now;
+		ctx->input_dt  = 0;
+	}
+	return result;
+}
+
 function void
 gl_debug_logger(u32 src, u32 type, u32 id, u32 lvl, s32 len, const char *msg, const void *userctx)
 {
@@ -275,6 +297,11 @@ key_callback(GLFWwindow *window, s32 key, s32 scancode, s32 action, s32 modifier
 	if (key == GLFW_KEY_LEFT && (action == GLFW_PRESS || action == GLFW_REPEAT))
 		ctx->input_dt -= 4.0f / (OUTPUT_TIME_SECONDS * OUTPUT_FRAME_RATE);
 
+	if (key == GLFW_KEY_F12 && action == GLFW_PRESS && ctx->output_frames_count == 0) {
+		ctx->output_frames_count = OUTPUT_TIME_SECONDS * OUTPUT_FRAME_RATE;
+		ctx->cycle_t = 0;
+	}
+
 	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;
 }
@@ -295,8 +322,9 @@ init_viewer(ViewerContext *ctx)
 	ctx->camera_angle  = -CAMERA_ELEVATION_ANGLE * PI / 180.0f;
 	ctx->camera_fov    = 60.0f;
 
-	if (!glfwInit()) os_fatal(str8("failed to start glfw\n"));
+	os_make_directory(OUTPUT_PATH);
 
+	if (!glfwInit()) os_fatal(str8("failed to start glfw\n"));
 
 	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
 	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
@@ -393,7 +421,8 @@ init_viewer(ViewerContext *ctx)
 	"layout(location = " str(MODEL_RENDER_GAMMA_LOC)         ") uniform float u_gamma     = 1;\n"
 	"layout(location = " str(MODEL_RENDER_LOG_SCALE_LOC)     ") uniform bool  u_log_scale;\n"
 	"layout(location = " str(MODEL_RENDER_BB_COLOUR_LOC)     ") uniform vec4  u_bb_colour   = vec4(" str(BOUNDING_BOX_COLOUR) ");\n"
-	"layout(location = " str(MODEL_RENDER_BB_FRACTION_LOC)   ") uniform float u_bb_fraction = " str(BOUNDING_BOX_FRACTION) ";\n\n"
+	"layout(location = " str(MODEL_RENDER_BB_FRACTION_LOC)   ") uniform float u_bb_fraction = " str(BOUNDING_BOX_FRACTION) ";\n"
+	"layout(location = " str(MODEL_RENDER_SWIZZLE_LOC)       ") uniform bool u_swizzle;\n\n"
 	"layout(binding = 0) uniform sampler3D u_texture;\n"
 	"\n#line 1\n");
 
@@ -516,6 +545,7 @@ draw_volume_item(ViewerContext *ctx, VolumeDisplayItem *v, f32 rotation)
 
 	glProgramUniform1f(program, MODEL_RENDER_CLIP_FRACTION_LOC, 1 - v->clip_fraction);
 	glProgramUniform1f(program, MODEL_RENDER_THRESHOLD_LOC,     v->threshold);
+	glProgramUniform1ui(program, MODEL_RENDER_SWIZZLE_LOC,      v->swizzle);
 
 	glBindTextureUnit(0, v->texture);
 	glBindVertexArray(ctx->unit_cube.vao);
@@ -526,7 +556,7 @@ draw_volume_item(ViewerContext *ctx, VolumeDisplayItem *v, f32 rotation)
 function void
 update_scene(ViewerContext *ctx, f32 dt)
 {
-	ctx->cycle_t += 0.25 * dt;
+	ctx->cycle_t += CYCLE_T_UPDATE_SPEED * dt;
 	if (ctx->cycle_t > 1) ctx->cycle_t -= 1;
 
 	f32 angle = ctx->cycle_t * 2 * PI;
@@ -579,9 +609,40 @@ update_scene(ViewerContext *ctx, f32 dt)
 }
 
 function void
+export_frame(Arena arena, u32 texture, str8 out_directory, u32 frame_index, u32 width, u32 height)
+{
+	Stream spath = arena_stream(arena);
+	stream_append_str8(&spath, out_directory);
+	if (spath.widx > 0 && spath.data[spath.widx - 1] != OS_PATH_SEPARATOR_CHAR)
+		stream_append_byte(&spath, OS_PATH_SEPARATOR_CHAR);
+	stream_append_str8(&spath, str8("frame_"));
+	stream_append_u64_width(&spath, frame_index, 4);
+	stream_append_str8(&spath, str8(".bin"));
+	str8 path = arena_stream_commit_zero(&arena, &spath);
+
+	sz padding   = -(uintptr_t)arena.beg & (64 - 1);
+	sz available = arena.end - arena.beg - padding;
+	sz needed    = width * height * sizeof(u32);
+	if (available > needed) {
+		glGetTextureImage(texture, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, needed, arena.beg);
+		str8 raw = {.len = needed, .data = arena.beg};
+		os_write_new_file((c8 *)path.data, raw);
+	}
+}
+
+function void
 viewer_frame_step(ViewerContext *ctx, f32 dt)
 {
-	if (dt != 0) update_scene(ctx, dt);
+	if (dt != 0) {
+		update_scene(ctx, dt);
+		if (ctx->output_frames_count) {
+			u32 total_frames = OUTPUT_FRAME_RATE * OUTPUT_TIME_SECONDS;
+			u32 frame_index  = total_frames - ctx->output_frames_count--;
+			printf("Saving Frame: [%u/%u]\n", frame_index, total_frames);
+			export_frame(ctx->arena, ctx->output_target.textures[0], str8(OUTPUT_PATH),
+			             frame_index, RENDER_TARGET_SIZE);
+		}
+	}
 
 	////////////////
 	// UI Overlay
diff --git a/main_linux.c b/main_linux.c
@@ -60,15 +60,11 @@ main(void)
 	fds[0].fd     = ctx->os.file_watch_context.handle;
 	fds[0].events = POLLIN;
 
-	f64 last_time = 0;
 	while (!ctx->should_exit) {
 		poll(fds, countof(fds), 0);
 		if (fds[0].revents & POLLIN)
 			dispatch_file_watch_events(&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;
+		viewer_frame_step(ctx, get_frame_time_step(ctx));
 		glfwSwapBuffers(ctx->window);
 		glfwPollEvents();
 	}
diff --git a/main_w32.c b/main_w32.c
@@ -89,13 +89,9 @@ main(void)
 
 	init_viewer(ctx);
 
-	f64 last_time = 0;
 	while (!ctx->should_exit) {
 		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;
+		viewer_frame_step(ctx, get_frame_time_step(ctx));
 		glfwSwapBuffers(ctx->window);
 		glfwPollEvents();
 	}
diff --git a/make_video.py b/make_video.py
@@ -0,0 +1,64 @@
+import cv2
+import subprocess
+import tempfile as tf
+import pandas   as pd
+import numpy    as np
+import os
+
+def read_raw(file_name, points):
+    raw = np.fromfile(file_name, dtype=np.uint8)
+    real_points = [points[0], points[1], 4]
+    raw = np.squeeze(raw.reshape(real_points))
+    img = raw[:,:,(1, 2, 3, 0)]
+    return img
+
+def get_files(directory):
+    files = []
+    for filename in os.listdir(directory):
+        base, ext = os.path.splitext(filename)
+        if ext == '.bin':
+            files.append(filename)
+    return files
+
+def make_frames(directory, points):
+    imgs = []
+    for f in get_files(directory):
+        raw_file_name = os.path.join(directory, f)
+        raw           = read_raw(raw_file_name, points)
+        imgs.append(raw)
+    return imgs
+
+
+def make_video(name, frames, framerate):
+    with tf.TemporaryDirectory() as tmp:
+        count = 0
+        for frame in frames:
+            cv2.imwrite(os.path.join(tmp, "frame_{:02d}.png".format(count)), frame)
+            count = count + 1
+
+        ffmpeg_command = [
+            "ffmpeg",
+            "-y",
+            "-framerate", "{:d}".format(framerate),
+            "-i",         os.path.join(tmp, 'frame_%02d.png'),
+            "-c:v",       "libx265",
+            "-crf",       "18",
+            name
+        ]
+        subprocess.run(ffmpeg_command)
+
+
+##################################
+save = True
+out_dir   = "/tmp/downloads"
+points    = [1080, 1920]
+framerate = 60
+if save:
+    os.makedirs(out_dir, mode=0o644, exist_ok=True)
+
+frames = make_frames("/tmp/downloads/out", points)
+if save:
+    make_video(os.path.join(out_dir, "volumes.mp4"), frames, framerate)
+
+#cv2.imshow("", frames[0])
+#cv2.waitKey(0)
diff --git a/os_linux.c b/os_linux.c
@@ -105,3 +105,9 @@ function OS_ADD_FILE_WATCH_FN(os_add_file_watch)
 	fw->callback  = callback;
 	fw->hash      = str8_hash(path);
 }
+
+function void
+os_make_directory(char *name)
+{
+	mkdir(name, 0770);
+}
diff --git a/os_win32.c b/os_win32.c
@@ -81,6 +81,7 @@ typedef struct {
 
 #define W32(r) __declspec(dllimport) r __stdcall
 W32(b32)    CloseHandle(sptr);
+W32(b32)    CreateDirectoryA(c8 *, void *);
 W32(sptr)   CreateFileA(c8 *, u32, u32, void *, u32, u32, void *);
 W32(sptr)   CreateFileMappingA(sptr, void *, u32, u32, u32, c8 *);
 W32(sptr)   CreateIoCompletionPort(sptr, sptr, uptr, u32);
@@ -236,3 +237,9 @@ function OS_ADD_FILE_WATCH_FN(os_add_file_watch)
 	fw->callback  = callback;
 	fw->hash      = str8_hash(path);
 }
+
+function void
+os_make_directory(char *name)
+{
+	CreateDirectoryA(name, 0);
+}
diff --git a/render_model.frag.glsl b/render_model.frag.glsl
@@ -20,7 +20,8 @@ bool bounding_box_test(vec3 coord, float p)
 
 void main()
 {
-	float smp = length(texture(u_texture, texture_coordinate).xy);
+	vec3 tex_coord = u_swizzle? texture_coordinate.xzy : texture_coordinate;
+	float smp = length(texture(u_texture, tex_coord).xy);
 	float threshold_val = pow(10.0f, u_threshold / 20.0f);
 	smp = clamp(smp, 0.0f, threshold_val);
 	smp = smp / threshold_val;
diff --git a/util.c b/util.c
@@ -305,6 +305,21 @@ stream_append_u64(Stream *s, u64 n)
 }
 
 function void
+stream_append_u64_width(Stream *s, u64 n, u64 min_width)
+{
+	u8 tmp[64];
+	u8 *end = tmp + sizeof(tmp);
+	u8 *beg = end;
+	min_width = MIN(sizeof(tmp), min_width);
+
+	do { *--beg = '0' + (n % 10); } while (n /= 10);
+	while (end - beg > 0 && end - beg < min_width)
+		*--beg = '0';
+
+	stream_append(s, beg, end - beg);
+}
+
+function void
 stream_append_s64(Stream *s, s64 n)
 {
 	if (n < 0) {
diff --git a/util.h b/util.h
@@ -307,6 +307,9 @@ typedef struct {
 	f32 camera_radius;
 	v3  camera_position;
 
+	u32 output_frames_count;
+
+	f32 last_time;
 	f32 input_dt;
 
 	b32 should_exit;