From 32a6d498b96872b3de40c207899cc0138fc2c277 Mon Sep 17 00:00:00 2001 From: samstalhandske Date: Tue, 3 Feb 2026 09:37:56 +0100 Subject: [PATCH] Allowed setting uniforms in shader --- material.odin | 4 ++ pass.odin | 30 +++++++- renderer.odin | 80 +++++++++++++-------- renderer_backend_opengl_windows.odin | 104 +++++++++++++++++++++++---- shader.odin | 70 ++++++++++++++++++ texture.odin | 27 ++++++- 6 files changed, 267 insertions(+), 48 deletions(-) diff --git a/material.odin b/material.odin index 7db6549..6cd1029 100644 --- a/material.odin +++ b/material.odin @@ -11,6 +11,8 @@ Material :: struct { textures: [MATERIAL_MAX_TEXTURES]^Texture, texture_count: u8, + + uv_scale: [2]f32, // TODO: SS - Move? } create_material :: proc(program: ^Shader_Program, textures: []^Texture) -> (Material, bool) { @@ -37,5 +39,7 @@ create_material :: proc(program: ^Shader_Program, textures: []^Texture) -> (Mate // TODO: SS - Should we return false here? } + m.uv_scale = { 2.0, 2.0 } // NOTE: SS - Hardcoded. + return m, true } \ No newline at end of file diff --git a/pass.odin b/pass.odin index 88dabe0..c12aa67 100644 --- a/pass.odin +++ b/pass.odin @@ -1,5 +1,6 @@ package renderer +import "core:math/linalg" import "core:fmt" MAX_DRAW_COMMANDS_CAPACITY :: 4096 @@ -28,8 +29,35 @@ Post_Processing_Pass :: struct { post_processing_nodes: [dynamic]Post_Processing_Node, // These nodes are executed after all commands have been drawn onto the render-target. } +Uniform :: union { + Uniform_Texture, + Uniform_Float, + Uniform_Matrix4f32, + Uniform_Vector3, +} + +Uniform_Texture :: struct { + index: u8, + value: ^Texture, +} + +Uniform_Float :: struct { + name: string, + value: ^f32, +} + +Uniform_Matrix4f32 :: struct { + name: string, + value: ^linalg.Matrix4f32, +} + +Uniform_Vector3 :: struct { + name: string, + value: ^[3]f32, +} + Post_Processing_Node :: struct { - input: []^Texture, + uniforms: []Uniform, output: ^Render_Target, program: ^Shader_Program, diff --git a/renderer.odin b/renderer.odin index a18fd93..c869360 100644 --- a/renderer.odin +++ b/renderer.odin @@ -135,6 +135,9 @@ render_frame :: proc(renderer: ^Renderer, texture_to_present: ^Texture, clear_co view_matrix, _ := get_camera_view_matrix(renderer.active_camera) projection_matrix, _ := get_camera_projection_matrix(renderer, renderer.active_camera) + set_clear_color(renderer, clear_color) + clear_screen(renderer, true, true) + for i in 0 ..< renderer.pipeline.amount_of_passes { execute_pass(renderer, renderer.pipeline.passes[i], view_matrix, projection_matrix) } @@ -149,7 +152,7 @@ render_frame :: proc(renderer: ^Renderer, texture_to_present: ^Texture, clear_co // Clear set_clear_color(renderer, clear_color) clear_screen(renderer, true, true) - + // Create a temporary Material. mat := Material { shader_program = &renderer.fullscreen_shader_program, @@ -159,12 +162,16 @@ render_frame :: proc(renderer: ^Renderer, texture_to_present: ^Texture, clear_co texture_count = 1, } + apply_blend_mode(renderer, .Alpha) + // Activate. activate_fullscreen_material(renderer, &mat) defer deactivate_fullscreen_material(renderer) // Draw. draw_mesh(&renderer.fullscreen_mesh) + + apply_blend_mode(renderer, .None) } when RENDER_BACKEND_OPENGL { @@ -182,6 +189,7 @@ execute_pass :: proc(renderer: ^Renderer, pass: ^Pass, view_matrix, projection_m case Scene_Pass: { assert(t.output_rt != nil) bind_render_target(renderer, t.output_rt) + defer bind_render_target(renderer, nil) should_write_depth := t.output_rt.depth_texture != nil should_test_depth := should_write_depth @@ -194,6 +202,7 @@ execute_pass :: proc(renderer: ^Renderer, pass: ^Pass, view_matrix, projection_m apply_depth(renderer, should_test_depth, should_write_depth) apply_blend_mode(renderer, t.blend_mode) + defer apply_blend_mode(renderer, .None) sort_draw_commands(renderer, &t) @@ -223,6 +232,7 @@ execute_pass :: proc(renderer: ^Renderer, pass: ^Pass, view_matrix, projection_m clear(&t.draw_commands) // TODO: SS - "Deactivate" the pass? + } case Post_Processing_Pass: { // Execute the post-processing nodes. @@ -243,47 +253,57 @@ execute_post_processing_node :: proc(renderer: ^Renderer, node: ^Post_Processing assert(node.output != nil) bind_render_target(renderer, node.output) + defer bind_render_target(renderer, nil) apply_depth(renderer, false, false) + set_clear_color(renderer, RGBA_Color { 0, 0, 0, 0 }) clear_screen(renderer, true, false) // fmt.printfln("TODO: SS - Execute post-processing node '%v' (VS: '%v', FS: '%v').", "NAME", node.program.vertex_shader.path, node.program.fragment_shader.path) - t: [MATERIAL_MAX_TEXTURES]^Texture - t_count := u8(0) - for input_texture, i in node.input { - if i > MATERIAL_MAX_TEXTURES { - break + mat: Material + mat.shader_program = node.program + + fs_path := node.program.fragment_shader != nil ? node.program.fragment_shader.path : "nil" + vs_path := node.program.vertex_shader != nil ? node.program.vertex_shader.path : "nil" + + for u, i in node.uniforms { + switch &t in u { + case Uniform_Texture: { + if mat.texture_count > MATERIAL_MAX_TEXTURES { + continue + } + + if set_shader_uniform(node.program, t) { + mat.textures[mat.texture_count] = t.value + mat.texture_count += 1 + } + else { + fmt.printfln("Failed to set uniform-texture %v in program (vs: '%s', fs: '%s').", t.index, vs_path, fs_path) + } + } + case Uniform_Float: { + if !set_shader_uniform(node.program, t) { + fmt.printfln("Failed to set uniform-float '%s' in program (vs: '%s', fs: '%s').", t.name, vs_path, fs_path) + } + } + case Uniform_Matrix4f32: { + if !set_shader_uniform(node.program, t) { + fmt.printfln("Failed to set uniform-matrix4f32 '%s' in program (vs: '%s', fs: '%s').", t.name, vs_path, fs_path) + } + } + case Uniform_Vector3: { + if !set_shader_uniform(node.program, t) { + fmt.printfln("Failed to set uniform-vector3 '%s' in program (vs: '%s', fs: '%s').", t.name, vs_path, fs_path) + } + } } - - set_shader_value(node.program, input_texture, u8(i)) - t[i] = input_texture - t_count += 1 - } - - mat := Material { - shader_program = node.program, - textures = t, - texture_count = t_count, } activate_fullscreen_material(renderer, &mat) draw_mesh(&renderer.fullscreen_mesh) } -set_shader_value :: proc { - set_shader_value_texture, -} - -set_shader_value_texture :: proc(program: ^Shader_Program, value: ^Texture, index: u8) { - assert(program != nil) - assert(value != nil) - - when RENDER_BACKEND_OPENGL { - opengl_set_shader_value_texture(program, value, index) - } -} - destroy :: proc(renderer: ^Renderer) { when RENDER_BACKEND_OPENGL { opengl_destroy(renderer) @@ -293,7 +313,7 @@ destroy :: proc(renderer: ^Renderer) { free(renderer) } -@(private) activate_material :: proc(material: ^Material, model_matrix, view_matrix, projection_matrix: linalg.Matrix4x4f32) { +@(private) activate_material :: proc(material: ^Material, model_matrix, view_matrix, projection_matrix: linalg.Matrix4x4f32, uv_scale: [2]f32 = { 1.0, 1.0 }) { assert(material != nil) when RENDER_BACKEND_OPENGL { diff --git a/renderer_backend_opengl_windows.odin b/renderer_backend_opengl_windows.odin index cd0917c..9a2eb00 100644 --- a/renderer_backend_opengl_windows.odin +++ b/renderer_backend_opengl_windows.odin @@ -293,6 +293,11 @@ when RENDER_BACKEND_OPENGL { projection_matrix_as_f32_array := transmute([16]f32)(projection_matrix) gl.UniformMatrix4fv(projection_matrix_loc, 1, false, &projection_matrix_as_f32_array[0]) } + + uv_scale_loc := gl.GetUniformLocation(material.shader_program.backend.handle, "in_uv_scale") + if uv_scale_loc >= 0 { + gl.Uniform2fv(uv_scale_loc, 1, &material.uv_scale[0]) + } } opengl_activate_fullscreen_material :: proc(material: ^Material) { // TODO: SS - Maybe remove. @@ -514,11 +519,13 @@ when RENDER_BACKEND_OPENGL { status := gl.CheckFramebufferStatus(gl.FRAMEBUFFER) assert(status == gl.FRAMEBUFFER_COMPLETE) + assert(len(rt.color_textures) > 0) + gl.Viewport( 0, 0, - i32(rt.color_texture.width), - i32(rt.color_texture.height), + i32(rt.color_textures[0].width), + i32(rt.color_textures[0].height), ) } @@ -527,13 +534,24 @@ when RENDER_BACKEND_OPENGL { gl.GenFramebuffers(1, &fb) gl.BindFramebuffer(gl.FRAMEBUFFER, fb) - gl.FramebufferTexture2D( - gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0, - gl.TEXTURE_2D, - rt.color_texture.backend.handle, - 0 - ) + for texture, i in rt.color_textures { + gl.FramebufferTexture2D( + gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + u32(i), + gl.TEXTURE_2D, + texture.backend.handle, + 0 + ) + } + + attachments: [MAX_COLOR_TEXTURES_IN_RENDER_TARGET]u32 + attachment_count := i32(0) + for i in 0.. bool { gl.UseProgram(program.backend.handle) - gl.ActiveTexture(gl.TEXTURE0 + u32(index)) - gl.BindTexture(gl.TEXTURE_2D, value.backend.handle) - loc := gl.GetUniformLocation(program.backend.handle, fmt.ctprintf("texture%v", index)) - gl.Uniform1i(loc, i32(index)) + i := uniform.index + + gl.ActiveTexture(gl.TEXTURE0 + u32(i)) + gl.BindTexture(gl.TEXTURE_2D, uniform.value.backend.handle) + loc := gl.GetUniformLocation(program.backend.handle, fmt.ctprintf("texture%d", i)) + if loc < 0 { + fmt.printfln("texture Loc: %v", loc) + return false + } + + gl.Uniform1i(loc, i32(i)) + + return true + } + + opengl_set_shader_uniform_float :: proc(program: ^Shader_Program, uniform: Uniform_Float) -> bool { + gl.UseProgram(program.backend.handle) + + loc := gl.GetUniformLocation(program.backend.handle, fmt.ctprintf("%v", uniform.name)) + if loc < 0 { + fmt.printfln("float Loc: %v", loc) + return false + } + + gl.Uniform1f(loc, uniform.value^) + + return true + } + + // TODO: SS - This procedure and _float ^^ are very similar and will do pretty much the same thing but with calls to different gl.Uniform-- procedures. Make it generic instead then switch on the type? + opengl_set_shader_uniform_matrix4f32 :: proc(program: ^Shader_Program, uniform: Uniform_Matrix4f32) -> bool { + gl.UseProgram(program.backend.handle) + + loc := gl.GetUniformLocation(program.backend.handle, fmt.ctprintf("%v", uniform.name)) + if loc < 0 { + fmt.printfln("matrixf32 loc: %v", loc) + return false + } + + data := transmute([16]f32)(uniform.value^) + gl.UniformMatrix4fv(loc, 1, gl.FALSE, &data[0]) + + return true + } + + opengl_set_shader_uniform_vector3 :: proc(program: ^Shader_Program, uniform: Uniform_Vector3) -> bool { + gl.UseProgram(program.backend.handle) + + loc := gl.GetUniformLocation(program.backend.handle, fmt.ctprintf("%v", uniform.name)) + if loc < 0 { + fmt.printfln("vector3 Loc: %v", loc) + return false + } + + gl.Uniform3fv(loc, 1, &uniform.value[0]) + + return true } } diff --git a/shader.odin b/shader.odin index 28982c9..44b8e34 100644 --- a/shader.odin +++ b/shader.odin @@ -122,3 +122,73 @@ reload_shader_program :: proc(renderer: ^Renderer, p: ^Shader_Program) -> bool { return true } + +set_shader_uniform :: proc { // TODO: SS - Improve setting shader uniforms. A bit bug-prone and annoying to explicitly add code-paths for every 'Uniform' type needed. + set_shader_uniform_texture, + set_shader_uniform_float, + set_shader_uniform_matrix4f32, + set_shader_uniform_vector3, +} + +set_shader_uniform_texture :: proc(program: ^Shader_Program, uniform: Uniform_Texture) -> bool { + assert(program != nil) + assert(uniform.index < MATERIAL_MAX_TEXTURES) + assert(uniform.value != nil) + + when RENDER_BACKEND_OPENGL { + return opengl_set_shader_uniform_texture(program, uniform) + } + + return false +} + +set_shader_uniform_float :: proc(program: ^Shader_Program, uniform: Uniform_Float) -> bool { + assert(program != nil) + assert(len(uniform.name) > 0) + + if uniform.value == nil { + return false + } + + // TODO: SS - Somehow verify that 'uniform.name' exists in the shader(s)? + + when RENDER_BACKEND_OPENGL { + return opengl_set_shader_uniform_float(program, uniform) + } + + return false +} + +set_shader_uniform_matrix4f32 :: proc(program: ^Shader_Program, uniform: Uniform_Matrix4f32) -> bool { + assert(program != nil) + assert(len(uniform.name) > 0) + + if uniform.value == nil { + return false + } + + // TODO: SS - Somehow verify that 'uniform.name' exists in the shader(s)? + + when RENDER_BACKEND_OPENGL { + return opengl_set_shader_uniform_matrix4f32(program, uniform) + } + + return false +} + +set_shader_uniform_vector3 :: proc(program: ^Shader_Program, uniform: Uniform_Vector3) -> bool { + assert(program != nil) + assert(len(uniform.name) > 0) + + if uniform.value == nil { + return false + } + + // TODO: SS - Somehow verify that 'uniform.name' exists in the shader(s)? + + when RENDER_BACKEND_OPENGL { + return opengl_set_shader_uniform_vector3(program, uniform) + } + + return false +} \ No newline at end of file diff --git a/texture.odin b/texture.odin index b0f819e..a130256 100644 --- a/texture.odin +++ b/texture.odin @@ -32,8 +32,12 @@ Texture_Format :: enum { Depth32F_Stencil8, } +MAX_COLOR_TEXTURES_IN_RENDER_TARGET :: 32 + Render_Target :: struct { - color_texture, depth_texture: ^Texture, + color_textures: [dynamic]^Texture, + depth_texture: ^Texture, + backend: Render_Target_Backend, } @@ -119,11 +123,16 @@ create_texture_raw :: proc(renderer: ^Renderer, width, height: u16, format: Text return t, true } -create_render_target :: proc(renderer: ^Renderer, color_texture, depth_texture: ^Texture) -> (Render_Target, bool) { +create_render_target :: proc(renderer: ^Renderer, color_textures: []^Texture, depth_texture: ^Texture) -> (Render_Target, bool) { rt: Render_Target - rt.color_texture = color_texture rt.depth_texture = depth_texture + rt.color_textures = make([dynamic]^Texture, 0, MAX_COLOR_TEXTURES_IN_RENDER_TARGET) + _, err := append(&rt.color_textures, ..color_textures) + if err != .None { + return {}, false + } + when RENDER_BACKEND_OPENGL { if !opengl_create_render_target(renderer, &rt) { return {}, false @@ -133,6 +142,18 @@ create_render_target :: proc(renderer: ^Renderer, color_texture, depth_texture: return rt, true } +destroy_render_target :: proc(renderer: ^Renderer, render_target: ^Render_Target) { + assert(renderer != nil) + assert(render_target != nil) + assert(render_target.color_textures != nil) + + delete(render_target.color_textures) + + when RENDER_BACKEND_OPENGL { + opengl_destroy_render_target(renderer, render_target) + } +} + delete_texture :: proc(renderer: ^Renderer, texture: ^Texture) { when RENDER_BACKEND_OPENGL {