use std::cell::Cell;
use windows::core::{Interface, PCSTR, PCWSTR};
use windows::Win32::{
Foundation::*,
Graphics::{Direct3D::*, Direct3D12::*, Dxgi::Common::*, Dxgi::*},
System::{Com::*, Threading::*, WindowsProgramming::*},
};
#[repr(C)]
struct Vertex {
position: [f32; 3],
uv: [f32; 2],
}
impl Vertex {
const fn new(position: [f32; 3], uv: [f32; 2]) -> Self {
Self { position, uv }
}
}
fn resource_barrier(
command_list: &ID3D12GraphicsCommandList,
resource: &ID3D12Resource,
before: D3D12_RESOURCE_STATES,
after: D3D12_RESOURCE_STATES,
) {
unsafe {
let mut 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: Some(resource.clone()),
Subresource: 0,
StateBefore: before,
StateAfter: after,
}),
},
}];
command_list.ResourceBarrier(&barrier);
std::mem::ManuallyDrop::drop(&mut barrier[0].Anonymous.Transition);
}
}
struct Application {
device: ID3D12Device,
command_queue: ID3D12CommandQueue,
command_allocator: ID3D12CommandAllocator,
command_list: ID3D12GraphicsCommandList,
swap_chain: IDXGISwapChain4,
rtv_heap: ID3D12DescriptorHeap,
rtv_descriptor_size: usize,
render_targets: Vec<ID3D12Resource>,
_vertex_buffer: ID3D12Resource,
vbv: D3D12_VERTEX_BUFFER_VIEW,
_index_buffer: ID3D12Resource,
ibv: D3D12_INDEX_BUFFER_VIEW,
tex: ID3D12Resource,
srv_heap: ID3D12DescriptorHeap,
root_signature: ID3D12RootSignature,
pipeline: ID3D12PipelineState,
fence: ID3D12Fence,
fence_value: Cell<u64>,
context: mltg::Context<mltg::Direct3D12>,
image: mltg::Image,
target: Option<mltg::d3d12::RenderTarget>,
}
impl Application {
fn new() -> anyhow::Result<Self> {
unsafe {
CoInitialize(std::ptr::null_mut())?;
let window = wita::WindowBuilder::new()
.title("mltg offscreen d3d12")
.build()?;
let window_size = window.inner_size();
if cfg!(debug_assertions) {
let debug = {
let mut debug: Option<ID3D12Debug> = None;
D3D12GetDebugInterface(&mut debug).map(|_| debug.unwrap())?
};
debug.EnableDebugLayer();
}
let device = {
let mut device: Option<ID3D12Device> = None;
D3D12CreateDevice(None, D3D_FEATURE_LEVEL_12_0, &mut device)
.map(|_| device.unwrap())?
};
let command_queue: ID3D12CommandQueue =
device.CreateCommandQueue(&D3D12_COMMAND_QUEUE_DESC {
Type: D3D12_COMMAND_LIST_TYPE_DIRECT,
..Default::default()
})?;
let command_allocator: ID3D12CommandAllocator =
device.CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT)?;
let command_list: ID3D12GraphicsCommandList = device.CreateCommandList(
0,
D3D12_COMMAND_LIST_TYPE_DIRECT,
&command_allocator,
None,
)?;
command_list.SetName("Application::command_list").unwrap();
command_list.Close().unwrap();
let dxgi_factory: IDXGIFactory4 = CreateDXGIFactory1()?;
let swap_chain: IDXGISwapChain4 = {
dxgi_factory
.CreateSwapChainForHwnd(
&command_queue,
HWND(window.raw_handle() as _),
&DXGI_SWAP_CHAIN_DESC1 {
Width: window_size.width as _,
Height: window_size.height as _,
Format: DXGI_FORMAT_R8G8B8A8_UNORM,
BufferCount: 2,
SwapEffect: DXGI_SWAP_EFFECT_FLIP_DISCARD,
Scaling: DXGI_SCALING_NONE,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
..Default::default()
},
std::ptr::null(),
None,
)?
.cast()?
};
let rtv_heap: ID3D12DescriptorHeap =
device.CreateDescriptorHeap(&D3D12_DESCRIPTOR_HEAP_DESC {
Type: D3D12_DESCRIPTOR_HEAP_TYPE_RTV,
NumDescriptors: 2,
..Default::default()
})?;
let rtv_descriptor_size =
device.GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV) as usize;
let render_targets = {
let mut handle = rtv_heap.GetCPUDescriptorHandleForHeapStart();
let mut buffers = Vec::with_capacity(2);
for i in 0..2 {
let buffer: ID3D12Resource = swap_chain.GetBuffer(i as _)?;
device.CreateRenderTargetView(&buffer, std::ptr::null(), handle);
buffers.push(buffer);
handle.ptr += rtv_descriptor_size;
}
buffers
};
const VERTICES: [Vertex; 4] = [
Vertex::new([-0.5, 0.5, 0.0], [0.0, 0.0]),
Vertex::new([0.5, 0.5, 0.0], [1.0, 0.0]),
Vertex::new([-0.5, -0.5, 0.0], [0.0, 1.0]),
Vertex::new([0.5, -0.5, 0.0], [1.0, 1.0]),
];
const INDICES: [u32; 6] = [0, 1, 2, 1, 3, 2];
let vertex_buffer = {
let mut vb: Option<ID3D12Resource> = None;
let vb = device
.CreateCommittedResource(
&D3D12_HEAP_PROPERTIES {
Type: D3D12_HEAP_TYPE_UPLOAD,
CreationNodeMask: 1,
VisibleNodeMask: 1,
..Default::default()
},
D3D12_HEAP_FLAG_NONE,
&D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_BUFFER,
Width: std::mem::size_of_val(&VERTICES) as _,
Height: 1,
DepthOrArraySize: 1,
MipLevels: 1,
Format: DXGI_FORMAT_UNKNOWN,
Layout: D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
..Default::default()
},
D3D12_RESOURCE_STATE_GENERIC_READ,
std::ptr::null(),
&mut vb,
)
.map(|_| vb.unwrap())?;
let mut p = std::ptr::null_mut();
vb.Map(0, std::ptr::null(), &mut p).unwrap();
std::ptr::copy_nonoverlapping(&VERTICES, p as _, VERTICES.len());
vb.Unmap(0, std::ptr::null());
vb
};
let vbv = D3D12_VERTEX_BUFFER_VIEW {
BufferLocation: vertex_buffer.GetGPUVirtualAddress(),
StrideInBytes: std::mem::size_of::<Vertex>() as _,
SizeInBytes: std::mem::size_of_val(&VERTICES) as _,
};
let index_buffer = {
let mut ib: Option<ID3D12Resource> = None;
let ib = device
.CreateCommittedResource(
&D3D12_HEAP_PROPERTIES {
Type: D3D12_HEAP_TYPE_UPLOAD,
CreationNodeMask: 1,
VisibleNodeMask: 1,
..Default::default()
},
D3D12_HEAP_FLAG_NONE,
&D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_BUFFER,
Width: std::mem::size_of_val(&INDICES) as _,
Height: 1,
DepthOrArraySize: 1,
MipLevels: 1,
Format: DXGI_FORMAT_UNKNOWN,
Layout: D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
..Default::default()
},
D3D12_RESOURCE_STATE_GENERIC_READ,
std::ptr::null(),
&mut ib,
)
.map(|_| ib.unwrap())?;
let mut p = std::ptr::null_mut();
ib.Map(0, std::ptr::null(), &mut p).unwrap();
std::ptr::copy_nonoverlapping(&INDICES, p as _, INDICES.len());
ib.Unmap(0, std::ptr::null());
ib
};
let ibv = D3D12_INDEX_BUFFER_VIEW {
BufferLocation: index_buffer.GetGPUVirtualAddress(),
SizeInBytes: std::mem::size_of_val(&INDICES) as _,
Format: DXGI_FORMAT_R32_UINT,
};
let tex = {
let mut tex: Option<ID3D12Resource> = None;
device
.CreateCommittedResource(
&D3D12_HEAP_PROPERTIES {
Type: D3D12_HEAP_TYPE_DEFAULT,
CreationNodeMask: 1,
VisibleNodeMask: 1,
..Default::default()
},
D3D12_HEAP_FLAG_NONE,
&D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_TEXTURE2D,
Width: window_size.width as _,
Height: window_size.height,
DepthOrArraySize: 1,
MipLevels: 1,
Format: DXGI_FORMAT_R8G8B8A8_UNORM,
Flags: D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET
| D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
..Default::default()
},
D3D12_RESOURCE_STATE_COMMON,
&D3D12_CLEAR_VALUE {
Format: DXGI_FORMAT_R8G8B8A8_UNORM,
Anonymous: D3D12_CLEAR_VALUE_0 {
Color: [0.0, 0.5, 0.0, 0.5],
},
},
&mut tex,
)
.map(|_| tex.unwrap())?
};
tex.SetName("Application::tex").unwrap();
let srv_heap: ID3D12DescriptorHeap =
device.CreateDescriptorHeap(&D3D12_DESCRIPTOR_HEAP_DESC {
Type: D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV,
Flags: D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE,
NumDescriptors: 1,
..Default::default()
})?;
device.CreateShaderResourceView(
&tex,
&D3D12_SHADER_RESOURCE_VIEW_DESC {
ViewDimension: D3D12_SRV_DIMENSION_TEXTURE2D,
Format: DXGI_FORMAT_R8G8B8A8_UNORM,
Shader4ComponentMapping: D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
Anonymous: D3D12_SHADER_RESOURCE_VIEW_DESC_0 {
Texture2D: D3D12_TEX2D_SRV {
MipLevels: 1,
..Default::default()
},
},
},
srv_heap.GetCPUDescriptorHandleForHeapStart(),
);
let root_signature: ID3D12RootSignature = {
let ranges = [D3D12_DESCRIPTOR_RANGE {
RangeType: D3D12_DESCRIPTOR_RANGE_TYPE_SRV,
NumDescriptors: 1,
OffsetInDescriptorsFromTableStart: D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND,
..Default::default()
}];
let params = [D3D12_ROOT_PARAMETER {
ParameterType: D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE,
ShaderVisibility: D3D12_SHADER_VISIBILITY_PIXEL,
Anonymous: D3D12_ROOT_PARAMETER_0 {
DescriptorTable: D3D12_ROOT_DESCRIPTOR_TABLE {
NumDescriptorRanges: ranges.len() as _,
pDescriptorRanges: ranges.as_ptr() as _,
},
},
}];
let sampler = [D3D12_STATIC_SAMPLER_DESC {
Filter: D3D12_FILTER_MIN_MAG_MIP_LINEAR,
AddressU: D3D12_TEXTURE_ADDRESS_MODE_CLAMP,
AddressV: D3D12_TEXTURE_ADDRESS_MODE_CLAMP,
AddressW: D3D12_TEXTURE_ADDRESS_MODE_CLAMP,
MinLOD: 0.0,
MaxLOD: f32::MAX,
ShaderVisibility: D3D12_SHADER_VISIBILITY_PIXEL,
..Default::default()
}];
let root = D3D12_ROOT_SIGNATURE_DESC {
NumParameters: params.len() as _,
pParameters: params.as_ptr() as _,
NumStaticSamplers: sampler.len() as _,
pStaticSamplers: sampler.as_ptr() as _,
Flags: D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT,
};
let blob = {
let mut p = None;
D3D12SerializeRootSignature(
&root,
D3D_ROOT_SIGNATURE_VERSION_1,
&mut p,
std::ptr::null_mut(),
)
.map(|_| p.unwrap())?
};
device.CreateRootSignature(
0,
std::slice::from_raw_parts(
blob.GetBufferPointer() as *const u8,
blob.GetBufferSize(),
),
)?
};
let pipeline: ID3D12PipelineState = {
let vs_blob = include_bytes!("d3d12_hlsl/tex.vs");
let ps_blob = include_bytes!("d3d12_hlsl/tex.ps");
let input_layout = [
D3D12_INPUT_ELEMENT_DESC {
SemanticName: PCSTR(b"POSITION\0".as_ptr() as _),
SemanticIndex: 0,
Format: DXGI_FORMAT_R32G32B32_FLOAT,
AlignedByteOffset: 0,
InputSlot: 0,
InputSlotClass: D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA,
InstanceDataStepRate: 0,
},
D3D12_INPUT_ELEMENT_DESC {
SemanticName: PCSTR(b"TEXCOORD\0".as_ptr() as _),
SemanticIndex: 0,
Format: DXGI_FORMAT_R32G32_FLOAT,
AlignedByteOffset: D3D12_APPEND_ALIGNED_ELEMENT,
InputSlot: 0,
InputSlotClass: D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA,
InstanceDataStepRate: 0,
},
];
let render_target_blend = D3D12_RENDER_TARGET_BLEND_DESC {
BlendEnable: true.into(),
LogicOpEnable: false.into(),
SrcBlend: D3D12_BLEND_SRC_ALPHA,
DestBlend: D3D12_BLEND_INV_SRC_ALPHA,
BlendOp: D3D12_BLEND_OP_ADD,
SrcBlendAlpha: D3D12_BLEND_ONE,
DestBlendAlpha: D3D12_BLEND_ZERO,
BlendOpAlpha: D3D12_BLEND_OP_ADD,
LogicOp: D3D12_LOGIC_OP_NOOP,
RenderTargetWriteMask: D3D12_COLOR_WRITE_ENABLE_ALL.0 as _,
};
let mut rtv_formats = [DXGI_FORMAT_UNKNOWN; 8];
rtv_formats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
let desc = D3D12_GRAPHICS_PIPELINE_STATE_DESC {
pRootSignature: Some(root_signature.clone()),
VS: D3D12_SHADER_BYTECODE {
pShaderBytecode: vs_blob.as_ptr() as _,
BytecodeLength: vs_blob.len() as _,
},
PS: D3D12_SHADER_BYTECODE {
pShaderBytecode: ps_blob.as_ptr() as _,
BytecodeLength: ps_blob.len() as _,
},
InputLayout: D3D12_INPUT_LAYOUT_DESC {
pInputElementDescs: input_layout.as_ptr() as _,
NumElements: input_layout.len() as _,
},
BlendState: D3D12_BLEND_DESC {
AlphaToCoverageEnable: false.into(),
IndependentBlendEnable: false.into(),
RenderTarget: [render_target_blend; 8],
},
RasterizerState: D3D12_RASTERIZER_DESC {
FillMode: D3D12_FILL_MODE_SOLID,
CullMode: D3D12_CULL_MODE_NONE,
..Default::default()
},
SampleMask: u32::MAX,
PrimitiveTopologyType: D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE,
NumRenderTargets: 1,
RTVFormats: rtv_formats,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
..Default::default()
};
device.CreateGraphicsPipelineState(&desc)?
};
let fence = device.CreateFence(0, D3D12_FENCE_FLAG_NONE)?;
let context = mltg::Context::new(mltg::Direct3D12::new(&device, &command_queue)?)?;
let factory = context.create_factory();
let target = context.create_render_target(&tex)?;
let image = factory.create_image("examples/ferris.png")?;
Ok(Self {
device,
command_queue,
command_allocator,
command_list,
swap_chain,
rtv_heap,
rtv_descriptor_size,
render_targets,
_vertex_buffer: vertex_buffer,
vbv,
_index_buffer: index_buffer,
ibv,
tex,
srv_heap,
root_signature,
pipeline,
fence,
fence_value: Cell::new(1),
context,
image,
target: Some(target),
})
}
}
fn wait_gpu(&self) {
unsafe {
let fv = self.fence_value.get();
self.command_queue.Signal(&self.fence, fv).unwrap();
if self.fence.GetCompletedValue() < fv {
let event =
CreateEventW(std::ptr::null_mut(), false, false, PCWSTR::default()).unwrap();
self.fence.SetEventOnCompletion(fv, event).unwrap();
WaitForSingleObject(event, INFINITE);
CloseHandle(event);
}
self.fence_value.set(fv + 1);
}
}
}
impl wita::EventHandler for Application {
fn draw(&mut self, ev: wita::event::Draw) {
let window_size = ev.window.inner_size();
unsafe {
let ret = self.context.draw(self.target.as_ref().unwrap(), |cmd| {
let desc = self.tex.GetDesc();
cmd.clear([0.0, 0.0, 0.0, 0.0]);
cmd.draw_image(
&self.image,
mltg::Rect::new((0.0, 0.0), (desc.Width as f32, desc.Height as f32)),
None,
mltg::Interpolation::HighQualityCubic,
);
});
match ret {
Ok(_) => {
self.swap_chain.Present(1, 0).unwrap();
}
Err(e) if e == mltg::ErrorKind::RecreateTarget => {
self.target = None;
self.context.flush();
self.target = Some(self.context.create_render_target(&self.tex).unwrap());
ev.window.redraw();
}
Err(e) => panic!("{:?}", e),
}
let index = self.swap_chain.GetCurrentBackBufferIndex() as usize;
let rtv_handle = {
let mut handle = self.rtv_heap.GetCPUDescriptorHandleForHeapStart();
handle.ptr += self.rtv_descriptor_size * index;
handle
};
self.command_allocator.Reset().unwrap();
self.command_list
.Reset(&self.command_allocator, &self.pipeline)
.unwrap();
self.command_list
.SetDescriptorHeaps(&[Some(self.srv_heap.clone())]);
self.command_list
.SetGraphicsRootSignature(&self.root_signature);
self.command_list.SetGraphicsRootDescriptorTable(
0,
self.srv_heap.GetGPUDescriptorHandleForHeapStart(),
);
self.command_list.RSSetViewports(&[D3D12_VIEWPORT {
Width: window_size.width as _,
Height: window_size.height as _,
MaxDepth: 1.0,
..Default::default()
}]);
self.command_list.RSSetScissorRects(&[RECT {
right: window_size.width as _,
bottom: window_size.height as _,
..Default::default()
}]);
resource_barrier(
&self.command_list,
&self.render_targets[index],
D3D12_RESOURCE_STATE_PRESENT,
D3D12_RESOURCE_STATE_RENDER_TARGET,
);
resource_barrier(
&self.command_list,
&self.tex,
D3D12_RESOURCE_STATE_COMMON,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
);
self.command_list
.ClearRenderTargetView(rtv_handle, [0.0, 0.0, 0.3, 0.0].as_ptr(), &[]);
self.command_list.OMSetRenderTargets(
1,
[rtv_handle.clone()].as_ptr(),
false,
std::ptr::null(),
);
self.command_list.IASetVertexBuffers(0, &[self.vbv.clone()]);
self.command_list.IASetIndexBuffer(&self.ibv);
self.command_list
.IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
self.command_list.DrawIndexedInstanced(6, 1, 0, 0, 0);
resource_barrier(
&self.command_list,
&self.tex,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
D3D12_RESOURCE_STATE_COMMON,
);
resource_barrier(
&self.command_list,
&self.render_targets[index],
D3D12_RESOURCE_STATE_RENDER_TARGET,
D3D12_RESOURCE_STATE_PRESENT,
);
self.command_list.Close().unwrap();
let command_lists = [Some(self.command_list.clone().cast().unwrap())];
self.command_queue.ExecuteCommandLists(&command_lists);
self.swap_chain.Present(1, 0).unwrap();
}
self.wait_gpu();
}
fn resizing(&mut self, ev: wita::event::Resizing) {
unsafe {
self.render_targets.clear();
self.swap_chain
.ResizeBuffers(0, ev.size.width, ev.size.height, DXGI_FORMAT_UNKNOWN, 0)
.unwrap();
self.render_targets = {
let mut handle = self.rtv_heap.GetCPUDescriptorHandleForHeapStart();
let mut buffers = vec![];
for i in 0..2 {
let buffer: ID3D12Resource = self.swap_chain.GetBuffer(i as _).unwrap();
self.device
.CreateRenderTargetView(&buffer, std::ptr::null(), handle);
buffers.push(buffer);
handle.ptr += self.rtv_descriptor_size;
}
buffers
};
}
ev.window.redraw();
}
}
fn main() {
wita::run(wita::RunType::Wait, Application::new).unwrap();
}