package d3d11_backend import "core:log" import "core:mem" import "core:strings" import d3d11 "vendor:directx/d3d11" import d3dc "vendor:directx/d3d_compiler" import dxgi "vendor:directx/dxgi" import bk ".." active_pool_entry_d3d11 :: 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) -> (^D3D11_Buffer_Entry, bool) { if g_d3d == nil do return nil, false return active_pool_entry_d3d11(&g_d3d.buffers, handle) } texture_entry :: proc(handle: bk.Texture_Handle) -> (^D3D11_Texture_Entry, bool) { if g_d3d == nil do return nil, false return active_pool_entry_d3d11(&g_d3d.textures, handle) } shader_entry :: proc(handle: bk.Shader_Handle) -> (^D3D11_Shader_Entry, bool) { if g_d3d == nil do return nil, false return active_pool_entry_d3d11(&g_d3d.shaders, handle) } pipeline_entry :: proc(handle: bk.Pipeline_Handle) -> (^D3D11_Pipeline_Entry, bool) { if g_d3d == nil do return nil, false return active_pool_entry_d3d11(&g_d3d.pipelines, handle) } descriptor_entry :: proc(handle: bk.Descriptor_Handle) -> (^D3D11_Descriptor_Entry, bool) { if g_d3d == nil do return nil, false return active_pool_entry_d3d11(&g_d3d.descriptors, handle) } descriptor_entry_of_kind :: proc( handle: bk.Descriptor_Handle, kind: Descriptor_Kind, ) -> ( ^D3D11_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) -> (^D3D11_Render_Pass_Entry, bool) { if g_d3d == nil do return nil, false return active_pool_entry_d3d11(&g_d3d.render_passes, handle) } framebuffer_entry :: proc(handle: bk.Framebuffer_Handle) -> (^D3D11_Framebuffer_Entry, bool) { if g_d3d == nil do return nil, false return active_pool_entry_d3d11(&g_d3d.framebuffers, handle) } sampler_entry :: proc(handle: bk.Sampler_Handle) -> (^D3D11_Sampler_Entry, bool) { if g_d3d == nil do return nil, false return active_pool_entry_d3d11(&g_d3d.samplers, handle) } // ============================================================================ // Buffer operations // ============================================================================ create_buffer_d3d11 :: 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 bind_flags: d3d11.BIND_FLAGS if .Vertex in desc.usage {bind_flags += {.VERTEX_BUFFER}} if .Index in desc.usage {bind_flags += {.INDEX_BUFFER}} if .Uniform in desc.usage {bind_flags += {.CONSTANT_BUFFER}} if .Storage in desc.usage {bind_flags += {.UNORDERED_ACCESS, .SHADER_RESOURCE}} usage: d3d11.USAGE = .DEFAULT cpu_access: d3d11.CPU_ACCESS_FLAGS if is_dynamic { usage = .DYNAMIC cpu_access = {.WRITE} } buf_desc := d3d11.BUFFER_DESC { ByteWidth = u32(desc.size), Usage = usage, BindFlags = bind_flags, CPUAccessFlags = cpu_access, } // Storage buffers need structured buffer flags if .Storage in desc.usage { buf_desc.MiscFlags = {.BUFFER_STRUCTURED} buf_desc.StructureByteStride = 4 // default stride, updated on bind } if .Indirect_Argument in desc.usage { buf_desc.MiscFlags += {.DRAWINDIRECT_ARGS} } buf: ^d3d11.IBuffer result := g_d3d.device->CreateBuffer(&buf_desc, nil, &buf) if result < 0 { log.errorf("gpu/d3d11: CreateBuffer failed: 0x%08X", u32(result)) return bk.NULL_BUFFER, false } entry := &g_d3d.buffers[handle] entry.buffer = buf entry.size = desc.size entry.usage = desc.usage entry.is_dynamic = is_dynamic entry.active = true // Create UAV and SRV for storage buffers if .Storage in desc.usage { num_elements := u32(desc.size) / buf_desc.StructureByteStride uav_desc: d3d11.UNORDERED_ACCESS_VIEW_DESC uav_desc.Format = .UNKNOWN uav_desc.ViewDimension = .BUFFER uav_desc.Buffer = { NumElements = num_elements, } g_d3d.device->CreateUnorderedAccessView(buf, &uav_desc, &entry.uav) srv_desc: d3d11.SHADER_RESOURCE_VIEW_DESC srv_desc.Format = .UNKNOWN srv_desc.ViewDimension = .BUFFER srv_desc.Buffer = { NumElements = num_elements, } g_d3d.device->CreateShaderResourceView(buf, &srv_desc, &entry.srv) } // Map persistent for dynamic buffers if is_dynamic { mapped: d3d11.MAPPED_SUBRESOURCE result = g_d3d.ctx->Map(buf, 0, .WRITE_DISCARD, {}, &mapped) if result >= 0 { entry.mapped_ptr = mapped.pData g_d3d.ctx->Unmap(buf, 0) } } return handle, true } create_buffer_staged_d3d11 :: 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} bind_flags: d3d11.BIND_FLAGS if .Vertex in usage {bind_flags += {.VERTEX_BUFFER}} if .Index in usage {bind_flags += {.INDEX_BUFFER}} if .Uniform in usage {bind_flags += {.CONSTANT_BUFFER}} if .Storage in usage {bind_flags += {.UNORDERED_ACCESS, .SHADER_RESOURCE}} buf_desc := d3d11.BUFFER_DESC { ByteWidth = u32(size), Usage = .DEFAULT, BindFlags = bind_flags, } // Storage buffers need structured buffer flags if .Storage in usage { buf_desc.MiscFlags = {.BUFFER_STRUCTURED} buf_desc.StructureByteStride = 4 } if .Indirect_Argument in usage { buf_desc.MiscFlags += {.DRAWINDIRECT_ARGS} } init_data := d3d11.SUBRESOURCE_DATA { pSysMem = data, } buf: ^d3d11.IBuffer result := g_d3d.device->CreateBuffer(&buf_desc, &init_data, &buf) if result < 0 { log.errorf("gpu/d3d11: CreateBuffer (staged) failed: 0x%08X", u32(result)) return bk.NULL_BUFFER, false } entry := &g_d3d.buffers[handle] entry.buffer = buf entry.size = u64(size) entry.usage = usage entry.is_dynamic = false entry.active = true // Create UAV and SRV for storage buffers if .Storage in usage { stride := buf_desc.StructureByteStride if buf_desc.StructureByteStride > 0 else 4 num_elements := u32(size) / stride uav_desc: d3d11.UNORDERED_ACCESS_VIEW_DESC uav_desc.Format = .UNKNOWN uav_desc.ViewDimension = .BUFFER uav_desc.Buffer = { NumElements = num_elements, } g_d3d.device->CreateUnorderedAccessView(buf, &uav_desc, &entry.uav) srv_desc: d3d11.SHADER_RESOURCE_VIEW_DESC srv_desc.Format = .UNKNOWN srv_desc.ViewDimension = .BUFFER srv_desc.Buffer = { NumElements = num_elements, } g_d3d.device->CreateShaderResourceView(buf, &srv_desc, &entry.srv) } return handle, true } destroy_buffer_d3d11 :: proc(handle: bk.Buffer_Handle) { entry, ok := buffer_entry(handle) if !ok {return} if entry.uav != nil {entry.uav->Release()} if entry.srv != nil {entry.srv->Release()} if entry.buffer != nil {entry.buffer->Release()} entry^ = {} } map_buffer_d3d11 :: proc(handle: bk.Buffer_Handle) -> rawptr { entry, ok := buffer_entry(handle) if !ok || entry.buffer == nil {return nil} if !entry.is_dynamic { log.error("gpu/d3d11: map_buffer requires a Host_Visible buffer") return nil } mapped: d3d11.MAPPED_SUBRESOURCE result := g_d3d.ctx->Map(entry.buffer, 0, .WRITE_DISCARD, {}, &mapped) if result < 0 {return nil} entry.mapped_ptr = mapped.pData return mapped.pData } unmap_buffer_d3d11 :: proc(handle: bk.Buffer_Handle) { entry, ok := buffer_entry(handle) if !ok || entry.buffer == nil {return} g_d3d.ctx->Unmap(entry.buffer, 0) // Keep mapped_ptr valid since D3D11 dynamic buffers can be re-mapped } get_buffer_mapped_d3d11 :: proc(handle: bk.Buffer_Handle) -> rawptr { entry, ok := buffer_entry(handle) if !ok {return nil} if !entry.is_dynamic { log.error("gpu/d3d11: get_buffer_mapped requires a Host_Visible buffer") return nil } // For dynamic buffers, re-map to get a valid pointer if entry.is_dynamic && entry.buffer != nil { mapped: d3d11.MAPPED_SUBRESOURCE result := g_d3d.ctx->Map(entry.buffer, 0, .WRITE_DISCARD, {}, &mapped) if result >= 0 { entry.mapped_ptr = mapped.pData // Leave mapped -- caller will write, then we unmap on next frame return mapped.pData } } return entry.mapped_ptr } bind_vertex_buffer_d3d11 :: proc(ctx: bk.Frame_Context, handle: bk.Buffer_Handle) { bind_vertex_buffer_slot_d3d11(ctx, 0, handle, 0, g_d3d.current_vertex_stride) } bind_vertex_buffer_slot_d3d11 :: proc( ctx: bk.Frame_Context, slot: u32, handle: bk.Buffer_Handle, offset: u64, stride: u32, ) { entry, ok := buffer_entry(handle) if !ok {return} // D3D11 requires Unmap before GPU can read the buffer if entry.mapped_ptr != nil { g_d3d.ctx->Unmap(entry.buffer, 0) entry.mapped_ptr = nil } bind_stride := stride if bind_stride == 0 { bind_stride = g_d3d.current_vertex_stride } if offset > u64(max(u32)) { log.error("gpu/d3d11: vertex buffer bind offset exceeds D3D11 u32 range") return } bind_offset := u32(offset) g_d3d.ctx->IASetVertexBuffers(slot, 1, &entry.buffer, &bind_stride, &bind_offset) } bind_index_buffer_d3d11 :: proc(ctx: bk.Frame_Context, handle: bk.Buffer_Handle) { entry, ok := buffer_entry(handle) if !ok {return} // D3D11 requires Unmap before GPU can read the buffer if entry.mapped_ptr != nil { g_d3d.ctx->Unmap(entry.buffer, 0) entry.mapped_ptr = nil } g_d3d.ctx->IASetIndexBuffer(entry.buffer, .R32_UINT, 0) } // ============================================================================ // Texture operations // ============================================================================ create_texture_d3d11 :: 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) pixel_size := format_pixel_size(desc.format) tex_desc := d3d11.TEXTURE2D_DESC { Width = desc.width, Height = desc.height, MipLevels = 1, ArraySize = 1, Format = dxgi_fmt, SampleDesc = {Count = 1, Quality = 0}, Usage = .DEFAULT, BindFlags = {.SHADER_RESOURCE}, } init_data := d3d11.SUBRESOURCE_DATA { pSysMem = pixels, SysMemPitch = desc.width * pixel_size, } tex: ^d3d11.ITexture2D result := g_d3d.device->CreateTexture2D(&tex_desc, &init_data if pixels != nil else nil, &tex) if result < 0 { log.errorf("gpu/d3d11: CreateTexture2D failed: 0x%08X", u32(result)) return bk.NULL_TEXTURE, false } // Create shader resource view srv_desc := d3d11.SHADER_RESOURCE_VIEW_DESC { Format = dxgi_fmt, ViewDimension = .TEXTURE2D, } srv_desc.Texture2D = { MostDetailedMip = 0, MipLevels = 1, } srv: ^d3d11.IShaderResourceView result = g_d3d.device->CreateShaderResourceView(tex, &srv_desc, &srv) if result < 0 { log.errorf("gpu/d3d11: CreateShaderResourceView failed: 0x%08X", u32(result)) tex->Release() return bk.NULL_TEXTURE, false } entry := &g_d3d.textures[handle] entry.texture = tex entry.srv = srv entry.width = desc.width entry.height = desc.height entry.format = dxgi_fmt entry.active = true return handle, true } destroy_texture_d3d11 :: proc(handle: bk.Texture_Handle) { entry, ok := texture_entry(handle) if !ok {return} if entry.srv != nil {entry.srv->Release()} if entry.rtv != nil {entry.rtv->Release()} if entry.dsv != nil {entry.dsv->Release()} if entry.texture != nil {entry.texture->Release()} entry^ = {} } // ============================================================================ // Image operations (render targets, depth buffers) // ============================================================================ create_image_d3d11 :: 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 // Depth textures need typeless format if also sampled texture_fmt := dxgi_fmt if is_depth && .Sampled in desc.usage { texture_fmt = depth_format_to_typeless(dxgi_fmt) } bind_flags: d3d11.BIND_FLAGS if .Sampled in desc.usage {bind_flags += {.SHADER_RESOURCE}} if .Color_Attachment in desc.usage {bind_flags += {.RENDER_TARGET}} if .Depth_Stencil_Attachment in desc.usage {bind_flags += {.DEPTH_STENCIL}} tex_desc := d3d11.TEXTURE2D_DESC { Width = desc.width, Height = desc.height, MipLevels = 1, ArraySize = 1, Format = texture_fmt, SampleDesc = {Count = 1, Quality = 0}, Usage = .DEFAULT, BindFlags = bind_flags, } tex: ^d3d11.ITexture2D result := g_d3d.device->CreateTexture2D(&tex_desc, nil, &tex) if result < 0 { log.errorf("gpu/d3d11: CreateTexture2D (image) failed: 0x%08X", u32(result)) return bk.NULL_TEXTURE, false } entry := &g_d3d.textures[handle] entry.texture = tex entry.usage = desc.usage entry.width = desc.width entry.height = desc.height entry.format = dxgi_fmt entry.active = true return handle, true } create_image_view_d3d11 :: 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 { // Create DSV if entry.dsv != nil { entry.dsv->Release() entry.dsv = nil } dsv_desc := d3d11.DEPTH_STENCIL_VIEW_DESC { Format = dxgi_fmt, ViewDimension = .TEXTURE2D, } result := g_d3d.device->CreateDepthStencilView(entry.texture, &dsv_desc, &entry.dsv) if result < 0 {return false} // Also create SRV for depth sampling (shadow maps) if entry.srv != nil { entry.srv->Release() entry.srv = nil } srv_fmt := depth_format_to_srv(dxgi_fmt) srv_desc := d3d11.SHADER_RESOURCE_VIEW_DESC { Format = srv_fmt, ViewDimension = .TEXTURE2D, } srv_desc.Texture2D = { MostDetailedMip = 0, MipLevels = 1, } g_d3d.device->CreateShaderResourceView(entry.texture, &srv_desc, &entry.srv) } else { if .Color_Attachment in entry.usage { if entry.rtv != nil { entry.rtv->Release() entry.rtv = nil } rtv_desc := d3d11.RENDER_TARGET_VIEW_DESC { Format = dxgi_fmt, ViewDimension = .TEXTURE2D, } result := g_d3d.device->CreateRenderTargetView(entry.texture, &rtv_desc, &entry.rtv) if result < 0 {return false} } if .Sampled in entry.usage { // Create SRV for color if entry.srv != nil { entry.srv->Release() entry.srv = nil } srv_desc := d3d11.SHADER_RESOURCE_VIEW_DESC { Format = dxgi_fmt, ViewDimension = .TEXTURE2D, } srv_desc.Texture2D = { MostDetailedMip = 0, MipLevels = 1, } result := g_d3d.device->CreateShaderResourceView(entry.texture, &srv_desc, &entry.srv) if result < 0 {return false} } } return true } read_texture_rgba8_d3d11 :: proc(desc: bk.Readback_Texture_Desc, out: []u8) -> bool { entry, ok := texture_entry(desc.texture) if !ok do return false if desc.width != entry.width || desc.height != entry.height { log.error("gpu/d3d11: 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/d3d11: 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/d3d11: read_texture_rgba8 requires an 8-bit RGBA/BGRA color texture") return false } staging_desc := d3d11.TEXTURE2D_DESC { Width = desc.width, Height = desc.height, MipLevels = 1, ArraySize = 1, Format = entry.format, SampleDesc = {Count = 1, Quality = 0}, Usage = .STAGING, CPUAccessFlags = {.READ}, } staging: ^d3d11.ITexture2D result := g_d3d.device->CreateTexture2D(&staging_desc, nil, &staging) if result < 0 { log.errorf("gpu/d3d11: read_texture_rgba8 staging texture failed: 0x%08X", u32(result)) return false } defer staging->Release() g_d3d.ctx->CopyResource(cast(^d3d11.IResource)staging, cast(^d3d11.IResource)entry.texture) mapped: d3d11.MAPPED_SUBRESOURCE result = g_d3d.ctx->Map(cast(^d3d11.IResource)staging, 0, .READ, {}, &mapped) if result < 0 { log.errorf("gpu/d3d11: read_texture_rgba8 map failed: 0x%08X", u32(result)) return false } defer g_d3d.ctx->Unmap(cast(^d3d11.IResource)staging, 0) row_size := int(desc.width * 4) for row in 0.. (bk.Sampler_Handle, bool) { handle, ok := alloc_sampler_handle() if !ok {return bk.NULL_SAMPLER, false} filter := to_d3d11_filter(desc.mag_filter, desc.min_filter, desc.mipmap_mode) if desc.enable_compare { // Use comparison filter for shadow sampling filter = .COMPARISON_MIN_MAG_LINEAR_MIP_POINT } sampler_desc := d3d11.SAMPLER_DESC { Filter = filter, AddressU = to_d3d11_address_mode(desc.address_mode_u), AddressV = to_d3d11_address_mode(desc.address_mode_v), AddressW = to_d3d11_address_mode(desc.address_mode_u), MipLODBias = 0, MaxAnisotropy = 16 if desc.enable_aniso else 1, ComparisonFunc = to_d3d11_compare_func(desc.compare_op) if desc.enable_compare else .ALWAYS, MinLOD = 0, MaxLOD = d3d11.FLOAT32_MAX, } // Border color: opaque black sampler_desc.BorderColor = {0, 0, 0, 1} ss: ^d3d11.ISamplerState result := g_d3d.device->CreateSamplerState(&sampler_desc, &ss) if result < 0 { log.errorf("gpu/d3d11: CreateSamplerState failed: 0x%08X", u32(result)) return bk.NULL_SAMPLER, false } entry := &g_d3d.samplers[handle] entry.state = ss entry.active = true return handle, true } destroy_sampler_d3d11 :: proc(handle: bk.Sampler_Handle) { entry, ok := sampler_entry(handle) if !ok {return} if entry.state != nil {entry.state->Release()} entry.active = false } // ============================================================================ // Shader operations // ============================================================================ create_shader_module_d3d11 :: 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/d3d11: 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" } entry_point: cstring = "main" if desc.entry == "" else strings.clone_to_cstring(desc.entry, context.temp_allocator) 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) result := d3dc.Compile( raw_data(desc.data), uint(len(desc.data)), name_cstr, nil, // defines nil, // includes entry_point, profile, compile_flags, 0, // effect flags &shader_blob, &error_blob, ) if result < 0 { if error_blob != nil { err_msg := cstring(error_blob->GetBufferPointer()) log.errorf("gpu/d3d11: D3DCompile failed for '%s': %s", desc.name, err_msg) error_blob->Release() } else { log.errorf("gpu/d3d11: D3DCompile failed for '%s': 0x%08X", desc.name, u32(result)) } return bk.NULL_SHADER, false } if error_blob != nil {error_blob->Release()} // Step 3: Create shader object entry := &g_d3d.shaders[handle] entry.stage = desc.stage entry.active = true blob_ptr := shader_blob->GetBufferPointer() blob_size := shader_blob->GetBufferSize() switch desc.stage { case .Vertex: vs: ^d3d11.IVertexShader result = g_d3d.device->CreateVertexShader(blob_ptr, blob_size, nil, &vs) if result < 0 { log.errorf("gpu/d3d11: CreateVertexShader failed: 0x%08X", u32(result)) shader_blob->Release() entry.active = false return bk.NULL_SHADER, false } entry.vs = vs entry.vs_blob = cast(^d3d11.IBlob)shader_blob // Keep alive for CreateInputLayout case .Fragment: ps: ^d3d11.IPixelShader result = g_d3d.device->CreatePixelShader(blob_ptr, blob_size, nil, &ps) if result < 0 { log.errorf("gpu/d3d11: CreatePixelShader failed: 0x%08X", u32(result)) shader_blob->Release() entry.active = false return bk.NULL_SHADER, false } entry.ps = ps shader_blob->Release() case .Compute: cs: ^d3d11.IComputeShader result = g_d3d.device->CreateComputeShader(blob_ptr, blob_size, nil, &cs) if result < 0 { log.errorf("gpu/d3d11: CreateComputeShader failed: 0x%08X", u32(result)) shader_blob->Release() entry.active = false return bk.NULL_SHADER, false } entry.cs = cs shader_blob->Release() } return handle, true } destroy_shader_d3d11 :: proc(handle: bk.Shader_Handle) { entry, ok := shader_entry(handle) if !ok {return} if entry.vs != nil {entry.vs->Release()} if entry.ps != nil {entry.ps->Release()} if entry.cs != nil {entry.cs->Release()} if entry.vs_blob != nil {entry.vs_blob->Release()} entry^ = {} } // ============================================================================ // Graphics pipeline operations // ============================================================================ input_rate_for_binding :: proc(desc: bk.Pipeline_Desc, binding: u32) -> bk.Vertex_Input_Rate { for vb in desc.vertex_bindings { if vb.binding == binding { return vb.input_rate } } return .Vertex } create_graphics_pipeline_d3d11 :: proc(desc: bk.Pipeline_Desc) -> (bk.Pipeline_Handle, bool) { handle, ok := alloc_pipeline_handle() if !ok {return bk.NULL_PIPELINE, false} entry := &g_d3d.pipelines[handle] // Get shader objects 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/d3d11: graphics pipeline shader handle is invalid") return bk.NULL_PIPELINE, false } entry.vs = vert_entry.vs entry.ps = frag_entry.ps // AddRef since pipeline keeps references if entry.vs != nil {entry.vs->AddRef()} if entry.ps != nil {entry.ps->AddRef()} // Create input layout from VS blob + vertex attributes if vert_entry.vs_blob != nil && len(desc.vertex_attributes) > 0 { input_descs: [16]d3d11.INPUT_ELEMENT_DESC for i in 0 ..< len(desc.vertex_attributes) { attr := &desc.vertex_attributes[i] // Luma HLSL uses TEXCOORD for all vertex inputs with incrementing index input_descs[i] = d3d11.INPUT_ELEMENT_DESC { SemanticName = "TEXCOORD", SemanticIndex = u32(attr.location), Format = to_dxgi_vertex_format(attr.format), InputSlot = attr.binding, AlignedByteOffset = attr.offset, InputSlotClass = .INSTANCE_DATA if input_rate_for_binding(desc, attr.binding) == .Instance else .VERTEX_DATA, InstanceDataStepRate = 1 if input_rate_for_binding(desc, attr.binding) == .Instance else 0, } } result := g_d3d.device->CreateInputLayout( &input_descs[0], u32(len(desc.vertex_attributes)), vert_entry.vs_blob->GetBufferPointer(), vert_entry.vs_blob->GetBufferSize(), &entry.input_layout, ) if result < 0 { log.errorf("gpu/d3d11: CreateInputLayout failed: 0x%08X", u32(result)) destroy_graphics_pipeline_d3d11(handle) return bk.NULL_PIPELINE, false } } // Create rasterizer state raster_desc := d3d11.RASTERIZER_DESC { FillMode = .SOLID, CullMode = to_d3d11_cull_mode(desc.cull_mode), FrontCounterClockwise = d3d11.BOOL(desc.front_face == .Counter_Clockwise), DepthBias = 0, DepthBiasClamp = 0, SlopeScaledDepthBias = 0, DepthClipEnable = d3d11.BOOL(true), ScissorEnable = d3d11.BOOL(true), } entry.rasterizer_desc = raster_desc result := g_d3d.device->CreateRasterizerState(&raster_desc, &entry.rasterizer_state) if result < 0 { log.errorf("gpu/d3d11: CreateRasterizerState failed: 0x%08X", u32(result)) destroy_graphics_pipeline_d3d11(handle) return bk.NULL_PIPELINE, false } // Create blend state factors := bk.blend_factors(desc.blend_mode) blend_desc: d3d11.BLEND_DESC color_count := desc.color_attachment_count if !desc.depth_only && color_count == 0 { color_count = 1 } for i in 0..CreateBlendState(&blend_desc, &entry.blend_state) if result < 0 { log.errorf("gpu/d3d11: CreateBlendState failed: 0x%08X", u32(result)) destroy_graphics_pipeline_d3d11(handle) return bk.NULL_PIPELINE, false } // Create depth stencil state ds_desc := d3d11.DEPTH_STENCIL_DESC { DepthEnable = d3d11.BOOL(desc.enable_depth_test), DepthWriteMask = .ALL, DepthFunc = .LESS, StencilEnable = d3d11.BOOL(desc.stencil.enable), StencilReadMask = desc.stencil.read_mask, StencilWriteMask = desc.stencil.write_mask, FrontFace = to_d3d11_stencil_face(desc.stencil.front), BackFace = to_d3d11_stencil_face(desc.stencil.back), } // For depth-only (shadow) passes, write depth but don't test color if desc.depth_only { ds_desc.DepthEnable = true } result = g_d3d.device->CreateDepthStencilState(&ds_desc, &entry.depth_stencil_state) if result < 0 { log.errorf("gpu/d3d11: CreateDepthStencilState failed: 0x%08X", u32(result)) destroy_graphics_pipeline_d3d11(handle) return bk.NULL_PIPELINE, false } entry.topology = to_d3d11_topology(desc.topology) entry.vertex_stride = desc.vertex_bindings[0].stride if len(desc.vertex_bindings) > 0 else 48 entry.push_constant_size = desc.push_constant_size entry.push_constant_stages = desc.push_constant_stages entry.stencil_ref = u32(desc.stencil.reference) entry.no_draw = desc.cull_mode == .Front_And_Back entry.is_compute = false entry.active = true return handle, true } destroy_graphics_pipeline_d3d11 :: proc(handle: bk.Pipeline_Handle) { entry, ok := pipeline_entry(handle) if !ok {return} if !entry.is_compute { if entry.vs != nil {entry.vs->Release()} if entry.ps != nil {entry.ps->Release()} if entry.input_layout != nil {entry.input_layout->Release()} if entry.rasterizer_state != nil {entry.rasterizer_state->Release()} if entry.blend_state != nil {entry.blend_state->Release()} if entry.depth_stencil_state != nil {entry.depth_stencil_state->Release()} } entry^ = {} } bind_graphics_pipeline_d3d11 :: proc(ctx: bk.Frame_Context, handle: bk.Pipeline_Handle) { if g_d3d == nil {return} entry, ok := pipeline_entry(handle) if !ok {return} g_d3d.ctx->VSSetShader(entry.vs, nil, 0) g_d3d.ctx->PSSetShader(entry.ps, nil, 0) g_d3d.ctx->IASetInputLayout(entry.input_layout) g_d3d.ctx->IASetPrimitiveTopology(entry.topology) g_d3d.ctx->RSSetState(entry.rasterizer_state) g_d3d.ctx->OMSetBlendState(entry.blend_state, nil, 0xFFFFFFFF) g_d3d.ctx->OMSetDepthStencilState(entry.depth_stencil_state, entry.stencil_ref) // Cache pipeline state for subsequent bind calls g_d3d.current_pipeline = handle g_d3d.current_vertex_stride = entry.vertex_stride g_d3d.current_rasterizer_desc = entry.rasterizer_desc if g_d3d.current_rasterizer_state != nil { g_d3d.current_rasterizer_state->Release() } g_d3d.current_rasterizer_state = entry.rasterizer_state entry.rasterizer_state->AddRef() } push_constants_d3d11 :: 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} // Update push constant buffer via Map/Unmap mapped: d3d11.MAPPED_SUBRESOURCE result := g_d3d.ctx->Map(g_d3d.push_constant_buf, 0, .WRITE_DISCARD, {}, &mapped) if result < 0 {return} dst := rawptr(uintptr(mapped.pData) + uintptr(offset)) mem.copy(dst, data, int(size)) g_d3d.ctx->Unmap(g_d3d.push_constant_buf, 0) // Bind to the reserved slot for all requested stages if .Vertex in stages { g_d3d.ctx->VSSetConstantBuffers(PUSH_CONSTANT_SLOT, 1, &g_d3d.push_constant_buf) } if .Fragment in stages { g_d3d.ctx->PSSetConstantBuffers(PUSH_CONSTANT_SLOT, 1, &g_d3d.push_constant_buf) } if .Compute in stages { g_d3d.ctx->CSSetConstantBuffers(PUSH_CONSTANT_SLOT, 1, &g_d3d.push_constant_buf) } } // ============================================================================ // Compute pipeline operations // ============================================================================ create_compute_pipeline_d3d11 :: 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 || shader_entry.cs == nil { return bk.NULL_PIPELINE, false } entry := &g_d3d.pipelines[handle] entry.cs = shader_entry.cs entry.cs->AddRef() entry.push_constant_size = push_constant_size entry.is_compute = true entry.active = true return handle, true } destroy_compute_pipeline_d3d11 :: proc(handle: bk.Pipeline_Handle) { entry, ok := pipeline_entry(handle) if !ok {return} if entry.is_compute && entry.cs != nil { entry.cs->Release() } entry^ = {} } bind_compute_pipeline_d3d11 :: proc(ctx: bk.Frame_Context, handle: bk.Pipeline_Handle) { if g_d3d == nil {return} entry, ok := pipeline_entry(handle) if !ok {return} g_d3d.ctx->CSSetShader(entry.cs, nil, 0) } // ============================================================================ // Draw commands // ============================================================================ draw_d3d11 :: 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 } if instance_count <= 1 && first_instance == 0 { g_d3d.ctx->Draw(vertex_count, first_vertex) } else { g_d3d.ctx->DrawInstanced(vertex_count, instance_count, first_vertex, first_instance) } } draw_indexed_d3d11 :: 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 } if instance_count <= 1 && first_instance == 0 { g_d3d.ctx->DrawIndexed(index_count, first_index, vertex_offset) } else { g_d3d.ctx->DrawIndexedInstanced( index_count, instance_count, first_index, vertex_offset, first_instance, ) } } draw_indirect_d3d11 :: 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/d3d11: draw_indirect currently supports one command") return } if argument_offset > u64(max(u32)) || (argument_offset & 3) != 0 { log.error("gpu/d3d11: draw_indirect received invalid argument offset") 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/d3d11: draw_indirect received invalid argument buffer") return } g_d3d.ctx->DrawInstancedIndirect(buffer.buffer, u32(argument_offset)) } draw_indexed_indirect_d3d11 :: 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/d3d11: draw_indexed_indirect currently supports one command") return } if argument_offset > u64(max(u32)) || (argument_offset & 3) != 0 { log.error("gpu/d3d11: draw_indexed_indirect received invalid argument offset") 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/d3d11: draw_indexed_indirect received invalid argument buffer") return } g_d3d.ctx->DrawIndexedInstancedIndirect(buffer.buffer, u32(argument_offset)) } @(private) to_d3d11_blend_factor :: proc(factor: bk.Blend_Factor) -> d3d11.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_d3d11 :: proc(ctx: bk.Frame_Context, groups_x, groups_y, groups_z: u32) { if g_d3d == nil {return} g_d3d.ctx->Dispatch(groups_x, groups_y, groups_z) } compute_barrier_d3d11 :: proc(ctx: bk.Frame_Context) { // D3D11 immediate context handles barriers implicitly between dispatches // No explicit barrier needed } // ============================================================================ // Descriptor operations // ============================================================================ create_descriptor_set_layout_d3d11 :: 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_d3d11 :: proc(handle: bk.Descriptor_Handle) { entry, ok := descriptor_entry_of_kind(handle, .Set_Layout) if !ok {return} entry.active = false } create_descriptor_pool_d3d11 :: proc( max_sets: u32, types: []bk.Descriptor_Type, counts: []u32, ) -> ( bk.Descriptor_Handle, bool, ) { // D3D11 has no descriptor pools -- just return a valid handle 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_d3d11 :: proc(handle: bk.Descriptor_Handle) { entry, ok := descriptor_entry_of_kind(handle, .Pool) if !ok {return} entry.active = false } allocate_descriptor_set_d3d11 :: 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 // Copy layout bindings into set as initial binding records 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_d3d11 :: 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] slot := b.binding switch b.type { case .Combined_Image_Sampler: // Bind texture SRV + sampler if b.texture != bk.NULL_TEXTURE { if tex_entry, tex_ok := texture_entry(b.texture); tex_ok && tex_entry.srv != nil { if is_compute { g_d3d.ctx->CSSetShaderResources(slot, 1, &tex_entry.srv) } else { g_d3d.ctx->VSSetShaderResources(slot, 1, &tex_entry.srv) g_d3d.ctx->PSSetShaderResources(slot, 1, &tex_entry.srv) } } } if b.sampler != bk.NULL_SAMPLER { if sam_entry, sam_ok := sampler_entry(b.sampler); sam_ok && sam_entry.state != nil { if is_compute { g_d3d.ctx->CSSetSamplers(slot, 1, &sam_entry.state) } else { g_d3d.ctx->VSSetSamplers(slot, 1, &sam_entry.state) g_d3d.ctx->PSSetSamplers(slot, 1, &sam_entry.state) } } } case .Uniform_Buffer: if b.buffer != bk.NULL_BUFFER { if buf_entry, buf_ok := buffer_entry(b.buffer); buf_ok && buf_entry.buffer != nil { if is_compute { g_d3d.ctx->CSSetConstantBuffers(slot, 1, &buf_entry.buffer) } else { g_d3d.ctx->VSSetConstantBuffers(slot, 1, &buf_entry.buffer) g_d3d.ctx->PSSetConstantBuffers(slot, 1, &buf_entry.buffer) } } } case .Storage_Buffer: if b.buffer != bk.NULL_BUFFER { if buf_entry, buf_ok := buffer_entry(b.buffer); buf_ok && buf_entry.buffer != nil { if is_compute { if buf_entry.uav != nil { g_d3d.ctx->CSSetUnorderedAccessViews(slot, 1, &buf_entry.uav, nil) } } else { if buf_entry.srv != nil { g_d3d.ctx->VSSetShaderResources(slot, 1, &buf_entry.srv) g_d3d.ctx->PSSetShaderResources(slot, 1, &buf_entry.srv) } } } } } } } update_descriptor_image_d3d11 :: 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_d3d11 :: 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_d3d11 :: proc(desc: bk.Render_Pass_Desc) -> (bk.Render_Pass_Handle, bool) { // D3D11 render passes are metadata-only 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_d3d11 :: proc(handle: bk.Render_Pass_Handle) { entry, ok := render_pass_entry(handle) if !ok {return} entry.active = false } create_framebuffer_d3d11 :: 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} entry := &g_d3d.framebuffers[handle] entry.color_tex = desc.color_view entry.depth_tex = desc.depth_view entry.width = desc.width entry.height = desc.height 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 } entry.rtv_count = color_count for i in 0..