package d3d12_backend import "core:log" import win32 "core:sys/windows" import d3d12 "vendor:directx/d3d12" import dxgi "vendor:directx/dxgi" import bk ".." // --- Global D3D12 state --- @(private) g_d3d: ^D3D12_State // Frame resource for double buffering Frame_Resource :: struct { command_allocator: ^d3d12.ICommandAllocator, fence_value: u64, } // Linear descriptor allocator for shader-visible GPU heaps Descriptor_Allocator :: struct { offset: u32, capacity: u32, } D3D12_State :: struct { // Core objects device: ^d3d12.IDevice, command_queue: ^d3d12.ICommandQueue, swap_chain: ^dxgi.ISwapChain3, command_list: ^d3d12.IGraphicsCommandList, // Double buffering frame_resources: [bk.MAX_FRAMES_IN_FLIGHT]Frame_Resource, backbuffers: [bk.MAX_FRAMES_IN_FLIGHT]^d3d12.IResource, fence: ^d3d12.IFence, fence_event: d3d12.HANDLE, fence_value: u64, current_frame: u32, back_buffer_idx: u32, // Depth buffer depth_buffer: ^d3d12.IResource, depth_format: dxgi.FORMAT, // Descriptor heaps (non-shader-visible, for staging) rtv_heap: ^d3d12.IDescriptorHeap, dsv_heap: ^d3d12.IDescriptorHeap, staging_cbv_srv_uav_heap: ^d3d12.IDescriptorHeap, staging_sampler_heap: ^d3d12.IDescriptorHeap, // Descriptor heaps (shader-visible, for GPU binding) gpu_cbv_srv_uav_heap: ^d3d12.IDescriptorHeap, gpu_sampler_heap: ^d3d12.IDescriptorHeap, // Per-frame linear allocators for GPU heaps gpu_cbv_srv_uav_alloc: [bk.MAX_FRAMES_IN_FLIGHT]Descriptor_Allocator, gpu_sampler_alloc: [bk.MAX_FRAMES_IN_FLIGHT]Descriptor_Allocator, // Descriptor increment sizes rtv_inc: u32, dsv_inc: u32, cbv_srv_uav_inc: u32, sampler_inc: u32, // Staging heap free indices (simple linear counter, no free list) staging_cbv_srv_uav_next: u32, staging_sampler_next: u32, // Root signatures graphics_root_sig: ^d3d12.IRootSignature, compute_root_sig: ^d3d12.IRootSignature, draw_indirect_signature: ^d3d12.ICommandSignature, draw_indexed_indirect_signature: ^d3d12.ICommandSignature, // Dimensions width: u32, height: u32, // Default render pass default_render_pass: bk.Render_Pass_Handle, // Frame state frame_active: bool, render_pass_active: bool, clear_color: [4]f32, deferred_releases: [dynamic]D3D12_Deferred_Release, // Current pipeline (for topology at draw time) current_pipeline: bk.Pipeline_Handle, pending_depth_bias_constant: i32, pending_depth_bias_slope: f32, // Handle pools buffers: [MAX_BUFFERS]D3D12_Buffer_Entry, textures: [MAX_TEXTURES]D3D12_Texture_Entry, pipelines: [MAX_PIPELINES]D3D12_Pipeline_Entry, shaders: [MAX_SHADERS]D3D12_Shader_Entry, descriptors: [MAX_DESCRIPTORS]D3D12_Descriptor_Entry, render_passes: [MAX_RENDER_PASSES]D3D12_Render_Pass_Entry, framebuffers: [MAX_FRAMEBUFFERS]D3D12_Framebuffer_Entry, samplers: [MAX_SAMPLERS]D3D12_Sampler_Entry, } // Pool sizes MAX_BUFFERS :: 1024 MAX_TEXTURES :: 512 MAX_PIPELINES :: 128 MAX_SHADERS :: 256 MAX_DESCRIPTORS :: 512 MAX_RENDER_PASSES :: 32 MAX_FRAMEBUFFERS :: 64 MAX_SAMPLERS :: 64 MAX_DEPTH_BIAS_PSO_VARIANTS :: 16 // Push constant slot (must match D3D11 convention: b13) PUSH_CONSTANT_SLOT :: 13 PUSH_CONSTANT_MAX_SIZE :: 240 // 60 DWORDs; D3D12 root sig limit is 64 DWORDs total, 3 used by descriptor tables // Descriptor heap sizes STAGING_CBV_SRV_UAV_SIZE :: 4096 STAGING_SAMPLER_SIZE :: 256 GPU_CBV_SRV_UAV_SIZE :: 4096 GPU_SAMPLER_SIZE :: 256 GPU_CBV_SRV_UAV_PER_FRAME :: GPU_CBV_SRV_UAV_SIZE / bk.MAX_FRAMES_IN_FLIGHT GPU_SAMPLER_PER_FRAME :: GPU_SAMPLER_SIZE / bk.MAX_FRAMES_IN_FLIGHT D3D12_Deferred_Release_Kind :: enum { Resource, Pipeline_State, } D3D12_Deferred_Release :: struct { kind: D3D12_Deferred_Release_Kind, fence_value: u64, resource: ^d3d12.IResource, pso: ^d3d12.IPipelineState, } // --- Pool entry types --- D3D12_Buffer_Entry :: struct { resource: ^d3d12.IResource, gpu_address: d3d12.GPU_VIRTUAL_ADDRESS, mapped_ptr: rawptr, size: u64, usage: bk.Buffer_Usage_Flags, is_dynamic: bool, state: d3d12.RESOURCE_STATES, active: bool, } D3D12_Texture_Entry :: struct { resource: ^d3d12.IResource, srv_index: u32, // index in staging CBV/SRV/UAV heap rtv_index: u32, // index in RTV heap dsv_index: u32, // index in DSV heap has_srv: bool, has_rtv: bool, has_dsv: bool, deny_srv: bool, // resource created with DENY_SHADER_RESOURCE width: u32, height: u32, format: dxgi.FORMAT, usage: bk.Image_Usage_Flags, state: d3d12.RESOURCE_STATES, active: bool, } D3D12_Pipeline_Entry :: struct { pso: ^d3d12.IPipelineState, topology: d3d12.PRIMITIVE_TOPOLOGY, root_sig: ^d3d12.IRootSignature, depth_bias_constant: i32, depth_bias_slope: f32, no_draw: bool, push_constant_size: u32, push_constant_stages: bk.Shader_Stage_Flags, vert_bytecode: []byte, frag_bytecode: []byte, vertex_bindings: [16]bk.Vertex_Binding, vertex_binding_count: u32, vertex_attributes: [16]bk.Vertex_Attribute, vertex_attribute_count: u32, pipeline_topology: bk.Topology, cull_mode: bk.Cull_Mode, front_face: bk.Front_Face, enable_blending: bool, blend_mode: bk.Blend_Mode, enable_depth_test: bool, depth_format: bk.Format, stencil: bk.Stencil_State, depth_only: bool, color_attachment_count: u32, color_formats: [bk.MAX_COLOR_TARGETS]bk.Format, color_write_masks: [bk.MAX_COLOR_TARGETS]u8, depth_bias_variants: [MAX_DEPTH_BIAS_PSO_VARIANTS]D3D12_Depth_Bias_PSO, depth_bias_count: u32, is_compute: bool, active: bool, } D3D12_Depth_Bias_PSO :: struct { constant: i32, slope: f32, pso: ^d3d12.IPipelineState, active: bool, } D3D12_Shader_Entry :: struct { bytecode: []byte, // Raw DXBC bytecode (kept alive for PSO creation) stage: bk.Shader_Stage, active: bool, } D3D12_Descriptor_Binding :: struct { binding: u32, type: bk.Descriptor_Type, buffer: bk.Buffer_Handle, texture: bk.Texture_Handle, sampler: bk.Sampler_Handle, buf_size: u64, } D3D12_Descriptor_Entry :: struct { kind: Descriptor_Kind, layout_bindings: [16]bk.Descriptor_Set_Layout_Binding, layout_count: u32, bindings: [16]D3D12_Descriptor_Binding, binding_count: u32, active: bool, } Descriptor_Kind :: enum { Set_Layout, Pool, Set, } D3D12_Render_Pass_Entry :: struct { desc: bk.Render_Pass_Desc, active: bool, } D3D12_Framebuffer_Entry :: struct { color_tex: bk.Texture_Handle, color_texs: [bk.MAX_COLOR_TARGETS]bk.Texture_Handle, color_count: u32, depth_tex: bk.Texture_Handle, width: u32, height: u32, active: bool, } D3D12_Sampler_Entry :: struct { cpu_handle: d3d12.CPU_DESCRIPTOR_HANDLE, staging_index: u32, active: bool, } // --- Init --- init_d3d12_backend :: proc( surface: bk.Surface_Desc, width, height: u32, title: cstring, ) -> ( backend: bk.Backend, ok: bool, ) { state := new(D3D12_State) if state == nil { log.error("gpu/d3d12: failed to allocate D3D12 state") return {}, false } g_d3d = state if surface.kind != .Win32 || surface.win32.hwnd == nil { log.error("gpu/d3d12: failed to get Win32 window handle") free(state) g_d3d = nil return {}, false } hwnd := dxgi.HWND(surface.win32.hwnd) // Enable debug layer in debug builds when ODIN_DEBUG { debug: ^d3d12.IDebug if d3d12.GetDebugInterface(d3d12.IDebug_UUID, cast(^rawptr)&debug) >= 0 { debug->EnableDebugLayer() debug->Release() } } // Create D3D12 device result := d3d12.CreateDevice(nil, ._12_0, d3d12.IDevice_UUID, cast(^rawptr)&state.device) if result < 0 { log.errorf("gpu/d3d12: D3D12CreateDevice failed: 0x%08X", u32(result)) free(state) g_d3d = nil return {}, false } // Create command queue queue_desc := d3d12.COMMAND_QUEUE_DESC { Type = .DIRECT, } result = state.device->CreateCommandQueue( &queue_desc, d3d12.ICommandQueue_UUID, cast(^rawptr)&state.command_queue, ) if result < 0 { log.errorf("gpu/d3d12: CreateCommandQueue failed: 0x%08X", u32(result)) shutdown_d3d12() return {}, false } // Create DXGI factory dxgi_flags: dxgi.CREATE_FACTORY when ODIN_DEBUG { dxgi_flags = {.DEBUG} } dxgi_factory: ^dxgi.IFactory4 result = dxgi.CreateDXGIFactory2(dxgi_flags, dxgi.IFactory4_UUID, cast(^rawptr)&dxgi_factory) if result < 0 { log.errorf("gpu/d3d12: CreateDXGIFactory2 failed: 0x%08X", u32(result)) shutdown_d3d12() return {}, false } defer dxgi_factory->Release() // Create swap chain sc_desc := dxgi.SWAP_CHAIN_DESC1 { Width = width, Height = height, Format = .R8G8B8A8_UNORM, SampleDesc = {Count = 1, Quality = 0}, BufferUsage = {.RENDER_TARGET_OUTPUT}, BufferCount = 2, SwapEffect = .FLIP_DISCARD, Scaling = .STRETCH, } swap_chain1: ^dxgi.ISwapChain1 result = dxgi_factory->CreateSwapChainForHwnd( state.command_queue, hwnd, &sc_desc, nil, nil, &swap_chain1, ) if result < 0 { log.errorf("gpu/d3d12: CreateSwapChainForHwnd failed: 0x%08X", u32(result)) shutdown_d3d12() return {}, false } // Query ISwapChain3 for GetCurrentBackBufferIndex result = swap_chain1->QueryInterface(dxgi.ISwapChain3_UUID, cast(^rawptr)&state.swap_chain) swap_chain1->Release() if result < 0 { log.errorf("gpu/d3d12: QueryInterface ISwapChain3 failed: 0x%08X", u32(result)) shutdown_d3d12() return {}, false } state.width = width state.height = height // Cache descriptor increment sizes state.rtv_inc = state.device->GetDescriptorHandleIncrementSize(.RTV) state.dsv_inc = state.device->GetDescriptorHandleIncrementSize(.DSV) state.cbv_srv_uav_inc = state.device->GetDescriptorHandleIncrementSize(.CBV_SRV_UAV) state.sampler_inc = state.device->GetDescriptorHandleIncrementSize(.SAMPLER) // Create descriptor heaps if !create_descriptor_heaps(state) { shutdown_d3d12() return {}, false } // Create backbuffer RTVs if !create_backbuffer_rtvs(state) { shutdown_d3d12() return {}, false } // Create depth buffer state.depth_format = .D32_FLOAT if !create_depth_buffer(state, width, height) { shutdown_d3d12() return {}, false } // Create per-frame command allocators for i in 0 ..< bk.MAX_FRAMES_IN_FLIGHT { result = state.device->CreateCommandAllocator( .DIRECT, d3d12.ICommandAllocator_UUID, cast(^rawptr)&state.frame_resources[i].command_allocator, ) if result < 0 { log.errorf("gpu/d3d12: CreateCommandAllocator failed: 0x%08X", u32(result)) shutdown_d3d12() return {}, false } } // Create command list (initially closed) result = state.device->CreateCommandList( 0, .DIRECT, state.frame_resources[0].command_allocator, nil, d3d12.IGraphicsCommandList_UUID, cast(^rawptr)&state.command_list, ) if result < 0 { log.errorf("gpu/d3d12: CreateCommandList failed: 0x%08X", u32(result)) shutdown_d3d12() return {}, false } state.command_list->Close() // Create fence result = state.device->CreateFence(0, {}, d3d12.IFence_UUID, cast(^rawptr)&state.fence) if result < 0 { log.errorf("gpu/d3d12: CreateFence failed: 0x%08X", u32(result)) shutdown_d3d12() return {}, false } state.fence_value = 1 when ODIN_OS == .Windows { state.fence_event = win32.CreateEventW(nil, false, false, nil) } // Create root signatures if !create_root_signatures(state) { shutdown_d3d12() return {}, false } // Register default render pass rp_handle, rp_ok := alloc_render_pass_handle() if !rp_ok { log.error("gpu/d3d12: failed to allocate default render pass handle") shutdown_d3d12() return {}, false } state.render_passes[rp_handle].desc = bk.Render_Pass_Desc { has_color = true, has_depth = true, color_format = .R8G8B8A8_UNORM, depth_format = .D32_SFLOAT, } state.render_passes[rp_handle].active = true state.default_render_pass = rp_handle // Initialize per-frame GPU descriptor allocators for i in 0 ..< bk.MAX_FRAMES_IN_FLIGHT { state.gpu_cbv_srv_uav_alloc[i] = Descriptor_Allocator { offset = u32(i) * GPU_CBV_SRV_UAV_PER_FRAME, capacity = GPU_CBV_SRV_UAV_PER_FRAME, } state.gpu_sampler_alloc[i] = Descriptor_Allocator { offset = u32(i) * GPU_SAMPLER_PER_FRAME, capacity = GPU_SAMPLER_PER_FRAME, } } // Populate backend vtable backend = bk.Backend { capabilities = bk.implemented_base_capabilities( bk.MAX_COLOR_TARGETS, PUSH_CONSTANT_MAX_SIZE, ), shutdown = shutdown_d3d12, wait_idle = wait_idle_d3d12, begin_frame = begin_frame_d3d12, end_frame = end_frame_d3d12, on_resize = on_resize_d3d12, get_extent = get_extent_d3d12, current_frame_index = get_current_frame_d3d12, begin_render_pass = begin_render_pass_d3d12, begin_default_pass = begin_default_pass_d3d12, end_render_pass = end_render_pass_d3d12, set_viewport = set_viewport_d3d12, set_scissor = set_scissor_d3d12, set_depth_bias = set_depth_bias_d3d12, create_graphics_pipeline = create_graphics_pipeline_d3d12, destroy_graphics_pipeline = destroy_graphics_pipeline_d3d12, bind_graphics_pipeline = bind_graphics_pipeline_d3d12, push_constants = push_constants_d3d12, create_buffer = create_buffer_d3d12, create_buffer_staged = create_buffer_staged_d3d12, destroy_buffer = destroy_buffer_d3d12, map_buffer = map_buffer_d3d12, unmap_buffer = unmap_buffer_d3d12, get_buffer_mapped = get_buffer_mapped_d3d12, bind_vertex_buffer = bind_vertex_buffer_d3d12, bind_vertex_buffer_slot = bind_vertex_buffer_slot_d3d12, bind_index_buffer = bind_index_buffer_d3d12, create_texture = create_texture_d3d12, destroy_texture = destroy_texture_d3d12, read_texture_rgba8 = read_texture_rgba8_d3d12, create_sampler = create_sampler_d3d12, destroy_sampler = destroy_sampler_d3d12, create_image = create_image_d3d12, create_image_view = create_image_view_d3d12, destroy_image = destroy_image_d3d12, create_descriptor_set_layout = create_descriptor_set_layout_d3d12, destroy_descriptor_set_layout = destroy_descriptor_set_layout_d3d12, create_descriptor_pool = create_descriptor_pool_d3d12, destroy_descriptor_pool = destroy_descriptor_pool_d3d12, allocate_descriptor_set = allocate_descriptor_set_d3d12, bind_descriptor_set = bind_descriptor_set_d3d12, update_descriptor_image = update_descriptor_image_d3d12, update_descriptor_buffer = update_descriptor_buffer_d3d12, create_render_pass = create_render_pass_d3d12, destroy_render_pass = destroy_render_pass_d3d12, create_framebuffer = create_framebuffer_d3d12, destroy_framebuffer = destroy_framebuffer_d3d12, create_shader_module = create_shader_module_d3d12, destroy_shader = destroy_shader_d3d12, draw = draw_d3d12, draw_indexed = draw_indexed_d3d12, draw_indirect = draw_indirect_d3d12, draw_indexed_indirect = draw_indexed_indirect_d3d12, create_compute_pipeline = create_compute_pipeline_d3d12, destroy_compute_pipeline = destroy_compute_pipeline_d3d12, bind_compute_pipeline = bind_compute_pipeline_d3d12, dispatch_compute = dispatch_compute_d3d12, compute_barrier = compute_barrier_d3d12, get_default_render_pass = get_default_render_pass_d3d12, get_depth_format = get_depth_format_d3d12, } bk.backend_initialized = true return backend, true } get_current_frame_d3d12 :: proc() -> u32 { return g_d3d.current_frame } // --- Handle allocation helpers --- @(private) alloc_buffer_handle :: proc() -> (bk.Buffer_Handle, bool) { for i in 1 ..< u64(MAX_BUFFERS) { if !g_d3d.buffers[i].active { return bk.Buffer_Handle(i), true } } return bk.NULL_BUFFER, false } @(private) alloc_texture_handle :: proc() -> (bk.Texture_Handle, bool) { for i in 1 ..< u64(MAX_TEXTURES) { if !g_d3d.textures[i].active { return bk.Texture_Handle(i), true } } return bk.NULL_TEXTURE, false } @(private) alloc_pipeline_handle :: proc() -> (bk.Pipeline_Handle, bool) { for i in 1 ..< u64(MAX_PIPELINES) { if !g_d3d.pipelines[i].active { return bk.Pipeline_Handle(i), true } } return bk.NULL_PIPELINE, false } @(private) alloc_shader_handle :: proc() -> (bk.Shader_Handle, bool) { for i in 1 ..< u64(MAX_SHADERS) { if !g_d3d.shaders[i].active { return bk.Shader_Handle(i), true } } return bk.NULL_SHADER, false } @(private) alloc_descriptor_handle :: proc() -> (bk.Descriptor_Handle, bool) { for i in 1 ..< u64(MAX_DESCRIPTORS) { if !g_d3d.descriptors[i].active { return bk.Descriptor_Handle(i), true } } return bk.NULL_DESCRIPTOR, false } @(private) alloc_render_pass_handle :: proc() -> (bk.Render_Pass_Handle, bool) { for i in 1 ..< u64(MAX_RENDER_PASSES) { if !g_d3d.render_passes[i].active { return bk.Render_Pass_Handle(i), true } } return bk.NULL_RENDER_PASS, false } @(private) alloc_framebuffer_handle :: proc() -> (bk.Framebuffer_Handle, bool) { for i in 1 ..< u64(MAX_FRAMEBUFFERS) { if !g_d3d.framebuffers[i].active { return bk.Framebuffer_Handle(i), true } } return bk.NULL_FRAMEBUFFER, false } @(private) alloc_sampler_handle :: proc() -> (bk.Sampler_Handle, bool) { for i in 1 ..< u64(MAX_SAMPLERS) { if !g_d3d.samplers[i].active { return bk.Sampler_Handle(i), true } } return bk.NULL_SAMPLER, false } // --- Internal helpers --- @(private) create_descriptor_heaps :: proc(state: ^D3D12_State) -> bool { result: d3d12.HRESULT // RTV heap (non-shader-visible) rtv_desc := d3d12.DESCRIPTOR_HEAP_DESC { Type = .RTV, NumDescriptors = bk.MAX_FRAMES_IN_FLIGHT + MAX_TEXTURES, // backbuffers + render targets } result = state.device->CreateDescriptorHeap( &rtv_desc, d3d12.IDescriptorHeap_UUID, cast(^rawptr)&state.rtv_heap, ) if result < 0 { log.error("gpu/d3d12: failed to create RTV heap") return false } // DSV heap (non-shader-visible) dsv_desc := d3d12.DESCRIPTOR_HEAP_DESC { Type = .DSV, NumDescriptors = 1 + MAX_TEXTURES, // default depth + depth targets } result = state.device->CreateDescriptorHeap( &dsv_desc, d3d12.IDescriptorHeap_UUID, cast(^rawptr)&state.dsv_heap, ) if result < 0 { log.error("gpu/d3d12: failed to create DSV heap") return false } // Staging CBV/SRV/UAV heap (non-shader-visible) staging_csv_desc := d3d12.DESCRIPTOR_HEAP_DESC { Type = .CBV_SRV_UAV, NumDescriptors = STAGING_CBV_SRV_UAV_SIZE, } result = state.device->CreateDescriptorHeap( &staging_csv_desc, d3d12.IDescriptorHeap_UUID, cast(^rawptr)&state.staging_cbv_srv_uav_heap, ) if result < 0 { log.error("gpu/d3d12: failed to create staging CBV/SRV/UAV heap") return false } // Staging sampler heap (non-shader-visible) staging_sampler_desc := d3d12.DESCRIPTOR_HEAP_DESC { Type = .SAMPLER, NumDescriptors = STAGING_SAMPLER_SIZE, } result = state.device->CreateDescriptorHeap( &staging_sampler_desc, d3d12.IDescriptorHeap_UUID, cast(^rawptr)&state.staging_sampler_heap, ) if result < 0 { log.error("gpu/d3d12: failed to create staging sampler heap") return false } // GPU CBV/SRV/UAV heap (shader-visible) gpu_csv_desc := d3d12.DESCRIPTOR_HEAP_DESC { Type = .CBV_SRV_UAV, NumDescriptors = GPU_CBV_SRV_UAV_SIZE, Flags = {.SHADER_VISIBLE}, } result = state.device->CreateDescriptorHeap( &gpu_csv_desc, d3d12.IDescriptorHeap_UUID, cast(^rawptr)&state.gpu_cbv_srv_uav_heap, ) if result < 0 { log.error("gpu/d3d12: failed to create GPU CBV/SRV/UAV heap") return false } // GPU sampler heap (shader-visible) gpu_sampler_desc := d3d12.DESCRIPTOR_HEAP_DESC { Type = .SAMPLER, NumDescriptors = GPU_SAMPLER_SIZE, Flags = {.SHADER_VISIBLE}, } result = state.device->CreateDescriptorHeap( &gpu_sampler_desc, d3d12.IDescriptorHeap_UUID, cast(^rawptr)&state.gpu_sampler_heap, ) if result < 0 { log.error("gpu/d3d12: failed to create GPU sampler heap") return false } return true } @(private) create_backbuffer_rtvs :: proc(state: ^D3D12_State) -> bool { rtv_start: d3d12.CPU_DESCRIPTOR_HANDLE state.rtv_heap->GetCPUDescriptorHandleForHeapStart(&rtv_start) for i in 0 ..< u32(bk.MAX_FRAMES_IN_FLIGHT) { result := state.swap_chain->GetBuffer( i, d3d12.IResource_UUID, cast(^rawptr)&state.backbuffers[i], ) if result < 0 { log.errorf("gpu/d3d12: GetBuffer(%d) failed: 0x%08X", i, u32(result)) return false } rtv_handle := d3d12.CPU_DESCRIPTOR_HANDLE { ptr = rtv_start.ptr + uint(i * state.rtv_inc), } // Create RTV with SRGB format for correct gamma (matching D3D11 behavior) rtv_desc := d3d12.RENDER_TARGET_VIEW_DESC { Format = .R8G8B8A8_UNORM_SRGB, ViewDimension = .TEXTURE2D, } state.device->CreateRenderTargetView(state.backbuffers[i], &rtv_desc, rtv_handle) } return true } @(private) create_depth_buffer :: proc(state: ^D3D12_State, width, height: u32) -> bool { heap_props := d3d12.HEAP_PROPERTIES { Type = .DEFAULT, } depth_desc := d3d12.RESOURCE_DESC { Dimension = .TEXTURE2D, Width = u64(width), Height = height, DepthOrArraySize = 1, MipLevels = 1, Format = state.depth_format, SampleDesc = {Count = 1, Quality = 0}, Flags = {.ALLOW_DEPTH_STENCIL}, } clear_value := d3d12.CLEAR_VALUE { Format = state.depth_format, } clear_value.DepthStencil = d3d12.DEPTH_STENCIL_VALUE { Depth = 1.0, Stencil = 0, } result := state.device->CreateCommittedResource( &heap_props, {}, &depth_desc, {.DEPTH_WRITE}, &clear_value, d3d12.IResource_UUID, cast(^rawptr)&state.depth_buffer, ) if result < 0 { log.errorf("gpu/d3d12: CreateCommittedResource (depth) failed: 0x%08X", u32(result)) return false } // Create DSV dsv_start: d3d12.CPU_DESCRIPTOR_HANDLE state.dsv_heap->GetCPUDescriptorHandleForHeapStart(&dsv_start) dsv_desc := d3d12.DEPTH_STENCIL_VIEW_DESC { Format = state.depth_format, ViewDimension = .TEXTURE2D, } state.device->CreateDepthStencilView(state.depth_buffer, &dsv_desc, dsv_start) return true } @(private) create_root_signatures :: proc(state: ^D3D12_State) -> bool { // Root parameter [0]: 64 DWORDs of push constants at b13 // Root parameter [1]: SRV table t0..t15 // Root parameter [2]: Sampler table s0..s15 // Root parameter [3]: CBV table b0..b12 srv_range := d3d12.DESCRIPTOR_RANGE { RangeType = .SRV, NumDescriptors = 16, BaseShaderRegister = 0, } sampler_range := d3d12.DESCRIPTOR_RANGE { RangeType = .SAMPLER, NumDescriptors = 16, BaseShaderRegister = 0, } cbv_range := d3d12.DESCRIPTOR_RANGE { RangeType = .CBV, NumDescriptors = 13, BaseShaderRegister = 0, } root_params: [4]d3d12.ROOT_PARAMETER // [0] Push constants root_params[0].ParameterType = ._32BIT_CONSTANTS root_params[0].Constants = d3d12.ROOT_CONSTANTS { ShaderRegister = PUSH_CONSTANT_SLOT, RegisterSpace = 0, Num32BitValues = PUSH_CONSTANT_MAX_SIZE / 4, } root_params[0].ShaderVisibility = .ALL // [1] SRV table root_params[1].ParameterType = .DESCRIPTOR_TABLE root_params[1].DescriptorTable = d3d12.ROOT_DESCRIPTOR_TABLE { NumDescriptorRanges = 1, pDescriptorRanges = &srv_range, } root_params[1].ShaderVisibility = .ALL // [2] Sampler table root_params[2].ParameterType = .DESCRIPTOR_TABLE root_params[2].DescriptorTable = d3d12.ROOT_DESCRIPTOR_TABLE { NumDescriptorRanges = 1, pDescriptorRanges = &sampler_range, } root_params[2].ShaderVisibility = .ALL // [3] CBV table root_params[3].ParameterType = .DESCRIPTOR_TABLE root_params[3].DescriptorTable = d3d12.ROOT_DESCRIPTOR_TABLE { NumDescriptorRanges = 1, pDescriptorRanges = &cbv_range, } root_params[3].ShaderVisibility = .ALL root_sig_desc := d3d12.ROOT_SIGNATURE_DESC { NumParameters = 4, pParameters = &root_params[0], Flags = {.ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT}, } sig_blob: ^d3d12.IBlob err_blob: ^d3d12.IBlob result := d3d12.SerializeRootSignature(&root_sig_desc, ._1_0, &sig_blob, &err_blob) if result < 0 { if err_blob != nil { log.errorf( "gpu/d3d12: SerializeRootSignature failed: %s", cstring(err_blob->GetBufferPointer()), ) err_blob->Release() } return false } defer sig_blob->Release() if err_blob != nil {err_blob->Release()} blob_ptr := sig_blob->GetBufferPointer() blob_size := sig_blob->GetBufferSize() result = state.device->CreateRootSignature( 0, blob_ptr, blob_size, d3d12.IRootSignature_UUID, cast(^rawptr)&state.graphics_root_sig, ) if result < 0 { log.errorf("gpu/d3d12: CreateRootSignature failed: 0x%08X", u32(result)) when ODIN_DEBUG {dump_debug_messages()} return false } // Use same root sig for compute state.compute_root_sig = state.graphics_root_sig state.graphics_root_sig->AddRef() return true } @(private) release_backbuffers_and_depth :: proc(state: ^D3D12_State) { for i in 0 ..< bk.MAX_FRAMES_IN_FLIGHT { if state.backbuffers[i] != nil { state.backbuffers[i]->Release() state.backbuffers[i] = nil } } if state.depth_buffer != nil { state.depth_buffer->Release() state.depth_buffer = nil } } // --- Descriptor heap helpers --- @(private) get_rtv_cpu_handle :: proc(index: u32) -> d3d12.CPU_DESCRIPTOR_HANDLE { start: d3d12.CPU_DESCRIPTOR_HANDLE g_d3d.rtv_heap->GetCPUDescriptorHandleForHeapStart(&start) return d3d12.CPU_DESCRIPTOR_HANDLE{ptr = start.ptr + uint(index * g_d3d.rtv_inc)} } @(private) get_dsv_cpu_handle :: proc(index: u32) -> d3d12.CPU_DESCRIPTOR_HANDLE { start: d3d12.CPU_DESCRIPTOR_HANDLE g_d3d.dsv_heap->GetCPUDescriptorHandleForHeapStart(&start) return d3d12.CPU_DESCRIPTOR_HANDLE{ptr = start.ptr + uint(index * g_d3d.dsv_inc)} } @(private) get_staging_cbv_srv_uav_cpu_handle :: proc(index: u32) -> d3d12.CPU_DESCRIPTOR_HANDLE { start: d3d12.CPU_DESCRIPTOR_HANDLE g_d3d.staging_cbv_srv_uav_heap->GetCPUDescriptorHandleForHeapStart(&start) return d3d12.CPU_DESCRIPTOR_HANDLE{ptr = start.ptr + uint(index * g_d3d.cbv_srv_uav_inc)} } @(private) get_staging_sampler_cpu_handle :: proc(index: u32) -> d3d12.CPU_DESCRIPTOR_HANDLE { start: d3d12.CPU_DESCRIPTOR_HANDLE g_d3d.staging_sampler_heap->GetCPUDescriptorHandleForHeapStart(&start) return d3d12.CPU_DESCRIPTOR_HANDLE{ptr = start.ptr + uint(index * g_d3d.sampler_inc)} } @(private) alloc_staging_cbv_srv_uav :: proc() -> (u32, bool) { if g_d3d.staging_cbv_srv_uav_next >= STAGING_CBV_SRV_UAV_SIZE { return 0, false } idx := g_d3d.staging_cbv_srv_uav_next g_d3d.staging_cbv_srv_uav_next += 1 return idx, true } @(private) alloc_staging_sampler :: proc() -> (u32, bool) { if g_d3d.staging_sampler_next >= STAGING_SAMPLER_SIZE { return 0, false } idx := g_d3d.staging_sampler_next g_d3d.staging_sampler_next += 1 return idx, true } // Allocate from GPU-visible CBV/SRV/UAV heap for current frame @(private) alloc_gpu_cbv_srv_uav :: proc( count: u32, ) -> ( d3d12.CPU_DESCRIPTOR_HANDLE, d3d12.GPU_DESCRIPTOR_HANDLE, bool, ) { alloc := &g_d3d.gpu_cbv_srv_uav_alloc[g_d3d.current_frame] base := u32(g_d3d.current_frame) * GPU_CBV_SRV_UAV_PER_FRAME local_offset := alloc.offset - base if local_offset + count > alloc.capacity { return {}, {}, false } cpu_start: d3d12.CPU_DESCRIPTOR_HANDLE gpu_start: d3d12.GPU_DESCRIPTOR_HANDLE g_d3d.gpu_cbv_srv_uav_heap->GetCPUDescriptorHandleForHeapStart(&cpu_start) g_d3d.gpu_cbv_srv_uav_heap->GetGPUDescriptorHandleForHeapStart(&gpu_start) cpu := d3d12.CPU_DESCRIPTOR_HANDLE { ptr = cpu_start.ptr + uint(alloc.offset * g_d3d.cbv_srv_uav_inc), } gpu := d3d12.GPU_DESCRIPTOR_HANDLE { ptr = gpu_start.ptr + u64(alloc.offset * g_d3d.cbv_srv_uav_inc), } alloc.offset += count return cpu, gpu, true } // Allocate from GPU-visible sampler heap for current frame @(private) alloc_gpu_sampler :: proc( count: u32, ) -> ( d3d12.CPU_DESCRIPTOR_HANDLE, d3d12.GPU_DESCRIPTOR_HANDLE, bool, ) { alloc := &g_d3d.gpu_sampler_alloc[g_d3d.current_frame] base := u32(g_d3d.current_frame) * GPU_SAMPLER_PER_FRAME local_offset := alloc.offset - base if local_offset + count > alloc.capacity { return {}, {}, false } cpu_start: d3d12.CPU_DESCRIPTOR_HANDLE gpu_start: d3d12.GPU_DESCRIPTOR_HANDLE g_d3d.gpu_sampler_heap->GetCPUDescriptorHandleForHeapStart(&cpu_start) g_d3d.gpu_sampler_heap->GetGPUDescriptorHandleForHeapStart(&gpu_start) cpu := d3d12.CPU_DESCRIPTOR_HANDLE { ptr = cpu_start.ptr + uint(alloc.offset * g_d3d.sampler_inc), } gpu := d3d12.GPU_DESCRIPTOR_HANDLE { ptr = gpu_start.ptr + u64(alloc.offset * g_d3d.sampler_inc), } alloc.offset += count return cpu, gpu, true } // --- Transition helpers --- @(private) transition_resource_on_command_list_d3d12 :: proc( list: ^d3d12.IGraphicsCommandList, resource: ^d3d12.IResource, before, after: d3d12.RESOURCE_STATES, ) { if before == after {return} if list == nil || resource == nil {return} barrier := d3d12.RESOURCE_BARRIER { Type = .TRANSITION, } barrier.Transition = d3d12.RESOURCE_TRANSITION_BARRIER { pResource = resource, Subresource = d3d12.RESOURCE_BARRIER_ALL_SUBRESOURCES, StateBefore = before, StateAfter = after, } list->ResourceBarrier(1, &barrier) } @(private) transition_resource :: proc(resource: ^d3d12.IResource, before, after: d3d12.RESOURCE_STATES) { if g_d3d == nil {return} transition_resource_on_command_list_d3d12(g_d3d.command_list, resource, before, after) } @(private) transition_texture_to_render_target_d3d12 :: proc(entry: ^D3D12_Texture_Entry) { if entry == nil {return} target_state: d3d12.RESOURCE_STATES = {.RENDER_TARGET} if entry.state == target_state {return} transition_resource(entry.resource, entry.state, target_state) entry.state = target_state } @(private) transition_texture_to_depth_write_d3d12 :: proc(entry: ^D3D12_Texture_Entry) { if entry == nil {return} target_state: d3d12.RESOURCE_STATES = {.DEPTH_WRITE} if entry.state == target_state {return} transition_resource(entry.resource, entry.state, target_state) entry.state = target_state } @(private) transition_texture_to_shader_resource_d3d12 :: proc(entry: ^D3D12_Texture_Entry) { if entry == nil {return} target_state := d3d12.RESOURCE_STATE_ALL_SHADER_RESOURCE if entry.state == target_state {return} transition_resource(entry.resource, entry.state, target_state) entry.state = target_state } @(private) transition_buffer_to_indirect_argument_d3d12 :: proc(entry: ^D3D12_Buffer_Entry) { if entry == nil {return} target_state: d3d12.RESOURCE_STATES = {.INDIRECT_ARGUMENT} if entry.state == target_state {return} transition_resource(entry.resource, entry.state, target_state) entry.state = target_state } @(private) transition_backbuffer_to_render_target_d3d12 :: proc(resource: ^d3d12.IResource) { transition_resource(resource, d3d12.RESOURCE_STATE_PRESENT, {.RENDER_TARGET}) } @(private) transition_backbuffer_to_present_d3d12 :: proc(resource: ^d3d12.IResource) { transition_resource(resource, {.RENDER_TARGET}, d3d12.RESOURCE_STATE_PRESENT) } @(private) transition_copy_dest_to_generic_read_d3d12 :: proc(resource: ^d3d12.IResource) -> d3d12.RESOURCE_STATES { target_state := d3d12.RESOURCE_STATE_GENERIC_READ transition_resource(resource, {.COPY_DEST}, target_state) return target_state } @(private) transition_copy_dest_to_shader_resource_d3d12 :: proc(resource: ^d3d12.IResource) -> d3d12.RESOURCE_STATES { target_state := d3d12.RESOURCE_STATE_ALL_SHADER_RESOURCE transition_resource(resource, {.COPY_DEST}, target_state) return target_state } @(private) transition_copy_dest_to_generic_read_on_list_d3d12 :: proc( list: ^d3d12.IGraphicsCommandList, resource: ^d3d12.IResource, ) -> d3d12.RESOURCE_STATES { target_state := d3d12.RESOURCE_STATE_GENERIC_READ transition_resource_on_command_list_d3d12(list, resource, {.COPY_DEST}, target_state) return target_state } @(private) transition_copy_dest_to_shader_resource_on_list_d3d12 :: proc( list: ^d3d12.IGraphicsCommandList, resource: ^d3d12.IResource, ) -> d3d12.RESOURCE_STATES { target_state := d3d12.RESOURCE_STATE_ALL_SHADER_RESOURCE transition_resource_on_command_list_d3d12(list, resource, {.COPY_DEST}, target_state) return target_state } // --- GPU idle helper --- @(private) wait_gpu_idle :: proc() { if g_d3d == nil || g_d3d.command_queue == nil || g_d3d.fence == nil {return} val := g_d3d.fence_value g_d3d.command_queue->Signal(g_d3d.fence, val) g_d3d.fence_value += 1 if g_d3d.fence->GetCompletedValue() < val { g_d3d.fence->SetEventOnCompletion(val, g_d3d.fence_event) when ODIN_OS == .Windows { win32.WaitForSingleObject(g_d3d.fence_event, win32.INFINITE) } } } // --- Debug helper --- @(private) dump_debug_messages :: proc() { if g_d3d == nil || g_d3d.device == nil {return} info_queue: ^d3d12.IInfoQueue iq_result := g_d3d.device->QueryInterface(d3d12.IInfoQueue_UUID, cast(^rawptr)&info_queue) if iq_result < 0 || info_queue == nil {return} defer info_queue->Release() num_msgs := info_queue->GetNumStoredMessages() if num_msgs == 0 {return} log.errorf("gpu/d3d12: %d debug messages queued:", num_msgs) for i in 0 ..< num_msgs { msg_size: d3d12.SIZE_T info_queue->GetMessageA(i, nil, &msg_size) if msg_size > 0 { buf := make([]byte, msg_size) defer delete(buf) msg := cast(^d3d12.MESSAGE)&buf[0] info_queue->GetMessageA(i, msg, &msg_size) if msg.pDescription != nil { log.errorf(" [%d] %s", i, msg.pDescription) } } } info_queue->ClearStoredMessages() }