Commit: cb637e808de46b397ef4d6b62872fecb0e068f0c
Parent: 64a87ca0208c054cda530c8fb020531c92fd4388
Author: Randy Palamar
Date:   Wed, 29 Oct 2025 12:11:11 -0600
add bitmap export
Diffstat:
| M | common.c | | | 86 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----- | 
| M | util.c | | | 15 | +++++++++++++++ | 
2 files changed, 96 insertions(+), 5 deletions(-)
diff --git a/common.c b/common.c
@@ -216,6 +216,74 @@ load_render_model(Arena arena, c8 *positions_file_name, c8 *indices_file_name, c
 	return result;
 }
 
+function b32
+export_bitmap(ViewerContext *ctx)
+{
+	#pragma pack(push, 1)
+	struct bmp_header {
+		u8  file_type[2];
+		u32 file_size;
+		u16 reserved_1;
+		u16 reserved_2;
+		u32 bitmap_offset;
+
+		u32 size;
+		s32 width;
+		s32 height;
+		u16 planes;
+		u16 bits_per_pixel;
+
+		u32 compression;
+		u32 size_of_bitmap;
+
+		s32 horizontal_resolution;
+		s32 vertical_resolution;
+		u32 colours_used;
+		u32 colours_important;
+
+		u32 red_mask;
+		u32 green_mask;
+		u32 blue_mask;
+		u32 alpha_mask;
+
+		u32 colour_space_type;
+		u32 cie_xyz_triples[9];
+		u32 gamma_red;
+		u32 gamma_green;
+		u32 gamma_blue;
+	};
+	#pragma pack(pop)
+
+	s32 texture_size = ctx->output_target.size.w * ctx->output_target.size.h * sizeof(u32);
+	s32 out_size     = texture_size + round_up_power_of_2(sizeof(struct bmp_header));
+
+	Arena arena   = ctx->video_arena;
+	void *out_buf = arena_alloc(&arena, out_size, 64, 1);
+
+	struct bmp_header *header = (struct bmp_header *)out_buf;
+	header->bitmap_offset  = out_size - texture_size;
+	header->file_type[0]   = 'B';
+	header->file_type[1]   = 'M';
+	header->file_size      = out_size;
+	header->size           = 108;
+	header->width          =  ctx->output_target.size.w;
+	header->height         = -ctx->output_target.size.h;
+	header->planes         = 1;
+	header->bits_per_pixel = 32;
+	header->compression    = 3;
+	header->size_of_bitmap = texture_size;
+	header->red_mask       = 0xFF000000;
+	header->green_mask     = 0x00FF0000;
+	header->blue_mask      = 0x0000FF00;
+	header->alpha_mask     = 0x000000FF;
+
+	glGetTextureImage(ctx->output_target.textures[0], 0, GL_RGBA,
+	                  GL_UNSIGNED_INT_8_8_8_8, texture_size,
+	                  (u8 *)out_buf + header->bitmap_offset);
+
+	return os_write_new_file("view.bmp", (str8){.len  = out_size, .data = out_buf});
+}
+
 function void
 scroll_callback(GLFWwindow *window, f64 x, f64 y)
 {
@@ -234,16 +302,24 @@ key_callback(GLFWwindow *window, s32 key, s32 scancode, s32 action, s32 modifier
 	if (key == GLFW_KEY_SPACE && action == GLFW_PRESS)
 		ctx->demo_mode = !ctx->demo_mode;
 
-	if (key == GLFW_KEY_F12 && action == GLFW_PRESS && ctx->output_frames_count == 0) {
+	if (key == GLFW_KEY_F11 || key == GLFW_KEY_F12) {
 		sz frames       = TOTAL_OUTPUT_FRAMES;
 		sz needed_bytes = sizeof(u32) * RENDER_TARGET_HEIGHT * RENDER_TARGET_WIDTH * frames;
 		if (!ctx->video_arena.beg) {
 			ctx->video_arena = os_alloc_arena(needed_bytes);
-			if (!ctx->video_arena.beg) {
-				fputs("failed to allocate space for output video, video "
-				      "won't be saved\n", stderr);
-			}
+			if (!ctx->video_arena.beg)
+				fputs("failed to allocate space for output data\n", stderr);
 		}
+	}
+
+	if (key == GLFW_KEY_F11) {
+		if (ctx->video_arena.beg) {
+			if (export_bitmap(ctx)) fputs("exported bitmap\n", stderr);
+			else                    fputs("failed to export bitmap\n", stderr);
+		}
+	}
+
+	if (key == GLFW_KEY_F12 && action == GLFW_PRESS && ctx->output_frames_count == 0) {
 		if (ctx->video_arena.beg) {
 			ctx->output_frames_count = TOTAL_OUTPUT_FRAMES;
 			ctx->cycle_t = 0;
diff --git a/util.c b/util.c
@@ -351,6 +351,21 @@ stream_append_f64(Stream *s, f64 f, s64 prec)
 	}
 }
 
+function u32
+clz_u32(u32 a)
+{
+	u32 result = 32;
+	if (a) result = (u32)__builtin_clz(a);
+	return result;
+}
+
+function u32
+round_up_power_of_2(u32 a)
+{
+	u32 result = 0x80000000UL >> (clz_u32(a - 1) - 1);
+	return result;
+}
+
 function Stream
 arena_stream(Arena a)
 {