package renderer import "core:log" import bk "../backend" import ir "../render_ir" import "../profile" import "../resource" Renderer_State :: struct { frame_active: bool, in_3d_mode: bool, batch_2d: Batch_2D, draw_queue_3d: Draw_Queue_3D, depth_buffer: Depth_Buffer, pipeline_3d: [bk.MAX_LAYOUT_VARIANTS]bk.Pipeline_Handle, pipeline_3d_double_sided: [bk.MAX_LAYOUT_VARIANTS]bk.Pipeline_Handle, builtin_cube: u32, builtin_sphere: u32, builtin_plane: u32, grid_mesh: u32, grid_slices: i32, grid_spacing: f32, grid_valid: bool, // Lighting light_state: Light_State, pipeline_3d_lit: [bk.MAX_LAYOUT_VARIANTS]bk.Pipeline_Handle, pipeline_3d_lit_double_sided: [bk.MAX_LAYOUT_VARIANTS]bk.Pipeline_Handle, pipeline_2d_lit: bk.Pipeline_Handle, lighting_enabled_3d: bool, lighting_enabled_2d: bool, shadows_enabled: bool, camera_pos_3d: [3]f32, camera_target_3d: [3]f32, // Shadows shadow_state: Shadow_State, pipeline_3d_lit_shadowed: [bk.MAX_LAYOUT_VARIANTS]bk.Pipeline_Handle, pipeline_3d_lit_shadowed_double_sided: [bk.MAX_LAYOUT_VARIANTS]bk.Pipeline_Handle, // Generic 3D billboards billboard_3d_batch: Billboard_3D_Batch, camera_right: [3]f32, camera_up: [3]f32, // Deferred render pass state render_pass_begun: bool, deferred_clear_color: [4]f32, // Per-frame render IR scratch used by incrementally migrated frontend paths. frame_ir: ir.Frame_IR, profile: profile.Counters, // Backend reference + frame context backend: ^bk.Backend, frame_ctx: bk.Frame_Context, } init_renderer :: proc(b: ^bk.Backend, res_state: ^resource.Resource_State) -> (state: Renderer_State, ok: bool) { state.backend = b extent := b.get_extent() // Create depth buffer db, db_ok := create_depth_buffer(b, extent.width, extent.height) if !db_ok { return {}, false } state.depth_buffer = db // Create 2D batch batch, batch_ok := init_batch_2d(b, res_state) if !batch_ok { destroy_depth_buffer(b, &state.depth_buffer) return {}, false } state.batch_2d = batch // Initialize 3D draw queue state.draw_queue_3d.resource_state = res_state // Create builtin meshes (PNU layout — 32 bytes/vertex, no vertex color) cube_verts, cube_idxs := resource.gen_cube_geometry(1, 1, 1) defer delete(cube_verts) defer delete(cube_idxs) cube_id, _, cube_ok := resource.upload_mesh_raw( res_state, b, raw_data(cube_verts), len(cube_verts) * size_of(resource.Mesh_Vertex_PNU), i32(len(cube_verts)), cube_idxs, bk.LAYOUT_POS_NORM_UV, ) if cube_ok { state.builtin_cube = cube_id } else { log.warn("gpu/renderer: failed to create builtin cube mesh") } sphere_verts, sphere_idxs := resource.gen_sphere_geometry(1, 16, 32) defer delete(sphere_verts) defer delete(sphere_idxs) sphere_id, _, sphere_ok := resource.upload_mesh_raw( res_state, b, raw_data(sphere_verts), len(sphere_verts) * size_of(resource.Mesh_Vertex_PNU), i32(len(sphere_verts)), sphere_idxs, bk.LAYOUT_POS_NORM_UV, ) if sphere_ok { state.builtin_sphere = sphere_id } else { log.warn("gpu/renderer: failed to create builtin sphere mesh") } plane_verts, plane_idxs := resource.gen_plane_geometry(1, 1, 1, 1) defer delete(plane_verts) defer delete(plane_idxs) plane_id, _, plane_ok := resource.upload_mesh_raw( res_state, b, raw_data(plane_verts), len(plane_verts) * size_of(resource.Mesh_Vertex_PNU), i32(len(plane_verts)), plane_idxs, bk.LAYOUT_POS_NORM_UV, ) if plane_ok { state.builtin_plane = plane_id } else { log.warn("gpu/renderer: failed to create builtin plane mesh") } if !init_billboard_3d_batch(&state.billboard_3d_batch, b) { log.warn("gpu/renderer: failed to create 3D billboard batch buffers") } // Initialize lighting system if !init_light_state(&state.light_state, b) { log.warn("gpu/renderer: failed to initialize lighting state") } state.frame_ir = ir.init_frame_ir() return state, true } shutdown_renderer :: proc(state: ^Renderer_State, b: ^bk.Backend) { ir.destroy_frame_ir(&state.frame_ir) shutdown_billboard_3d_batch(&state.billboard_3d_batch, b) for v in 0.. bool { if !state.frame_active { return false } prof_start := profile.now() // Flush 2D batch before ending render pass flush_batch_2d(&state.batch_2d) // Ensure render pass was started (even if no draws happened) ensure_render_pass(state) // End render pass state.backend.end_render_pass(state.frame_ctx) // End frame and present resized := state.backend.end_frame(state.frame_ctx) state.profile.frame_end_ns += profile.elapsed_ns(prof_start, profile.now()) state.frame_active = false if resized { on_swapchain_recreated(state) return true } return false }