package renderer import "core:math/linalg" import "core:fmt" import "core:log" MAX_DRAW_COMMANDS_CAPACITY :: 4096 MAX_POST_PROCESS_NODES_PER_PASS :: 8 Pass :: struct { name: string, type: Pass_Type, } Pass_Type :: union { Scene_Pass, Post_Processing_Pass, } Scene_Pass :: struct { blend_mode: Blend_Mode, sort_mode: Sort_Mode, cull_mode: Cull_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 { 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_Float_Pointer, Uniform_Matrix4f32, Uniform_Matrix4f32_Pointer, Uniform_Vector3, Uniform_Vector3_Pointer, Uniform_Vector4, Uniform_Vector4_Pointer, Uniform_Color, Uniform_Color_Pointer, } Uniform_Texture :: struct { index: u8, value: ^Texture, } Uniform_Float :: struct { name: string, value: f32, } Uniform_Float_Pointer :: struct { name: string, value: ^f32, } Uniform_Matrix4f32 :: struct { name: string, value: linalg.Matrix4f32, } Uniform_Matrix4f32_Pointer :: struct { name: string, value: ^linalg.Matrix4f32, } Uniform_Vector3 :: struct { name: string, value: [3]f32, } Uniform_Vector3_Pointer :: struct { name: string, value: ^[3]f32, } Uniform_Vector4 :: struct { name: string, value: [4]f32, } Uniform_Vector4_Pointer :: struct { name: string, value: ^[4]f32, } Uniform_Color :: struct { name: string, value: Color, } Uniform_Color_Pointer :: struct { name: string, value: ^Color, } Post_Processing_Node :: struct { uniforms: []Uniform, output: ^Render_Target, program: ^Shader_Program, } // TODO: SS - Create a pool of 'Draw_Command's and reuse them. MAX_UNIFORMS_PER_DRAW_COMMAND :: 8 Draw_Command :: struct { renderer: ^Renderer, // Needed for sorting. mesh: Mesh, material: Material, uniforms: [MAX_UNIFORMS_PER_DRAW_COMMAND]Uniform, uniform_count: u8, position: [3]f32, rotation: [3]f32, scale: [3]f32, } create_draw_command :: proc(renderer: ^Renderer, mesh: Mesh, material: Material, uniforms: []Uniform, position, rotation, scale: [3]f32, loc := #caller_location) -> Draw_Command { dc: Draw_Command dc.renderer = renderer dc.mesh = mesh dc.material = material dc.position = position dc.rotation = rotation dc.scale = scale for u in uniforms { if dc.uniform_count >= MAX_UNIFORMS_PER_DRAW_COMMAND { log.warnf("Hit max capacity (%v) of uniforms per draw command! %v", MAX_UNIFORMS_PER_DRAW_COMMAND, loc) break } dc.uniforms[dc.uniform_count] = u dc.uniform_count += 1 } return dc } Blend_Mode :: enum { None, Alpha, Additive, Multiply, } Blend_Factor :: enum { Zero, One, Src_Color, One_Minus_Src_Color, Dst_Color, One_Minus_Dst_Color, Src_Alpha, One_Minus_Src_Alpha, Dst_Alpha, One_Minus_Dst_Alpha, // .. } Blend_Factors :: struct { source, destination: Blend_Factor } @(private) BLEND_FACTOR_TABLE :: [Blend_Mode]Blend_Factors { .None = { .One, .Zero }, .Alpha = { .Src_Alpha, .One_Minus_Src_Alpha }, .Additive = { .Src_Alpha, .One }, .Multiply = { .Dst_Color, .Zero }, } Sort_Mode :: enum { None, // Draws the commands in the order they're placed in the 'draw_commmands' array. Back_To_Front, // Sorts the commands in the 'draw_commmands' array (from back to front) before drawing them. Front_To_Back, // Sorts the commands in the 'draw_commmands' array (from front to back) before drawing them. } Cull_Mode :: enum { None, Back, Front, Front_Back, } create_scene_pass :: proc( name: string, blend_mode: Blend_Mode, sort_mode: Sort_Mode, cull_mode: Cull_Mode, output_rt: ^Render_Target, ) -> Pass { assert(len(name) > 0) p: Pass p.name = name p.type = Scene_Pass { blend_mode = blend_mode, sort_mode = sort_mode, cull_mode = cull_mode, draw_commands = make([dynamic]Draw_Command, 0, MAX_DRAW_COMMANDS_CAPACITY), output_rt = output_rt, } return p } 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(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) { assert(pass.draw_commands != nil) delete(pass.draw_commands) } @(private) delete_post_processing_pass :: proc(pass: ^Post_Processing_Pass) { assert(pass.post_processing_nodes != nil) delete(pass.post_processing_nodes) } add_command_to_pass :: proc(renderer: ^Renderer, pass: ^Pass, command: Draw_Command) -> bool { assert(renderer != nil) assert(pass != nil) 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 } execute_pass :: proc(renderer: ^Renderer, pass: ^Pass, view_matrix, projection_matrix: linalg.Matrix4x4f32) { // TODO: SS - Move to 'pass.odin'. // fmt.printfln("Executing pass '%v'.", pass.name) assert(renderer != nil) assert(pass != nil) switch &t in &pass.type { case Scene_Pass: { apply_polygon_mode(renderer, renderer.polygon_mode) 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 should_clear_depth := should_write_depth should_clear_color := true should_scissor :: false set_clear_color(renderer, RGBA_Color { 0, 0, 0, 0 }) clear_screen(renderer, should_clear_color, should_clear_depth, should_scissor) apply_depth(renderer, should_test_depth, should_write_depth) apply_blend_mode(renderer, t.blend_mode) defer apply_blend_mode(renderer, .None) apply_cull_mode(renderer, t.cull_mode) defer apply_cull_mode(renderer, .None) 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 // Apply uniforms. activate_material(&dc.material, model_matrix, view_matrix, projection_matrix) if dc.uniform_count > 0 { set_shader_uniforms(dc.material.shader_program, dc.uniforms[:dc.uniform_count]) } // Draw the mesh. draw_mesh(&dc.mesh) } // Clear the pass' draw-commands. clear(&t.draw_commands) // TODO: SS - "Deactivate" the pass? apply_polygon_mode(renderer, .Fill) } 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) } // TODO: SS - "Deactivate" the pass? } } }