Commit: 719cb1e490abf88c1acd38bbc08ca540a9dd60ca
Parent: e509d444b697072fc122f7b12d3b0eee5d75cec2
Author: Randy Palamar
Date:   Fri, 23 May 2025 20:59:00 -0600
render to separate render target
this will be needed to export a video with a specific resolution
independent of the current window resolution.
Diffstat:
5 files changed, 192 insertions(+), 48 deletions(-)
diff --git a/common.c b/common.c
@@ -3,7 +3,14 @@
 #include "glad/gl.h"
 #include "GLFW/glfw3.h"
 
-#define static_path_join(a, b) (a OS_PATH_SEPARATOR b)
+#define BG_CLEAR_COLOUR    (v4){{0.12, 0.1, 0.1, 1}}
+#define RENDER_TARGET_SIZE 720, 720
+
+/* 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.0, 0.2, 0.0, 1}}
 
 #define VIEWER_RENDER_DYNAMIC_RANGE_LOC (0)
 #define VIEWER_RENDER_THRESHOLD_LOC     (1)
@@ -114,50 +121,25 @@ load_shader(OS *os, Arena arena, str8 vs_text, str8 fs_text, str8 info_name, str
 	return result;
 }
 
-function FILE_WATCH_CALLBACK_FN(reload_render_shader)
-{
-	RenderContext *ctx = (typeof(ctx))user_data;
-
-	local_persist str8 vertex = str8(""
-	"#version 460 core\n"
-	"\n"
-	"layout(location = 0) in vec3 v_normal;\n"
-	"layout(location = 1) in vec3 v_position;\n"
-	"layout(location = 2) in vec3 v_texture_coordinate;\n"
-	"layout(location = 3) in vec3 v_colour;\n"
-	"layout(location = 4) in uint v_flags;\n"
-	"\n"
-	"layout(location = 0) out vec3 f_texture_coordinate;\n"
-	"layout(location = 1) out vec3 f_colour;\n"
-	"\n"
-	"void main()\n"
-	"{\n"
-	"\tf_texture_coordinate = v_texture_coordinate;\n"
-	"\tf_colour             = v_colour;\n"
-	"\tgl_Position = vec4(v_position, 1);\n"
-	"}\n");
-
-	str8 header = push_str8(&tmp, str8(""
-	"#version 460 core\n\n"
-	"layout(location = 0) in  vec3 texture_coordinate;\n"
-	"layout(location = 1) in  vec3 colour;\n"
-	"layout(location = 0) out vec4 out_colour;\n\n"
-	"layout(location = " str(VIEWER_RENDER_DYNAMIC_RANGE_LOC) ") uniform float u_db_cutoff = 60;\n"
-	"layout(location = " str(VIEWER_RENDER_THRESHOLD_LOC)     ") uniform float u_threshold = 40;\n"
-	"layout(location = " str(VIEWER_RENDER_GAMMA_LOC)         ") uniform float u_gamma     = 1;\n"
-	"layout(location = " str(VIEWER_RENDER_LOG_SCALE_LOC)     ") uniform bool  u_log_scale;\n"
-	"\n#line 1\n"));
+typedef struct {
+	RenderContext *render_context;
+	str8 vertex_text;
+	str8 fragment_header;
+} ShaderReloadContext;
 
+function FILE_WATCH_CALLBACK_FN(reload_shader)
+{
+	ShaderReloadContext *ctx = (typeof(ctx))user_data;
+	str8 header    = push_str8(&tmp, ctx->fragment_header);
 	str8 fragment  = os->read_whole_file(&tmp, (c8 *)path.data);
 	fragment.data -= header.len;
 	fragment.len  += header.len;
 	assert(fragment.data == header.data);
-	u32 new_program = load_shader(os, tmp, vertex, fragment, path, str8("Render Shader"));
+	u32 new_program = load_shader(os, tmp, ctx->vertex_text, fragment, path, path);
 	if (new_program) {
-		glDeleteProgram(ctx->shader);
-		ctx->shader = new_program;
+		glDeleteProgram(ctx->render_context->shader);
+		ctx->render_context->shader = new_program;
 	}
-
 	return 1;
 }
 
@@ -174,7 +156,6 @@ fb_callback(GLFWwindow *window, s32 w, s32 h)
 {
 	ViewerContext *ctx = glfwGetWindowUserPointer(window);
 	ctx->window_size   = (sv2){.w = w, .h = h};
-	glViewport(0, 0, w, h);
 }
 
 function void
@@ -207,7 +188,7 @@ init_viewer(ViewerContext *ctx)
 	glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
 #endif
 
-	RenderContext *rc = &ctx->render_context;
+	RenderContext *rc = &ctx->model_render_context;
 
 	Vertex vertices[] = {
 		{.position = {{-0.5, -0.5, 0.0}}, .colour = {{1.0, 0.0, 0.0}}},
@@ -232,20 +213,149 @@ init_viewer(ViewerContext *ctx)
 	glEnableVertexAttribArray(3);
 	glEnableVertexAttribArray(4);
 
-	str8 render = str8("render.frag.glsl");
-	reload_render_shader(&ctx->os, render, (sptr)rc, ctx->arena);
-	os_add_file_watch(&ctx->os, &ctx->arena, render, reload_render_shader, (sptr)rc);
+	RenderTarget *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[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);
+
+	ShaderReloadContext *model_rc = push_struct(&ctx->arena, ShaderReloadContext);
+	model_rc->render_context = rc;
+	model_rc->vertex_text = str8(""
+	"#version 460 core\n"
+	"\n"
+	"layout(location = 0) in vec3 v_normal;\n"
+	"layout(location = 1) in vec3 v_position;\n"
+	"layout(location = 2) in vec3 v_texture_coordinate;\n"
+	"layout(location = 3) in vec3 v_colour;\n"
+	"layout(location = 4) in uint v_flags;\n"
+	"\n"
+	"layout(location = 0) out vec3 f_texture_coordinate;\n"
+	"layout(location = 1) out vec3 f_colour;\n"
+	"\n"
+	"void main()\n"
+	"{\n"
+	"\tf_texture_coordinate = v_texture_coordinate;\n"
+	"\tf_colour             = v_colour;\n"
+	"\tgl_Position = vec4(v_position, 1);\n"
+	"}\n");
+
+	model_rc->fragment_header = str8(""
+	"#version 460 core\n\n"
+	"layout(location = 0) in  vec3 texture_coordinate;\n"
+	"layout(location = 1) in  vec3 colour;\n"
+	"layout(location = 0) out vec4 out_colour;\n\n"
+	"layout(location = " str(VIEWER_RENDER_DYNAMIC_RANGE_LOC) ") uniform float u_db_cutoff = 60;\n"
+	"layout(location = " str(VIEWER_RENDER_THRESHOLD_LOC)     ") uniform float u_threshold = 40;\n"
+	"layout(location = " str(VIEWER_RENDER_GAMMA_LOC)         ") uniform float u_gamma     = 1;\n"
+	"layout(location = " str(VIEWER_RENDER_LOG_SCALE_LOC)     ") uniform bool  u_log_scale;\n"
+	"\n#line 1\n");
+
+	str8 render_model = str8("render_model.frag.glsl");
+	reload_shader(&ctx->os, render_model, (sptr)model_rc, ctx->arena);
+	os_add_file_watch(&ctx->os, &ctx->arena, render_model, reload_shader, (sptr)model_rc);
+
+	rc = &ctx->overlay_render_context;
+	ShaderReloadContext *overlay_rc = push_struct(&ctx->arena, ShaderReloadContext);
+	overlay_rc->render_context = rc;
+	overlay_rc->vertex_text = str8(""
+	"#version 460 core\n"
+	"\n"
+	"layout(location = 0) in vec2 v_position;\n"
+	"layout(location = 1) in vec2 v_texture_coordinate;\n"
+	"layout(location = 2) in vec3 v_colour;\n"
+	"layout(location = 3) in uint v_flags;\n"
+	"\n"
+	"layout(location = 0) out vec2 f_texture_coordinate;\n"
+	"layout(location = 1) out vec3 f_colour;\n"
+	"layout(location = 2) out uint f_flags;\n"
+	"\n"
+	"layout(location = 0) uniform ivec2 u_screen_size;\n"
+	"\n"
+	"void main()\n"
+	"{\n"
+	"\tf_texture_coordinate = 1 - v_texture_coordinate;\n"
+	"\tf_colour             = v_colour;\n"
+	"\tf_flags              = v_flags;\n"
+	"\tgl_Position = vec4(v_position, 0, 1);\n"
+	//"\tgl_Position = vec4(2 * (v_position / vec2(u_screen_size)) - 1, 0, 1);\n"
+	"}\n");
+
+	overlay_rc->fragment_header = str8(""
+	"#version 460 core\n\n"
+	"layout(location = 0) in  vec2 texture_coordinate;\n"
+	"layout(location = 1) in  vec3 colour;\n"
+	"layout(location = 0) out vec4 out_colour;\n"
+	"\n#line 1\n");
+
+	f32 overlay_vertices[] = {
+		-1,  1, 0, 0,
+		-1, -1, 0, 1,
+		 1, -1, 1, 1,
+		-1,  1, 0, 0,
+		 1, -1, 1, 1,
+		 1,  1, 1, 0,
+	};
+	glGenVertexArrays(1, &rc->vao);
+	glBindVertexArray(rc->vao);
+	glGenBuffers(1, &rc->vbo);
+	glBindBuffer(GL_ARRAY_BUFFER, rc->vbo);
+	glBufferData(GL_ARRAY_BUFFER, sizeof(overlay_vertices), overlay_vertices, GL_STATIC_DRAW);
+	glVertexAttribPointer(0, 2, GL_FLOAT, 0, 4 * sizeof(f32), 0);
+	glVertexAttribPointer(1, 2, GL_FLOAT, 0, 4 * sizeof(f32), (void *)(2 * sizeof(f32)));
+	glEnableVertexAttribArray(0);
+	glEnableVertexAttribArray(1);
+	glBindVertexArray(0);
+
+	str8 render_overlay = str8("render_overlay.frag.glsl");
+	reload_shader(&ctx->os, render_overlay, (sptr)overlay_rc, ctx->arena);
+	os_add_file_watch(&ctx->os, &ctx->arena, render_overlay, reload_shader, (sptr)overlay_rc);
 }
 
 function void
 viewer_frame_step(ViewerContext *ctx)
 {
-	glClear(GL_COLOR_BUFFER_BIT);
-
-	glUseProgram(ctx->render_context.shader);
-	glBindVertexArray(ctx->render_context.vao);
+	//////////////
+	// 3D Models
+	glBindFramebuffer(GL_FRAMEBUFFER, ctx->output_target.fb);
+	glClearNamedFramebufferfv(ctx->output_target.fb, GL_COLOR, 0, OUTPUT_BG_CLEAR_COLOUR.E);
+	glViewport(0, 0, ctx->output_target.size.w, ctx->output_target.size.h);
+
+	glUseProgram(ctx->model_render_context.shader);
+	glBindVertexArray(ctx->model_render_context.vao);
 	glDrawArrays(GL_TRIANGLES, 0, 3);
 
+	////////////////
+	// UI Overlay
+	glBindFramebuffer(GL_FRAMEBUFFER, 0);
+	glClearNamedFramebufferfv(0, GL_COLOR, 0, BG_CLEAR_COLOUR.E);
+
+	f32 aspect_ratio = (f32)ctx->output_target.size.w / (f32)ctx->output_target.size.h;
+	sv2 target_size  = ctx->window_size;
+	if (aspect_ratio > 1) target_size.h = target_size.w / aspect_ratio;
+	else                  target_size.w = target_size.h * aspect_ratio;
+
+	if (target_size.w > ctx->window_size.w) {
+		target_size.w = ctx->window_size.w;
+		target_size.h = ctx->window_size.w / aspect_ratio;
+	} else if (target_size.h > ctx->window_size.h) {
+		target_size.h = ctx->window_size.h;
+		target_size.w = target_size.h * aspect_ratio;
+	}
+
+	sv2 size_delta = sv2_sub(ctx->window_size, target_size);
+
+	glViewport(size_delta.x / 2 + 0.5, size_delta.y / 2 + 0.5, target_size.w, target_size.h);
+
+	glUseProgram(ctx->overlay_render_context.shader);
+	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/render.frag.glsl b/render_model.frag.glsl
diff --git a/render_overlay.frag.glsl b/render_overlay.frag.glsl
@@ -0,0 +1,16 @@
+/* See LICENSE for license details. */
+layout(binding = 0) uniform sampler2D u_texture;
+
+/* input:  h [0,360] | s,v [0, 1] *
+ * output: rgb [0,1]              */
+vec3 hsv2rgb(vec3 hsv)
+{
+	vec3 k = mod(vec3(5, 3, 1) + hsv.x / 60, 6);
+	k = max(min(min(k, 4 - k), 1), 0);
+	return hsv.z - hsv.z * hsv.y * k;
+}
+
+void main()
+{
+	out_colour = vec4(texture(u_texture, texture_coordinate).xyz, 1);
+}
diff --git a/util.c b/util.c
@@ -407,6 +407,15 @@ parse_f64(str8 s)
 	return result;
 }
 
+function sv2
+sv2_sub(sv2 a, sv2 b)
+{
+	sv2 result;
+	result.x = a.x - b.x;
+	result.y = a.y - b.y;
+	return result;
+}
+
 function FileWatchDirectory *
 lookup_file_watch_directory(FileWatchContext *ctx, u64 hash)
 {
diff --git a/util.h b/util.h
@@ -280,10 +280,19 @@ typedef struct {
 } RenderContext;
 
 typedef struct {
+	u32 fb;
+	u32 textures[2];
+	sv2 size;
+} RenderTarget;
+
+typedef struct {
 	Arena arena;
 	OS    os;
 
-	RenderContext render_context;
+	RenderContext model_render_context;
+	RenderContext overlay_render_context;
+
+	RenderTarget output_target;
 
 	sv2 window_size;