package d3d11_backend import "core:log" import "core:mem" import win32 "core:sys/windows" import d3d11 "vendor:directx/d3d11" import dxgi "vendor:directx/dxgi" import bk ".." // --- Global D3D11 state --- @(private) g_d3d: ^D3D11_State D3D11_State :: struct { // Core D3D11 objects device: ^d3d11.IDevice, ctx: ^d3d11.IDeviceContext, swap_chain: ^dxgi.ISwapChain1, feature_level: d3d11.FEATURE_LEVEL, // Backbuffer backbuffer_rtv: ^d3d11.IRenderTargetView, depth_tex: ^d3d11.ITexture2D, depth_dsv: ^d3d11.IDepthStencilView, width: u32, height: u32, // Push constants emulation: constant buffer at slot b15 push_constant_buf: ^d3d11.IBuffer, // Depth format depth_format: dxgi.FORMAT, // Default render pass handle default_render_pass: bk.Render_Pass_Handle, // Current frame state current_frame: u32, frame_active: bool, clear_color: [4]f32, // Current render pass state render_pass_active: bool, // Current pipeline state (for depth bias caching and vertex stride) current_rasterizer_desc: d3d11.RASTERIZER_DESC, current_rasterizer_state: ^d3d11.IRasterizerState, current_vertex_stride: u32, current_pipeline: bk.Pipeline_Handle, // Handle pools buffers: [MAX_BUFFERS]D3D11_Buffer_Entry, textures: [MAX_TEXTURES]D3D11_Texture_Entry, pipelines: [MAX_PIPELINES]D3D11_Pipeline_Entry, shaders: [MAX_SHADERS]D3D11_Shader_Entry, descriptors: [MAX_DESCRIPTORS]D3D11_Descriptor_Entry, render_passes: [MAX_RENDER_PASSES]D3D11_Render_Pass_Entry, framebuffers: [MAX_FRAMEBUFFERS]D3D11_Framebuffer_Entry, samplers: [MAX_SAMPLERS]D3D11_Sampler_Entry, } // Pool sizes (same as Vulkan backend) 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 // Push constant buffer slot (reserved, must not conflict with user cbuffers) // SM5.0 supports slots 0-13 (14 total). Slot 13 is the highest available. PUSH_CONSTANT_SLOT :: 13 PUSH_CONSTANT_MAX_SIZE :: 256 // bytes, covers the 128-byte Vulkan guaranteed minimum // --- Pool entry types --- D3D11_Buffer_Entry :: struct { buffer: ^d3d11.IBuffer, srv: ^d3d11.IShaderResourceView, uav: ^d3d11.IUnorderedAccessView, mapped_ptr: rawptr, size: u64, usage: bk.Buffer_Usage_Flags, is_dynamic: bool, active: bool, } D3D11_Texture_Entry :: struct { texture: ^d3d11.ITexture2D, srv: ^d3d11.IShaderResourceView, rtv: ^d3d11.IRenderTargetView, dsv: ^d3d11.IDepthStencilView, usage: bk.Image_Usage_Flags, width: u32, height: u32, format: dxgi.FORMAT, active: bool, } D3D11_Pipeline_Entry :: struct { // Graphics pipeline state vs: ^d3d11.IVertexShader, ps: ^d3d11.IPixelShader, cs: ^d3d11.IComputeShader, input_layout: ^d3d11.IInputLayout, rasterizer_state: ^d3d11.IRasterizerState, blend_state: ^d3d11.IBlendState, depth_stencil_state: ^d3d11.IDepthStencilState, stencil_ref: u32, topology: d3d11.PRIMITIVE_TOPOLOGY, rasterizer_desc: d3d11.RASTERIZER_DESC, // Pipeline layout info vertex_stride: u32, push_constant_size: u32, push_constant_stages: bk.Shader_Stage_Flags, no_draw: bool, is_compute: bool, active: bool, } D3D11_Shader_Entry :: struct { vs_blob: ^d3d11.IBlob, // Keep VS blob for InputLayout creation (alias for d3d_common.ID3DBlob) vs: ^d3d11.IVertexShader, ps: ^d3d11.IPixelShader, cs: ^d3d11.IComputeShader, stage: bk.Shader_Stage, active: bool, } // Descriptor binding record (D3D11 has no descriptor objects) D3D11_Descriptor_Binding :: struct { binding: u32, type: bk.Descriptor_Type, // Resource references (filled by update_descriptor_*) buffer: bk.Buffer_Handle, texture: bk.Texture_Handle, sampler: bk.Sampler_Handle, buf_size: u64, } D3D11_Descriptor_Entry :: struct { kind: Descriptor_Kind, // For set layout: binding metadata layout_bindings: [16]bk.Descriptor_Set_Layout_Binding, layout_count: u32, // For set: actual resource bindings bindings: [16]D3D11_Descriptor_Binding, binding_count: u32, active: bool, } Descriptor_Kind :: enum { Set_Layout, Pool, Set, } D3D11_Render_Pass_Entry :: struct { desc: bk.Render_Pass_Desc, active: bool, } D3D11_Framebuffer_Entry :: struct { rtv: ^d3d11.IRenderTargetView, rtvs: [bk.MAX_COLOR_TARGETS]^d3d11.IRenderTargetView, rtv_count: u32, dsv: ^d3d11.IDepthStencilView, color_tex: bk.Texture_Handle, color_texs: [bk.MAX_COLOR_TARGETS]bk.Texture_Handle, depth_tex: bk.Texture_Handle, width: u32, height: u32, active: bool, } D3D11_Sampler_Entry :: struct { state: ^d3d11.ISamplerState, active: bool, } // --- Init --- init_d3d11_backend :: proc( surface: bk.Surface_Desc, width, height: u32, title: cstring, ) -> ( backend: bk.Backend, ok: bool, ) { state := new(D3D11_State) if state == nil { log.error("gpu/d3d11: failed to allocate D3D11 state") return {}, false } g_d3d = state if surface.kind != .Win32 || surface.win32.hwnd == nil { log.error("gpu/d3d11: failed to get Win32 window handle") free(state) g_d3d = nil return {}, false } hwnd := dxgi.HWND(surface.win32.hwnd) // Create swap chain description 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, } feature_levels := [?]d3d11.FEATURE_LEVEL{._11_1, ._11_0} flags: d3d11.CREATE_DEVICE_FLAGS when ODIN_DEBUG { flags += {.DEBUG} } // Create device base_device: ^d3d11.IDevice base_ctx: ^d3d11.IDeviceContext result := d3d11.CreateDevice( nil, // default adapter .HARDWARE, nil, // no software rasterizer flags, &feature_levels[0], u32(len(feature_levels)), d3d11.SDK_VERSION, &base_device, &state.feature_level, &base_ctx, ) if result < 0 { log.errorf("gpu/d3d11: D3D11CreateDevice failed: 0x%08X", u32(result)) free(state) g_d3d = nil return {}, false } state.device = base_device state.ctx = base_ctx // Get DXGI factory from device dxgi_device: ^dxgi.IDevice result = state.device->QueryInterface(dxgi.IDevice_UUID, cast(^rawptr)&dxgi_device) if result < 0 { log.error("gpu/d3d11: failed to get DXGI device") shutdown_d3d11() return {}, false } defer dxgi_device->Release() dxgi_adapter: ^dxgi.IAdapter result = dxgi_device->GetAdapter(&dxgi_adapter) if result < 0 { log.error("gpu/d3d11: failed to get DXGI adapter") shutdown_d3d11() return {}, false } defer dxgi_adapter->Release() dxgi_factory: ^dxgi.IFactory2 result = dxgi_adapter->GetParent(dxgi.IFactory2_UUID, cast(^rawptr)&dxgi_factory) if result < 0 { log.error("gpu/d3d11: failed to get DXGI factory") shutdown_d3d11() return {}, false } defer dxgi_factory->Release() // Create swap chain result = dxgi_factory->CreateSwapChainForHwnd( state.device, hwnd, &sc_desc, nil, // no fullscreen desc nil, // no restrict to output &state.swap_chain, ) if result < 0 { log.errorf("gpu/d3d11: CreateSwapChainForHwnd failed: 0x%08X", u32(result)) shutdown_d3d11() return {}, false } state.width = width state.height = height // Create backbuffer RTV if !create_backbuffer_rtv(state) { shutdown_d3d11() return {}, false } // Find and create depth buffer state.depth_format = .D32_FLOAT if !create_depth_buffer(state, width, height) { shutdown_d3d11() return {}, false } // Create push constant buffer pc_buf_desc := d3d11.BUFFER_DESC { ByteWidth = PUSH_CONSTANT_MAX_SIZE, Usage = .DYNAMIC, BindFlags = {.CONSTANT_BUFFER}, CPUAccessFlags = {.WRITE}, } result = state.device->CreateBuffer(&pc_buf_desc, nil, &state.push_constant_buf) if result < 0 { log.error("gpu/d3d11: failed to create push constant buffer") shutdown_d3d11() return {}, false } // Register default render pass in pool rp_handle, rp_ok := alloc_render_pass_handle() if !rp_ok { log.error("gpu/d3d11: failed to allocate default render pass handle") shutdown_d3d11() 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 // Populate backend vtable backend = bk.Backend { capabilities = bk.implemented_base_capabilities( bk.MAX_COLOR_TARGETS, PUSH_CONSTANT_MAX_SIZE, ), // Lifecycle shutdown = shutdown_d3d11, wait_idle = wait_idle_d3d11, // Frame begin_frame = begin_frame_d3d11, end_frame = end_frame_d3d11, on_resize = on_resize_d3d11, get_extent = get_extent_d3d11, current_frame_index = get_current_frame_d3d11, // Render pass begin_render_pass = begin_render_pass_d3d11, begin_default_pass = begin_default_pass_d3d11, end_render_pass = end_render_pass_d3d11, set_viewport = set_viewport_d3d11, set_scissor = set_scissor_d3d11, set_depth_bias = set_depth_bias_d3d11, // Pipeline create_graphics_pipeline = create_graphics_pipeline_d3d11, destroy_graphics_pipeline = destroy_graphics_pipeline_d3d11, bind_graphics_pipeline = bind_graphics_pipeline_d3d11, push_constants = push_constants_d3d11, // Buffers create_buffer = create_buffer_d3d11, create_buffer_staged = create_buffer_staged_d3d11, destroy_buffer = destroy_buffer_d3d11, map_buffer = map_buffer_d3d11, unmap_buffer = unmap_buffer_d3d11, get_buffer_mapped = get_buffer_mapped_d3d11, bind_vertex_buffer = bind_vertex_buffer_d3d11, bind_vertex_buffer_slot = bind_vertex_buffer_slot_d3d11, bind_index_buffer = bind_index_buffer_d3d11, // Textures create_texture = create_texture_d3d11, destroy_texture = destroy_texture_d3d11, read_texture_rgba8 = read_texture_rgba8_d3d11, // Samplers create_sampler = create_sampler_d3d11, destroy_sampler = destroy_sampler_d3d11, // Images create_image = create_image_d3d11, create_image_view = create_image_view_d3d11, destroy_image = destroy_image_d3d11, // Descriptors create_descriptor_set_layout = create_descriptor_set_layout_d3d11, destroy_descriptor_set_layout = destroy_descriptor_set_layout_d3d11, create_descriptor_pool = create_descriptor_pool_d3d11, destroy_descriptor_pool = destroy_descriptor_pool_d3d11, allocate_descriptor_set = allocate_descriptor_set_d3d11, bind_descriptor_set = bind_descriptor_set_d3d11, update_descriptor_image = update_descriptor_image_d3d11, update_descriptor_buffer = update_descriptor_buffer_d3d11, // Render pass objects create_render_pass = create_render_pass_d3d11, destroy_render_pass = destroy_render_pass_d3d11, create_framebuffer = create_framebuffer_d3d11, destroy_framebuffer = destroy_framebuffer_d3d11, // Shaders create_shader_module = create_shader_module_d3d11, destroy_shader = destroy_shader_d3d11, // Draw draw = draw_d3d11, draw_indexed = draw_indexed_d3d11, draw_indirect = draw_indirect_d3d11, draw_indexed_indirect = draw_indexed_indirect_d3d11, // Compute create_compute_pipeline = create_compute_pipeline_d3d11, destroy_compute_pipeline = destroy_compute_pipeline_d3d11, bind_compute_pipeline = bind_compute_pipeline_d3d11, dispatch_compute = dispatch_compute_d3d11, compute_barrier = compute_barrier_d3d11, // Sync get_default_render_pass = get_default_render_pass_d3d11, get_depth_format = get_depth_format_d3d11, } bk.backend_initialized = true return backend, true } get_current_frame_d3d11 :: 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_backbuffer_rtv :: proc(state: ^D3D11_State) -> bool { backbuffer: ^d3d11.ITexture2D result := state.swap_chain->GetBuffer(0, d3d11.ITexture2D_UUID, cast(^rawptr)&backbuffer) if result < 0 { log.error("gpu/d3d11: failed to get backbuffer") return false } defer backbuffer->Release() // Create RTV with SRGB format for correct gamma. // The swapchain is R8G8B8A8_UNORM but we view it as SRGB so the GPU // applies linear→sRGB conversion on write, matching Vulkan's B8G8R8A8_SRGB. rtv_desc := d3d11.RENDER_TARGET_VIEW_DESC { Format = .R8G8B8A8_UNORM_SRGB, ViewDimension = .TEXTURE2D, } result = state.device->CreateRenderTargetView(backbuffer, &rtv_desc, &state.backbuffer_rtv) if result < 0 { log.error("gpu/d3d11: failed to create backbuffer RTV") return false } return true } @(private) create_depth_buffer :: proc(state: ^D3D11_State, width, height: u32) -> bool { depth_desc := d3d11.TEXTURE2D_DESC { Width = width, Height = height, MipLevels = 1, ArraySize = 1, Format = state.depth_format, SampleDesc = {Count = 1, Quality = 0}, Usage = .DEFAULT, BindFlags = {.DEPTH_STENCIL}, } result := state.device->CreateTexture2D(&depth_desc, nil, &state.depth_tex) if result < 0 { log.error("gpu/d3d11: failed to create depth texture") return false } result = state.device->CreateDepthStencilView(state.depth_tex, nil, &state.depth_dsv) if result < 0 { log.error("gpu/d3d11: failed to create depth stencil view") state.depth_tex->Release() state.depth_tex = nil return false } return true } @(private) release_backbuffer_and_depth :: proc(state: ^D3D11_State) { if state.backbuffer_rtv != nil { state.backbuffer_rtv->Release() state.backbuffer_rtv = nil } if state.depth_dsv != nil { state.depth_dsv->Release() state.depth_dsv = nil } if state.depth_tex != nil { state.depth_tex->Release() state.depth_tex = nil } }