package renderer import "core:log" import "core:math" import glsl "core:math/linalg/glsl" import bk "../backend" import "../resource" MAX_DRAW_3D_COMMANDS :: 1024 Push_Constants_3D :: struct { proj_view: glsl.mat4x4, model: glsl.mat4x4, } // Lit 3D push constants: model matrix + material properties. // Odin aligns to 16 bytes (mat4x4 alignment), so actual size is 128 bytes. Push_Constants_3D_Lit :: struct { model: glsl.mat4x4, // 64 bytes, offset 0 base_color: [4]f32, // 16 bytes, offset 64 metallic: f32, // 4 bytes, offset 80 roughness: f32, // 4 bytes, offset 84 reflectance: f32, // 4 bytes, offset 88 _pad: f32, // 4 bytes, offset 92 emissive: [4]f32, // 16 bytes, offset 96 } // Actual: 128 bytes (16-byte aligned, padded after emissive) Draw_Command_3D :: struct { mesh_id: u32, texture_id: u32, normal_map_id: u32, layout_variant: bk.Layout_Variant, model: glsl.mat4x4, double_sided: bool, // Material properties (used by lit path) base_color: [4]f32, metallic: f32, roughness: f32, reflectance: f32, emissive: [4]f32, } Draw_Queue_3D :: struct { commands: [MAX_DRAW_3D_COMMANDS]Draw_Command_3D, count: u32, proj_view: glsl.mat4x4, resource_state: ^resource.Resource_State, } reset_draw_queue_3d :: proc(q: ^Draw_Queue_3D) { q.count = 0 } push_draw_command_3d :: proc( q: ^Draw_Queue_3D, mesh_id, texture_id: u32, layout_variant: bk.Layout_Variant = .PNU, model: glsl.mat4x4, double_sided: bool = false, base_color: [4]f32 = {1, 1, 1, 1}, metallic: f32 = 0.0, roughness: f32 = 0.5, reflectance: f32 = 0.5, emissive: [4]f32 = {0, 0, 0, 0}, normal_map_id: u32 = resource.NORMAL_TEXTURE_ID, ) { if q.count >= MAX_DRAW_3D_COMMANDS { log.warnf("gpu/renderer: draw queue full (%d), command dropped", MAX_DRAW_3D_COMMANDS) return } q.commands[q.count] = { mesh_id = mesh_id, texture_id = texture_id, normal_map_id = normal_map_id, layout_variant = layout_variant, model = model, double_sided = double_sided, base_color = base_color, metallic = metallic, roughness = roughness, reflectance = reflectance, emissive = emissive, } q.count += 1 } flush_draw_queue_3d :: proc( q: ^Draw_Queue_3D, b: ^bk.Backend, ctx: bk.Frame_Context, pipelines_culled: [bk.MAX_LAYOUT_VARIANTS]bk.Pipeline_Handle, pipelines_no_cull: [bk.MAX_LAYOUT_VARIANTS]bk.Pipeline_Handle, ) { if q.count == 0 { return } // Sort by (layout_variant, double_sided) to minimize pipeline switches. cmds := q.commands[:q.count] for i in 1.. 0 && draw_sort_key_3d(&cmds[j]) < draw_sort_key_3d(&cmds[j - 1]) { cmds[j], cmds[j - 1] = cmds[j - 1], cmds[j] j -= 1 } } current_variant: bk.Layout_Variant = cmds[0].layout_variant current_double_sided: bool = cmds[0].double_sided current_pipeline := select_pipeline(pipelines_culled, pipelines_no_cull, current_variant, current_double_sided) pipeline_valid := current_pipeline != {} if pipeline_valid { b.bind_graphics_pipeline(ctx, current_pipeline) } last_texture_id: u32 = max(u32) for i in 0.. 0 && draw_sort_key_3d(&cmds[j]) < draw_sort_key_3d(&cmds[j - 1]) { cmds[j], cmds[j - 1] = cmds[j - 1], cmds[j] j -= 1 } } current_variant: bk.Layout_Variant = cmds[0].layout_variant current_double_sided: bool = cmds[0].double_sided current_pipeline := select_pipeline(pipelines_culled, pipelines_no_cull, current_variant, current_double_sided) pipeline_valid := current_pipeline != {} if pipeline_valid { b.bind_graphics_pipeline(ctx, current_pipeline) if light_descriptor_set != bk.Descriptor_Handle(0) { b.bind_descriptor_set(ctx, current_pipeline, light_descriptor_set, 1) } } last_texture_id: u32 = max(u32) last_normal_map_id: u32 = max(u32) for i in 0.. 0 && draw_sort_key_3d(&cmds[j]) < draw_sort_key_3d(&cmds[j - 1]) { cmds[j], cmds[j - 1] = cmds[j - 1], cmds[j] j -= 1 } } current_variant: bk.Layout_Variant = cmds[0].layout_variant current_double_sided: bool = cmds[0].double_sided current_pipeline := select_pipeline(pipelines_culled, pipelines_no_cull, current_variant, current_double_sided) pipeline_valid := current_pipeline != {} if pipeline_valid { b.bind_graphics_pipeline(ctx, current_pipeline) b.bind_descriptor_set(ctx, current_pipeline, light_descriptor_set, 1) b.bind_descriptor_set(ctx, current_pipeline, shadow_descriptor_set, 2) } last_texture_id: u32 = max(u32) last_normal_map_id: u32 = max(u32) for i in 0.. glsl.mat4x4 { fovy_rad := fovy_degrees * math.PI / 180.0 f := 1.0 / math.tan(fovy_rad * 0.5) m: glsl.mat4x4 m[0, 0] = f / aspect m[1, 1] = -f // Y-flip for Vulkan m[2, 2] = far / (near - far) m[2, 3] = (near * far) / (near - far) m[3, 2] = -1.0 return m } // Right-handed look-at matrix. Camera looks down -Z. // m[row, col] convention, column-major storage. look_at :: proc(eye, target, world_up: glsl.vec3) -> glsl.mat4x4 { f := normalize(target - eye) // forward (into screen) r := normalize(cross(f, world_up)) // right u := cross(r, f) // true up m: glsl.mat4x4 m[0, 0] = r.x; m[0, 1] = r.y; m[0, 2] = r.z m[1, 0] = u.x; m[1, 1] = u.y; m[1, 2] = u.z m[2, 0] = -f.x; m[2, 1] = -f.y; m[2, 2] = -f.z m[0, 3] = -dot(r, eye) m[1, 3] = -dot(u, eye) m[2, 3] = dot(f, eye) m[3, 3] = 1.0 return m } // --- Draw command helpers --- // Sort key: layout_variant in high bits, double_sided in low bit. draw_sort_key_3d :: proc(cmd: ^Draw_Command_3D) -> u16 { return u16(cmd.layout_variant) << 1 | u16(cmd.double_sided) } // Returns true for layout variants that include tangent data (and need normal map at set 3). variant_has_tangent :: proc(v: bk.Layout_Variant) -> bool { return v == .PNUT || v == .PNUCT } // Select pipeline from arrays based on variant and cull mode. select_pipeline :: proc( culled: [bk.MAX_LAYOUT_VARIANTS]bk.Pipeline_Handle, no_cull: [bk.MAX_LAYOUT_VARIANTS]bk.Pipeline_Handle, variant: bk.Layout_Variant, double_sided: bool, ) -> bk.Pipeline_Handle { arr := no_cull if double_sided else culled return arr[variant] } // --- Vector math helpers --- @(private) normalize :: proc(v: glsl.vec3) -> glsl.vec3 { l := math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z) if l < 1e-8 { return {0, 0, 0} } return v / l } @(private) cross :: proc(a, b: glsl.vec3) -> glsl.vec3 { return { a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x, } } @(private) dot :: proc(a, b: glsl.vec3) -> f32 { return a.x * b.x + a.y * b.y + a.z * b.z }