package d3d12_backend import "core:log" import "core:mem" import "core:strings" import d3d12 "vendor:directx/d3d12" import d3dc "vendor:directx/d3d_compiler" import dxgi "vendor:directx/dxgi" import bk ".." active_pool_entry_d3d12 :: proc(pool: ^[$N]$E, handle: $T) -> (^E, bool) { idx, ok := bk.handle_index(handle, N) if !ok do return nil, false entry := &pool[idx] if !entry.active do return nil, false return entry, true } buffer_entry :: proc(handle: bk.Buffer_Handle) -> (^D3D12_Buffer_Entry, bool) { if g_d3d == nil do return nil, false return active_pool_entry_d3d12(&g_d3d.buffers, handle) } texture_entry :: proc(handle: bk.Texture_Handle) -> (^D3D12_Texture_Entry, bool) { if g_d3d == nil do return nil, false return active_pool_entry_d3d12(&g_d3d.textures, handle) } shader_entry :: proc(handle: bk.Shader_Handle) -> (^D3D12_Shader_Entry, bool) { if g_d3d == nil do return nil, false return active_pool_entry_d3d12(&g_d3d.shaders, handle) } pipeline_entry :: proc(handle: bk.Pipeline_Handle) -> (^D3D12_Pipeline_Entry, bool) { if g_d3d == nil do return nil, false return active_pool_entry_d3d12(&g_d3d.pipelines, handle) } descriptor_entry :: proc(handle: bk.Descriptor_Handle) -> (^D3D12_Descriptor_Entry, bool) { if g_d3d == nil do return nil, false return active_pool_entry_d3d12(&g_d3d.descriptors, handle) } descriptor_entry_of_kind :: proc( handle: bk.Descriptor_Handle, kind: Descriptor_Kind, ) -> ( ^D3D12_Descriptor_Entry, bool, ) { entry, ok := descriptor_entry(handle) if !ok || entry.kind != kind do return nil, false return entry, true } render_pass_entry :: proc(handle: bk.Render_Pass_Handle) -> (^D3D12_Render_Pass_Entry, bool) { if g_d3d == nil do return nil, false return active_pool_entry_d3d12(&g_d3d.render_passes, handle) } framebuffer_entry :: proc(handle: bk.Framebuffer_Handle) -> (^D3D12_Framebuffer_Entry, bool) { if g_d3d == nil do return nil, false return active_pool_entry_d3d12(&g_d3d.framebuffers, handle) } sampler_entry :: proc(handle: bk.Sampler_Handle) -> (^D3D12_Sampler_Entry, bool) { if g_d3d == nil do return nil, false return active_pool_entry_d3d12(&g_d3d.samplers, handle) } completed_fence_value_d3d12 :: proc() -> u64 { if g_d3d == nil || g_d3d.fence == nil do return 0 return g_d3d.fence->GetCompletedValue() } deferred_release_ready_d3d12 :: proc(item: D3D12_Deferred_Release, completed_fence: u64) -> bool { return item.fence_value == 0 || completed_fence >= item.fence_value } retire_fence_value_d3d12 :: proc() -> u64 { if g_d3d == nil || g_d3d.fence == nil do return 0 if g_d3d.frame_active do return g_d3d.fence_value if g_d3d.fence_value > 1 do return g_d3d.fence_value - 1 return 0 } release_deferred_item_d3d12 :: proc(item: D3D12_Deferred_Release) { switch item.kind { case .Resource: if item.resource != nil do item.resource->Release() case .Pipeline_State: if item.pso != nil do item.pso->Release() } } drain_deferred_releases_d3d12 :: proc(completed_fence: u64) { if g_d3d == nil || len(g_d3d.deferred_releases) == 0 do return write_idx := 0 for item in g_d3d.deferred_releases { if deferred_release_ready_d3d12(item, completed_fence) { release_deferred_item_d3d12(item) } else { g_d3d.deferred_releases[write_idx] = item write_idx += 1 } } resize(&g_d3d.deferred_releases, write_idx) } drain_deferred_releases_now_d3d12 :: proc() { if g_d3d == nil do return drain_deferred_releases_d3d12(completed_fence_value_d3d12()) } defer_release_resource_d3d12 :: proc(resource: ^d3d12.IResource) { if resource == nil do return if g_d3d == nil || g_d3d.fence == nil { resource->Release() return } fence_value := retire_fence_value_d3d12() if deferred_release_ready_d3d12( {kind = .Resource, fence_value = fence_value}, completed_fence_value_d3d12(), ) { resource->Release() return } append( &g_d3d.deferred_releases, D3D12_Deferred_Release{kind = .Resource, fence_value = fence_value, resource = resource}, ) } defer_release_pso_d3d12 :: proc(pso: ^d3d12.IPipelineState) { if pso == nil do return if g_d3d == nil || g_d3d.fence == nil { pso->Release() return } fence_value := retire_fence_value_d3d12() if deferred_release_ready_d3d12( {kind = .Pipeline_State, fence_value = fence_value}, completed_fence_value_d3d12(), ) { pso->Release() return } append( &g_d3d.deferred_releases, D3D12_Deferred_Release{kind = .Pipeline_State, fence_value = fence_value, pso = pso}, ) } transition_texture_for_srv_d3d12 :: proc(entry: ^D3D12_Texture_Entry) { if g_d3d == nil || entry == nil || entry.resource == nil do return transition_texture_to_shader_resource_d3d12(entry) } // ============================================================================ // Buffer operations // ============================================================================ create_buffer_d3d12 :: proc(desc: bk.Buffer_Desc) -> (bk.Buffer_Handle, bool) { handle, ok := alloc_buffer_handle() if !ok {return bk.NULL_BUFFER, false} is_dynamic := .Host_Visible in desc.memory heap_props := d3d12.HEAP_PROPERTIES { Type = .UPLOAD if is_dynamic else .DEFAULT, } res_desc := d3d12.RESOURCE_DESC { Dimension = .BUFFER, Width = desc.size, Height = 1, DepthOrArraySize = 1, MipLevels = 1, SampleDesc = {Count = 1}, Layout = .ROW_MAJOR, } initial_state: d3d12.RESOURCE_STATES = d3d12.RESOURCE_STATE_GENERIC_READ if is_dynamic else d3d12.RESOURCE_STATE_COMMON resource: ^d3d12.IResource result := g_d3d.device->CreateCommittedResource( &heap_props, {}, &res_desc, initial_state, nil, d3d12.IResource_UUID, cast(^rawptr)&resource, ) if result < 0 { log.errorf("gpu/d3d12: CreateCommittedResource (buffer) failed: 0x%08X", u32(result)) return bk.NULL_BUFFER, false } entry := &g_d3d.buffers[handle] entry.resource = resource entry.gpu_address = resource->GetGPUVirtualAddress() entry.size = desc.size entry.usage = desc.usage entry.is_dynamic = is_dynamic entry.state = initial_state entry.active = true // Persistently map upload buffers if is_dynamic { mapped: rawptr read_range := d3d12.RANGE{} // No CPU reads result = resource->Map(0, &read_range, &mapped) if result >= 0 { entry.mapped_ptr = mapped } else { log.errorf("gpu/d3d12: persistent buffer Map failed: 0x%08X", u32(result)) } } return handle, true } create_buffer_staged_d3d12 :: proc( data: rawptr, size: int, usage: bk.Buffer_Usage_Flags, ) -> ( bk.Buffer_Handle, bool, ) { handle, ok := alloc_buffer_handle() if !ok {return bk.NULL_BUFFER, false} // Create default heap buffer heap_props := d3d12.HEAP_PROPERTIES { Type = .DEFAULT, } res_desc := d3d12.RESOURCE_DESC { Dimension = .BUFFER, Width = u64(size), Height = 1, DepthOrArraySize = 1, MipLevels = 1, SampleDesc = {Count = 1}, Layout = .ROW_MAJOR, } resource: ^d3d12.IResource result := g_d3d.device->CreateCommittedResource( &heap_props, {}, &res_desc, {.COPY_DEST}, nil, d3d12.IResource_UUID, cast(^rawptr)&resource, ) if result < 0 { log.errorf( "gpu/d3d12: CreateCommittedResource (staged buffer) failed: 0x%08X", u32(result), ) return bk.NULL_BUFFER, false } // Create upload buffer for the initial data upload_props := d3d12.HEAP_PROPERTIES { Type = .UPLOAD, } upload_resource: ^d3d12.IResource result = g_d3d.device->CreateCommittedResource( &upload_props, {}, &res_desc, d3d12.RESOURCE_STATE_GENERIC_READ, nil, d3d12.IResource_UUID, cast(^rawptr)&upload_resource, ) if result < 0 { resource->Release() return bk.NULL_BUFFER, false } // Map upload buffer and copy data mapped: rawptr read_range := d3d12.RANGE{} result = upload_resource->Map(0, &read_range, &mapped) if result >= 0 { mem.copy(mapped, data, size) upload_resource->Unmap(0, nil) } // Use a temporary command allocator + list for the copy // We reuse the current frame's allocator if a frame is active, // otherwise create a one-shot copy if g_d3d.frame_active { g_d3d.command_list->CopyBufferRegion(resource, 0, upload_resource, 0, u64(size)) target_state := transition_copy_dest_to_generic_read_d3d12(resource) defer_release_resource_d3d12(upload_resource) entry := &g_d3d.buffers[handle] entry.resource = resource entry.gpu_address = resource->GetGPUVirtualAddress() entry.size = u64(size) entry.usage = usage entry.is_dynamic = false entry.state = target_state entry.active = true } else { // Outside a frame: create a temp command list, execute, and wait temp_alloc: ^d3d12.ICommandAllocator result = g_d3d.device->CreateCommandAllocator( .DIRECT, d3d12.ICommandAllocator_UUID, cast(^rawptr)&temp_alloc, ) if result < 0 { upload_resource->Release() resource->Release() return bk.NULL_BUFFER, false } temp_list: ^d3d12.IGraphicsCommandList result = g_d3d.device->CreateCommandList( 0, .DIRECT, temp_alloc, nil, d3d12.IGraphicsCommandList_UUID, cast(^rawptr)&temp_list, ) if result < 0 { temp_alloc->Release() upload_resource->Release() resource->Release() return bk.NULL_BUFFER, false } temp_list->CopyBufferRegion(resource, 0, upload_resource, 0, u64(size)) target_state := transition_copy_dest_to_generic_read_on_list_d3d12(temp_list, resource) temp_list->Close() cmd := cast(^d3d12.ICommandList)temp_list g_d3d.command_queue->ExecuteCommandLists(1, &cmd) wait_gpu_idle() temp_list->Release() temp_alloc->Release() upload_resource->Release() entry := &g_d3d.buffers[handle] entry.resource = resource entry.gpu_address = resource->GetGPUVirtualAddress() entry.size = u64(size) entry.usage = usage entry.is_dynamic = false entry.state = target_state entry.active = true } return handle, true } destroy_buffer_d3d12 :: proc(handle: bk.Buffer_Handle) { entry, ok := buffer_entry(handle) if !ok {return} if entry.resource != nil { if entry.mapped_ptr != nil { entry.resource->Unmap(0, nil) } defer_release_resource_d3d12(entry.resource) } entry^ = {} } map_buffer_d3d12 :: proc(handle: bk.Buffer_Handle) -> rawptr { entry, ok := buffer_entry(handle) if !ok || entry.resource == nil {return nil} if entry.mapped_ptr != nil {return entry.mapped_ptr} if !entry.is_dynamic { log.error("gpu/d3d12: map_buffer requires a Host_Visible buffer") return nil } mapped: rawptr read_range := d3d12.RANGE{} result := entry.resource->Map(0, &read_range, &mapped) if result < 0 { log.errorf("gpu/d3d12: buffer Map failed: 0x%08X", u32(result)) return nil } entry.mapped_ptr = mapped return mapped } unmap_buffer_d3d12 :: proc(handle: bk.Buffer_Handle) { entry, ok := buffer_entry(handle) if !ok || entry.resource == nil {return} // Upload heap buffers stay persistently mapped until destroy. if entry.is_dynamic do return if entry.mapped_ptr != nil { entry.resource->Unmap(0, nil) entry.mapped_ptr = nil } } get_buffer_mapped_d3d12 :: proc(handle: bk.Buffer_Handle) -> rawptr { entry, ok := buffer_entry(handle) if !ok {return nil} // Upload buffers are persistently mapped if entry.mapped_ptr != nil {return entry.mapped_ptr} // Try to map return map_buffer_d3d12(handle) } bind_vertex_buffer_d3d12 :: proc(ctx: bk.Frame_Context, handle: bk.Buffer_Handle) { bind_vertex_buffer_slot_d3d12(ctx, 0, handle, 0, 48) } bind_vertex_buffer_slot_d3d12 :: proc( ctx: bk.Frame_Context, slot: u32, handle: bk.Buffer_Handle, offset: u64, stride: u32, ) { entry, ok := buffer_entry(handle) if !ok {return} if offset > entry.size { log.error("gpu/d3d12: vertex buffer bind offset exceeds buffer size") return } vbv := d3d12.VERTEX_BUFFER_VIEW { BufferLocation = entry.gpu_address + d3d12.GPU_VIRTUAL_ADDRESS(offset), SizeInBytes = u32(entry.size - offset), StrideInBytes = stride, } g_d3d.command_list->IASetVertexBuffers(slot, 1, &vbv) } bind_index_buffer_d3d12 :: proc(ctx: bk.Frame_Context, handle: bk.Buffer_Handle) { entry, ok := buffer_entry(handle) if !ok {return} ibv := d3d12.INDEX_BUFFER_VIEW { BufferLocation = entry.gpu_address, SizeInBytes = u32(entry.size), Format = .R32_UINT, } g_d3d.command_list->IASetIndexBuffer(&ibv) } // ============================================================================ // Texture operations // ============================================================================ create_texture_d3d12 :: proc(desc: bk.Texture_Desc, pixels: rawptr) -> (bk.Texture_Handle, bool) { handle, ok := alloc_texture_handle() if !ok {return bk.NULL_TEXTURE, false} dxgi_fmt := to_dxgi_format(desc.format) heap_props := d3d12.HEAP_PROPERTIES { Type = .DEFAULT, } tex_desc := d3d12.RESOURCE_DESC { Dimension = .TEXTURE2D, Width = u64(desc.width), Height = desc.height, DepthOrArraySize = 1, MipLevels = 1, Format = dxgi_fmt, SampleDesc = {Count = 1}, } resource: ^d3d12.IResource result := g_d3d.device->CreateCommittedResource( &heap_props, {}, &tex_desc, {.COPY_DEST}, nil, d3d12.IResource_UUID, cast(^rawptr)&resource, ) if result < 0 { log.errorf("gpu/d3d12: CreateCommittedResource (texture) failed: 0x%08X", u32(result)) return bk.NULL_TEXTURE, false } // Upload pixel data if provided if pixels != nil { pixel_size := format_pixel_size(desc.format) upload_size := u64(desc.width) * u64(desc.height) * u64(pixel_size) // Get the actual row pitch the GPU needs (may be larger due to alignment) layout: d3d12.PLACED_SUBRESOURCE_FOOTPRINT num_rows: u32 row_size: u64 total_bytes: u64 g_d3d.device->GetCopyableFootprints( &tex_desc, 0, 1, 0, &layout, &num_rows, &row_size, &total_bytes, ) // Create upload buffer upload_props := d3d12.HEAP_PROPERTIES { Type = .UPLOAD, } upload_desc := d3d12.RESOURCE_DESC { Dimension = .BUFFER, Width = total_bytes, Height = 1, DepthOrArraySize = 1, MipLevels = 1, SampleDesc = {Count = 1}, Layout = .ROW_MAJOR, } upload_resource: ^d3d12.IResource result = g_d3d.device->CreateCommittedResource( &upload_props, {}, &upload_desc, d3d12.RESOURCE_STATE_GENERIC_READ, nil, d3d12.IResource_UUID, cast(^rawptr)&upload_resource, ) if result < 0 { resource->Release() return bk.NULL_TEXTURE, false } // Map and copy with proper row pitch mapped: rawptr read_range := d3d12.RANGE{} result = upload_resource->Map(0, &read_range, &mapped) if result >= 0 { src_row_pitch := desc.width * pixel_size dst_row_pitch := layout.Footprint.RowPitch for row in 0 ..< desc.height { src := rawptr(uintptr(pixels) + uintptr(row * src_row_pitch)) dst := rawptr(uintptr(mapped) + uintptr(row * dst_row_pitch)) mem.copy(dst, src, int(src_row_pitch)) } upload_resource->Unmap(0, nil) } // Copy from upload to texture dst_loc := d3d12.TEXTURE_COPY_LOCATION { pResource = resource, Type = .SUBRESOURCE_INDEX, } dst_loc.SubresourceIndex = 0 src_loc := d3d12.TEXTURE_COPY_LOCATION { pResource = upload_resource, Type = .PLACED_FOOTPRINT, } src_loc.PlacedFootprint = layout if g_d3d.frame_active { g_d3d.command_list->CopyTextureRegion(&dst_loc, 0, 0, 0, &src_loc, nil) _ = transition_copy_dest_to_shader_resource_d3d12(resource) defer_release_resource_d3d12(upload_resource) } else { temp_alloc: ^d3d12.ICommandAllocator g_d3d.device->CreateCommandAllocator( .DIRECT, d3d12.ICommandAllocator_UUID, cast(^rawptr)&temp_alloc, ) temp_list: ^d3d12.IGraphicsCommandList g_d3d.device->CreateCommandList( 0, .DIRECT, temp_alloc, nil, d3d12.IGraphicsCommandList_UUID, cast(^rawptr)&temp_list, ) temp_list->CopyTextureRegion(&dst_loc, 0, 0, 0, &src_loc, nil) _ = transition_copy_dest_to_shader_resource_on_list_d3d12(temp_list, resource) temp_list->Close() cmd := cast(^d3d12.ICommandList)temp_list g_d3d.command_queue->ExecuteCommandLists(1, &cmd) wait_gpu_idle() temp_list->Release() temp_alloc->Release() upload_resource->Release() } } else { _ = transition_copy_dest_to_shader_resource_d3d12(resource) } // Create SRV in staging heap srv_idx, srv_ok := alloc_staging_cbv_srv_uav() if !srv_ok { defer_release_resource_d3d12(resource) return bk.NULL_TEXTURE, false } srv_desc := d3d12.SHADER_RESOURCE_VIEW_DESC { Format = dxgi_fmt, ViewDimension = .TEXTURE2D, Shader4ComponentMapping = d3d12.DEFAULT_SHADER_4_COMPONENT_MAPPING, } srv_desc.Texture2D = { MostDetailedMip = 0, MipLevels = 1, } g_d3d.device->CreateShaderResourceView( resource, &srv_desc, get_staging_cbv_srv_uav_cpu_handle(srv_idx), ) entry := &g_d3d.textures[handle] entry.resource = resource entry.srv_index = srv_idx entry.has_srv = true entry.width = desc.width entry.height = desc.height entry.format = dxgi_fmt entry.state = d3d12.RESOURCE_STATE_ALL_SHADER_RESOURCE entry.active = true return handle, true } destroy_texture_d3d12 :: proc(handle: bk.Texture_Handle) { entry, ok := texture_entry(handle) if !ok {return} if entry.resource != nil {defer_release_resource_d3d12(entry.resource)} entry^ = {} } // ============================================================================ // Image operations (render targets, depth buffers) // ============================================================================ create_image_d3d12 :: proc(desc: bk.Texture_Desc) -> (bk.Texture_Handle, bool) { handle, ok := alloc_texture_handle() if !ok {return bk.NULL_TEXTURE, false} dxgi_fmt := to_dxgi_format(desc.format) is_depth := .Depth_Stencil_Attachment in desc.usage texture_fmt := dxgi_fmt if is_depth && .Sampled in desc.usage { texture_fmt = depth_format_to_typeless(dxgi_fmt) } flags: d3d12.RESOURCE_FLAGS if .Color_Attachment in desc.usage {flags += {.ALLOW_RENDER_TARGET}} if .Depth_Stencil_Attachment in desc.usage {flags += {.ALLOW_DEPTH_STENCIL}} if is_depth && !(.Sampled in desc.usage) {flags += {.DENY_SHADER_RESOURCE}} heap_props := d3d12.HEAP_PROPERTIES { Type = .DEFAULT, } tex_desc := d3d12.RESOURCE_DESC { Dimension = .TEXTURE2D, Width = u64(desc.width), Height = desc.height, DepthOrArraySize = 1, MipLevels = 1, Format = texture_fmt, SampleDesc = {Count = 1}, Flags = flags, } initial_state: d3d12.RESOURCE_STATES if is_depth { initial_state = {.DEPTH_WRITE} } else { initial_state = d3d12.RESOURCE_STATE_COMMON } resource: ^d3d12.IResource result := g_d3d.device->CreateCommittedResource( &heap_props, {}, &tex_desc, initial_state, nil, d3d12.IResource_UUID, cast(^rawptr)&resource, ) if result < 0 { log.errorf("gpu/d3d12: CreateCommittedResource (image) failed: 0x%08X", u32(result)) return bk.NULL_TEXTURE, false } entry := &g_d3d.textures[handle] entry.resource = resource entry.width = desc.width entry.height = desc.height entry.format = dxgi_fmt entry.usage = desc.usage entry.state = initial_state entry.deny_srv = .DENY_SHADER_RESOURCE in flags entry.active = true return handle, true } create_image_view_d3d12 :: proc( texture: bk.Texture_Handle, format: bk.Format, aspect: bk.Image_Aspect_Flags, ) -> bool { entry, ok := texture_entry(texture) if !ok {return false} dxgi_fmt := to_dxgi_format(format) is_depth := .Depth in aspect || .Stencil in aspect if is_depth { // Allocate DSV in DSV heap (offset by 1 since index 0 is the default depth buffer) dsv_idx := u32(texture) + 1 dsv_desc := d3d12.DEPTH_STENCIL_VIEW_DESC { Format = dxgi_fmt, ViewDimension = .TEXTURE2D, } g_d3d.device->CreateDepthStencilView( entry.resource, &dsv_desc, get_dsv_cpu_handle(dsv_idx), ) entry.dsv_index = dsv_idx entry.has_dsv = true // Also create SRV for depth sampling (shadow maps), if resource allows it if !entry.deny_srv { srv_idx, srv_ok := alloc_staging_cbv_srv_uav() if srv_ok { srv_fmt := depth_format_to_srv(dxgi_fmt) srv_desc := d3d12.SHADER_RESOURCE_VIEW_DESC { Format = srv_fmt, ViewDimension = .TEXTURE2D, Shader4ComponentMapping = d3d12.DEFAULT_SHADER_4_COMPONENT_MAPPING, } srv_desc.Texture2D = { MostDetailedMip = 0, MipLevels = 1, } g_d3d.device->CreateShaderResourceView( entry.resource, &srv_desc, get_staging_cbv_srv_uav_cpu_handle(srv_idx), ) entry.srv_index = srv_idx entry.has_srv = true } } } else { if .Color_Attachment in entry.usage { rtv_idx := u32(bk.MAX_FRAMES_IN_FLIGHT) + u32(texture) rtv_desc := d3d12.RENDER_TARGET_VIEW_DESC { Format = dxgi_fmt, ViewDimension = .TEXTURE2D, } g_d3d.device->CreateRenderTargetView( entry.resource, &rtv_desc, get_rtv_cpu_handle(rtv_idx), ) entry.rtv_index = rtv_idx entry.has_rtv = true } if .Sampled in entry.usage { // Create SRV srv_idx, srv_ok := alloc_staging_cbv_srv_uav() if !srv_ok {return false} srv_desc := d3d12.SHADER_RESOURCE_VIEW_DESC { Format = dxgi_fmt, ViewDimension = .TEXTURE2D, Shader4ComponentMapping = d3d12.DEFAULT_SHADER_4_COMPONENT_MAPPING, } srv_desc.Texture2D = { MostDetailedMip = 0, MipLevels = 1, } g_d3d.device->CreateShaderResourceView( entry.resource, &srv_desc, get_staging_cbv_srv_uav_cpu_handle(srv_idx), ) entry.srv_index = srv_idx entry.has_srv = true } } return true } read_texture_rgba8_d3d12 :: proc(desc: bk.Readback_Texture_Desc, out: []u8) -> bool { entry, ok := texture_entry(desc.texture) if !ok do return false if g_d3d.frame_active { log.error("gpu/d3d12: read_texture_rgba8 requires no active frame") return false } if desc.width != entry.width || desc.height != entry.height { log.error("gpu/d3d12: read_texture_rgba8 dimensions do not match texture") return false } required_size := int(desc.width * desc.height * 4) if len(out) < required_size { log.error("gpu/d3d12: read_texture_rgba8 output buffer is too small") return false } if entry.format != .R8G8B8A8_UNORM && entry.format != .R8G8B8A8_UNORM_SRGB && entry.format != .B8G8R8A8_UNORM_SRGB { log.error("gpu/d3d12: read_texture_rgba8 requires an 8-bit RGBA/BGRA color texture") return false } texture_desc := d3d12.RESOURCE_DESC { Dimension = .TEXTURE2D, Width = u64(desc.width), Height = desc.height, DepthOrArraySize = 1, MipLevels = 1, Format = entry.format, SampleDesc = {Count = 1}, } layout: d3d12.PLACED_SUBRESOURCE_FOOTPRINT num_rows: u32 row_size: u64 total_bytes: u64 g_d3d.device->GetCopyableFootprints(&texture_desc, 0, 1, 0, &layout, &num_rows, &row_size, &total_bytes) readback_props := d3d12.HEAP_PROPERTIES {Type = .READBACK} readback_desc := d3d12.RESOURCE_DESC { Dimension = .BUFFER, Width = total_bytes, Height = 1, DepthOrArraySize = 1, MipLevels = 1, SampleDesc = {Count = 1}, Layout = .ROW_MAJOR, } readback: ^d3d12.IResource result := g_d3d.device->CreateCommittedResource( &readback_props, {}, &readback_desc, {.COPY_DEST}, nil, d3d12.IResource_UUID, cast(^rawptr)&readback, ) if result < 0 { log.errorf("gpu/d3d12: read_texture_rgba8 readback resource failed: 0x%08X", u32(result)) return false } defer readback->Release() temp_alloc: ^d3d12.ICommandAllocator result = g_d3d.device->CreateCommandAllocator( .DIRECT, d3d12.ICommandAllocator_UUID, cast(^rawptr)&temp_alloc, ) if result < 0 do return false defer temp_alloc->Release() temp_list: ^d3d12.IGraphicsCommandList result = g_d3d.device->CreateCommandList( 0, .DIRECT, temp_alloc, nil, d3d12.IGraphicsCommandList_UUID, cast(^rawptr)&temp_list, ) if result < 0 do return false defer temp_list->Release() transition_resource_on_command_list_d3d12(temp_list, entry.resource, entry.state, {.COPY_SOURCE}) dst_loc := d3d12.TEXTURE_COPY_LOCATION { pResource = readback, Type = .PLACED_FOOTPRINT, } dst_loc.PlacedFootprint = layout src_loc := d3d12.TEXTURE_COPY_LOCATION { pResource = entry.resource, Type = .SUBRESOURCE_INDEX, } src_loc.SubresourceIndex = 0 temp_list->CopyTextureRegion(&dst_loc, 0, 0, 0, &src_loc, nil) transition_resource_on_command_list_d3d12(temp_list, entry.resource, {.COPY_SOURCE}, entry.state) temp_list->Close() cmd := cast(^d3d12.ICommandList)temp_list g_d3d.command_queue->ExecuteCommandLists(1, &cmd) wait_gpu_idle() mapped: rawptr read_range := d3d12.RANGE {Begin = 0, End = uint(total_bytes)} result = readback->Map(0, &read_range, &mapped) if result < 0 { log.errorf("gpu/d3d12: read_texture_rgba8 map failed: 0x%08X", u32(result)) return false } defer readback->Unmap(0, nil) src_row_pitch := int(layout.Footprint.RowPitch) dst_row_pitch := int(desc.width * 4) for row in 0.. (bk.Sampler_Handle, bool) { handle, ok := alloc_sampler_handle() if !ok {return bk.NULL_SAMPLER, false} staging_idx, staging_ok := alloc_staging_sampler() if !staging_ok {return bk.NULL_SAMPLER, false} filter := to_d3d12_filter(desc.mag_filter, desc.min_filter, desc.mipmap_mode) if desc.enable_compare { filter = .COMPARISON_MIN_MAG_LINEAR_MIP_POINT } sampler_desc := d3d12.SAMPLER_DESC { Filter = filter, AddressU = to_d3d12_address_mode(desc.address_mode_u), AddressV = to_d3d12_address_mode(desc.address_mode_v), AddressW = to_d3d12_address_mode(desc.address_mode_u), MaxAnisotropy = 16 if desc.enable_aniso else 1, ComparisonFunc = to_d3d12_compare_func(desc.compare_op) if desc.enable_compare else .ALWAYS, MinLOD = 0, MaxLOD = d3d12.FLOAT32_MAX, } sampler_desc.BorderColor = {0, 0, 0, 1} cpu_handle := get_staging_sampler_cpu_handle(staging_idx) g_d3d.device->CreateSampler(&sampler_desc, cpu_handle) entry := &g_d3d.samplers[handle] entry.cpu_handle = cpu_handle entry.staging_index = staging_idx entry.active = true return handle, true } destroy_sampler_d3d12 :: proc(handle: bk.Sampler_Handle) { entry, ok := sampler_entry(handle) if !ok {return} entry.active = false } // ============================================================================ // Shader operations // ============================================================================ create_shader_module_d3d12 :: proc(desc: bk.Shader_Module_Desc) -> (bk.Shader_Handle, bool) { handle, ok := alloc_shader_handle() if !ok {return bk.NULL_SHADER, false} if desc.format != .HLSL { log.errorf("gpu/d3d12: shader '%s' must be HLSL", desc.name) return bk.NULL_SHADER, false } profile: cstring switch desc.stage { case .Vertex: profile = "vs_5_0" case .Fragment: profile = "ps_5_0" case .Compute: profile = "cs_5_0" } compile_flags: u32 = 0 when ODIN_DEBUG { compile_flags = transmute(u32)d3dc.D3DCOMPILE{.DEBUG, .SKIP_OPTIMIZATION} } shader_blob: ^d3dc.ID3DBlob error_blob: ^d3dc.ID3DBlob name_cstr := strings.clone_to_cstring(desc.name, context.temp_allocator) entry_point: cstring = "main" if desc.entry == "" else strings.clone_to_cstring(desc.entry, context.temp_allocator) result := d3dc.Compile( raw_data(desc.data), uint(len(desc.data)), name_cstr, nil, nil, entry_point, profile, compile_flags, 0, &shader_blob, &error_blob, ) if result < 0 { if error_blob != nil { err_msg := cstring(error_blob->GetBufferPointer()) log.errorf("gpu/d3d12: D3DCompile failed for '%s': %s", desc.name, err_msg) error_blob->Release() } else { log.errorf("gpu/d3d12: D3DCompile failed for '%s': 0x%08X", desc.name, u32(result)) } return bk.NULL_SHADER, false } if error_blob != nil {error_blob->Release()} // Step 3: Copy bytecode (D3D12 embeds bytecode in PSO, no shader objects) blob_ptr := shader_blob->GetBufferPointer() blob_size := shader_blob->GetBufferSize() bytecode := make([]byte, blob_size) mem.copy(raw_data(bytecode), blob_ptr, int(blob_size)) shader_blob->Release() entry := &g_d3d.shaders[handle] entry.bytecode = bytecode entry.stage = desc.stage entry.active = true return handle, true } destroy_shader_d3d12 :: proc(handle: bk.Shader_Handle) { entry, ok := shader_entry(handle) if !ok {return} delete(entry.bytecode) entry^ = {} } // ============================================================================ // Graphics pipeline operations // ============================================================================ create_graphics_pipeline_d3d12 :: proc(desc: bk.Pipeline_Desc) -> (bk.Pipeline_Handle, bool) { handle, ok := alloc_pipeline_handle() if !ok {return bk.NULL_PIPELINE, false} vert_entry, vert_ok := shader_entry(desc.vert_shader) frag_entry, frag_ok := shader_entry(desc.frag_shader) if !vert_ok || !frag_ok { log.error("gpu/d3d12: graphics pipeline shader handle is invalid") return bk.NULL_PIPELINE, false } entry := &g_d3d.pipelines[handle] entry.vert_bytecode = make([]byte, len(vert_entry.bytecode)) entry.frag_bytecode = make([]byte, len(frag_entry.bytecode)) mem.copy( raw_data(entry.vert_bytecode), raw_data(vert_entry.bytecode), len(vert_entry.bytecode), ) mem.copy( raw_data(entry.frag_bytecode), raw_data(frag_entry.bytecode), len(frag_entry.bytecode), ) entry.vertex_binding_count = u32( min(len(desc.vertex_bindings), len(entry.vertex_bindings)), ) for i in 0 ..< int(entry.vertex_binding_count) { entry.vertex_bindings[i] = desc.vertex_bindings[i] } entry.vertex_attribute_count = u32( min(len(desc.vertex_attributes), len(entry.vertex_attributes)), ) for i in 0 ..< int(entry.vertex_attribute_count) { entry.vertex_attributes[i] = desc.vertex_attributes[i] } entry.pipeline_topology = desc.topology entry.topology = to_d3d12_topology(desc.topology) entry.root_sig = g_d3d.graphics_root_sig entry.cull_mode = desc.cull_mode entry.front_face = desc.front_face entry.enable_blending = desc.enable_blending entry.blend_mode = desc.blend_mode entry.enable_depth_test = desc.enable_depth_test entry.depth_format = desc.depth_format entry.stencil = desc.stencil entry.depth_only = desc.depth_only entry.color_attachment_count = desc.color_attachment_count entry.color_formats = desc.color_formats entry.color_write_masks = desc.color_write_masks entry.push_constant_size = desc.push_constant_size entry.push_constant_stages = desc.push_constant_stages entry.no_draw = desc.cull_mode == .Front_And_Back entry.is_compute = false pso, pso_ok := create_graphics_pso_for_entry_d3d12(entry, 0, 0) if !pso_ok { delete(entry.vert_bytecode) delete(entry.frag_bytecode) entry^ = {} return bk.NULL_PIPELINE, false } entry.pso = pso entry.active = true return handle, true } @(private) input_rate_for_binding_d3d12 :: proc( entry: ^D3D12_Pipeline_Entry, binding: u32, ) -> bk.Vertex_Input_Rate { for i in 0 ..< int(entry.vertex_binding_count) { if entry.vertex_bindings[i].binding == binding { return entry.vertex_bindings[i].input_rate } } return .Vertex } @(private) create_graphics_pso_for_entry_d3d12 :: proc( entry: ^D3D12_Pipeline_Entry, constant: i32, slope: f32, ) -> ( ^d3d12.IPipelineState, bool, ) { if g_d3d == nil || entry == nil || len(entry.vert_bytecode) == 0 || len(entry.frag_bytecode) == 0 { return nil, false } input_descs: [16]d3d12.INPUT_ELEMENT_DESC for i in 0 ..< int(entry.vertex_attribute_count) { attr := &entry.vertex_attributes[i] input_rate := input_rate_for_binding_d3d12(entry, attr.binding) input_descs[i] = d3d12.INPUT_ELEMENT_DESC { SemanticName = "TEXCOORD", SemanticIndex = u32(attr.location), Format = to_dxgi_vertex_format(attr.format), InputSlot = attr.binding, AlignedByteOffset = attr.offset, InputSlotClass = .PER_INSTANCE_DATA if input_rate == .Instance else .PER_VERTEX_DATA, InstanceDataStepRate = 1 if input_rate == .Instance else 0, } } raster_desc := d3d12.RASTERIZER_DESC { FillMode = .SOLID, CullMode = to_d3d12_cull_mode(entry.cull_mode), FrontCounterClockwise = d3d12.BOOL(entry.front_face == .Counter_Clockwise), DepthBias = constant, DepthBiasClamp = 0, SlopeScaledDepthBias = slope, DepthClipEnable = true, } factors := bk.blend_factors(entry.blend_mode) blend_desc: d3d12.BLEND_DESC color_count := entry.color_attachment_count if !entry.depth_only && color_count == 0 { color_count = 1 } for i in 0.. 0 else nil, NumElements = entry.vertex_attribute_count, }, PrimitiveTopologyType = to_d3d12_topology_type(entry.pipeline_topology), NumRenderTargets = 0 if entry.depth_only else color_count, DSVFormat = to_dxgi_format(entry.depth_format) if entry.depth_format != .Undefined else .D32_FLOAT, SampleDesc = {Count = 1}, } if !entry.depth_only { for i in 0..CreateGraphicsPipelineState( &pso_desc, d3d12.IPipelineState_UUID, cast(^rawptr)&pso, ) if result < 0 { log.errorf("gpu/d3d12: CreateGraphicsPipelineState failed: 0x%08X", u32(result)) when ODIN_DEBUG {dump_debug_messages()} return nil, false } return pso, true } depth_bias_pso_d3d12 :: proc( entry: ^D3D12_Pipeline_Entry, constant: i32, slope: f32, ) -> ( ^d3d12.IPipelineState, bool, ) { if entry == nil {return nil, false} if constant == 0 && slope == 0 { return entry.pso, entry.pso != nil } for i in 0 ..< int(entry.depth_bias_count) { variant := &entry.depth_bias_variants[i] if variant.active && variant.constant == constant && variant.slope == slope { return variant.pso, variant.pso != nil } } if entry.depth_bias_count >= MAX_DEPTH_BIAS_PSO_VARIANTS { log.error("gpu/d3d12: depth-bias PSO variant cache exhausted") return nil, false } pso, ok := create_graphics_pso_for_entry_d3d12(entry, constant, slope) if !ok {return nil, false} variant := &entry.depth_bias_variants[entry.depth_bias_count] variant.constant = constant variant.slope = slope variant.pso = pso variant.active = true entry.depth_bias_count += 1 return pso, true } destroy_graphics_pipeline_d3d12 :: proc(handle: bk.Pipeline_Handle) { entry, ok := pipeline_entry(handle) if !ok {return} if entry.pso != nil {defer_release_pso_d3d12(entry.pso)} for i in 0 ..< int(entry.depth_bias_count) { if entry.depth_bias_variants[i].pso != nil { defer_release_pso_d3d12(entry.depth_bias_variants[i].pso) } } delete(entry.vert_bytecode) delete(entry.frag_bytecode) entry^ = {} } bind_graphics_pipeline_d3d12 :: proc(ctx: bk.Frame_Context, handle: bk.Pipeline_Handle) { if g_d3d == nil {return} entry, ok := pipeline_entry(handle) if !ok {return} pso := entry.pso if biased_pso, bias_ok := depth_bias_pso_d3d12( entry, g_d3d.pending_depth_bias_constant, g_d3d.pending_depth_bias_slope, ); bias_ok { pso = biased_pso } g_d3d.command_list->SetPipelineState(pso) g_d3d.command_list->IASetPrimitiveTopology(entry.topology) g_d3d.command_list->OMSetStencilRef(u32(entry.stencil.reference)) g_d3d.current_pipeline = handle } push_constants_d3d12 :: proc( ctx: bk.Frame_Context, pipeline: bk.Pipeline_Handle, stages: bk.Shader_Stage_Flags, offset, size: u32, data: rawptr, ) { if g_d3d == nil || data == nil || size == 0 {return} num_32bit := (size + 3) / 4 offset_32bit := offset / 4 g_d3d.command_list->SetGraphicsRoot32BitConstants(0, num_32bit, data, offset_32bit) } // ============================================================================ // Compute pipeline operations // ============================================================================ create_compute_pipeline_d3d12 :: proc( shader: bk.Shader_Handle, num_buffers: u32, push_constant_size: u32, ) -> ( bk.Pipeline_Handle, bool, ) { handle, ok := alloc_pipeline_handle() if !ok {return bk.NULL_PIPELINE, false} shader_entry, shader_ok := shader_entry(shader) if !shader_ok {return bk.NULL_PIPELINE, false} pso_desc := d3d12.COMPUTE_PIPELINE_STATE_DESC { pRootSignature = g_d3d.compute_root_sig, CS = d3d12.SHADER_BYTECODE { pShaderBytecode = raw_data(shader_entry.bytecode), BytecodeLength = uint(len(shader_entry.bytecode)), }, } pso: ^d3d12.IPipelineState result := g_d3d.device->CreateComputePipelineState( &pso_desc, d3d12.IPipelineState_UUID, cast(^rawptr)&pso, ) if result < 0 { log.errorf("gpu/d3d12: CreateComputePipelineState failed: 0x%08X", u32(result)) return bk.NULL_PIPELINE, false } entry := &g_d3d.pipelines[handle] entry.pso = pso entry.root_sig = g_d3d.compute_root_sig entry.push_constant_size = push_constant_size entry.is_compute = true entry.active = true return handle, true } destroy_compute_pipeline_d3d12 :: proc(handle: bk.Pipeline_Handle) { destroy_graphics_pipeline_d3d12(handle) } bind_compute_pipeline_d3d12 :: proc(ctx: bk.Frame_Context, handle: bk.Pipeline_Handle) { if g_d3d == nil {return} entry, ok := pipeline_entry(handle) if !ok {return} g_d3d.command_list->SetPipelineState(entry.pso) g_d3d.command_list->SetComputeRootSignature(entry.root_sig) } // ============================================================================ // Draw commands // ============================================================================ draw_d3d12 :: proc( ctx: bk.Frame_Context, vertex_count, instance_count: u32, first_vertex: u32, first_instance: u32, ) { if g_d3d == nil {return} if entry, ok := pipeline_entry(g_d3d.current_pipeline); ok && entry.no_draw { return } g_d3d.command_list->DrawInstanced( vertex_count, max(instance_count, 1), first_vertex, first_instance, ) } draw_indexed_d3d12 :: proc( ctx: bk.Frame_Context, index_count, instance_count: u32, first_index: u32, vertex_offset: i32, first_instance: u32, ) { if g_d3d == nil {return} if entry, ok := pipeline_entry(g_d3d.current_pipeline); ok && entry.no_draw { return } g_d3d.command_list->DrawIndexedInstanced( index_count, max(instance_count, 1), first_index, vertex_offset, first_instance, ) } @(private) d3d12_indirect_signature :: proc(indexed: bool) -> (^d3d12.ICommandSignature, bool) { if g_d3d == nil || g_d3d.device == nil { return nil, false } if indexed { if g_d3d.draw_indexed_indirect_signature != nil { return g_d3d.draw_indexed_indirect_signature, true } } else if g_d3d.draw_indirect_signature != nil { return g_d3d.draw_indirect_signature, true } arg := d3d12.INDIRECT_ARGUMENT_DESC { Type = .DRAW_INDEXED if indexed else .DRAW, } desc := d3d12.COMMAND_SIGNATURE_DESC { ByteStride = u32(size_of(bk.Indirect_Draw_Indexed_Args)) if indexed else u32(size_of(bk.Indirect_Draw_Args)), NumArgumentDescs = 1, pArgumentDescs = &arg, } signature: ^d3d12.ICommandSignature result := g_d3d.device->CreateCommandSignature( &desc, nil, d3d12.ICommandSignature_UUID, cast(^rawptr)&signature, ) if result < 0 { log.errorf("gpu/d3d12: CreateCommandSignature failed: 0x%08X", u32(result)) return nil, false } if indexed { g_d3d.draw_indexed_indirect_signature = signature } else { g_d3d.draw_indirect_signature = signature } return signature, true } @(private) transition_buffer_for_indirect_argument_d3d12 :: proc(entry: ^D3D12_Buffer_Entry) { transition_buffer_to_indirect_argument_d3d12(entry) } draw_indirect_d3d12 :: proc( ctx: bk.Frame_Context, argument_buffer: bk.Buffer_Handle, argument_offset: u64, draw_count: u32, stride: u32, ) { _ = ctx _ = stride if g_d3d == nil {return} if draw_count != 1 { log.error("gpu/d3d12: draw_indirect currently supports one command") return } if entry, ok := pipeline_entry(g_d3d.current_pipeline); ok && entry.no_draw { return } buffer, buffer_ok := buffer_entry(argument_buffer) if !buffer_ok { log.error("gpu/d3d12: draw_indirect received invalid argument buffer") return } signature, signature_ok := d3d12_indirect_signature(false) if !signature_ok { return } transition_buffer_for_indirect_argument_d3d12(buffer) g_d3d.command_list->ExecuteIndirect(signature, draw_count, buffer.resource, argument_offset, nil, 0) } draw_indexed_indirect_d3d12 :: proc( ctx: bk.Frame_Context, argument_buffer: bk.Buffer_Handle, argument_offset: u64, draw_count: u32, stride: u32, ) { _ = ctx _ = stride if g_d3d == nil {return} if draw_count != 1 { log.error("gpu/d3d12: draw_indexed_indirect currently supports one command") return } if entry, ok := pipeline_entry(g_d3d.current_pipeline); ok && entry.no_draw { return } buffer, buffer_ok := buffer_entry(argument_buffer) if !buffer_ok { log.error("gpu/d3d12: draw_indexed_indirect received invalid argument buffer") return } signature, signature_ok := d3d12_indirect_signature(true) if !signature_ok { return } transition_buffer_for_indirect_argument_d3d12(buffer) g_d3d.command_list->ExecuteIndirect(signature, draw_count, buffer.resource, argument_offset, nil, 0) } @(private) to_d3d12_blend_factor :: proc(factor: bk.Blend_Factor) -> d3d12.BLEND { switch factor { case .Zero: return .ZERO case .One: return .ONE case .Src_Alpha: return .SRC_ALPHA case .One_Minus_Src_Alpha: return .INV_SRC_ALPHA } return .ONE } // ============================================================================ // Compute dispatch // ============================================================================ dispatch_compute_d3d12 :: proc(ctx: bk.Frame_Context, groups_x, groups_y, groups_z: u32) { if g_d3d == nil {return} g_d3d.command_list->Dispatch(groups_x, groups_y, groups_z) } compute_barrier_d3d12 :: proc(ctx: bk.Frame_Context) { if g_d3d == nil {return} // UAV barrier for compute writes -> reads barrier := d3d12.RESOURCE_BARRIER { Type = .UAV, } g_d3d.command_list->ResourceBarrier(1, &barrier) } // ============================================================================ // Descriptor operations // ============================================================================ create_descriptor_set_layout_d3d12 :: proc( bindings: []bk.Descriptor_Set_Layout_Binding, ) -> ( bk.Descriptor_Handle, bool, ) { handle, ok := alloc_descriptor_handle() if !ok {return bk.NULL_DESCRIPTOR, false} entry := &g_d3d.descriptors[handle] entry.kind = .Set_Layout entry.layout_count = u32(min(len(bindings), 16)) for i in 0 ..< int(entry.layout_count) { entry.layout_bindings[i] = bindings[i] } entry.active = true return handle, true } destroy_descriptor_set_layout_d3d12 :: proc(handle: bk.Descriptor_Handle) { entry, ok := descriptor_entry_of_kind(handle, .Set_Layout) if !ok {return} entry.active = false } create_descriptor_pool_d3d12 :: proc( max_sets: u32, types: []bk.Descriptor_Type, counts: []u32, ) -> ( bk.Descriptor_Handle, bool, ) { handle, ok := alloc_descriptor_handle() if !ok {return bk.NULL_DESCRIPTOR, false} entry := &g_d3d.descriptors[handle] entry.kind = .Pool entry.active = true return handle, true } destroy_descriptor_pool_d3d12 :: proc(handle: bk.Descriptor_Handle) { entry, ok := descriptor_entry_of_kind(handle, .Pool) if !ok {return} entry.active = false } allocate_descriptor_set_d3d12 :: proc( pool: bk.Descriptor_Handle, layout: bk.Descriptor_Handle, ) -> ( bk.Descriptor_Handle, bool, ) { if _, pool_ok := descriptor_entry_of_kind(pool, .Pool); !pool_ok {return bk.NULL_DESCRIPTOR, false} layout_entry, layout_ok := descriptor_entry_of_kind(layout, .Set_Layout) if !layout_ok {return bk.NULL_DESCRIPTOR, false} handle, ok := alloc_descriptor_handle() if !ok {return bk.NULL_DESCRIPTOR, false} entry := &g_d3d.descriptors[handle] entry.kind = .Set entry.binding_count = layout_entry.layout_count for i in 0 ..< int(entry.binding_count) { entry.bindings[i].binding = layout_entry.layout_bindings[i].binding entry.bindings[i].type = layout_entry.layout_bindings[i].type } entry.active = true return handle, true } bind_descriptor_set_d3d12 :: proc( ctx: bk.Frame_Context, pipeline_handle: bk.Pipeline_Handle, set: bk.Descriptor_Handle, index: u32, ) { if g_d3d == nil {return} entry, entry_ok := descriptor_entry_of_kind(set, .Set) if !entry_ok {return} p, pipeline_ok := pipeline_entry(pipeline_handle) if !pipeline_ok {return} is_compute := p.is_compute for i in 0 ..< int(entry.binding_count) { b := &entry.bindings[i] switch b.type { case .Combined_Image_Sampler: // Copy SRV from staging to GPU heap if b.texture != bk.NULL_TEXTURE { if tex_entry, tex_ok := texture_entry(b.texture); tex_ok && tex_entry.has_srv { transition_texture_for_srv_d3d12(tex_entry) cpu, gpu, alloc_ok := alloc_gpu_cbv_srv_uav(1) if alloc_ok { g_d3d.device->CopyDescriptorsSimple( 1, cpu, get_staging_cbv_srv_uav_cpu_handle(tex_entry.srv_index), .CBV_SRV_UAV, ) if is_compute { g_d3d.command_list->SetComputeRootDescriptorTable(1, gpu) } else { g_d3d.command_list->SetGraphicsRootDescriptorTable(1, gpu) } } } } // Copy sampler from staging to GPU heap if b.sampler != bk.NULL_SAMPLER { if sam_entry, sam_ok := sampler_entry(b.sampler); sam_ok { cpu, gpu, alloc_ok := alloc_gpu_sampler(1) if alloc_ok { g_d3d.device->CopyDescriptorsSimple(1, cpu, sam_entry.cpu_handle, .SAMPLER) if is_compute { g_d3d.command_list->SetComputeRootDescriptorTable(2, gpu) } else { g_d3d.command_list->SetGraphicsRootDescriptorTable(2, gpu) } } } } case .Uniform_Buffer: if b.buffer != bk.NULL_BUFFER { if buf_entry, buf_ok := buffer_entry(b.buffer); buf_ok { // Create CBV in GPU heap cpu, gpu, alloc_ok := alloc_gpu_cbv_srv_uav(1) if alloc_ok { cbv_desc := d3d12.CONSTANT_BUFFER_VIEW_DESC { BufferLocation = buf_entry.gpu_address, SizeInBytes = u32((b.buf_size + 255) & ~u64(255)), // 256-byte aligned } g_d3d.device->CreateConstantBufferView(&cbv_desc, cpu) if is_compute { g_d3d.command_list->SetComputeRootDescriptorTable(3, gpu) } else { g_d3d.command_list->SetGraphicsRootDescriptorTable(3, gpu) } } } } case .Storage_Buffer: log.error( "gpu/d3d12: compute storage-buffer descriptors are not supported by the current root signature", ) } } } update_descriptor_image_d3d12 :: proc( set: bk.Descriptor_Handle, binding: u32, texture: bk.Texture_Handle, sampler: bk.Sampler_Handle, layout: bk.Image_Layout, ) { entry, ok := descriptor_entry_of_kind(set, .Set) if !ok {return} for i in 0 ..< int(entry.binding_count) { if entry.bindings[i].binding == binding { entry.bindings[i].texture = texture entry.bindings[i].sampler = sampler return } } } update_descriptor_buffer_d3d12 :: proc( set: bk.Descriptor_Handle, binding: u32, buffer: bk.Buffer_Handle, size: u64, ) { entry, ok := descriptor_entry_of_kind(set, .Set) if !ok {return} for i in 0 ..< int(entry.binding_count) { if entry.bindings[i].binding == binding { entry.bindings[i].buffer = buffer entry.bindings[i].buf_size = size return } } } // ============================================================================ // Render pass / framebuffer operations // ============================================================================ create_render_pass_d3d12 :: proc(desc: bk.Render_Pass_Desc) -> (bk.Render_Pass_Handle, bool) { handle, ok := alloc_render_pass_handle() if !ok {return bk.NULL_RENDER_PASS, false} entry := &g_d3d.render_passes[handle] entry.desc = desc entry.active = true return handle, true } destroy_render_pass_d3d12 :: proc(handle: bk.Render_Pass_Handle) { entry, ok := render_pass_entry(handle) if !ok {return} entry.active = false } create_framebuffer_d3d12 :: proc(desc: bk.Framebuffer_Desc) -> (bk.Framebuffer_Handle, bool) { handle, ok := alloc_framebuffer_handle() if !ok {return bk.NULL_FRAMEBUFFER, false} pass_entry, pass_ok := render_pass_entry(desc.pass) if !pass_ok {return bk.NULL_FRAMEBUFFER, false} if pass_entry.desc.has_color { color_count := desc.color_count if color_count == 0 { color_count = pass_entry.desc.color_count } if color_count == 0 { color_count = 1 } for i in 0..