volviewer

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

Commit: 84a0929c638a45db0d27910d291a68bae52d2492
Parent: 6f70fb0962e6c9cd37882b12821159b434f61c2e
Author: Randy Palamar
Date:   Sun, 25 May 2025 15:50:24 -0600

finish constant update rate video output

Diffstat:
Mbuild.c | 13-------------
Mcommon.c | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mmain_linux.c | 6+-----
Mmain_w32.c | 6+-----
Amake_video.py | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mos_linux.c | 6++++++
Mos_win32.c | 7+++++++
Mrender_model.frag.glsl | 3++-
Mutil.c | 15+++++++++++++++
Mutil.h | 3+++
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;