package d3d12_backend import "core:log" import win32 "core:sys/windows" import d3d12 "vendor:directx/d3d12" import dxgi "vendor:directx/dxgi" import bk ".." // --- Lifecycle --- shutdown_d3d12 :: proc() { if g_d3d == nil {return} wait_gpu_idle() drain_deferred_releases_now_d3d12() // Destroy all active samplers for &entry in g_d3d.samplers { if entry.active { entry.active = false } } // Destroy framebuffers (metadata only) for &entry in g_d3d.framebuffers { entry.active = false } // Destroy render passes (metadata only) for &entry in g_d3d.render_passes { entry.active = false } // Destroy descriptors (metadata only) for &entry in g_d3d.descriptors { entry.active = false } // Destroy shaders for i in 0 ..< MAX_SHADERS { if g_d3d.shaders[i].active do destroy_shader_d3d12(bk.Shader_Handle(i)) } // Destroy pipelines for i in 0 ..< MAX_PIPELINES { if g_d3d.pipelines[i].active do destroy_graphics_pipeline_d3d12(bk.Pipeline_Handle(i)) } // Destroy textures for i in 0 ..< MAX_TEXTURES { if g_d3d.textures[i].active do destroy_texture_d3d12(bk.Texture_Handle(i)) } // Destroy buffers for i in 0 ..< MAX_BUFFERS { if g_d3d.buffers[i].active do destroy_buffer_d3d12(bk.Buffer_Handle(i)) } drain_deferred_releases_now_d3d12() // Destroy core objects if g_d3d.draw_indirect_signature != nil {g_d3d.draw_indirect_signature->Release()} if g_d3d.draw_indexed_indirect_signature != nil {g_d3d.draw_indexed_indirect_signature->Release()} if g_d3d.graphics_root_sig != nil {g_d3d.graphics_root_sig->Release()} if g_d3d.compute_root_sig != nil {g_d3d.compute_root_sig->Release()} when ODIN_OS == .Windows { if g_d3d.fence_event != nil { win32.CloseHandle(g_d3d.fence_event) } } if g_d3d.fence != nil {g_d3d.fence->Release()} if g_d3d.command_list != nil {g_d3d.command_list->Release()} for i in 0 ..< bk.MAX_FRAMES_IN_FLIGHT { if g_d3d.frame_resources[i].command_allocator != nil { g_d3d.frame_resources[i].command_allocator->Release() } } release_backbuffers_and_depth(g_d3d) if g_d3d.gpu_sampler_heap != nil {g_d3d.gpu_sampler_heap->Release()} if g_d3d.gpu_cbv_srv_uav_heap != nil {g_d3d.gpu_cbv_srv_uav_heap->Release()} if g_d3d.staging_sampler_heap != nil {g_d3d.staging_sampler_heap->Release()} if g_d3d.staging_cbv_srv_uav_heap != nil {g_d3d.staging_cbv_srv_uav_heap->Release()} if g_d3d.dsv_heap != nil {g_d3d.dsv_heap->Release()} if g_d3d.rtv_heap != nil {g_d3d.rtv_heap->Release()} if g_d3d.swap_chain != nil {g_d3d.swap_chain->Release()} if g_d3d.command_queue != nil {g_d3d.command_queue->Release()} if g_d3d.device != nil {g_d3d.device->Release()} delete(g_d3d.deferred_releases) free(g_d3d) g_d3d = nil bk.backend_initialized = false } wait_idle_d3d12 :: proc() { wait_gpu_idle() drain_deferred_releases_now_d3d12() } // --- Frame lifecycle --- begin_frame_d3d12 :: proc(clear_color: [4]f32) -> (bk.Frame_Context, bool) { if g_d3d == nil {return {}, false} frame_idx := g_d3d.current_frame fr := &g_d3d.frame_resources[frame_idx] // Wait for this frame slot's previous work to complete if g_d3d.fence->GetCompletedValue() < fr.fence_value { g_d3d.fence->SetEventOnCompletion(fr.fence_value, g_d3d.fence_event) when ODIN_OS == .Windows { win32.WaitForSingleObject(g_d3d.fence_event, win32.INFINITE) } } drain_deferred_releases_now_d3d12() // Reset allocator and command list fr.command_allocator->Reset() g_d3d.command_list->Reset(fr.command_allocator, nil) // Get current back buffer index from swap chain g_d3d.back_buffer_idx = g_d3d.swap_chain->GetCurrentBackBufferIndex() // Reset per-frame GPU descriptor allocators base_csv := frame_idx * GPU_CBV_SRV_UAV_PER_FRAME g_d3d.gpu_cbv_srv_uav_alloc[frame_idx].offset = base_csv base_sampler := frame_idx * GPU_SAMPLER_PER_FRAME g_d3d.gpu_sampler_alloc[frame_idx].offset = base_sampler // Set descriptor heaps heaps := [2]^d3d12.IDescriptorHeap{g_d3d.gpu_cbv_srv_uav_heap, g_d3d.gpu_sampler_heap} g_d3d.command_list->SetDescriptorHeaps(2, &heaps[0]) // Set root signature g_d3d.command_list->SetGraphicsRootSignature(g_d3d.graphics_root_sig) g_d3d.frame_active = true g_d3d.clear_color = clear_color return bk.Frame_Context{frame_index = frame_idx}, true } end_frame_d3d12 :: proc(ctx: bk.Frame_Context) -> bool { if g_d3d == nil || !g_d3d.frame_active {return false} // Transition backbuffer to PRESENT bb := g_d3d.backbuffers[g_d3d.back_buffer_idx] transition_backbuffer_to_present_d3d12(bb) // Close command list result := g_d3d.command_list->Close() if result < 0 { log.errorf("gpu/d3d12: command list Close failed: 0x%08X", u32(result)) } // Execute cmd_list := cast(^d3d12.ICommandList)g_d3d.command_list g_d3d.command_queue->ExecuteCommandLists(1, &cmd_list) // Present with vsync present_result := g_d3d.swap_chain->Present(1, {}) // Signal fence for this frame fr := &g_d3d.frame_resources[g_d3d.current_frame] fr.fence_value = g_d3d.fence_value g_d3d.command_queue->Signal(g_d3d.fence, g_d3d.fence_value) g_d3d.fence_value += 1 drain_deferred_releases_now_d3d12() g_d3d.frame_active = false g_d3d.render_pass_active = false g_d3d.current_frame = (g_d3d.current_frame + 1) % bk.MAX_FRAMES_IN_FLIGHT if present_result == dxgi.STATUS_OCCLUDED { return false } if present_result < 0 { log.errorf("gpu/d3d12: Present failed: 0x%08X", u32(present_result)) return true } return false } on_resize_d3d12 :: proc(width, height: u32) { if g_d3d == nil {return} if width == 0 || height == 0 {return} wait_gpu_idle() release_backbuffers_and_depth(g_d3d) result := g_d3d.swap_chain->ResizeBuffers(0, width, height, .UNKNOWN, {}) if result < 0 { log.errorf("gpu/d3d12: ResizeBuffers failed: 0x%08X", u32(result)) return } g_d3d.width = width g_d3d.height = height create_backbuffer_rtvs(g_d3d) create_depth_buffer(g_d3d, width, height) } get_extent_d3d12 :: proc() -> bk.Extent { if g_d3d == nil {return {}} return bk.Extent{width = g_d3d.width, height = g_d3d.height} } // --- Render pass commands --- begin_render_pass_d3d12 :: proc(ctx: bk.Frame_Context, desc: bk.Render_Pass_Begin_Desc) { if g_d3d == nil {return} fb_entry, fb_ok := framebuffer_entry(desc.framebuffer) if !fb_ok {return} pass_entry, pass_ok := render_pass_entry(desc.pass) is_depth_only := pass_ok && pass_entry.desc.depth_only rtv_handles: [bk.MAX_COLOR_TARGETS]d3d12.CPU_DESCRIPTOR_HANDLE rtv_count: u32 for i in 0..OMSetRenderTargets(0, nil, false, &dsv_handle if has_dsv else nil) } else { g_d3d.command_list->OMSetRenderTargets( rtv_count, &rtv_handles[0], false, &dsv_handle if has_dsv else nil, ) for i in 0.. 0 else desc.clear_color g_d3d.command_list->ClearRenderTargetView(rtv_handles[i], &cc, 0, nil) } } if has_dsv { clear_flags := d3d12.CLEAR_FLAGS{.DEPTH} if pass, pass_ok := render_pass_entry(desc.pass); pass_ok && pass.desc.has_stencil { clear_flags += {.STENCIL} } g_d3d.command_list->ClearDepthStencilView( dsv_handle, clear_flags, desc.clear_depth, u8(desc.clear_stencil), 0, nil, ) } // Set viewport viewport := d3d12.VIEWPORT { Width = f32(desc.width), Height = f32(desc.height), MaxDepth = 1, } g_d3d.command_list->RSSetViewports(1, &viewport) scissor := d3d12.RECT { right = i32(desc.width), bottom = i32(desc.height), } g_d3d.command_list->RSSetScissorRects(1, &scissor) g_d3d.render_pass_active = true } begin_default_pass_d3d12 :: proc(ctx: bk.Frame_Context, clear_color: [4]f32) { if g_d3d == nil {return} // Transition backbuffer to RENDER_TARGET bb := g_d3d.backbuffers[g_d3d.back_buffer_idx] transition_backbuffer_to_render_target_d3d12(bb) // Get RTV for current backbuffer rtv_handle := get_rtv_cpu_handle(g_d3d.back_buffer_idx) // Get DSV dsv_handle := get_dsv_cpu_handle(0) // Default depth buffer is at index 0 g_d3d.command_list->OMSetRenderTargets(1, &rtv_handle, false, &dsv_handle) // Clear cc := clear_color g_d3d.command_list->ClearRenderTargetView(rtv_handle, &cc, 0, nil) g_d3d.command_list->ClearDepthStencilView(dsv_handle, {.DEPTH}, 1.0, 0, 0, nil) // Set default viewport viewport := d3d12.VIEWPORT { Width = f32(g_d3d.width), Height = f32(g_d3d.height), MaxDepth = 1, } g_d3d.command_list->RSSetViewports(1, &viewport) scissor := d3d12.RECT { right = i32(g_d3d.width), bottom = i32(g_d3d.height), } g_d3d.command_list->RSSetScissorRects(1, &scissor) g_d3d.render_pass_active = true } end_render_pass_d3d12 :: proc(ctx: bk.Frame_Context) { if g_d3d == nil {return} g_d3d.render_pass_active = false } set_viewport_d3d12 :: proc(ctx: bk.Frame_Context, x, y, w, h: f32) { if g_d3d == nil {return} viewport := d3d12.VIEWPORT { TopLeftX = x, TopLeftY = y, Width = w, Height = h, MaxDepth = 1, } g_d3d.command_list->RSSetViewports(1, &viewport) } set_scissor_d3d12 :: proc(ctx: bk.Frame_Context, x, y: i32, w, h: u32) { if g_d3d == nil {return} scissor := d3d12.RECT { left = x, top = y, right = x + i32(w), bottom = y + i32(h), } g_d3d.command_list->RSSetScissorRects(1, &scissor) } set_depth_bias_d3d12 :: proc(ctx: bk.Frame_Context, constant, slope: f32) { if g_d3d == nil {return} g_d3d.pending_depth_bias_constant = i32(constant) g_d3d.pending_depth_bias_slope = slope if entry, ok := pipeline_entry(g_d3d.current_pipeline); ok { if pso, pso_ok := depth_bias_pso_d3d12( entry, g_d3d.pending_depth_bias_constant, g_d3d.pending_depth_bias_slope, ); pso_ok { g_d3d.command_list->SetPipelineState(pso) } } } // --- Sync queries --- get_default_render_pass_d3d12 :: proc() -> bk.Render_Pass_Handle { if g_d3d == nil {return bk.NULL_RENDER_PASS} return g_d3d.default_render_pass } get_depth_format_d3d12 :: proc() -> bk.Format { if g_d3d == nil {return .Undefined} return from_dxgi_format(g_d3d.depth_format) }