Harbor

branch main
showing the latest snapshot on main
d3d12_frame.odin 10.5 KB · Plain text
gpu/backend/d3d12/d3d12_frame.odin 0644 Raw
package d3d12_backend

import "core:log"
import win32 "core:sys/windows"

import d3d12 "vendor:directx/d3d12"
import dxgi "vendor:directx/dxgi"

import bk ".."

// --- Lifecycle ---

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

	wait_gpu_idle()
	drain_deferred_releases_now_d3d12()

	// Destroy all active samplers
	for &entry in g_d3d.samplers {
		if entry.active {
			entry.active = false
		}
	}

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

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

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

	// Destroy shaders
	for i in 0 ..< MAX_SHADERS {
		if g_d3d.shaders[i].active do destroy_shader_d3d12(bk.Shader_Handle(i))
	}

	// Destroy pipelines
	for i in 0 ..< MAX_PIPELINES {
		if g_d3d.pipelines[i].active do destroy_graphics_pipeline_d3d12(bk.Pipeline_Handle(i))
	}

	// Destroy textures
	for i in 0 ..< MAX_TEXTURES {
		if g_d3d.textures[i].active do destroy_texture_d3d12(bk.Texture_Handle(i))
	}

	// Destroy buffers
	for i in 0 ..< MAX_BUFFERS {
		if g_d3d.buffers[i].active do destroy_buffer_d3d12(bk.Buffer_Handle(i))
	}

	drain_deferred_releases_now_d3d12()

	// Destroy core objects
	if g_d3d.draw_indirect_signature != nil {g_d3d.draw_indirect_signature->Release()}
	if g_d3d.draw_indexed_indirect_signature != nil {g_d3d.draw_indexed_indirect_signature->Release()}
	if g_d3d.graphics_root_sig != nil {g_d3d.graphics_root_sig->Release()}
	if g_d3d.compute_root_sig != nil {g_d3d.compute_root_sig->Release()}

	when ODIN_OS == .Windows {
		if g_d3d.fence_event != nil {
			win32.CloseHandle(g_d3d.fence_event)
		}
	}
	if g_d3d.fence != nil {g_d3d.fence->Release()}

	if g_d3d.command_list != nil {g_d3d.command_list->Release()}

	for i in 0 ..< bk.MAX_FRAMES_IN_FLIGHT {
		if g_d3d.frame_resources[i].command_allocator != nil {
			g_d3d.frame_resources[i].command_allocator->Release()
		}
	}

	release_backbuffers_and_depth(g_d3d)

	if g_d3d.gpu_sampler_heap != nil {g_d3d.gpu_sampler_heap->Release()}
	if g_d3d.gpu_cbv_srv_uav_heap != nil {g_d3d.gpu_cbv_srv_uav_heap->Release()}
	if g_d3d.staging_sampler_heap != nil {g_d3d.staging_sampler_heap->Release()}
	if g_d3d.staging_cbv_srv_uav_heap != nil {g_d3d.staging_cbv_srv_uav_heap->Release()}
	if g_d3d.dsv_heap != nil {g_d3d.dsv_heap->Release()}
	if g_d3d.rtv_heap != nil {g_d3d.rtv_heap->Release()}

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

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

wait_idle_d3d12 :: proc() {
	wait_gpu_idle()
	drain_deferred_releases_now_d3d12()
}

// --- Frame lifecycle ---

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

	frame_idx := g_d3d.current_frame
	fr := &g_d3d.frame_resources[frame_idx]

	// Wait for this frame slot's previous work to complete
	if g_d3d.fence->GetCompletedValue() < fr.fence_value {
		g_d3d.fence->SetEventOnCompletion(fr.fence_value, g_d3d.fence_event)
		when ODIN_OS == .Windows {
			win32.WaitForSingleObject(g_d3d.fence_event, win32.INFINITE)
		}
	}
	drain_deferred_releases_now_d3d12()

	// Reset allocator and command list
	fr.command_allocator->Reset()
	g_d3d.command_list->Reset(fr.command_allocator, nil)

	// Get current back buffer index from swap chain
	g_d3d.back_buffer_idx = g_d3d.swap_chain->GetCurrentBackBufferIndex()

	// Reset per-frame GPU descriptor allocators
	base_csv := frame_idx * GPU_CBV_SRV_UAV_PER_FRAME
	g_d3d.gpu_cbv_srv_uav_alloc[frame_idx].offset = base_csv

	base_sampler := frame_idx * GPU_SAMPLER_PER_FRAME
	g_d3d.gpu_sampler_alloc[frame_idx].offset = base_sampler

	// Set descriptor heaps
	heaps := [2]^d3d12.IDescriptorHeap{g_d3d.gpu_cbv_srv_uav_heap, g_d3d.gpu_sampler_heap}
	g_d3d.command_list->SetDescriptorHeaps(2, &heaps[0])

	// Set root signature
	g_d3d.command_list->SetGraphicsRootSignature(g_d3d.graphics_root_sig)

	g_d3d.frame_active = true
	g_d3d.clear_color = clear_color

	return bk.Frame_Context{frame_index = frame_idx}, true
}

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

	// Transition backbuffer to PRESENT
	bb := g_d3d.backbuffers[g_d3d.back_buffer_idx]
	transition_backbuffer_to_present_d3d12(bb)

	// Close command list
	result := g_d3d.command_list->Close()
	if result < 0 {
		log.errorf("gpu/d3d12: command list Close failed: 0x%08X", u32(result))
	}

	// Execute
	cmd_list := cast(^d3d12.ICommandList)g_d3d.command_list
	g_d3d.command_queue->ExecuteCommandLists(1, &cmd_list)

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

	// Signal fence for this frame
	fr := &g_d3d.frame_resources[g_d3d.current_frame]
	fr.fence_value = g_d3d.fence_value
	g_d3d.command_queue->Signal(g_d3d.fence, g_d3d.fence_value)
	g_d3d.fence_value += 1
	drain_deferred_releases_now_d3d12()

	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 present_result == dxgi.STATUS_OCCLUDED {
		return false
	}
	if present_result < 0 {
		log.errorf("gpu/d3d12: Present failed: 0x%08X", u32(present_result))
		return true
	}
	return false
}

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

	wait_gpu_idle()

	release_backbuffers_and_depth(g_d3d)

	result := g_d3d.swap_chain->ResizeBuffers(0, width, height, .UNKNOWN, {})
	if result < 0 {
		log.errorf("gpu/d3d12: ResizeBuffers failed: 0x%08X", u32(result))
		return
	}

	g_d3d.width = width
	g_d3d.height = height

	create_backbuffer_rtvs(g_d3d)
	create_depth_buffer(g_d3d, width, height)
}

get_extent_d3d12 :: 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_d3d12 :: 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}

	pass_entry, pass_ok := render_pass_entry(desc.pass)
	is_depth_only := pass_ok && pass_entry.desc.depth_only

	rtv_handles: [bk.MAX_COLOR_TARGETS]d3d12.CPU_DESCRIPTOR_HANDLE
	rtv_count: u32
	for i in 0..<fb_entry.color_count {
		color_tex := fb_entry.color_texs[i]
		if color_tex == bk.NULL_TEXTURE {
			continue
		}
		if tex_entry, tex_ok := texture_entry(color_tex); tex_ok && tex_entry.has_rtv {
			transition_texture_to_render_target_d3d12(tex_entry)
			rtv_handles[rtv_count] = get_rtv_cpu_handle(tex_entry.rtv_index)
			rtv_count += 1
		}
	}

	// Set DSV
	dsv_handle: d3d12.CPU_DESCRIPTOR_HANDLE
	has_dsv := false
	if fb_entry.depth_tex != bk.NULL_TEXTURE {
		if tex_entry, tex_ok := texture_entry(fb_entry.depth_tex); tex_ok && tex_entry.has_dsv {
			transition_texture_to_depth_write_d3d12(tex_entry)
			dsv_handle = get_dsv_cpu_handle(tex_entry.dsv_index)
			has_dsv = true
		}
	}

	if is_depth_only || rtv_count == 0 {
		g_d3d.command_list->OMSetRenderTargets(0, nil, false, &dsv_handle if has_dsv else nil)
	} else {
		g_d3d.command_list->OMSetRenderTargets(
			rtv_count,
			&rtv_handles[0],
			false,
			&dsv_handle if has_dsv else nil,
		)
		for i in 0..<rtv_count {
			cc := desc.clear_colors[i] if desc.color_count > 0 else desc.clear_color
			g_d3d.command_list->ClearRenderTargetView(rtv_handles[i], &cc, 0, nil)
		}
	}

	if has_dsv {
		clear_flags := d3d12.CLEAR_FLAGS{.DEPTH}
		if pass, pass_ok := render_pass_entry(desc.pass); pass_ok && pass.desc.has_stencil {
			clear_flags += {.STENCIL}
		}
		g_d3d.command_list->ClearDepthStencilView(
			dsv_handle,
			clear_flags,
			desc.clear_depth,
			u8(desc.clear_stencil),
			0,
			nil,
		)
	}

	// Set viewport
	viewport := d3d12.VIEWPORT {
		Width    = f32(desc.width),
		Height   = f32(desc.height),
		MaxDepth = 1,
	}
	g_d3d.command_list->RSSetViewports(1, &viewport)

	scissor := d3d12.RECT {
		right  = i32(desc.width),
		bottom = i32(desc.height),
	}
	g_d3d.command_list->RSSetScissorRects(1, &scissor)

	g_d3d.render_pass_active = true
}

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

	// Transition backbuffer to RENDER_TARGET
	bb := g_d3d.backbuffers[g_d3d.back_buffer_idx]
	transition_backbuffer_to_render_target_d3d12(bb)

	// Get RTV for current backbuffer
	rtv_handle := get_rtv_cpu_handle(g_d3d.back_buffer_idx)

	// Get DSV
	dsv_handle := get_dsv_cpu_handle(0) // Default depth buffer is at index 0

	g_d3d.command_list->OMSetRenderTargets(1, &rtv_handle, false, &dsv_handle)

	// Clear
	cc := clear_color
	g_d3d.command_list->ClearRenderTargetView(rtv_handle, &cc, 0, nil)
	g_d3d.command_list->ClearDepthStencilView(dsv_handle, {.DEPTH}, 1.0, 0, 0, nil)

	// Set default viewport
	viewport := d3d12.VIEWPORT {
		Width    = f32(g_d3d.width),
		Height   = f32(g_d3d.height),
		MaxDepth = 1,
	}
	g_d3d.command_list->RSSetViewports(1, &viewport)

	scissor := d3d12.RECT {
		right  = i32(g_d3d.width),
		bottom = i32(g_d3d.height),
	}
	g_d3d.command_list->RSSetScissorRects(1, &scissor)

	g_d3d.render_pass_active = true
}

end_render_pass_d3d12 :: proc(ctx: bk.Frame_Context) {
	if g_d3d == nil {return}
	g_d3d.render_pass_active = false
}

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

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

set_depth_bias_d3d12 :: proc(ctx: bk.Frame_Context, constant, slope: f32) {
	if g_d3d == nil {return}
	g_d3d.pending_depth_bias_constant = i32(constant)
	g_d3d.pending_depth_bias_slope = slope
	if entry, ok := pipeline_entry(g_d3d.current_pipeline); ok {
		if pso, pso_ok := depth_bias_pso_d3d12(
			entry,
			g_d3d.pending_depth_bias_constant,
			g_d3d.pending_depth_bias_slope,
		); pso_ok {
			g_d3d.command_list->SetPipelineState(pso)
		}
	}
}

// --- Sync queries ---

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

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