Harbor

branch main
showing the latest snapshot on main
d3d11_backend.odin 16.3 KB · Plain text
gpu/backend/d3d11/d3d11_backend.odin 0644 Raw
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
	}
}