Harbor

branch main
showing the latest snapshot on main
d3d11_frame.odin 8.1 KB · Plain text
backend/d3d11/d3d11_frame.odin 0644 Raw
package d3d11_backend

import "core:log"

import d3d11 "vendor:directx/d3d11"
import dxgi "vendor:directx/dxgi"

import bk ".."

// --- Lifecycle ---

shutdown_d3d11 :: proc() {
	if g_d3d == nil {return}

	// Flush pending commands
	if g_d3d.ctx != nil {
		g_d3d.ctx->Flush()
		// Unbind all state to avoid release-order issues
		g_d3d.ctx->OMSetRenderTargets(0, nil, nil)
		g_d3d.ctx->IASetInputLayout(nil)
		g_d3d.ctx->VSSetShader(nil, nil, 0)
		g_d3d.ctx->PSSetShader(nil, nil, 0)
		g_d3d.ctx->CSSetShader(nil, nil, 0)
	}

	// Destroy all active samplers
	for &entry in g_d3d.samplers {
		if entry.active && entry.state != nil {
			entry.state->Release()
			entry.active = false
		}
	}

	// Destroy all active framebuffers
	for &entry in g_d3d.framebuffers {
		if entry.active {
			// RTV and DSV are owned by texture entries, not duplicated here
			entry.active = false
		}
	}

	// Destroy render passes (metadata only, nothing to release)
	for &entry in g_d3d.render_passes {
		entry.active = false
	}

	// Destroy all active descriptors (metadata only)
	for &entry in g_d3d.descriptors {
		entry.active = false
	}

	// Destroy all active shaders
	for &entry in g_d3d.shaders {
		if entry.active {
			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.active = false
		}
	}

	// Destroy all active pipelines
	for &entry in g_d3d.pipelines {
		if entry.active {
			if entry.vs != nil {entry.vs->Release()}
			if entry.ps != nil {entry.ps->Release()}
			if entry.cs != nil {entry.cs->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.active = false
		}
	}

	// Destroy all active textures
	for &entry in g_d3d.textures {
		if entry.active {
			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.active = false
		}
	}

	// Destroy all active buffers
	for &entry in g_d3d.buffers {
		if entry.active {
			if entry.buffer != nil {entry.buffer->Release()}
			entry.active = false
		}
	}

	// Destroy core objects
	if g_d3d.current_rasterizer_state != nil {
		g_d3d.current_rasterizer_state->Release()
	}
	if g_d3d.push_constant_buf != nil {
		g_d3d.push_constant_buf->Release()
	}

	release_backbuffer_and_depth(g_d3d)

	if g_d3d.swap_chain != nil {g_d3d.swap_chain->Release()}
	if g_d3d.ctx != nil {g_d3d.ctx->Release()}
	if g_d3d.device != nil {g_d3d.device->Release()}

	free(g_d3d)
	g_d3d = nil
	bk.backend_initialized = false
}

wait_idle_d3d11 :: proc() {
	if g_d3d != nil && g_d3d.ctx != nil {
		g_d3d.ctx->Flush()
	}
}

// --- Frame lifecycle ---

begin_frame_d3d11 :: proc(clear_color: [4]f32) -> (bk.Frame_Context, bool) {
	if g_d3d == nil {return {}, false}

	g_d3d.frame_active = true
	g_d3d.clear_color = clear_color

	return bk.Frame_Context{frame_index = g_d3d.current_frame}, true
}

end_frame_d3d11 :: proc(ctx: bk.Frame_Context) -> bool {
	if g_d3d == nil || !g_d3d.frame_active {return false}

	// Present with vsync (SyncInterval = 1)
	result := g_d3d.swap_chain->Present(1, {})

	g_d3d.frame_active = false
	g_d3d.render_pass_active = false
	g_d3d.current_frame = (g_d3d.current_frame + 1) % bk.MAX_FRAMES_IN_FLIGHT

	if result == dxgi.STATUS_OCCLUDED {
		return false
	}

	if result < 0 {
		log.errorf("gpu/d3d11: Present failed: 0x%08X", u32(result))
		// Could be a device lost situation -- handle resize
		return true
	}

	return false
}

on_resize_d3d11 :: proc(width, height: u32) {
	if g_d3d == nil {return}
	if width == 0 || height == 0 {return}

	// Flush pending work
	g_d3d.ctx->Flush()

	// Unbind render targets before resizing
	g_d3d.ctx->OMSetRenderTargets(0, nil, nil)

	// Release old backbuffer and depth
	release_backbuffer_and_depth(g_d3d)

	// Resize swap chain buffers (0 = keep existing buffer count)
	result := g_d3d.swap_chain->ResizeBuffers(0, width, height, .UNKNOWN, {})

	if result < 0 {
		log.errorf("gpu/d3d11: ResizeBuffers failed: 0x%08X", u32(result))
		return
	}

	g_d3d.width = width
	g_d3d.height = height

	// Recreate backbuffer RTV
	create_backbuffer_rtv(g_d3d)

	// Recreate depth buffer
	create_depth_buffer(g_d3d, width, height)
}

get_extent_d3d11 :: proc() -> bk.Extent {
	if g_d3d == nil {return {}}
	return bk.Extent{width = g_d3d.width, height = g_d3d.height}
}

// --- Render pass commands ---

begin_render_pass_d3d11 :: proc(ctx: bk.Frame_Context, desc: bk.Render_Pass_Begin_Desc) {
	if g_d3d == nil {return}

	fb_entry, fb_ok := framebuffer_entry(desc.framebuffer)
	if !fb_ok {return}

	// Set render targets
	if fb_entry.rtv_count > 0 {
		g_d3d.ctx->OMSetRenderTargets(fb_entry.rtv_count, &fb_entry.rtvs[0], fb_entry.dsv)
		for i in 0..<fb_entry.rtv_count {
			cc := desc.clear_colors[i] if desc.color_count > 0 else desc.clear_color
			g_d3d.ctx->ClearRenderTargetView(fb_entry.rtvs[i], &cc)
		}
	} else {
		g_d3d.ctx->OMSetRenderTargets(0, nil, fb_entry.dsv)
	}

	if fb_entry.dsv != nil {
		clear_flags := d3d11.CLEAR_FLAGS{.DEPTH}
		if pass, pass_ok := render_pass_entry(desc.pass); pass_ok && pass.desc.has_stencil {
			clear_flags += {.STENCIL}
		}
		g_d3d.ctx->ClearDepthStencilView(fb_entry.dsv, clear_flags, desc.clear_depth, u8(desc.clear_stencil))
	}

	// Set viewport
	viewport := d3d11.VIEWPORT {
		TopLeftX = 0,
		TopLeftY = 0,
		Width    = f32(desc.width),
		Height   = f32(desc.height),
		MinDepth = 0,
		MaxDepth = 1,
	}
	g_d3d.ctx->RSSetViewports(1, &viewport)

	g_d3d.render_pass_active = true
}

begin_default_pass_d3d11 :: proc(ctx: bk.Frame_Context, clear_color: [4]f32) {
	if g_d3d == nil {return}

	// Bind backbuffer + depth
	g_d3d.ctx->OMSetRenderTargets(1, &g_d3d.backbuffer_rtv, g_d3d.depth_dsv)

	// Clear
	cc := clear_color
	g_d3d.ctx->ClearRenderTargetView(g_d3d.backbuffer_rtv, &cc)
	if g_d3d.depth_dsv != nil {
		g_d3d.ctx->ClearDepthStencilView(g_d3d.depth_dsv, {.DEPTH}, 1.0, 0)
	}

	// Set default viewport
	viewport := d3d11.VIEWPORT {
		TopLeftX = 0,
		TopLeftY = 0,
		Width    = f32(g_d3d.width),
		Height   = f32(g_d3d.height),
		MinDepth = 0,
		MaxDepth = 1,
	}
	g_d3d.ctx->RSSetViewports(1, &viewport)

	// Set default scissor (full screen)
	scissor := d3d11.RECT {
		left   = 0,
		top    = 0,
		right  = i32(g_d3d.width),
		bottom = i32(g_d3d.height),
	}
	g_d3d.ctx->RSSetScissorRects(1, &scissor)

	g_d3d.render_pass_active = true
}

end_render_pass_d3d11 :: proc(ctx: bk.Frame_Context) {
	if g_d3d == nil {return}
	g_d3d.render_pass_active = false
	// D3D11 has no explicit render pass end -- nothing to do
}

set_viewport_d3d11 :: proc(ctx: bk.Frame_Context, x, y, w, h: f32) {
	if g_d3d == nil {return}
	viewport := d3d11.VIEWPORT {
		TopLeftX = x,
		TopLeftY = y,
		Width    = w,
		Height   = h,
		MinDepth = 0,
		MaxDepth = 1,
	}
	g_d3d.ctx->RSSetViewports(1, &viewport)
}

set_scissor_d3d11 :: proc(ctx: bk.Frame_Context, x, y: i32, w, h: u32) {
	if g_d3d == nil {return}
	scissor := d3d11.RECT {
		left   = x,
		top    = y,
		right  = x + i32(w),
		bottom = y + i32(h),
	}
	g_d3d.ctx->RSSetScissorRects(1, &scissor)
}

set_depth_bias_d3d11 :: proc(ctx: bk.Frame_Context, constant, slope: f32) {
	if g_d3d == nil {return}

	// D3D11 requires recreating the rasterizer state to change depth bias
	desc := g_d3d.current_rasterizer_desc
	desc.DepthBias = i32(constant)
	desc.SlopeScaledDepthBias = slope
	desc.DepthBiasClamp = 0

	rs: ^d3d11.IRasterizerState
	result := g_d3d.device->CreateRasterizerState(&desc, &rs)
	if result < 0 {return}

	if g_d3d.current_rasterizer_state != nil {
		g_d3d.current_rasterizer_state->Release()
	}
	g_d3d.current_rasterizer_state = rs
	g_d3d.current_rasterizer_desc = desc
	g_d3d.ctx->RSSetState(rs)
}

// --- Sync queries ---

get_default_render_pass_d3d11 :: proc() -> bk.Render_Pass_Handle {
	if g_d3d == nil {return bk.NULL_RENDER_PASS}
	return g_d3d.default_render_pass
}

get_depth_format_d3d11 :: proc() -> bk.Format {
	if g_d3d == nil {return .Undefined}
	return from_dxgi_format(g_d3d.depth_format)
}