Skip to content

Instantly share code, notes, and snippets.

@cwfitzgerald
Created May 20, 2024 13:37
Show Gist options
  • Save cwfitzgerald/f5e6f819e172c3e32084abd89b5f56d0 to your computer and use it in GitHub Desktop.
Save cwfitzgerald/f5e6f819e172c3e32084abd89b5f56d0 to your computer and use it in GitHub Desktop.
D3D12/SDL2 Init
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();
}
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