test(d3d12): Add D3D12 render test
This commit is contained in:
parent
d33c2a84b2
commit
5c726efe21
|
@ -28,7 +28,7 @@ opengl = ["librashader/runtime-gl", "dep:glow", "dep:glfw"]
|
||||||
wgpu = ["librashader/runtime-wgpu", "dep:wgpu", "dep:wgpu-types"]
|
wgpu = ["librashader/runtime-wgpu", "dep:wgpu", "dep:wgpu-types"]
|
||||||
|
|
||||||
d3d11 = ["librashader/runtime-d3d11", "dep:windows"]
|
d3d11 = ["librashader/runtime-d3d11", "dep:windows"]
|
||||||
d3d12 = ["librashader/runtime-d3d12", "dep:windows"]
|
d3d12 = ["librashader/runtime-d3d12", "dep:windows", "dep:d3d12-descriptor-heap"]
|
||||||
d3d9 = ["librashader/runtime-d3d9", "dep:windows"]
|
d3d9 = ["librashader/runtime-d3d9", "dep:windows"]
|
||||||
|
|
||||||
metal = ["librashader/runtime-metal", "dep:objc2", "dep:objc2-metal"]
|
metal = ["librashader/runtime-metal", "dep:objc2", "dep:objc2-metal"]
|
||||||
|
|
35
librashader-test/src/render/d3d12/descriptor_heap.rs
Normal file
35
librashader-test/src/render/d3d12/descriptor_heap.rs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
use d3d12_descriptor_heap::D3D12DescriptorHeapType;
|
||||||
|
use windows::Win32::Graphics::Direct3D12::{
|
||||||
|
D3D12_DESCRIPTOR_HEAP_DESC, D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
|
||||||
|
D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_TYPE_RTV,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct CpuStagingHeap;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct RenderTargetHeap;
|
||||||
|
|
||||||
|
impl D3D12DescriptorHeapType for RenderTargetHeap {
|
||||||
|
// Lut texture heaps are CPU only and get bound to the descriptor heap of the shader.
|
||||||
|
fn create_desc(size: usize) -> D3D12_DESCRIPTOR_HEAP_DESC {
|
||||||
|
D3D12_DESCRIPTOR_HEAP_DESC {
|
||||||
|
Type: D3D12_DESCRIPTOR_HEAP_TYPE_RTV,
|
||||||
|
NumDescriptors: size as u32,
|
||||||
|
Flags: D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
|
||||||
|
NodeMask: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl D3D12DescriptorHeapType for CpuStagingHeap {
|
||||||
|
// Lut texture heaps are CPU only and get bound to the descriptor heap of the shader.
|
||||||
|
fn create_desc(size: usize) -> D3D12_DESCRIPTOR_HEAP_DESC {
|
||||||
|
D3D12_DESCRIPTOR_HEAP_DESC {
|
||||||
|
Type: D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV,
|
||||||
|
NumDescriptors: size as u32,
|
||||||
|
Flags: D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
|
||||||
|
NodeMask: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
416
librashader-test/src/render/d3d12/mod.rs
Normal file
416
librashader-test/src/render/d3d12/mod.rs
Normal file
|
@ -0,0 +1,416 @@
|
||||||
|
mod descriptor_heap;
|
||||||
|
mod util;
|
||||||
|
|
||||||
|
use crate::render::d3d12::descriptor_heap::{CpuStagingHeap, RenderTargetHeap};
|
||||||
|
use crate::render::RenderTest;
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use d3d12_descriptor_heap::{D3D12DescriptorHeap, D3D12DescriptorHeapSlot};
|
||||||
|
use image::RgbaImage;
|
||||||
|
use librashader::runtime::d3d12::{
|
||||||
|
D3D12InputImage, D3D12OutputView, FilterChain, FilterChainOptions,
|
||||||
|
};
|
||||||
|
use librashader::runtime::Viewport;
|
||||||
|
use librashader_runtime::image::{Image, PixelFormat, UVDirection, BGRA8};
|
||||||
|
use std::ffi::CStr;
|
||||||
|
use std::path::Path;
|
||||||
|
use windows::core::Interface;
|
||||||
|
use windows::Win32::Foundation::CloseHandle;
|
||||||
|
use windows::Win32::Graphics::Direct3D::{D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_12_1};
|
||||||
|
use windows::Win32::Graphics::Direct3D12::{
|
||||||
|
D3D12CreateDevice, ID3D12CommandAllocator, ID3D12CommandQueue, ID3D12Device, ID3D12Fence,
|
||||||
|
ID3D12GraphicsCommandList, ID3D12InfoQueue1, ID3D12Resource, D3D12_COMMAND_LIST_TYPE_DIRECT,
|
||||||
|
D3D12_COMMAND_QUEUE_DESC, D3D12_COMMAND_QUEUE_FLAG_NONE, D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
|
||||||
|
D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE, D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
|
||||||
|
D3D12_FENCE_FLAG_NONE, D3D12_HEAP_FLAG_NONE, D3D12_HEAP_PROPERTIES, D3D12_HEAP_TYPE_CUSTOM,
|
||||||
|
D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_TYPE_UPLOAD, D3D12_MEMORY_POOL_L0,
|
||||||
|
D3D12_MEMORY_POOL_UNKNOWN, D3D12_MESSAGE_CALLBACK_FLAG_NONE, D3D12_MESSAGE_CATEGORY,
|
||||||
|
D3D12_MESSAGE_ID, D3D12_MESSAGE_SEVERITY, D3D12_PLACED_SUBRESOURCE_FOOTPRINT,
|
||||||
|
D3D12_RENDER_TARGET_VIEW_DESC, D3D12_RENDER_TARGET_VIEW_DESC_0, D3D12_RESOURCE_DESC,
|
||||||
|
D3D12_RESOURCE_DIMENSION_BUFFER, D3D12_RESOURCE_DIMENSION_TEXTURE2D,
|
||||||
|
D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET, D3D12_RESOURCE_STATE_ALL_SHADER_RESOURCE,
|
||||||
|
D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_GENERIC_READ,
|
||||||
|
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, D3D12_RESOURCE_STATE_RENDER_TARGET,
|
||||||
|
D3D12_RTV_DIMENSION_TEXTURE2D, D3D12_SHADER_RESOURCE_VIEW_DESC,
|
||||||
|
D3D12_SHADER_RESOURCE_VIEW_DESC_0, D3D12_SRV_DIMENSION_TEXTURE2D, D3D12_SUBRESOURCE_DATA,
|
||||||
|
D3D12_TEX2D_RTV, D3D12_TEX2D_SRV, D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
|
||||||
|
};
|
||||||
|
use windows::Win32::Graphics::Dxgi::Common::{DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_SAMPLE_DESC};
|
||||||
|
use windows::Win32::Graphics::Dxgi::{
|
||||||
|
CreateDXGIFactory2, IDXGIAdapter1, IDXGIFactory4, DXGI_ADAPTER_FLAG_NONE,
|
||||||
|
DXGI_ADAPTER_FLAG_SOFTWARE, DXGI_CREATE_FACTORY_DEBUG, DXGI_CREATE_FACTORY_FLAGS,
|
||||||
|
};
|
||||||
|
use windows::Win32::System::Threading::{CreateEventA, WaitForSingleObject, INFINITE};
|
||||||
|
|
||||||
|
pub struct Direct3D12 {
|
||||||
|
device: ID3D12Device,
|
||||||
|
cpu_heap: D3D12DescriptorHeap<CpuStagingHeap>,
|
||||||
|
rtv_heap: D3D12DescriptorHeap<RenderTargetHeap>,
|
||||||
|
|
||||||
|
texture: D3D12InputImage,
|
||||||
|
heap_slot: D3D12DescriptorHeapSlot<CpuStagingHeap>,
|
||||||
|
command_pool: ID3D12CommandAllocator,
|
||||||
|
queue: ID3D12CommandQueue,
|
||||||
|
image: Image<BGRA8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderTest for Direct3D12 {
|
||||||
|
fn new(path: impl AsRef<Path>) -> anyhow::Result<Self>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
Direct3D12::new(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&mut self, path: impl AsRef<Path>, frame_count: usize) -> anyhow::Result<RgbaImage> {
|
||||||
|
unsafe {
|
||||||
|
let descriptor = self.rtv_heap.allocate_descriptor()?;
|
||||||
|
|
||||||
|
let cmd: ID3D12GraphicsCommandList = self.device.CreateCommandList(
|
||||||
|
0,
|
||||||
|
D3D12_COMMAND_LIST_TYPE_DIRECT,
|
||||||
|
&self.command_pool,
|
||||||
|
None,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let fence_event = CreateEventA(None, false, false, None)?;
|
||||||
|
let fence: ID3D12Fence = self.device.CreateFence(0, D3D12_FENCE_FLAG_NONE)?;
|
||||||
|
|
||||||
|
let mut filter_chain = FilterChain::load_from_path(
|
||||||
|
path,
|
||||||
|
&self.device,
|
||||||
|
Some(&FilterChainOptions {
|
||||||
|
force_hlsl_pipeline: false,
|
||||||
|
force_no_mipmaps: false,
|
||||||
|
disable_cache: false,
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let mut output_texture = None;
|
||||||
|
let desc = D3D12_RESOURCE_DESC {
|
||||||
|
Dimension: D3D12_RESOURCE_DIMENSION_TEXTURE2D,
|
||||||
|
Alignment: 0,
|
||||||
|
Width: self.image.size.width as u64,
|
||||||
|
Height: self.image.size.height,
|
||||||
|
DepthOrArraySize: 1,
|
||||||
|
MipLevels: 1,
|
||||||
|
Format: DXGI_FORMAT_B8G8R8A8_UNORM,
|
||||||
|
SampleDesc: DXGI_SAMPLE_DESC {
|
||||||
|
Count: 1,
|
||||||
|
Quality: 0,
|
||||||
|
},
|
||||||
|
Layout: Default::default(),
|
||||||
|
Flags: D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET,
|
||||||
|
};
|
||||||
|
|
||||||
|
// We only need to draw one frame, so don't worry about the performance impact
|
||||||
|
// of this heap.
|
||||||
|
self.device.CreateCommittedResource(
|
||||||
|
&D3D12_HEAP_PROPERTIES {
|
||||||
|
Type: D3D12_HEAP_TYPE_CUSTOM,
|
||||||
|
CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE,
|
||||||
|
MemoryPoolPreference: D3D12_MEMORY_POOL_L0,
|
||||||
|
CreationNodeMask: 1,
|
||||||
|
VisibleNodeMask: 1,
|
||||||
|
},
|
||||||
|
D3D12_HEAP_FLAG_NONE,
|
||||||
|
&desc,
|
||||||
|
D3D12_RESOURCE_STATE_COMMON | D3D12_RESOURCE_STATE_RENDER_TARGET,
|
||||||
|
None,
|
||||||
|
&mut output_texture,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let output_texture: ID3D12Resource =
|
||||||
|
output_texture.ok_or_else(|| anyhow!("Failed to allocate resource"))?;
|
||||||
|
|
||||||
|
self.device
|
||||||
|
.CreateRenderTargetView(&output_texture, None, *descriptor.as_ref());
|
||||||
|
|
||||||
|
filter_chain.frame(
|
||||||
|
&cmd,
|
||||||
|
self.texture.clone(),
|
||||||
|
&Viewport::new_render_target_sized_origin(
|
||||||
|
D3D12OutputView::new_from_raw(
|
||||||
|
*descriptor.as_ref(),
|
||||||
|
self.image.size,
|
||||||
|
DXGI_FORMAT_B8G8R8A8_UNORM,
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)?,
|
||||||
|
frame_count,
|
||||||
|
None,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
cmd.Close()?;
|
||||||
|
self.queue.ExecuteCommandLists(&[Some(cmd.cast()?)]);
|
||||||
|
self.queue.Signal(&fence, 1)?;
|
||||||
|
|
||||||
|
if fence.GetCompletedValue() < 1 {
|
||||||
|
fence.SetEventOnCompletion(1, fence_event)?;
|
||||||
|
WaitForSingleObject(fence_event, INFINITE);
|
||||||
|
CloseHandle(fence_event)?;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut buffer = vec![0u8; self.image.bytes.len()];
|
||||||
|
|
||||||
|
output_texture.ReadFromSubresource(
|
||||||
|
buffer.as_mut_ptr().cast(),
|
||||||
|
4 * self.image.size.width,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
None,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
BGRA8::convert(&mut buffer);
|
||||||
|
|
||||||
|
let image = RgbaImage::from_raw(
|
||||||
|
self.image.size.width,
|
||||||
|
self.image.size.height,
|
||||||
|
Vec::from(buffer),
|
||||||
|
)
|
||||||
|
.ok_or(anyhow!("Unable to create image from data"))?;
|
||||||
|
|
||||||
|
Ok(image)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Direct3D12 {
|
||||||
|
pub fn new(image_path: impl AsRef<Path>) -> anyhow::Result<Self> {
|
||||||
|
let device = Self::create_device()?;
|
||||||
|
let mut heap = unsafe { D3D12DescriptorHeap::new(&device, 8)? };
|
||||||
|
let mut rtv_heap = unsafe { D3D12DescriptorHeap::new(&device, 16)? };
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let command_pool: ID3D12CommandAllocator =
|
||||||
|
device.CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT)?;
|
||||||
|
|
||||||
|
let queue: ID3D12CommandQueue =
|
||||||
|
device.CreateCommandQueue(&D3D12_COMMAND_QUEUE_DESC {
|
||||||
|
Type: D3D12_COMMAND_LIST_TYPE_DIRECT,
|
||||||
|
Priority: 0,
|
||||||
|
Flags: D3D12_COMMAND_QUEUE_FLAG_NONE,
|
||||||
|
NodeMask: 0,
|
||||||
|
})?;
|
||||||
|
let (image, texture, heap_slot) =
|
||||||
|
Self::load_image(&device, &command_pool, &queue, &mut heap, image_path)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
device,
|
||||||
|
cpu_heap: heap,
|
||||||
|
rtv_heap,
|
||||||
|
texture,
|
||||||
|
heap_slot,
|
||||||
|
command_pool,
|
||||||
|
image,
|
||||||
|
queue,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_device() -> anyhow::Result<ID3D12Device> {
|
||||||
|
let dxgi_factory_flags = DXGI_CREATE_FACTORY_DEBUG;
|
||||||
|
let dxgi_factory: IDXGIFactory4 = unsafe { CreateDXGIFactory2(dxgi_factory_flags) }?;
|
||||||
|
|
||||||
|
let adapter = Self::get_hardware_adapter(&dxgi_factory)?;
|
||||||
|
|
||||||
|
let mut device: Option<ID3D12Device> = None;
|
||||||
|
unsafe { D3D12CreateDevice(&adapter, D3D_FEATURE_LEVEL_12_1, &mut device) }?;
|
||||||
|
let device = device.ok_or(anyhow!("Failed to initialize D3D12 device"))?;
|
||||||
|
|
||||||
|
Ok(device)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_image(
|
||||||
|
device: &ID3D12Device,
|
||||||
|
command_pool: &ID3D12CommandAllocator,
|
||||||
|
queue: &ID3D12CommandQueue,
|
||||||
|
heap: &mut D3D12DescriptorHeap<CpuStagingHeap>,
|
||||||
|
path: impl AsRef<Path>,
|
||||||
|
) -> anyhow::Result<(
|
||||||
|
Image<BGRA8>,
|
||||||
|
D3D12InputImage,
|
||||||
|
D3D12DescriptorHeapSlot<CpuStagingHeap>,
|
||||||
|
)> {
|
||||||
|
// 1 time queue infrastructure for lut uploads
|
||||||
|
let image: Image<BGRA8> = Image::load(path, UVDirection::TopLeft)?;
|
||||||
|
|
||||||
|
let desc = D3D12_RESOURCE_DESC {
|
||||||
|
Dimension: D3D12_RESOURCE_DIMENSION_TEXTURE2D,
|
||||||
|
Alignment: 0,
|
||||||
|
Width: image.size.width as u64,
|
||||||
|
Height: image.size.height,
|
||||||
|
DepthOrArraySize: 1,
|
||||||
|
MipLevels: 1,
|
||||||
|
Format: DXGI_FORMAT_B8G8R8A8_UNORM,
|
||||||
|
SampleDesc: DXGI_SAMPLE_DESC {
|
||||||
|
Count: 1,
|
||||||
|
Quality: 0,
|
||||||
|
},
|
||||||
|
Layout: Default::default(),
|
||||||
|
Flags: Default::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let descriptor = heap.allocate_descriptor()?;
|
||||||
|
|
||||||
|
// create handles on GPU
|
||||||
|
let mut resource: Option<ID3D12Resource> = None;
|
||||||
|
unsafe {
|
||||||
|
let cmd: ID3D12GraphicsCommandList = device.CreateCommandList(
|
||||||
|
0,
|
||||||
|
D3D12_COMMAND_LIST_TYPE_DIRECT,
|
||||||
|
&command_pool.clone(),
|
||||||
|
None,
|
||||||
|
)?;
|
||||||
|
let fence_event = CreateEventA(None, false, false, None)?;
|
||||||
|
let fence: ID3D12Fence = device.CreateFence(0, D3D12_FENCE_FLAG_NONE)?;
|
||||||
|
|
||||||
|
device.CreateCommittedResource(
|
||||||
|
&D3D12_HEAP_PROPERTIES {
|
||||||
|
Type: D3D12_HEAP_TYPE_DEFAULT,
|
||||||
|
CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
|
||||||
|
MemoryPoolPreference: D3D12_MEMORY_POOL_UNKNOWN,
|
||||||
|
CreationNodeMask: 1,
|
||||||
|
VisibleNodeMask: 1,
|
||||||
|
},
|
||||||
|
D3D12_HEAP_FLAG_NONE,
|
||||||
|
&desc,
|
||||||
|
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
|
||||||
|
None,
|
||||||
|
&mut resource,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let resource = resource.ok_or_else(|| anyhow!("Failed to allocate resource"))?;
|
||||||
|
let srv_desc = D3D12_SHADER_RESOURCE_VIEW_DESC {
|
||||||
|
Format: desc.Format,
|
||||||
|
ViewDimension: D3D12_SRV_DIMENSION_TEXTURE2D,
|
||||||
|
Shader4ComponentMapping: D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
|
||||||
|
Anonymous: D3D12_SHADER_RESOURCE_VIEW_DESC_0 {
|
||||||
|
Texture2D: D3D12_TEX2D_SRV {
|
||||||
|
MipLevels: u32::MAX,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
device.CreateShaderResourceView(&resource, Some(&srv_desc), *descriptor.as_ref());
|
||||||
|
|
||||||
|
let mut buffer_desc = D3D12_RESOURCE_DESC {
|
||||||
|
Dimension: D3D12_RESOURCE_DIMENSION_BUFFER,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut layout = D3D12_PLACED_SUBRESOURCE_FOOTPRINT::default();
|
||||||
|
let mut total = 0;
|
||||||
|
// texture upload
|
||||||
|
device.GetCopyableFootprints(
|
||||||
|
&desc,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
Some(&mut layout),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
Some(&mut total),
|
||||||
|
);
|
||||||
|
|
||||||
|
buffer_desc.Width = total;
|
||||||
|
buffer_desc.Height = 1;
|
||||||
|
buffer_desc.DepthOrArraySize = 1;
|
||||||
|
buffer_desc.MipLevels = 1;
|
||||||
|
buffer_desc.SampleDesc.Count = 1;
|
||||||
|
buffer_desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
|
||||||
|
|
||||||
|
let mut upload: Option<ID3D12Resource> = None;
|
||||||
|
|
||||||
|
device.CreateCommittedResource(
|
||||||
|
&D3D12_HEAP_PROPERTIES {
|
||||||
|
Type: D3D12_HEAP_TYPE_UPLOAD,
|
||||||
|
CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
|
||||||
|
MemoryPoolPreference: D3D12_MEMORY_POOL_UNKNOWN,
|
||||||
|
CreationNodeMask: 1,
|
||||||
|
VisibleNodeMask: 1,
|
||||||
|
},
|
||||||
|
D3D12_HEAP_FLAG_NONE,
|
||||||
|
&buffer_desc,
|
||||||
|
D3D12_RESOURCE_STATE_GENERIC_READ,
|
||||||
|
None,
|
||||||
|
&mut upload,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let upload = upload.ok_or_else(|| anyhow!("Failed to allocate copy texture"))?;
|
||||||
|
|
||||||
|
let subresource = [D3D12_SUBRESOURCE_DATA {
|
||||||
|
pData: image.bytes.as_ptr().cast(),
|
||||||
|
RowPitch: 4 * image.size.width as isize,
|
||||||
|
SlicePitch: (4 * image.size.width * image.size.height) as isize,
|
||||||
|
}];
|
||||||
|
|
||||||
|
util::d3d12_resource_transition(
|
||||||
|
&cmd,
|
||||||
|
&resource,
|
||||||
|
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
|
||||||
|
D3D12_RESOURCE_STATE_COPY_DEST,
|
||||||
|
);
|
||||||
|
|
||||||
|
util::d3d12_update_subresources(&cmd, &resource, &upload, 0, 0, 1, &subresource)?;
|
||||||
|
|
||||||
|
util::d3d12_resource_transition(
|
||||||
|
&cmd,
|
||||||
|
&resource,
|
||||||
|
D3D12_RESOURCE_STATE_COPY_DEST,
|
||||||
|
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
|
||||||
|
);
|
||||||
|
|
||||||
|
cmd.Close()?;
|
||||||
|
queue.ExecuteCommandLists(&[Some(cmd.cast()?)]);
|
||||||
|
queue.Signal(&fence, 1)?;
|
||||||
|
|
||||||
|
if fence.GetCompletedValue() < 1 {
|
||||||
|
fence.SetEventOnCompletion(1, fence_event)?;
|
||||||
|
WaitForSingleObject(fence_event, INFINITE);
|
||||||
|
CloseHandle(fence_event)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
image,
|
||||||
|
D3D12InputImage {
|
||||||
|
resource,
|
||||||
|
descriptor: descriptor.as_ref().clone(),
|
||||||
|
},
|
||||||
|
descriptor,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_hardware_adapter(factory: &IDXGIFactory4) -> windows::core::Result<IDXGIAdapter1> {
|
||||||
|
for i in 0.. {
|
||||||
|
let adapter = unsafe { factory.EnumAdapters1(i)? };
|
||||||
|
|
||||||
|
let mut desc = unsafe { adapter.GetDesc1()? };
|
||||||
|
|
||||||
|
if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE.0 as u32) != DXGI_ADAPTER_FLAG_NONE.0 as u32
|
||||||
|
{
|
||||||
|
// Don't select the Basic Render Driver adapter. If you want a
|
||||||
|
// software adapter, pass in "/warp" on the command line.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check to see whether the adapter supports Direct3D 12, but don't
|
||||||
|
// create the actual device yet.
|
||||||
|
if unsafe {
|
||||||
|
D3D12CreateDevice(
|
||||||
|
&adapter,
|
||||||
|
D3D_FEATURE_LEVEL_11_0,
|
||||||
|
std::ptr::null_mut::<Option<ID3D12Device>>(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
return Ok(adapter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to warp
|
||||||
|
unsafe { factory.EnumWarpAdapter() }
|
||||||
|
}
|
||||||
|
}
|
213
librashader-test/src/render/d3d12/util.rs
Normal file
213
librashader-test/src/render/d3d12/util.rs
Normal file
|
@ -0,0 +1,213 @@
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use std::mem::ManuallyDrop;
|
||||||
|
use windows::Win32::Graphics::Direct3D12::{
|
||||||
|
ID3D12Device, ID3D12GraphicsCommandList, ID3D12Resource, D3D12_FEATURE_FORMAT_SUPPORT,
|
||||||
|
D3D12_MEMCPY_DEST, D3D12_PLACED_SUBRESOURCE_FOOTPRINT, D3D12_RESOURCE_BARRIER,
|
||||||
|
D3D12_RESOURCE_BARRIER_0, D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES,
|
||||||
|
D3D12_RESOURCE_BARRIER_FLAG_NONE, D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
|
||||||
|
D3D12_RESOURCE_DIMENSION_BUFFER, D3D12_RESOURCE_STATES, D3D12_RESOURCE_TRANSITION_BARRIER,
|
||||||
|
D3D12_SUBRESOURCE_DATA, D3D12_TEXTURE_COPY_LOCATION, D3D12_TEXTURE_COPY_LOCATION_0,
|
||||||
|
D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT, D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn d3d12_resource_transition(
|
||||||
|
cmd: &ID3D12GraphicsCommandList,
|
||||||
|
resource: &ID3D12Resource,
|
||||||
|
before: D3D12_RESOURCE_STATES,
|
||||||
|
after: D3D12_RESOURCE_STATES,
|
||||||
|
) {
|
||||||
|
d3d12_resource_transition_subresource(
|
||||||
|
cmd,
|
||||||
|
resource,
|
||||||
|
before,
|
||||||
|
after,
|
||||||
|
D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn d3d12_get_resource_transition_subresource(
|
||||||
|
resource: &ID3D12Resource,
|
||||||
|
before: D3D12_RESOURCE_STATES,
|
||||||
|
after: D3D12_RESOURCE_STATES,
|
||||||
|
subresource: u32,
|
||||||
|
) -> D3D12_RESOURCE_BARRIER {
|
||||||
|
D3D12_RESOURCE_BARRIER {
|
||||||
|
Type: D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
|
||||||
|
Flags: D3D12_RESOURCE_BARRIER_FLAG_NONE,
|
||||||
|
Anonymous: D3D12_RESOURCE_BARRIER_0 {
|
||||||
|
Transition: ManuallyDrop::new(D3D12_RESOURCE_TRANSITION_BARRIER {
|
||||||
|
pResource: ManuallyDrop::new(Some(resource.clone())),
|
||||||
|
Subresource: subresource,
|
||||||
|
StateBefore: before,
|
||||||
|
StateAfter: after,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn d3d12_resource_transition_subresource(
|
||||||
|
cmd: &ID3D12GraphicsCommandList,
|
||||||
|
resource: &ID3D12Resource,
|
||||||
|
before: D3D12_RESOURCE_STATES,
|
||||||
|
after: D3D12_RESOURCE_STATES,
|
||||||
|
subresource: u32,
|
||||||
|
) {
|
||||||
|
let barrier = [d3d12_get_resource_transition_subresource(
|
||||||
|
resource,
|
||||||
|
before,
|
||||||
|
after,
|
||||||
|
subresource,
|
||||||
|
)];
|
||||||
|
unsafe { cmd.ResourceBarrier(&barrier) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn d3d12_update_subresources(
|
||||||
|
cmd: &ID3D12GraphicsCommandList,
|
||||||
|
destination_resource: &ID3D12Resource,
|
||||||
|
intermediate_resource: &ID3D12Resource,
|
||||||
|
intermediate_offset: u64,
|
||||||
|
first_subresouce: u32,
|
||||||
|
num_subresources: u32,
|
||||||
|
source: &[D3D12_SUBRESOURCE_DATA],
|
||||||
|
) -> anyhow::Result<u64> {
|
||||||
|
// let allocation_size = std::mem::size_of::<D3D12_PLACED_SUBRESOURCE_FOOTPRINT>()
|
||||||
|
// + std::mem::size_of::<u32>()
|
||||||
|
// + std::mem::size_of::<u64>() * num_subresources;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let destination_desc = destination_resource.GetDesc();
|
||||||
|
let mut device: Option<ID3D12Device> = None;
|
||||||
|
destination_resource.GetDevice(&mut device)?;
|
||||||
|
let device = device.ok_or_else(|| anyhow!("Unable to get device"))?;
|
||||||
|
|
||||||
|
let mut layouts =
|
||||||
|
vec![D3D12_PLACED_SUBRESOURCE_FOOTPRINT::default(); num_subresources as usize];
|
||||||
|
let mut num_rows = vec![0; num_subresources as usize];
|
||||||
|
let mut row_sizes_in_bytes = vec![0; num_subresources as usize];
|
||||||
|
let mut required_size = 0;
|
||||||
|
|
||||||
|
// texture upload
|
||||||
|
device.GetCopyableFootprints(
|
||||||
|
&destination_desc,
|
||||||
|
first_subresouce,
|
||||||
|
num_subresources,
|
||||||
|
intermediate_offset,
|
||||||
|
Some(layouts.as_mut_ptr()),
|
||||||
|
Some(num_rows.as_mut_ptr()),
|
||||||
|
Some(row_sizes_in_bytes.as_mut_ptr()),
|
||||||
|
Some(&mut required_size),
|
||||||
|
);
|
||||||
|
|
||||||
|
update_subresources(
|
||||||
|
cmd,
|
||||||
|
destination_resource,
|
||||||
|
intermediate_resource,
|
||||||
|
first_subresouce,
|
||||||
|
num_subresources,
|
||||||
|
required_size,
|
||||||
|
&layouts,
|
||||||
|
&num_rows,
|
||||||
|
&row_sizes_in_bytes,
|
||||||
|
source,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn update_subresources(
|
||||||
|
cmd: &ID3D12GraphicsCommandList,
|
||||||
|
destination_resource: &ID3D12Resource,
|
||||||
|
intermediate_resource: &ID3D12Resource,
|
||||||
|
first_subresouce: u32,
|
||||||
|
num_subresources: u32,
|
||||||
|
required_size: u64,
|
||||||
|
layouts: &[D3D12_PLACED_SUBRESOURCE_FOOTPRINT],
|
||||||
|
num_rows: &[u32],
|
||||||
|
row_sizes_in_bytes: &[u64],
|
||||||
|
source_data: &[D3D12_SUBRESOURCE_DATA],
|
||||||
|
) -> anyhow::Result<u64> {
|
||||||
|
// ToDo: implement validation as in the original function
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let mut data = std::ptr::null_mut();
|
||||||
|
intermediate_resource.Map(0, None, Some(&mut data))?;
|
||||||
|
|
||||||
|
for i in 0..num_subresources as usize {
|
||||||
|
let dest_data = D3D12_MEMCPY_DEST {
|
||||||
|
pData: data.offset(layouts[i].Offset as isize) as *mut std::ffi::c_void,
|
||||||
|
RowPitch: layouts[i].Footprint.RowPitch as usize,
|
||||||
|
SlicePitch: ((layouts[i].Footprint.RowPitch) * num_rows[i]) as usize,
|
||||||
|
};
|
||||||
|
|
||||||
|
memcpy_subresource(
|
||||||
|
&dest_data,
|
||||||
|
&source_data[i],
|
||||||
|
row_sizes_in_bytes[i],
|
||||||
|
num_rows[i],
|
||||||
|
layouts[i].Footprint.Depth,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
intermediate_resource.Unmap(0, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let destination_desc = destination_resource.GetDesc();
|
||||||
|
if destination_desc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER {
|
||||||
|
cmd.CopyBufferRegion(
|
||||||
|
destination_resource,
|
||||||
|
0,
|
||||||
|
intermediate_resource,
|
||||||
|
layouts[0].Offset,
|
||||||
|
layouts[0].Footprint.Width as u64,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
for i in 0..num_subresources as usize {
|
||||||
|
let dest_location = D3D12_TEXTURE_COPY_LOCATION {
|
||||||
|
pResource: ManuallyDrop::new(Some(destination_resource.clone())),
|
||||||
|
Type: D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
|
||||||
|
Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 {
|
||||||
|
SubresourceIndex: i as u32 + first_subresouce,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let source_location = D3D12_TEXTURE_COPY_LOCATION {
|
||||||
|
pResource: ManuallyDrop::new(Some(intermediate_resource.clone())),
|
||||||
|
Type: D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT,
|
||||||
|
Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 {
|
||||||
|
PlacedFootprint: layouts[i],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
cmd.CopyTextureRegion(&dest_location, 0, 0, 0, &source_location, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(required_size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this function should not leak to the public API, so
|
||||||
|
// there is no point in using struct wrappers
|
||||||
|
unsafe fn memcpy_subresource(
|
||||||
|
dest: &D3D12_MEMCPY_DEST,
|
||||||
|
src: &D3D12_SUBRESOURCE_DATA,
|
||||||
|
row_sizes_in_bytes: u64,
|
||||||
|
num_rows: u32,
|
||||||
|
num_slices: u32,
|
||||||
|
) {
|
||||||
|
unsafe {
|
||||||
|
for z in 0..num_slices as usize {
|
||||||
|
let dest_slice = dest.pData.add(dest.SlicePitch * z);
|
||||||
|
let src_slice = src.pData.offset(src.SlicePitch * z as isize);
|
||||||
|
|
||||||
|
for y in 0..num_rows as usize {
|
||||||
|
std::ptr::copy_nonoverlapping(
|
||||||
|
src_slice.offset(src.RowPitch * y as isize),
|
||||||
|
dest_slice.add(dest.RowPitch * y),
|
||||||
|
row_sizes_in_bytes as usize,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,7 +37,7 @@ pub trait RenderTest {
|
||||||
/// shader presets, so the actual image that a shader will be applied to
|
/// shader presets, so the actual image that a shader will be applied to
|
||||||
/// will often be part of the test harness object.
|
/// will often be part of the test harness object.
|
||||||
fn render(
|
fn render(
|
||||||
&self,
|
&mut self,
|
||||||
path: impl AsRef<Path>,
|
path: impl AsRef<Path>,
|
||||||
frame_count: usize,
|
frame_count: usize,
|
||||||
) -> anyhow::Result<image::RgbaImage>;
|
) -> anyhow::Result<image::RgbaImage>;
|
||||||
|
@ -51,13 +51,13 @@ mod test {
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
|
||||||
const IMAGE_PATH: &str = "../triangle.png";
|
const IMAGE_PATH: &str = "../triangle.png";
|
||||||
const FILTER_PATH: &str = "../test/shaders_slang/crt/crt-geom.slangp";
|
const FILTER_PATH: &str = "../test/shaders_slang/crt/crt-royale.slangp";
|
||||||
|
|
||||||
// const FILTER_PATH: &str =
|
// const FILTER_PATH: &str =
|
||||||
// "../test/shaders_slang/bezel/Mega_Bezel/Presets/MBZ__0__SMOOTH-ADV.slangp";
|
// "../test/shaders_slang/bezel/Mega_Bezel/Presets/MBZ__0__SMOOTH-ADV.slangp";
|
||||||
|
|
||||||
fn do_test<T: RenderTest>() -> anyhow::Result<()> {
|
fn do_test<T: RenderTest>() -> anyhow::Result<()> {
|
||||||
let test = T::new(IMAGE_PATH)?;
|
let mut test = T::new(IMAGE_PATH)?;
|
||||||
let image = test.render(FILTER_PATH, 100)?;
|
let image = test.render(FILTER_PATH, 100)?;
|
||||||
|
|
||||||
let out = File::create("out.png")?;
|
let out = File::create("out.png")?;
|
||||||
|
@ -107,9 +107,15 @@ mod test {
|
||||||
do_test::<crate::render::d3d9::Direct3D9>()
|
do_test::<crate::render::d3d9::Direct3D9>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "d3d12")]
|
||||||
|
pub fn test_d3d12() -> anyhow::Result<()> {
|
||||||
|
do_test::<crate::render::d3d12::Direct3D12>()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn compare<A: RenderTest, B: RenderTest>() -> anyhow::Result<()> {
|
pub fn compare<A: RenderTest, B: RenderTest>() -> anyhow::Result<()> {
|
||||||
let a = A::new(IMAGE_PATH)?;
|
let mut a = A::new(IMAGE_PATH)?;
|
||||||
let b = B::new(IMAGE_PATH)?;
|
let mut b = B::new(IMAGE_PATH)?;
|
||||||
|
|
||||||
let a_image = a.render(FILTER_PATH, 100)?;
|
let a_image = a.render(FILTER_PATH, 100)?;
|
||||||
let b_image = b.render(FILTER_PATH, 100)?;
|
let b_image = b.render(FILTER_PATH, 100)?;
|
||||||
|
|
Loading…
Reference in a new issue