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;