From b6a32ebe7f06e07dcad9be69e337a242f2dd2006 Mon Sep 17 00:00:00 2001 From: samstalhandske Date: Sat, 31 Jan 2026 02:31:19 +0100 Subject: [PATCH] Restructured passes --- material.odin | 35 +++- pass.odin | 138 +++++++------ pipeline.odin | 70 ++----- renderer.odin | 290 +++++++++++++++++---------- renderer_backend_opengl_windows.odin | 69 +++++-- shader.odin | 2 + 6 files changed, 356 insertions(+), 248 deletions(-) diff --git a/material.odin b/material.odin index b11a11a..7db6549 100644 --- a/material.odin +++ b/material.odin @@ -1,16 +1,41 @@ package renderer +import "core:log" +import "core:fmt" + +MATERIAL_MAX_TEXTURES :: 32 + Material :: struct { - // 'Name'? + // TODO: SS - 'Name'? shader_program: ^Shader_Program, - texture: ^Texture, // Diffuse, normal etc later. - // uniforms, textures, etc. + + textures: [MATERIAL_MAX_TEXTURES]^Texture, + texture_count: u8, } -create_material :: proc(program: ^Shader_Program, texture0: ^Texture) -> (Material, bool) { +create_material :: proc(program: ^Shader_Program, textures: []^Texture) -> (Material, bool) { m: Material m.shader_program = program - m.texture = texture0 + + for t, i in textures { + if t == nil { + log.warnf("Found nil texture at index %v in when creating material.", i) + continue + } + + if m.texture_count >= MATERIAL_MAX_TEXTURES { + log.warnf("Too many textures passed when creating material. Max is %v.", MATERIAL_MAX_TEXTURES) + break + } + + m.textures[m.texture_count] = t + m.texture_count += 1 + } + + if m.texture_count == 0 { + log.warn("No textures passed when creating material.") + // TODO: SS - Should we return false here? + } return m, true } \ No newline at end of file diff --git a/pass.odin b/pass.odin index f710496..88dabe0 100644 --- a/pass.odin +++ b/pass.odin @@ -3,32 +3,36 @@ package renderer import "core:fmt" MAX_DRAW_COMMANDS_CAPACITY :: 4096 +MAX_POST_PROCESS_NODES_PER_PASS :: 8 -PASS_MAX_INPUT_TEXTURES :: 8 +Pass :: struct { + name: string, + type: Pass_Type, +} + +Pass_Type :: union { + Scene_Pass, + Post_Processing_Pass, +} Scene_Pass :: struct { - name: string, - - input_textures: [PASS_MAX_INPUT_TEXTURES]^Texture, // TODO: SS - Make this a map? - input_texture_count: u8, - - output_rt: ^Render_Target, - - draw_commands: [dynamic]Draw_Command, // Capacity is 'MAX_DRAW_COMMANDS_CAPACITY'. - - clear_color: Maybe(RGB_Color), blend_mode: Blend_Mode, sort_mode: Sort_Mode, + + draw_commands: [dynamic]Draw_Command, // Capacity is 'MAX_DRAW_COMMANDS_CAPACITY'. + + output_rt: ^Render_Target, // Commands draw to this render-target. } Post_Processing_Pass :: struct { - name: string, - - input_texture: ^Texture, // TODO: SS - Make this an array of texture-pointers, or maybe a map. - input_depth_texture: ^Texture, - output_rt: ^Render_Target, + post_processing_nodes: [dynamic]Post_Processing_Node, // These nodes are executed after all commands have been drawn onto the render-target. +} - shader_program: Shader_Program, +Post_Processing_Node :: struct { + input: []^Texture, + output: ^Render_Target, + + program: ^Shader_Program, } Draw_Command :: struct { @@ -81,76 +85,82 @@ Sort_Mode :: enum { create_scene_pass :: proc( name: string, - input_textures: []^Texture, - output_rt: ^Render_Target, - clear_color: Maybe(RGB_Color), blend_mode: Blend_Mode, sort_mode: Sort_Mode, -) -> Scene_Pass + output_rt: ^Render_Target, +) -> Pass { - // if input_depth_texture == nil { - // if test_depth || clear_depth || write_depth { - // assert(false) - // } - // } + assert(len(name) > 0) - p := Scene_Pass { - name = name, - - output_rt = output_rt, - - draw_commands = make([dynamic]Draw_Command, 0, MAX_DRAW_COMMANDS_CAPACITY), - - clear_color = clear_color, + p: Pass + p.name = name + p.type = Scene_Pass { blend_mode = blend_mode, sort_mode = sort_mode, + + draw_commands = make([dynamic]Draw_Command, 0, MAX_DRAW_COMMANDS_CAPACITY), + + output_rt = output_rt, } - for texture, i in input_textures { - if p.input_texture_count >= PASS_MAX_INPUT_TEXTURES { - fmt.printfln("Hit max capacity of textures per pass.") - break - } - - p.input_textures[p.input_texture_count] = texture - p.input_texture_count += 1 - } - return p } -create_post_processing_pass :: proc( - name: string, - input_texture, input_depth_texture: ^Texture, - output_rt: ^Render_Target, - shader_program: Shader_Program, -) -> Post_Processing_Pass -{ - return { - name = name, - input_texture = input_texture, - input_depth_texture = input_depth_texture, - output_rt = output_rt, - shader_program = shader_program, +create_post_processing_pass :: proc(name: string, post_processing_nodes: []Post_Processing_Node) -> Pass { + assert(len(name) > 0) + + p: Pass + p.name = name + + ppp := Post_Processing_Pass { + post_processing_nodes = make([dynamic]Post_Processing_Node, 0, MAX_POST_PROCESS_NODES_PER_PASS) } + + append(&ppp.post_processing_nodes, ..post_processing_nodes) + + p.type = ppp + return p } -delete_pass :: proc { - delete_scene_pass, +delete_pass :: proc(pass: ^Pass) { + assert(pass != nil) + + switch &t in &pass.type { + case Scene_Pass: delete_scene_pass(&t) + case Post_Processing_Pass: delete_post_processing_pass(&t) + } } @(private) delete_scene_pass :: proc(pass: ^Scene_Pass) { delete(pass.draw_commands) } -add_command_to_pass :: proc(renderer: ^Renderer, pass: ^Scene_Pass, command: Draw_Command) -> bool { +@(private) delete_post_processing_pass :: proc(pass: ^Post_Processing_Pass) { + delete(pass.post_processing_nodes) +} + +add_command_to_pass :: proc(renderer: ^Renderer, pass: ^Pass, command: Draw_Command) -> bool { assert(renderer != nil) assert(pass != nil) - cmd := command - cmd.renderer = renderer - n, err := append(&pass.draw_commands, cmd) - assert(err == .None) + switch &t in &pass.type { + case Post_Processing_Pass: { + fmt.printfln("Can't add commands to a post-processing pass.") + return false + } + case Scene_Pass: { + if t.output_rt == nil { + fmt.printfln("Pass '%v' does not have a output render-target so you're not allowed to add commands to it.", pass.name) + return false + } + + cmd := command + cmd.renderer = renderer + n, err := append(&t.draw_commands, cmd) + assert(err == .None) + } + } + return true } \ No newline at end of file diff --git a/pipeline.odin b/pipeline.odin index c1c86c0..577d3c1 100644 --- a/pipeline.odin +++ b/pipeline.odin @@ -2,93 +2,51 @@ package renderer import "core:mem" import "core:log" +import "core:fmt" -PIPELINE_MAX_SCENE_PASSES :: 8 -PIPELINE_MAX_POST_PROCESSING_PASSES :: 8 +PIPELINE_MAX_PASSES :: 8 Pipeline :: struct { - scene_passes: [PIPELINE_MAX_SCENE_PASSES]^Scene_Pass, - amount_of_scene_passes: u8, - - post_processing_passes: [PIPELINE_MAX_POST_PROCESSING_PASSES]^Post_Processing_Pass, - amount_of_post_processing_passes: u8, - - fullscreen_material: ^Material, - fullscreen_mesh: ^Mesh, + passes: [PIPELINE_MAX_PASSES]^Pass, + amount_of_passes: u8, } clear_pipeline :: proc(renderer: ^Renderer) { assert(renderer != nil) p := &renderer.pipeline - p.amount_of_scene_passes = 0; - mem.set(&p.scene_passes[0], 0, PIPELINE_MAX_SCENE_PASSES * size_of(^Scene_Pass)) - - p.amount_of_post_processing_passes = 0; - mem.set(&p.post_processing_passes[0], 0, PIPELINE_MAX_POST_PROCESSING_PASSES * size_of(^Post_Processing_Pass)) + p.amount_of_passes = 0; + mem.set(&p.passes[0], 0, PIPELINE_MAX_PASSES * size_of(^Scene_Pass)) } set_pipeline :: proc( renderer: ^Renderer, - fullscreen_material: ^Material, fullscreen_mesh: ^Mesh, - scene_passes: []^Scene_Pass, - post_processing_passes: []^Post_Processing_Pass, + passes: []^Pass, ){ assert(renderer != nil) clear_pipeline(renderer) - renderer.pipeline.fullscreen_material = fullscreen_material - renderer.pipeline.fullscreen_mesh = fullscreen_mesh - - for p in scene_passes { - if !add_pass_to_pipeline(renderer, p) { - break - } - } - - for p in post_processing_passes { + for p in passes { if !add_pass_to_pipeline(renderer, p) { break } } } -add_pass_to_pipeline :: proc { - add_scene_pass_to_pipeline, - add_post_processing_pass_to_pipeline, -} - -@(private="file") add_scene_pass_to_pipeline :: proc(renderer: ^Renderer, scene_pass: ^Scene_Pass) -> bool { +add_pass_to_pipeline :: proc(renderer: ^Renderer, pass: ^Pass) -> bool { assert(renderer != nil) - assert(scene_pass != nil) + assert(pass != nil) pipeline := &renderer.pipeline - if pipeline.amount_of_scene_passes == PIPELINE_MAX_SCENE_PASSES { - log.warnf("Failed to add scene-pass '%v' to renderer's pipeline; hit max capacity (%v).", scene_pass.name, PIPELINE_MAX_SCENE_PASSES) + if pipeline.amount_of_passes == PIPELINE_MAX_PASSES { + log.warnf("Failed to add scene-pass '%v' to renderer's pipeline; hit max capacity (%v).", pass.name, PIPELINE_MAX_PASSES) return false } - pipeline.scene_passes[pipeline.amount_of_scene_passes] = scene_pass - pipeline.amount_of_scene_passes += 1 - - return true -} - -@(private="file") add_post_processing_pass_to_pipeline :: proc(renderer: ^Renderer, post_processing_pass: ^Post_Processing_Pass) -> bool { - assert(renderer != nil) - assert(post_processing_pass != nil) - - pipeline := &renderer.pipeline - - if pipeline.amount_of_post_processing_passes == PIPELINE_MAX_POST_PROCESSING_PASSES { - log.warnf("Failed to add post-processing pass '%v' to renderer's pipeline; hit max capacity (%v).", post_processing_pass.name, PIPELINE_MAX_POST_PROCESSING_PASSES) - return false - } - - pipeline.post_processing_passes[pipeline.amount_of_post_processing_passes] = post_processing_pass - pipeline.amount_of_post_processing_passes += 1 + pipeline.passes[pipeline.amount_of_passes] = pass + pipeline.amount_of_passes += 1 return true } \ No newline at end of file diff --git a/renderer.odin b/renderer.odin index 625d327..a18fd93 100644 --- a/renderer.odin +++ b/renderer.odin @@ -18,7 +18,11 @@ Renderer :: struct { pipeline: Pipeline, - active_camera: ^Camera, + active_camera: ^Camera, // NOTE: SS - Hardcoded to 1 active camera. Split-screen is likely not possible due to this. Fix(?). + + fullscreen_vertex_shader, fullscreen_fragment_shader: Shader, + fullscreen_shader_program: Shader_Program, + fullscreen_mesh: Mesh, } Viewport :: struct { @@ -32,6 +36,11 @@ get_aspect_ratio :: proc(renderer: ^Renderer) -> f32 { return f32(viewport.width) / f32(viewport.height) } +Color :: union { + RGB_Color, + RGBA_Color, +} + RGB_Color :: [3]u8 RGBA_Color :: [4]u8 @@ -52,6 +61,40 @@ create :: proc(surface_ptr: rawptr) -> (^Renderer, bool) { fmt.printfln("Unhandled backend or no backend selected.") return nil, false } + + { // Create the fullscreen shaders, material and mesh. + fs_vertex_shader, fs_vertex_shader_ok := create_shader(renderer, .Vertex, "fs_vertex.glsl") + assert(fs_vertex_shader_ok) + + fs_frag_shader, fs_frag_shader_ok := create_shader(renderer, .Fragment, "fs_frag.glsl") + assert(fs_frag_shader_ok) + + fs_program, fs_program_ok := create_shader_program(renderer, &fs_vertex_shader, &fs_frag_shader) + assert(fs_program_ok) + + fs_quad_mesh, fs_quad_mesh_created := create_mesh( + renderer = renderer, + layout = { + { "position", 2, size_of(f32), }, + }, + vertices = []f32 { + -1, -1, + 1, -1, + 1, 1, + -1, 1, + }, + indices = []u32 { + 0, 1, 2, + 0, 2, 3, + } + ) + assert(fs_quad_mesh_created) + + renderer.fullscreen_vertex_shader = fs_vertex_shader + renderer.fullscreen_fragment_shader = fs_frag_shader + renderer.fullscreen_shader_program = fs_program + renderer.fullscreen_mesh = fs_quad_mesh + } return renderer, true } @@ -71,7 +114,7 @@ set_viewport :: proc(renderer: ^Renderer, x, y, width, height: u16) { } } -@(private="file") set_clear_color :: proc(renderer: ^Renderer, color: RGB_Color) { +@(private="file") set_clear_color :: proc(renderer: ^Renderer, color: Color) { when RENDER_BACKEND_OPENGL { opengl_set_clear_color(renderer, color) } @@ -83,7 +126,7 @@ set_viewport :: proc(renderer: ^Renderer, x, y, width, height: u16) { } } -render_frame :: proc(renderer: ^Renderer) { +render_frame :: proc(renderer: ^Renderer, texture_to_present: ^Texture, clear_color: Color) { if renderer.active_camera == nil { fmt.printfln("No active camera!") return @@ -92,100 +135,153 @@ render_frame :: proc(renderer: ^Renderer) { view_matrix, _ := get_camera_view_matrix(renderer.active_camera) projection_matrix, _ := get_camera_projection_matrix(renderer, renderer.active_camera) - pipeline := &renderer.pipeline - - for i in 0 ..< pipeline.amount_of_scene_passes { - execute_scene_pass(renderer, pipeline.scene_passes[i], view_matrix, projection_matrix) + for i in 0 ..< renderer.pipeline.amount_of_passes { + execute_pass(renderer, renderer.pipeline.passes[i], view_matrix, projection_matrix) } + + if texture_to_present != nil { // Present. + // Bind to the screen. + bind_render_target(renderer, nil) - for i in 0 ..< pipeline.amount_of_post_processing_passes { - execute_post_processing_pass(renderer, pipeline.post_processing_passes[i], view_matrix, projection_matrix) - } + // Disable depth + apply_depth(renderer, false, false) - bind_render_target(renderer, nil) // Det görs ju här.. - - if pipeline.fullscreen_material != nil { - if pipeline.fullscreen_material.texture != nil { - activate_fullscreen_material(renderer) - - if pipeline.fullscreen_mesh != nil { - draw_mesh(pipeline.fullscreen_mesh) - } - else { - log.warn("Renderer is missing a fullscreen-mesh.") - } - - deactivate_fullscreen_material(renderer) + // Clear + set_clear_color(renderer, clear_color) + clear_screen(renderer, true, true) + + // Create a temporary Material. + mat := Material { + shader_program = &renderer.fullscreen_shader_program, + textures = { + 0 = texture_to_present, + }, + texture_count = 1, } - else { - log.warn("Fullscreen-material does not have a texture.") - } - } - else { - log.warn("Renderer is missing a fullscreen-material.") - } + // Activate. + activate_fullscreen_material(renderer, &mat) + defer deactivate_fullscreen_material(renderer) + + // Draw. + draw_mesh(&renderer.fullscreen_mesh) + } + when RENDER_BACKEND_OPENGL { opengl_swap_buffers(renderer) } } -execute_scene_pass :: proc(renderer: ^Renderer, pass: ^Scene_Pass, view_matrix, projection_matrix: linalg.Matrix4x4f32) { - // fmt.printfln("Rendering pass '%v' to output_rt %v.", pass.name, pass.output_rt) - - // TODO: SS - Use 'pass.input_textures' +execute_pass :: proc(renderer: ^Renderer, pass: ^Pass, view_matrix, projection_matrix: linalg.Matrix4x4f32) { + // fmt.printfln("Executing pass '%v'.", pass.name) - assert(pass.output_rt != nil) - bind_render_target(renderer, pass.output_rt) - - should_write_depth := pass.output_rt.depth_texture != nil - should_test_depth := should_write_depth - should_clear_depth := should_write_depth - - cc, has_clear_color := pass.clear_color.? - if has_clear_color { - set_clear_color(renderer, cc) - } - - should_clear_color := has_clear_color && pass.output_rt.color_texture != nil + assert(renderer != nil) + assert(pass != nil) - clear_screen(renderer, should_clear_color, should_clear_depth) - apply_depth(renderer, should_test_depth, should_write_depth) + switch &t in &pass.type { + case Scene_Pass: { + assert(t.output_rt != nil) + bind_render_target(renderer, t.output_rt) - apply_blend_mode(renderer, pass.blend_mode) + should_write_depth := t.output_rt.depth_texture != nil + should_test_depth := should_write_depth + should_clear_depth := should_write_depth - sort_draw_commands(renderer, pass) + should_clear_color := true + set_clear_color(renderer, RGBA_Color { 0, 0, 0, 0 }) - for &dc in &pass.draw_commands { // TODO: SS - Don't think we need the address of the draw-commands. - model_matrix := linalg.identity(linalg.Matrix4x4f32) - - // Translate. - translation := linalg.matrix4_translate(dc.position) + clear_screen(renderer, should_clear_color, should_clear_depth) + apply_depth(renderer, should_test_depth, should_write_depth) - // Rotate. - rot_x := linalg.matrix4_rotate(linalg.to_radians(dc.rotation.x), [3]f32 { 1, 0, 0 }) - rot_y := linalg.matrix4_rotate(linalg.to_radians(dc.rotation.y), [3]f32 { 0, 1, 0 }) - rot_z := linalg.matrix4_rotate(linalg.to_radians(dc.rotation.z), [3]f32 { 0, 0, 1 }) - rotation := rot_z * rot_y * rot_x + apply_blend_mode(renderer, t.blend_mode) - // Scale. - scale := linalg.matrix4_scale(dc.scale) + sort_draw_commands(renderer, &t) + + for &dc in &t.draw_commands { // TODO: SS - Don't think we need the address of the draw-commands. + model_matrix := linalg.identity(linalg.Matrix4x4f32) + + // Translate. + translation := linalg.matrix4_translate(dc.position) + + // Rotate. + rot_x := linalg.matrix4_rotate(linalg.to_radians(dc.rotation.x), [3]f32 { 1, 0, 0 }) + rot_y := linalg.matrix4_rotate(linalg.to_radians(dc.rotation.y), [3]f32 { 0, 1, 0 }) + rot_z := linalg.matrix4_rotate(linalg.to_radians(dc.rotation.z), [3]f32 { 0, 0, 1 }) + rotation := rot_z * rot_y * rot_x + + // Scale. + scale := linalg.matrix4_scale(dc.scale) + + model_matrix *= translation * rotation * scale + + activate_material(&dc.material, model_matrix, view_matrix, projection_matrix) + + draw_mesh(&dc.mesh) + } - model_matrix *= translation * rotation * scale + // Clear the pass' draw-commands. + clear(&t.draw_commands) - activate_material(&dc.material, model_matrix, view_matrix, projection_matrix) + // TODO: SS - "Deactivate" the pass? + } + case Post_Processing_Pass: { + // Execute the post-processing nodes. + for &pp in &t.post_processing_nodes { + execute_post_processing_node(renderer, &pp, view_matrix, projection_matrix) + } - draw_mesh(&dc.mesh) + // TODO: SS - "Deactivate" the pass? + } } - - // TODO: SS - "Deactivate" the pass. - - // Clear the pass' draw-commands. - clear(&pass.draw_commands) } -execute_post_processing_pass :: proc(renderer: ^Renderer, pass: ^Post_Processing_Pass, view_matrix, projection_matrix: linalg.Matrix4x4f32) { - fmt.printfln("TODO: SS - Execute post-processing pass '%v'.", pass.name) +execute_post_processing_node :: proc(renderer: ^Renderer, node: ^Post_Processing_Node, view_matrix, projection_matrix: linalg.Matrix4x4f32) { + assert(renderer != nil) + assert(node != nil) + assert(node.program != nil) + assert(node.output != nil) + + assert(node.output != nil) + bind_render_target(renderer, node.output) + + apply_depth(renderer, false, false) + 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 + } + + 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) { @@ -205,21 +301,6 @@ destroy :: proc(renderer: ^Renderer) { } } -@(private) activate_fullscreen_material :: proc(renderer: ^Renderer) { - assert(renderer != nil) - mat := renderer.pipeline.fullscreen_material - when RENDER_BACKEND_OPENGL { - opengl_activate_fullscreen_material(mat) - } -} - -@(private) deactivate_fullscreen_material :: proc(renderer: ^Renderer) { - assert(renderer != nil) - when RENDER_BACKEND_OPENGL { - opengl_deactivate_fullscreen_material() - } -} - @(private) draw_mesh :: proc(mesh: ^Mesh) { assert(mesh != nil) @@ -293,24 +374,23 @@ distance_to_camera :: proc(camera: ^Camera, position: [3]f32) -> f32 { return linalg.distance(camera.position, position) } -@(private) bind_framebuffer :: proc(renderer: ^Renderer, rt: ^Render_Target) -> bool { - // when RENDER_BACKEND_OPENGL { - // return opengl_bind_framebuffer(renderer, rt) - // } - - return false -} - -@(private) bind_depth_texture :: proc(renderer: ^Renderer, rt: ^Render_Target) -> bool { - // when RENDER_BACKEND_OPENGL { - // return opengl_bind_depth_texture(renderer, rt) - // } - - return false -} - @(private) bind_render_target :: proc(renderer: ^Renderer, rt: ^Render_Target) { when RENDER_BACKEND_OPENGL { opengl_bind_render_target(renderer, rt) } +} + + +@(private) activate_fullscreen_material :: proc(renderer: ^Renderer, material: ^Material) { // TODO: SS - Maybe remove. + assert(renderer != nil) + when RENDER_BACKEND_OPENGL { + opengl_activate_fullscreen_material(material) + } +} + +@(private) deactivate_fullscreen_material :: proc(renderer: ^Renderer) { // TODO: SS - Maybe remove. + assert(renderer != nil) + when RENDER_BACKEND_OPENGL { + opengl_deactivate_fullscreen_material() + } } \ No newline at end of file diff --git a/renderer_backend_opengl_windows.odin b/renderer_backend_opengl_windows.odin index 6a094fb..fe6213f 100644 --- a/renderer_backend_opengl_windows.odin +++ b/renderer_backend_opengl_windows.odin @@ -85,19 +85,24 @@ when RENDER_BACKEND_OPENGL { opengl_viewport_changed :: proc(renderer: ^Renderer) { gl.Viewport( - i32(renderer.viewport.x), - i32(renderer.viewport.y), - i32(renderer.viewport.width), - i32(renderer.viewport.height) + i32(renderer.viewport.x), i32(renderer.viewport.y), + i32(renderer.viewport.width), i32(renderer.viewport.height) ) } - opengl_set_clear_color :: proc(renderer: ^Renderer, color: RGB_Color) { + opengl_set_clear_color :: proc(renderer: ^Renderer, color: Color) { + r, g, b, a: u8 = max(u8), max(u8), max(u8), max(u8) + + switch &c in color { + case RGB_Color: r, g, b = expand_values(c.rgb) + case RGBA_Color: r, g, b, a = expand_values(c.rgba) + } + gl.ClearColor( - f32(color.r) / f32(max(u8)), - f32(color.g) / f32(max(u8)), - f32(color.b) / f32(max(u8)), - 1.0 + f32(r) / f32(max(u8)), + f32(g) / f32(max(u8)), + f32(b) / f32(max(u8)), + f32(a) / f32(max(u8)), ) } @@ -253,33 +258,50 @@ when RENDER_BACKEND_OPENGL { gl.DeleteProgram(shader_program.backend.handle) } + opengl_activate_bind_textures_in_material :: proc(material: ^Material) { + assert(material != nil) + + for t, i in material.textures[:material.texture_count] { + assert(t != nil) + gl.ActiveTexture(gl.TEXTURE0 + u32(i)) + gl.BindTexture(gl.TEXTURE_2D, t.backend.handle) + + loc := gl.GetUniformLocation(material.shader_program.backend.handle, fmt.ctprintf("texture%d", i)) + gl.Uniform1i(loc, i32(i)) + } + } + opengl_activate_material :: proc(material: ^Material, model_matrix, view_matrix, projection_matrix: linalg.Matrix4x4f32) { gl.UseProgram(material.shader_program.backend.handle) - gl.ActiveTexture(gl.TEXTURE0) - gl.BindTexture(gl.TEXTURE_2D, material.texture.backend.handle) + opengl_activate_bind_textures_in_material(material) model_matrix_loc := gl.GetUniformLocation(material.shader_program.backend.handle, "in_model_matrix") + assert(model_matrix_loc >= 0) model_matrix_as_f32_array := transmute([16]f32)(model_matrix) gl.UniformMatrix4fv(model_matrix_loc, 1, false, &model_matrix_as_f32_array[0]) view_matrix_loc := gl.GetUniformLocation(material.shader_program.backend.handle, "in_view_matrix") + assert(view_matrix_loc >= 0) view_matrix_as_f32_array := transmute([16]f32)(view_matrix) gl.UniformMatrix4fv(view_matrix_loc, 1, false, &view_matrix_as_f32_array[0]) - + projection_matrix_loc := gl.GetUniformLocation(material.shader_program.backend.handle, "in_projection_matrix") + assert(projection_matrix_loc >= 0) projection_matrix_as_f32_array := transmute([16]f32)(projection_matrix) gl.UniformMatrix4fv(projection_matrix_loc, 1, false, &projection_matrix_as_f32_array[0]) } - - opengl_activate_fullscreen_material :: proc(material: ^Material) { + + opengl_activate_fullscreen_material :: proc(material: ^Material) { // TODO: SS - Maybe remove. assert(material != nil) gl.UseProgram(material.shader_program.backend.handle) + assert(material.texture_count > 0) + gl.ActiveTexture(gl.TEXTURE0) - gl.BindTexture(gl.TEXTURE_2D, material.texture.backend.handle) + gl.BindTexture(gl.TEXTURE_2D, material.textures[0].backend.handle) - loc := gl.GetUniformLocation(material.shader_program.backend.handle, "texture0") // eller "uTex" + loc := gl.GetUniformLocation(material.shader_program.backend.handle, "texture0") if loc != -1 { gl.Uniform1i(loc, 0) } @@ -288,7 +310,7 @@ when RENDER_BACKEND_OPENGL { gl.DepthMask(gl.FALSE) } - opengl_deactivate_fullscreen_material :: proc() { + opengl_deactivate_fullscreen_material :: proc() { // TODO: SS - Maybe remove. gl.DepthMask(gl.TRUE) gl.Enable(gl.DEPTH_TEST) } @@ -486,6 +508,8 @@ when RENDER_BACKEND_OPENGL { } gl.BindFramebuffer(gl.FRAMEBUFFER, rt.backend.handle) + status := gl.CheckFramebufferStatus(gl.FRAMEBUFFER) + assert(status == gl.FRAMEBUFFER_COMPLETE) gl.Viewport( 0, @@ -529,4 +553,13 @@ when RENDER_BACKEND_OPENGL { return true } -} \ No newline at end of file + opengl_set_shader_value_texture :: proc(program: ^Shader_Program, value: ^Texture, index: u8) { + 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)) + } +} + diff --git a/shader.odin b/shader.odin index b60526b..6a1ca3d 100644 --- a/shader.odin +++ b/shader.odin @@ -3,6 +3,7 @@ package renderer import os "core:os/os2" Shader :: struct { + path: string, type: Shader_Type, backend: Shader_Backend, } @@ -27,6 +28,7 @@ create_shader :: proc(renderer: ^Renderer, type: Shader_Type, path: string) -> ( s: Shader s.type = type + s.path = path when RENDER_BACKEND_OPENGL { shader_opengl, ok := opengl_create_shader(renderer, type, bytes)