Created
May 20, 2024 13:37
-
-
Save cwfitzgerald/f5e6f819e172c3e32084abd89b5f56d0 to your computer and use it in GitHub Desktop.
D3D12/SDL2 Init
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use glam::UVec2; | |
use raw_window_handle::{HasRawWindowHandle, RawWindowHandle}; | |
use sdl2::{ | |
event::{Event, WindowEvent}, | |
keyboard::Keycode, | |
}; | |
use windows::Win32::Foundation::HWND; | |
#[no_mangle] | |
pub static D3D12SDKVersion: u32 = 614; | |
#[no_mangle] | |
pub static D3D12SDKPath: &[u8] = b"../../d3d12\0"; | |
fn main() { | |
let sdl_context = sdl2::init().unwrap(); | |
let video_subsystem = sdl_context.video().unwrap(); | |
let window = video_subsystem.window("Name", 1280, 720).resizable().build().unwrap(); | |
let RawWindowHandle::Win32(handle) = window.raw_window_handle() else { | |
panic!("Unsupported platform"); | |
}; | |
let mut renderer = | |
fantasy_render::Renderer::new(HWND(handle.hwnd as _), UVec2::from(window.size())); | |
let mut event_pump = sdl_context.event_pump().unwrap(); | |
'quit: loop { | |
for event in event_pump.poll_iter() { | |
match event { | |
Event::Quit { .. } | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => { | |
break 'quit | |
} | |
Event::Window { win_event: WindowEvent::Resized(x, y), .. } => { | |
renderer.resize(UVec2::new(x as _, y as _)); | |
} | |
_ => {} | |
} | |
} | |
renderer.render(); | |
} | |
renderer.destroy(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::{os::raw::c_void, panic::catch_unwind, ptr, slice}; | |
use arrayvec::ArrayVec; | |
use windows::{ | |
core::*, | |
Win32::{ | |
Foundation::*, | |
Graphics::{ | |
Direct3D::*, | |
Direct3D12::*, | |
Dxgi::{Common::*, *}, | |
}, | |
System::Threading::WaitForSingleObject, | |
}, | |
}; | |
fn string_from_utf16(array: &[u16]) -> String { | |
let null = array.iter().position(|&c| c == 0).unwrap_or(array.len()); | |
String::from_utf16_lossy(&array[..null]) | |
} | |
fn str_from_blob(blob: &ID3DBlob) -> &str { | |
let bytes = array_from_blob(blob); | |
std::str::from_utf8(bytes).unwrap() | |
} | |
fn array_from_blob(blob: &ID3DBlob) -> &[u8] { | |
let ptr = unsafe { blob.GetBufferPointer() }; | |
let size = unsafe { blob.GetBufferSize() }; | |
unsafe { std::slice::from_raw_parts(ptr as *const u8, size) } | |
} | |
unsafe extern "system" fn debug_callback( | |
category: D3D12_MESSAGE_CATEGORY, | |
severity: D3D12_MESSAGE_SEVERITY, | |
id: D3D12_MESSAGE_ID, | |
pdescription: PCSTR, | |
_pcontext: *mut core::ffi::c_void, | |
) { | |
let e = catch_unwind(|| { | |
let description = pdescription.to_string().unwrap(); | |
eprintln!("D3D12: {:?} {:?} {:?} {:?}", category, severity, id, description); | |
}); | |
if e.is_err() { | |
eprintln!("Paniced in debug callback!"); | |
} | |
} | |
const FRAMES_IN_FLIGHT: usize = 2; | |
const SWAPCHAIN_FLAGS: u32 = (DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING.0 | |
| DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT.0) as _; | |
struct FrameSet { | |
rtv_heap: ID3D12DescriptorHeap, | |
frame: ArrayVec<FrameData, FRAMES_IN_FLIGHT>, | |
swapchain: ArrayVec<SwapchainData, FRAMES_IN_FLIGHT>, | |
index: u32, | |
} | |
impl FrameSet { | |
fn set_index(&mut self, index: usize) { | |
self.index = index as u32; | |
} | |
fn get(&mut self) -> (&mut FrameData, &mut SwapchainData) { | |
let frame = &mut self.frame[self.index as usize]; | |
let swapchain = &mut self.swapchain[self.index as usize]; | |
(frame, swapchain) | |
} | |
} | |
struct FrameData { | |
fence_value: u64, | |
command_allocator: ID3D12CommandAllocator, | |
command_list: ID3D12GraphicsCommandList, | |
} | |
struct SwapchainData { | |
render_target: ID3D12Resource2, | |
rtv: D3D12_CPU_DESCRIPTOR_HANDLE, | |
} | |
pub struct Renderer { | |
dxgi_debug: Option<IDXGIDebug1>, | |
device: ID3D12Device8, | |
command_queue: ID3D12CommandQueue, | |
swapchain: IDXGISwapChain4, | |
waitable_object: HANDLE, | |
fence: ID3D12Fence1, | |
fence_value: u64, | |
frames: FrameSet, | |
root_signature: ID3D12RootSignature, | |
pipeline_state: ID3D12PipelineState, | |
size: glam::UVec2, | |
} | |
impl Renderer { | |
pub fn new(hwnd: HWND, size: glam::UVec2) -> Self { | |
unsafe { | |
let mut dxgi_debug: Option<IDXGIDebug1> = None; | |
let mut d3d12_debug: Option<ID3D12Debug6> = None; | |
if cfg!(debug_assertions) { | |
dxgi_debug = Some(DXGIGetDebugInterface1(0).unwrap()); | |
D3D12GetDebugInterface(&mut d3d12_debug).unwrap(); | |
if let Some(ref debug) = d3d12_debug { | |
debug.EnableDebugLayer(); | |
debug.SetEnableGPUBasedValidation(true); | |
} | |
} | |
dbg!(dxgi_debug.is_some()); | |
dbg!(d3d12_debug.is_some()); | |
let factory_flags = | |
if cfg!(debug_assertions) { DXGI_CREATE_FACTORY_DEBUG } else { 0x0 }; | |
let factory: IDXGIFactory7 = CreateDXGIFactory2(factory_flags).unwrap(); | |
let adapter: IDXGIAdapter4 = factory | |
.EnumAdapterByGpuPreference(0, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE) | |
.unwrap(); | |
let mut pdesc = DXGI_ADAPTER_DESC3::default(); | |
adapter.GetDesc3(&mut pdesc).unwrap(); | |
println!("Adapter: {:?}", string_from_utf16(&pdesc.Description)); | |
let mut device: Option<ID3D12Device8> = None; | |
D3D12CreateDevice(&adapter, D3D_FEATURE_LEVEL_11_0, &mut device).unwrap(); | |
let device = device.unwrap(); | |
let mut support_data = D3D12_FEATURE_DATA_D3D12_OPTIONS12::default(); | |
device | |
.CheckFeatureSupport( | |
D3D12_FEATURE_D3D12_OPTIONS12, | |
&mut support_data as *mut _ as *mut c_void, | |
std::mem::size_of_val(&support_data) as u32, | |
) | |
.unwrap(); | |
println!("Enhanced Barriers: {:?}", support_data.EnhancedBarriersSupported); | |
let info_queue = device.cast::<ID3D12InfoQueue1>().ok(); | |
if let Some(ref info_queue) = info_queue { | |
info_queue.SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, true).unwrap(); | |
info_queue.SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, true).unwrap(); | |
info_queue.SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_WARNING, true).unwrap(); | |
let mut cookie = 0; | |
info_queue | |
.RegisterMessageCallback( | |
Some(debug_callback), | |
D3D12_MESSAGE_CALLBACK_FLAG_NONE, | |
ptr::null_mut(), | |
&mut cookie, | |
) | |
.unwrap(); | |
assert_ne!(cookie, 0); | |
} | |
let fence: ID3D12Fence1 = device.CreateFence(0, D3D12_FENCE_FLAG_NONE).unwrap(); | |
let command_queue: ID3D12CommandQueue = device | |
.CreateCommandQueue(&D3D12_COMMAND_QUEUE_DESC { | |
Type: D3D12_COMMAND_LIST_TYPE_DIRECT, | |
..Default::default() | |
}) | |
.unwrap(); | |
let swapchain_desc = DXGI_SWAP_CHAIN_DESC1 { | |
Width: size.x, | |
Height: size.y, | |
Format: DXGI_FORMAT_R8G8B8A8_UNORM, | |
SampleDesc: DXGI_SAMPLE_DESC { Count: 1, ..Default::default() }, | |
BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT, | |
BufferCount: FRAMES_IN_FLIGHT as _, | |
SwapEffect: DXGI_SWAP_EFFECT_FLIP_DISCARD, | |
AlphaMode: DXGI_ALPHA_MODE_IGNORE, | |
Flags: SWAPCHAIN_FLAGS, | |
..Default::default() | |
}; | |
let swapchain: IDXGISwapChain4 = factory | |
.CreateSwapChainForHwnd(&command_queue, hwnd, &swapchain_desc, None, None) | |
.unwrap() | |
.cast() | |
.unwrap(); | |
let waitable_object = swapchain.GetFrameLatencyWaitableObject(); | |
factory.MakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER).unwrap(); | |
let frame_index = swapchain.GetCurrentBackBufferIndex(); | |
let rtv_heap: ID3D12DescriptorHeap = device | |
.CreateDescriptorHeap(&D3D12_DESCRIPTOR_HEAP_DESC { | |
Type: D3D12_DESCRIPTOR_HEAP_TYPE_RTV, | |
NumDescriptors: FRAMES_IN_FLIGHT as _, | |
..Default::default() | |
}) | |
.unwrap(); | |
let rtv_descriptor_size = | |
device.GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); | |
let rtv_heap_base_ptr = rtv_heap.GetCPUDescriptorHandleForHeapStart().ptr; | |
let mut frames = FrameSet { | |
rtv_heap, | |
frame: ArrayVec::new(), | |
swapchain: ArrayVec::new(), | |
index: frame_index, | |
}; | |
for i in 0..FRAMES_IN_FLIGHT { | |
let render_target: ID3D12Resource2 = swapchain.GetBuffer(i as _).unwrap(); | |
let rtv_desc_handle = D3D12_CPU_DESCRIPTOR_HANDLE { | |
ptr: rtv_heap_base_ptr + i * rtv_descriptor_size as usize, | |
}; | |
// Creates the RTV in the RTV heap | |
device.CreateRenderTargetView( | |
&render_target, | |
Some(&D3D12_RENDER_TARGET_VIEW_DESC { | |
Format: DXGI_FORMAT_R8G8B8A8_UNORM_SRGB, | |
ViewDimension: D3D12_RTV_DIMENSION_TEXTURE2D, | |
..Default::default() | |
}), | |
rtv_desc_handle, | |
); | |
let command_allocator: ID3D12CommandAllocator = | |
device.CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT).unwrap(); | |
let command_list: ID3D12GraphicsCommandList = device | |
.CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, &command_allocator, None) | |
.unwrap(); | |
command_list.Close().unwrap(); | |
frames.frame.push(FrameData { fence_value: 0, command_allocator, command_list }); | |
frames.swapchain.push(SwapchainData { render_target, rtv: rtv_desc_handle }); | |
} | |
let vs_dxil = include_bytes!("../shaders/dxil/triangle.vs.cso"); | |
let ps_dxil = include_bytes!("../shaders/dxil/triangle.ps.cso"); | |
let root_signature: ID3D12RootSignature = { | |
let root_signature_desc = D3D12_ROOT_SIGNATURE_DESC { | |
NumParameters: 0, | |
pParameters: ptr::null(), | |
NumStaticSamplers: 0, | |
pStaticSamplers: ptr::null(), | |
Flags: D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT, | |
}; | |
let mut root_sig_blob = None; | |
let mut error = None; | |
D3D12SerializeRootSignature( | |
&root_signature_desc, | |
D3D_ROOT_SIGNATURE_VERSION_1, | |
&mut root_sig_blob, | |
Some(&mut error), | |
) | |
.unwrap(); | |
if let Some(error) = error { | |
panic!("Error serializing root signature: {:?}", str_from_blob(&error)); | |
} | |
device.CreateRootSignature(0, array_from_blob(&root_sig_blob.unwrap())).unwrap() | |
}; | |
let pipeline_desc = D3D12_GRAPHICS_PIPELINE_STATE_DESC { | |
pRootSignature: std::mem::transmute_copy(&root_signature), | |
VS: D3D12_SHADER_BYTECODE { | |
pShaderBytecode: vs_dxil.as_ptr() as *const c_void, | |
BytecodeLength: vs_dxil.len(), | |
}, | |
PS: D3D12_SHADER_BYTECODE { | |
pShaderBytecode: ps_dxil.as_ptr() as *const c_void, | |
BytecodeLength: ps_dxil.len(), | |
}, | |
BlendState: { | |
let mut blend_state = D3D12_BLEND_DESC::default(); | |
blend_state.RenderTarget[0].RenderTargetWriteMask = | |
D3D12_COLOR_WRITE_ENABLE_ALL.0 as u8; | |
blend_state | |
}, | |
SampleMask: u32::MAX, | |
RasterizerState: D3D12_RASTERIZER_DESC { | |
FillMode: D3D12_FILL_MODE_SOLID, | |
CullMode: D3D12_CULL_MODE_NONE, | |
..Default::default() | |
}, | |
DepthStencilState: D3D12_DEPTH_STENCIL_DESC::default(), | |
InputLayout: D3D12_INPUT_LAYOUT_DESC::default(), | |
PrimitiveTopologyType: D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE, | |
NumRenderTargets: 1, | |
RTVFormats: { | |
let mut formats = [DXGI_FORMAT::default(); 8]; | |
formats[0] = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; | |
formats | |
}, | |
SampleDesc: DXGI_SAMPLE_DESC { Count: 1, ..Default::default() }, | |
..Default::default() | |
}; | |
let pipeline_state: ID3D12PipelineState = | |
device.CreateGraphicsPipelineState(&pipeline_desc).unwrap(); | |
Self { | |
dxgi_debug, | |
device, | |
command_queue, | |
swapchain, | |
waitable_object, | |
fence, | |
fence_value: 1, | |
frames, | |
root_signature, | |
pipeline_state, | |
size, | |
} | |
} | |
} | |
pub fn resize(&mut self, size: glam::UVec2) { | |
unsafe { | |
// Wait for idle | |
self.command_queue.Signal(&self.fence, self.fence_value).unwrap(); | |
self.fence.SetEventOnCompletion(self.fence_value, None).unwrap(); | |
// Resize the swapchain | |
self.frames.swapchain.clear(); | |
self.swapchain | |
.ResizeBuffers( | |
FRAMES_IN_FLIGHT as _, | |
size.x, | |
size.y, | |
DXGI_FORMAT_R8G8B8A8_UNORM, | |
SWAPCHAIN_FLAGS, | |
) | |
.unwrap(); | |
// Recreate the RTVs | |
let rtv_base_ptr = self.frames.rtv_heap.GetCPUDescriptorHandleForHeapStart().ptr; | |
let rtv_descriptor_size = | |
self.device.GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); | |
for i in 0..FRAMES_IN_FLIGHT { | |
let render_target: ID3D12Resource2 = self.swapchain.GetBuffer(i as _).unwrap(); | |
let rtv_desc_handle = D3D12_CPU_DESCRIPTOR_HANDLE { | |
ptr: rtv_base_ptr + i * rtv_descriptor_size as usize, | |
}; | |
// Creates the RTV in the RTV heap | |
self.device.CreateRenderTargetView( | |
&render_target, | |
Some(&D3D12_RENDER_TARGET_VIEW_DESC { | |
Format: DXGI_FORMAT_R8G8B8A8_UNORM_SRGB, | |
ViewDimension: D3D12_RTV_DIMENSION_TEXTURE2D, | |
..Default::default() | |
}), | |
rtv_desc_handle, | |
); | |
self.frames.swapchain.push(SwapchainData { render_target, rtv: rtv_desc_handle }); | |
} | |
// Update the state | |
self.size = size; | |
self.fence_value += 1; | |
} | |
} | |
pub fn render(&mut self) { | |
unsafe { | |
self.frames.set_index(self.swapchain.GetCurrentBackBufferIndex() as usize); | |
let (frame, swapchain) = self.frames.get(); | |
// --- Wait for the previous frame to finish --- | |
self.fence.SetEventOnCompletion(frame.fence_value, None).unwrap(); | |
let wait_result = WaitForSingleObject(self.waitable_object, 5_000); | |
assert_eq!(wait_result, WAIT_OBJECT_0); | |
// --- Reset the command allocator and command list --- | |
frame.command_allocator.Reset().unwrap(); | |
frame.command_list.Reset(&frame.command_allocator, None).unwrap(); | |
// --- Record the command list --- | |
let viewport = D3D12_VIEWPORT { | |
TopLeftX: 0.0, | |
TopLeftY: 0.0, | |
Width: self.size.x as f32, | |
Height: self.size.y as f32, | |
MinDepth: 0.0, | |
MaxDepth: 1.0, | |
}; | |
let scissor_rect = | |
RECT { left: 0, top: 0, right: self.size.x as i32, bottom: self.size.y as i32 }; | |
frame.command_list.RSSetViewports(slice::from_ref(&viewport)); | |
frame.command_list.RSSetScissorRects(slice::from_ref(&scissor_rect)); | |
frame.command_list.ResourceBarrier(&[transition_barrier( | |
&swapchain.render_target, | |
D3D12_RESOURCE_STATE_PRESENT, | |
D3D12_RESOURCE_STATE_RENDER_TARGET, | |
)]); | |
frame.command_list.OMSetRenderTargets(1, Some(&swapchain.rtv), false, None); | |
frame.command_list.ClearRenderTargetView(swapchain.rtv, &[0.0, 0.2, 0.4, 1.0], None); | |
frame.command_list.SetGraphicsRootSignature(&self.root_signature); | |
frame.command_list.SetPipelineState(&self.pipeline_state); | |
frame.command_list.IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); | |
frame.command_list.DrawInstanced(3, 1, 0, 0); | |
frame.command_list.ResourceBarrier(&[transition_barrier( | |
&swapchain.render_target, | |
D3D12_RESOURCE_STATE_RENDER_TARGET, | |
D3D12_RESOURCE_STATE_PRESENT, | |
)]); | |
// --- Execute the command list --- | |
frame.command_list.Close().unwrap(); | |
let command_lists = [Some(frame.command_list.cast::<ID3D12CommandList>().unwrap())]; | |
self.command_queue.ExecuteCommandLists(&command_lists); | |
self.command_queue.Signal(&self.fence, self.fence_value).unwrap(); | |
frame.fence_value = self.fence_value; | |
// --- Present the frame --- | |
self.swapchain.Present(1, 0).unwrap(); | |
// --- Prepare for the next frame --- | |
self.fence_value += 1; | |
} | |
} | |
pub fn destroy(self) { | |
unsafe { | |
self.command_queue.Signal(&self.fence, self.fence_value).unwrap(); | |
self.fence.SetEventOnCompletion(self.fence_value, None).unwrap() | |
}; | |
let dxgi_debug = self.dxgi_debug.clone(); | |
drop(self); | |
if let Some(debug) = dxgi_debug { | |
unsafe { | |
debug.ReportLiveObjects(DXGI_DEBUG_ALL, DXGI_DEBUG_RLO_DETAIL).unwrap(); | |
} | |
} | |
} | |
} | |
fn transition_barrier( | |
resource: &ID3D12Resource, | |
state_before: D3D12_RESOURCE_STATES, | |
state_after: D3D12_RESOURCE_STATES, | |
) -> D3D12_RESOURCE_BARRIER { | |
D3D12_RESOURCE_BARRIER { | |
Type: D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, | |
Flags: D3D12_RESOURCE_BARRIER_FLAG_NONE, | |
Anonymous: D3D12_RESOURCE_BARRIER_0 { | |
Transition: std::mem::ManuallyDrop::new(D3D12_RESOURCE_TRANSITION_BARRIER { | |
pResource: unsafe { std::mem::transmute_copy(resource) }, | |
StateBefore: state_before, | |
StateAfter: state_after, | |
Subresource: D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, | |
}), | |
}, | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment