use windows::Win32::Graphics::Direct3D12::*;
use windows::Win32::Graphics::Dxgi::Common::*;
use windows::core::Interface;
use super::allocator::{DeviceAllocator, PooledBuffer, PooledTexture};
use super::com;
pub(super) struct GpuResource<R = PooledTexture> {
pub resource: R,
pub srv_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
pub srv_gpu: D3D12_GPU_DESCRIPTOR_HANDLE,
}
pub(super) fn one_shot_submit_nowait<F>(
device: &ID3D12Device,
queue: &ID3D12CommandQueue,
f: F,
) -> Result<(ID3D12CommandAllocator, ID3D12GraphicsCommandList), String>
where
F: FnOnce(&ID3D12GraphicsCommandList),
{
let allocator: ID3D12CommandAllocator =
unsafe { device.CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT) }
.map_err(|e| format!("one_shot allocator: {e}"))?;
let cmd: ID3D12GraphicsCommandList =
unsafe { device.CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, &allocator, None) }
.map_err(|e| format!("one_shot cmd list: {e}"))?;
f(&cmd);
unsafe { cmd.Close() }.map_err(|e| format!("one_shot close: {e}"))?;
let cmd_list: ID3D12CommandList = cmd.cast().map_err(|e| format!("one_shot cast: {e}"))?;
unsafe { queue.ExecuteCommandLists(&[Some(cmd_list)]) };
Ok((allocator, cmd))
}
pub(super) fn one_shot_submit<F>(
device: &ID3D12Device,
queue: &ID3D12CommandQueue,
f: F,
) -> Result<(), String>
where
F: FnOnce(&ID3D12GraphicsCommandList),
{
let _keep_alive = one_shot_submit_nowait(device, queue, f)?;
let fence: ID3D12Fence = unsafe { device.CreateFence(0, D3D12_FENCE_FLAG_NONE) }
.map_err(|e| format!("one_shot fence: {e}"))?;
let event =
unsafe { windows::Win32::System::Threading::CreateEventW(None, false, false, None) }
.map_err(|e| format!("one_shot event: {e}"))?;
unsafe { queue.Signal(&fence, 1) }.map_err(|e| format!("one_shot signal: {e}"))?;
if unsafe { fence.GetCompletedValue() } < 1 {
unsafe { fence.SetEventOnCompletion(1, event) }
.map_err(|e| format!("one_shot set event: {e}"))?;
unsafe { windows::Win32::System::Threading::WaitForSingleObject(event, u32::MAX) };
}
unsafe { windows::Win32::Foundation::CloseHandle(event) }.ok();
Ok(())
}
pub(super) fn create_buffer(
alloc: &DeviceAllocator,
size: u64,
heap_type: D3D12_HEAP_TYPE,
initial_state: D3D12_RESOURCE_STATES,
) -> Result<PooledBuffer, String> {
alloc.alloc_buffer(size, heap_type, initial_state)
}
pub(super) fn create_uav_buffer(
device: &ID3D12Device,
size: u64,
initial_state: D3D12_RESOURCE_STATES,
) -> Result<ID3D12Resource, String> {
let heap_props = D3D12_HEAP_PROPERTIES {
Type: D3D12_HEAP_TYPE_DEFAULT,
..Default::default()
};
let desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_BUFFER,
Width: size,
Height: 1,
DepthOrArraySize: 1,
MipLevels: 1,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Layout: D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
Flags: D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS,
..Default::default()
};
let mut resource: Option<ID3D12Resource> = None;
unsafe {
device.CreateCommittedResource(
&heap_props,
D3D12_HEAP_FLAG_NONE,
&desc,
initial_state,
None,
&mut resource,
)
}
.map_err(|e| format!("create_uav_buffer: {e}"))?;
resource.ok_or_else(|| "create_uav_buffer returned None".to_string())
}
pub(super) fn upload_buffer(
alloc: &DeviceAllocator,
data: &[u8],
usage_state: D3D12_RESOURCE_STATES,
) -> Result<PooledBuffer, String> {
upload_buffer_padded(alloc, data, data.len() as u64, usage_state)
}
pub(super) fn upload_buffer_padded(
alloc: &DeviceAllocator,
data: &[u8],
size: u64,
usage_state: D3D12_RESOURCE_STATES,
) -> Result<PooledBuffer, String> {
let size = size.max(data.len() as u64).max(4);
let upload = create_buffer(
alloc,
size,
D3D12_HEAP_TYPE_UPLOAD,
D3D12_RESOURCE_STATE_GENERIC_READ,
)?;
let mut ptr = std::ptr::null_mut::<std::ffi::c_void>();
unsafe { upload.Map(0, None, Some(&mut ptr)) }.map_err(|e| format!("upload map: {e}"))?;
unsafe {
std::ptr::copy_nonoverlapping(data.as_ptr(), ptr as *mut u8, data.len());
let pad = size as usize - data.len();
if pad > 0 {
std::ptr::write_bytes((ptr as *mut u8).add(data.len()), 0, pad);
}
upload.Unmap(0, None);
}
let dest = create_buffer(
alloc,
size,
D3D12_HEAP_TYPE_DEFAULT,
D3D12_RESOURCE_STATE_COMMON,
)?;
one_shot_submit(alloc.device(), alloc.queue(), |cmd| unsafe {
cmd.CopyBufferRegion(&*dest, 0, &*upload, 0, size);
let barrier = transition_barrier(&dest, D3D12_RESOURCE_STATE_COPY_DEST, usage_state);
cmd.ResourceBarrier(&[barrier]);
})?;
Ok(dest)
}
pub(super) struct StreamedUploadRetire {
#[expect(
dead_code,
reason = "held only so dropping the entry releases the COM reference"
)]
pub texture: PooledTexture,
#[expect(
dead_code,
reason = "held only so dropping the entry releases the COM reference"
)]
pub upload: PooledBuffer,
#[expect(
dead_code,
reason = "held only so dropping the entry releases the COM reference"
)]
pub allocator: ID3D12CommandAllocator,
#[expect(
dead_code,
reason = "held only so dropping the entry releases the COM reference"
)]
pub cmd: ID3D12GraphicsCommandList,
pub retire_at: u64,
}
pub(super) struct UploadInFlight {
pub upload: PooledBuffer,
pub allocator: ID3D12CommandAllocator,
pub cmd: ID3D12GraphicsCommandList,
}
pub(super) fn upload_texture_resource_deferred(
alloc: &DeviceAllocator,
width: u32,
height: u32,
pixels: &[u8],
) -> Result<(PooledTexture, UploadInFlight), String> {
let base = (width as usize) * (height as usize) * 4;
if pixels.len() < base {
return Err(format!(
"pixel data too short for {}x{} texture ({} bytes, need {})",
width,
height,
pixels.len(),
base
));
}
let chain = crate::gfx::mipmap::generate_mip_chain(width, height, pixels);
let levels: Vec<TextureLevel<'_>> = chain
.iter()
.map(|m| TextureLevel {
width: m.width,
height: m.height,
data: &m.pixels,
})
.collect();
upload_texture_levels_deferred(alloc, DXGI_FORMAT_R8G8B8A8_UNORM, &levels)
}
pub(super) struct TextureLevel<'a> {
pub width: u32,
pub height: u32,
pub data: &'a [u8],
}
fn dxgi_texture_format(format: concinnity_core::build::texture::TextureFormat) -> DXGI_FORMAT {
use concinnity_core::build::texture::TextureFormat;
match format {
TextureFormat::Rgba8 => DXGI_FORMAT_R8G8B8A8_UNORM,
TextureFormat::Bc1 => DXGI_FORMAT_BC1_UNORM,
TextureFormat::Bc3 => DXGI_FORMAT_BC3_UNORM,
TextureFormat::Bc5 => DXGI_FORMAT_BC5_UNORM,
TextureFormat::Bc7 => DXGI_FORMAT_BC7_UNORM,
}
}
pub(super) fn upload_texture_image_deferred(
alloc: &DeviceAllocator,
image: &concinnity_core::build::texture::TextureImage,
) -> Result<(PooledTexture, UploadInFlight), String> {
use concinnity_core::build::texture::TextureFormat;
if image.format == TextureFormat::Rgba8 {
let mip = image
.mips
.first()
.ok_or("RGBA8 texture image has no mip level")?;
return upload_texture_resource_deferred(alloc, mip.width, mip.height, &mip.data);
}
let levels: Vec<TextureLevel<'_>> = image
.mips
.iter()
.map(|m| TextureLevel {
width: m.width,
height: m.height,
data: &m.data,
})
.collect();
upload_texture_levels_deferred(alloc, dxgi_texture_format(image.format), &levels)
}
pub(super) fn upload_texture_image(
alloc: &DeviceAllocator,
image: &concinnity_core::build::texture::TextureImage,
) -> Result<PooledTexture, String> {
let (texture, in_flight) = upload_texture_image_deferred(alloc, image)?;
wait_for_upload(alloc.device(), alloc.queue())?;
drop(in_flight);
Ok(texture)
}
fn upload_texture_levels_deferred(
alloc: &DeviceAllocator,
format: DXGI_FORMAT,
levels: &[TextureLevel<'_>],
) -> Result<(PooledTexture, UploadInFlight), String> {
let device = alloc.device();
let base = levels.first().ok_or("texture upload has no mip level")?;
let (width, height) = (base.width, base.height);
let mip_count = levels.len() as u32;
let desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_TEXTURE2D,
Width: width as u64,
Height: height,
DepthOrArraySize: 1,
MipLevels: mip_count as u16,
Format: format,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
..Default::default()
};
let texture = alloc.alloc_texture(
&desc,
D3D12_HEAP_TYPE_DEFAULT,
D3D12_RESOURCE_STATE_COPY_DEST,
)?;
let mut layouts = vec![D3D12_PLACED_SUBRESOURCE_FOOTPRINT::default(); mip_count as usize];
let mut row_counts = vec![0u32; mip_count as usize];
let mut row_sizes = vec![0u64; mip_count as usize];
let mut total_size: u64 = 0;
unsafe {
device.GetCopyableFootprints(
&desc,
0,
mip_count,
0,
Some(layouts.as_mut_ptr()),
Some(row_counts.as_mut_ptr()),
Some(row_sizes.as_mut_ptr()),
Some(&mut total_size),
);
}
let upload = create_buffer(
alloc,
total_size,
D3D12_HEAP_TYPE_UPLOAD,
D3D12_RESOURCE_STATE_GENERIC_READ,
)?;
let mut map_ptr = std::ptr::null_mut::<std::ffi::c_void>();
unsafe { upload.Map(0, None, Some(&mut map_ptr)) }
.map_err(|e| format!("upload tex map: {e}"))?;
for (m, level) in levels.iter().enumerate() {
let src_row = row_sizes[m] as usize;
let rows = row_counts[m] as usize;
let needed = src_row * rows;
if level.data.len() < needed {
unsafe { upload.Unmap(0, None) };
return Err(format!(
"texture mip {} ({}x{}) is {} bytes, need {}",
m,
level.width,
level.height,
level.data.len(),
needed
));
}
let dst_pitch = layouts[m].Footprint.RowPitch as usize;
let base_off = layouts[m].Offset as usize;
for row in 0..rows {
let src = &level.data[row * src_row..(row + 1) * src_row];
let dst = unsafe { (map_ptr as *mut u8).add(base_off + row * dst_pitch) };
unsafe { std::ptr::copy_nonoverlapping(src.as_ptr(), dst, src_row) };
}
}
unsafe { upload.Unmap(0, None) };
let (allocator, cmd) = one_shot_submit_nowait(device, alloc.queue(), |cmd| {
let mut src = D3D12_TEXTURE_COPY_LOCATION {
pResource: com::borrowed(&*upload),
Type: D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT,
Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 {
PlacedFootprint: layouts[0],
},
};
let mut dst = D3D12_TEXTURE_COPY_LOCATION {
pResource: com::borrowed(&*texture),
Type: D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 {
SubresourceIndex: 0,
},
};
for m in 0..mip_count {
src.Anonymous = D3D12_TEXTURE_COPY_LOCATION_0 {
PlacedFootprint: layouts[m as usize],
};
dst.Anonymous = D3D12_TEXTURE_COPY_LOCATION_0 {
SubresourceIndex: m,
};
unsafe { cmd.CopyTextureRegion(&dst, 0, 0, 0, &src, None) };
}
let barrier = transition_barrier(
&texture,
D3D12_RESOURCE_STATE_COPY_DEST,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
);
unsafe { cmd.ResourceBarrier(&[barrier]) };
})?;
Ok((
texture,
UploadInFlight {
upload,
allocator,
cmd,
},
))
}
pub(super) fn upload_texture_resource(
alloc: &DeviceAllocator,
width: u32,
height: u32,
pixels: &[u8],
) -> Result<PooledTexture, String> {
let (texture, in_flight) = upload_texture_resource_deferred(alloc, width, height, pixels)?;
wait_for_upload(alloc.device(), alloc.queue())?;
drop(in_flight);
Ok(texture)
}
fn wait_for_upload(device: &ID3D12Device, queue: &ID3D12CommandQueue) -> Result<(), String> {
let fence: ID3D12Fence = unsafe { device.CreateFence(0, D3D12_FENCE_FLAG_NONE) }
.map_err(|e| format!("upload fence: {e}"))?;
let event =
unsafe { windows::Win32::System::Threading::CreateEventW(None, false, false, None) }
.map_err(|e| format!("upload event: {e}"))?;
unsafe { queue.Signal(&fence, 1) }.map_err(|e| format!("upload signal: {e}"))?;
if unsafe { fence.GetCompletedValue() } < 1 {
unsafe { fence.SetEventOnCompletion(1, event) }
.map_err(|e| format!("upload set event: {e}"))?;
unsafe { windows::Win32::System::Threading::WaitForSingleObject(event, u32::MAX) };
}
unsafe { windows::Win32::Foundation::CloseHandle(event) }.ok();
Ok(())
}
pub(super) fn write_texture_srv(
device: &ID3D12Device,
resource: &ID3D12Resource,
srv_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
) {
let desc = unsafe { resource.GetDesc() };
let mip_levels = desc.MipLevels as u32;
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: mip_levels,
..Default::default()
},
},
};
unsafe { device.CreateShaderResourceView(resource, Some(&srv_desc), srv_cpu) };
}
pub(super) fn upload_texture(
alloc: &DeviceAllocator,
width: u32,
height: u32,
pixels: &[u8],
srv_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
srv_gpu: D3D12_GPU_DESCRIPTOR_HANDLE,
) -> Result<GpuResource, String> {
let texture = upload_texture_resource(alloc, width, height, pixels)?;
write_texture_srv(alloc.device(), &texture, srv_cpu);
Ok(GpuResource {
resource: texture,
srv_cpu,
srv_gpu,
})
}
pub(super) fn create_fallback_white_resource(
alloc: &DeviceAllocator,
) -> Result<PooledTexture, String> {
upload_texture_resource(alloc, 1, 1, &[255u8, 255, 255, 255])
}
pub(super) fn create_fallback_flat_normal_resource(
alloc: &DeviceAllocator,
) -> Result<PooledTexture, String> {
upload_texture_resource(alloc, 1, 1, &[128u8, 128, 255, 255])
}
pub(super) fn create_fallback_shadow_array(
alloc: &DeviceAllocator,
srv_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
srv_gpu: D3D12_GPU_DESCRIPTOR_HANDLE,
) -> Result<GpuResource<ID3D12Resource>, String> {
let device = alloc.device();
let heap_props = D3D12_HEAP_PROPERTIES {
Type: D3D12_HEAP_TYPE_DEFAULT,
..Default::default()
};
let desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_TEXTURE2D,
Width: 1,
Height: 1,
DepthOrArraySize: 1,
MipLevels: 1,
Format: DXGI_FORMAT_R32_FLOAT,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
..Default::default()
};
let mut tex_opt: Option<ID3D12Resource> = None;
unsafe {
device.CreateCommittedResource(
&heap_props,
D3D12_HEAP_FLAG_NONE,
&desc,
D3D12_RESOURCE_STATE_COPY_DEST,
None,
&mut tex_opt,
)
}
.map_err(|e| format!("create fallback shadow array: {e}"))?;
let texture =
tex_opt.ok_or_else(|| "create fallback shadow array returned None".to_string())?;
let mut layout = D3D12_PLACED_SUBRESOURCE_FOOTPRINT::default();
unsafe {
device.GetCopyableFootprints(&desc, 0, 1, 0, Some(&mut layout), None, None, None);
}
let upload = create_buffer(
alloc,
layout.Footprint.RowPitch as u64,
D3D12_HEAP_TYPE_UPLOAD,
D3D12_RESOURCE_STATE_GENERIC_READ,
)?;
let mut map_ptr = std::ptr::null_mut::<std::ffi::c_void>();
unsafe { upload.Map(0, None, Some(&mut map_ptr)) }
.map_err(|e| format!("map fallback shadow array: {e}"))?;
unsafe {
*(map_ptr as *mut f32) = 0.0f32;
upload.Unmap(0, None);
}
one_shot_submit(device, alloc.queue(), |cmd| {
let src = D3D12_TEXTURE_COPY_LOCATION {
pResource: com::borrowed(&*upload),
Type: D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT,
Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 {
PlacedFootprint: layout,
},
};
let dst = D3D12_TEXTURE_COPY_LOCATION {
pResource: com::borrowed(&texture),
Type: D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 {
SubresourceIndex: 0,
},
};
unsafe {
cmd.CopyTextureRegion(&dst, 0, 0, 0, &src, None);
let barrier = transition_barrier(
&texture,
D3D12_RESOURCE_STATE_COPY_DEST,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
);
cmd.ResourceBarrier(&[barrier]);
}
})?;
let srv_desc = D3D12_SHADER_RESOURCE_VIEW_DESC {
Format: DXGI_FORMAT_R32_FLOAT,
ViewDimension: D3D12_SRV_DIMENSION_TEXTURE2DARRAY,
Shader4ComponentMapping: D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
Anonymous: D3D12_SHADER_RESOURCE_VIEW_DESC_0 {
Texture2DArray: D3D12_TEX2D_ARRAY_SRV {
MostDetailedMip: 0,
MipLevels: 1,
FirstArraySlice: 0,
ArraySize: 1,
PlaneSlice: 0,
ResourceMinLODClamp: 0.0,
},
},
};
unsafe { device.CreateShaderResourceView(&texture, Some(&srv_desc), srv_cpu) };
Ok(GpuResource {
resource: texture,
srv_cpu,
srv_gpu,
})
}
pub(super) fn create_main_depth_texture(
device: &ID3D12Device,
width: u32,
height: u32,
dsv_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
sample_count: u32,
shader_readable: bool,
) -> Result<ID3D12Resource, String> {
let heap_props = D3D12_HEAP_PROPERTIES {
Type: D3D12_HEAP_TYPE_DEFAULT,
..Default::default()
};
let clear_value = D3D12_CLEAR_VALUE {
Format: DXGI_FORMAT_D32_FLOAT,
Anonymous: D3D12_CLEAR_VALUE_0 {
DepthStencil: D3D12_DEPTH_STENCIL_VALUE {
Depth: 1.0,
Stencil: 0,
},
},
};
let mut flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
if !shader_readable {
flags |= D3D12_RESOURCE_FLAG_DENY_SHADER_RESOURCE;
}
let desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_TEXTURE2D,
Width: width as u64,
Height: height,
DepthOrArraySize: 1,
MipLevels: 1,
Format: DXGI_FORMAT_R32_TYPELESS,
SampleDesc: DXGI_SAMPLE_DESC {
Count: sample_count,
Quality: 0,
},
Flags: flags,
..Default::default()
};
let mut tex_opt: Option<ID3D12Resource> = None;
unsafe {
device.CreateCommittedResource(
&heap_props,
D3D12_HEAP_FLAG_NONE,
&desc,
D3D12_RESOURCE_STATE_DEPTH_WRITE,
Some(&clear_value),
&mut tex_opt,
)
}
.map_err(|e| format!("create main depth texture: {e}"))?;
let texture = tex_opt.ok_or_else(|| "create main depth texture returned None".to_string())?;
let dsv_desc = D3D12_DEPTH_STENCIL_VIEW_DESC {
Format: DXGI_FORMAT_D32_FLOAT,
ViewDimension: if sample_count > 1 {
D3D12_DSV_DIMENSION_TEXTURE2DMS
} else {
D3D12_DSV_DIMENSION_TEXTURE2D
},
Flags: D3D12_DSV_FLAG_NONE,
Anonymous: D3D12_DEPTH_STENCIL_VIEW_DESC_0 {
Texture2D: D3D12_TEX2D_DSV { MipSlice: 0 },
},
};
unsafe { device.CreateDepthStencilView(&texture, Some(&dsv_desc), dsv_cpu) };
Ok(texture)
}
pub(super) fn create_shadow_map_array(
device: &ID3D12Device,
size: u32,
layers: u32,
dsv_cpu_base: D3D12_CPU_DESCRIPTOR_HANDLE,
dsv_stride: usize,
srv_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
srv_gpu: D3D12_GPU_DESCRIPTOR_HANDLE,
) -> Result<
(
GpuResource<ID3D12Resource>,
Vec<D3D12_CPU_DESCRIPTOR_HANDLE>,
),
String,
> {
let heap_props = D3D12_HEAP_PROPERTIES {
Type: D3D12_HEAP_TYPE_DEFAULT,
..Default::default()
};
let clear_value = D3D12_CLEAR_VALUE {
Format: DXGI_FORMAT_D32_FLOAT,
Anonymous: D3D12_CLEAR_VALUE_0 {
DepthStencil: D3D12_DEPTH_STENCIL_VALUE {
Depth: 1.0,
Stencil: 0,
},
},
};
let desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_TEXTURE2D,
Width: size as u64,
Height: size,
DepthOrArraySize: layers as u16,
MipLevels: 1,
Format: DXGI_FORMAT_R32_TYPELESS,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Flags: D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL,
..Default::default()
};
let mut tex_opt: Option<ID3D12Resource> = None;
unsafe {
device.CreateCommittedResource(
&heap_props,
D3D12_HEAP_FLAG_NONE,
&desc,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
Some(&clear_value),
&mut tex_opt,
)
}
.map_err(|e| format!("create shadow map array: {e}"))?;
let texture = tex_opt.ok_or_else(|| "create shadow map array returned None".to_string())?;
let mut dsvs = Vec::with_capacity(layers as usize);
for i in 0..layers {
let dsv_cpu = D3D12_CPU_DESCRIPTOR_HANDLE {
ptr: dsv_cpu_base.ptr + (i as usize) * dsv_stride,
};
let dsv_desc = D3D12_DEPTH_STENCIL_VIEW_DESC {
Format: DXGI_FORMAT_D32_FLOAT,
ViewDimension: D3D12_DSV_DIMENSION_TEXTURE2DARRAY,
Flags: D3D12_DSV_FLAG_NONE,
Anonymous: D3D12_DEPTH_STENCIL_VIEW_DESC_0 {
Texture2DArray: D3D12_TEX2D_ARRAY_DSV {
MipSlice: 0,
FirstArraySlice: i,
ArraySize: 1,
},
},
};
unsafe { device.CreateDepthStencilView(&texture, Some(&dsv_desc), dsv_cpu) };
dsvs.push(dsv_cpu);
}
let srv_desc = D3D12_SHADER_RESOURCE_VIEW_DESC {
Format: DXGI_FORMAT_R32_FLOAT,
ViewDimension: D3D12_SRV_DIMENSION_TEXTURE2DARRAY,
Shader4ComponentMapping: D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
Anonymous: D3D12_SHADER_RESOURCE_VIEW_DESC_0 {
Texture2DArray: D3D12_TEX2D_ARRAY_SRV {
MostDetailedMip: 0,
MipLevels: 1,
FirstArraySlice: 0,
ArraySize: layers,
PlaneSlice: 0,
ResourceMinLODClamp: 0.0,
},
},
};
unsafe { device.CreateShaderResourceView(&texture, Some(&srv_desc), srv_cpu) };
Ok((
GpuResource {
resource: texture,
srv_cpu,
srv_gpu,
},
dsvs,
))
}
pub(super) const HDR_FORMAT: DXGI_FORMAT = DXGI_FORMAT_R16G16B16A16_FLOAT;
pub(super) fn create_hdr_color_target(
device: &ID3D12Device,
width: u32,
height: u32,
sample_count: u32,
rtv_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
clear_color: [f32; 4],
) -> Result<ID3D12Resource, String> {
let heap_props = D3D12_HEAP_PROPERTIES {
Type: D3D12_HEAP_TYPE_DEFAULT,
..Default::default()
};
let clear_value = D3D12_CLEAR_VALUE {
Format: HDR_FORMAT,
Anonymous: D3D12_CLEAR_VALUE_0 { Color: clear_color },
};
let desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_TEXTURE2D,
Width: width as u64,
Height: height,
DepthOrArraySize: 1,
MipLevels: 1,
Format: HDR_FORMAT,
SampleDesc: DXGI_SAMPLE_DESC {
Count: sample_count,
Quality: 0,
},
Flags: D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET,
..Default::default()
};
let mut res_opt: Option<ID3D12Resource> = None;
unsafe {
device.CreateCommittedResource(
&heap_props,
D3D12_HEAP_FLAG_NONE,
&desc,
D3D12_RESOURCE_STATE_RENDER_TARGET,
Some(&clear_value),
&mut res_opt,
)
}
.map_err(|e| format!("create hdr color target: {e}"))?;
let res = res_opt.ok_or_else(|| "create hdr color returned None".to_string())?;
let rtv_desc = D3D12_RENDER_TARGET_VIEW_DESC {
Format: HDR_FORMAT,
ViewDimension: if sample_count > 1 {
D3D12_RTV_DIMENSION_TEXTURE2DMS
} else {
D3D12_RTV_DIMENSION_TEXTURE2D
},
..Default::default()
};
unsafe { device.CreateRenderTargetView(&res, Some(&rtv_desc), rtv_cpu) };
Ok(res)
}
pub(super) fn create_hdr_resolve_target(
device: &ID3D12Device,
width: u32,
height: u32,
) -> Result<ID3D12Resource, String> {
let heap_props = D3D12_HEAP_PROPERTIES {
Type: D3D12_HEAP_TYPE_DEFAULT,
..Default::default()
};
let clear_value = D3D12_CLEAR_VALUE {
Format: HDR_FORMAT,
Anonymous: D3D12_CLEAR_VALUE_0 { Color: [0.0; 4] },
};
let desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_TEXTURE2D,
Width: width as u64,
Height: height,
DepthOrArraySize: 1,
MipLevels: 1,
Format: HDR_FORMAT,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Flags: D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET,
..Default::default()
};
let mut res_opt: Option<ID3D12Resource> = None;
unsafe {
device.CreateCommittedResource(
&heap_props,
D3D12_HEAP_FLAG_NONE,
&desc,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
Some(&clear_value),
&mut res_opt,
)
}
.map_err(|e| format!("create hdr resolve target: {e}"))?;
res_opt.ok_or_else(|| "create hdr resolve returned None".to_string())
}
pub(super) fn write_hdr_srv(
device: &ID3D12Device,
resource: &ID3D12Resource,
srv_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
) {
let srv_desc = D3D12_SHADER_RESOURCE_VIEW_DESC {
Format: HDR_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: 1,
..Default::default()
},
},
};
unsafe { device.CreateShaderResourceView(resource, Some(&srv_desc), srv_cpu) };
}
pub(super) fn create_rt_target(
device: &ID3D12Device,
width: u32,
height: u32,
format: DXGI_FORMAT,
) -> Result<ID3D12Resource, String> {
create_rt_target_with_clear(device, width, height, format, [0.0; 4])
}
pub(super) fn create_rt_target_with_clear(
device: &ID3D12Device,
width: u32,
height: u32,
format: DXGI_FORMAT,
clear_color: [f32; 4],
) -> Result<ID3D12Resource, String> {
let heap_props = D3D12_HEAP_PROPERTIES {
Type: D3D12_HEAP_TYPE_DEFAULT,
..Default::default()
};
let clear_value = D3D12_CLEAR_VALUE {
Format: format,
Anonymous: D3D12_CLEAR_VALUE_0 { Color: clear_color },
};
let desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_TEXTURE2D,
Width: width.max(1) as u64,
Height: height.max(1),
DepthOrArraySize: 1,
MipLevels: 1,
Format: format,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Flags: D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET,
..Default::default()
};
let mut res_opt: Option<ID3D12Resource> = None;
unsafe {
device.CreateCommittedResource(
&heap_props,
D3D12_HEAP_FLAG_NONE,
&desc,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
Some(&clear_value),
&mut res_opt,
)
}
.map_err(|e| format!("create rt target: {e}"))?;
res_opt.ok_or_else(|| "create rt target returned None".to_string())
}
pub(super) fn write_format_rtv(
device: &ID3D12Device,
resource: &ID3D12Resource,
rtv_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
format: DXGI_FORMAT,
) {
let rtv_desc = D3D12_RENDER_TARGET_VIEW_DESC {
Format: format,
ViewDimension: D3D12_RTV_DIMENSION_TEXTURE2D,
..Default::default()
};
unsafe { device.CreateRenderTargetView(resource, Some(&rtv_desc), rtv_cpu) };
}
pub(super) fn write_format_srv(
device: &ID3D12Device,
resource: &ID3D12Resource,
srv_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
format: DXGI_FORMAT,
) {
let srv_desc = D3D12_SHADER_RESOURCE_VIEW_DESC {
Format: 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: 1,
..Default::default()
},
},
};
unsafe { device.CreateShaderResourceView(resource, Some(&srv_desc), srv_cpu) };
}
pub(super) fn transition_barrier(
resource: &ID3D12Resource,
before: D3D12_RESOURCE_STATES,
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: com::borrowed(resource),
StateBefore: before,
StateAfter: after,
Subresource: D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES,
}),
},
}
}
pub(super) fn uav_barrier(resource: &ID3D12Resource) -> D3D12_RESOURCE_BARRIER {
D3D12_RESOURCE_BARRIER {
Type: D3D12_RESOURCE_BARRIER_TYPE_UAV,
Flags: D3D12_RESOURCE_BARRIER_FLAG_NONE,
Anonymous: D3D12_RESOURCE_BARRIER_0 {
UAV: std::mem::ManuallyDrop::new(D3D12_RESOURCE_UAV_BARRIER {
pResource: com::borrowed(resource),
}),
},
}
}
pub(super) fn aliasing_barrier(after: &ID3D12Resource) -> D3D12_RESOURCE_BARRIER {
D3D12_RESOURCE_BARRIER {
Type: D3D12_RESOURCE_BARRIER_TYPE_ALIASING,
Flags: D3D12_RESOURCE_BARRIER_FLAG_NONE,
Anonymous: D3D12_RESOURCE_BARRIER_0 {
Aliasing: std::mem::ManuallyDrop::new(D3D12_RESOURCE_ALIASING_BARRIER {
pResourceBefore: std::mem::ManuallyDrop::new(None),
pResourceAfter: com::borrowed(after),
}),
},
}
}
pub(super) struct EnvironmentMapTextures {
pub irradiance: GpuResource,
pub prefilter: GpuResource,
pub prefilter_mip_count: u32,
}
fn write_cube_srv_single_mip(
device: &ID3D12Device,
resource: &ID3D12Resource,
srv_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
) {
let srv_desc = D3D12_SHADER_RESOURCE_VIEW_DESC {
Format: DXGI_FORMAT_R32G32B32A32_FLOAT,
ViewDimension: D3D12_SRV_DIMENSION_TEXTURECUBE,
Shader4ComponentMapping: D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
Anonymous: D3D12_SHADER_RESOURCE_VIEW_DESC_0 {
TextureCube: D3D12_TEXCUBE_SRV {
MostDetailedMip: 0,
MipLevels: 1,
ResourceMinLODClamp: 0.0,
},
},
};
unsafe { device.CreateShaderResourceView(resource, Some(&srv_desc), srv_cpu) };
}
pub(super) fn write_cube_srv_mips(
device: &ID3D12Device,
resource: &ID3D12Resource,
mip_count: u32,
srv_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
) {
let srv_desc = D3D12_SHADER_RESOURCE_VIEW_DESC {
Format: DXGI_FORMAT_R32G32B32A32_FLOAT,
ViewDimension: D3D12_SRV_DIMENSION_TEXTURECUBE,
Shader4ComponentMapping: D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
Anonymous: D3D12_SHADER_RESOURCE_VIEW_DESC_0 {
TextureCube: D3D12_TEXCUBE_SRV {
MostDetailedMip: 0,
MipLevels: mip_count,
ResourceMinLODClamp: 0.0,
},
},
};
unsafe { device.CreateShaderResourceView(resource, Some(&srv_desc), srv_cpu) };
}
pub(super) fn create_fallback_cubemap(
alloc: &DeviceAllocator,
value: [f32; 4],
srv_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
srv_gpu: D3D12_GPU_DESCRIPTOR_HANDLE,
) -> Result<GpuResource, String> {
let face_bytes = [value; 1]; let mut all_faces = Vec::with_capacity(6 * 16);
for _ in 0..6 {
for v in &face_bytes {
all_faces.extend_from_slice(&v[0].to_le_bytes());
all_faces.extend_from_slice(&v[1].to_le_bytes());
all_faces.extend_from_slice(&v[2].to_le_bytes());
all_faces.extend_from_slice(&v[3].to_le_bytes());
}
}
let resource = upload_cube_resource(alloc, 1, 1, &all_faces)?;
write_cube_srv_single_mip(alloc.device(), &resource, srv_cpu);
Ok(GpuResource {
resource,
srv_cpu,
srv_gpu,
})
}
pub(super) struct EnvironmentMapPayload<'a> {
pub irradiance_face: u32,
pub irradiance_bytes: &'a [u8],
pub prefilter_face: u32,
pub mip_bytes: &'a [&'a [u8]],
}
#[derive(Clone, Copy)]
pub(super) struct EnvironmentMapDescriptors {
pub irr_srv_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
pub irr_srv_gpu: D3D12_GPU_DESCRIPTOR_HANDLE,
pub pre_srv_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
pub pre_srv_gpu: D3D12_GPU_DESCRIPTOR_HANDLE,
}
pub(super) fn upload_environment_map(
alloc: &DeviceAllocator,
payload: EnvironmentMapPayload,
descriptors: EnvironmentMapDescriptors,
) -> Result<EnvironmentMapTextures, String> {
let device = alloc.device();
let EnvironmentMapPayload {
irradiance_face,
irradiance_bytes,
prefilter_face,
mip_bytes,
} = payload;
let EnvironmentMapDescriptors {
irr_srv_cpu,
irr_srv_gpu,
pre_srv_cpu,
pre_srv_gpu,
} = descriptors;
if mip_bytes.is_empty() {
return Err("envmap upload: prefilter mip_bytes must not be empty".into());
}
let irradiance_res = upload_cube_resource(alloc, irradiance_face, 1, irradiance_bytes)
.map_err(|e| format!("envmap irradiance: {e}"))?;
write_cube_srv_single_mip(device, &irradiance_res, irr_srv_cpu);
let prefilter_res = upload_prefilter_cube_resource(alloc, prefilter_face, mip_bytes)
.map_err(|e| format!("envmap prefilter: {e}"))?;
write_cube_srv_mips(device, &prefilter_res, mip_bytes.len() as u32, pre_srv_cpu);
Ok(EnvironmentMapTextures {
irradiance: GpuResource {
resource: irradiance_res,
srv_cpu: irr_srv_cpu,
srv_gpu: irr_srv_gpu,
},
prefilter: GpuResource {
resource: prefilter_res,
srv_cpu: pre_srv_cpu,
srv_gpu: pre_srv_gpu,
},
prefilter_mip_count: mip_bytes.len() as u32,
})
}
pub(super) fn upload_probe_prefilter_cube(
alloc: &DeviceAllocator,
face_size: u32,
mip_bytes: &[&[u8]],
) -> Result<PooledTexture, String> {
upload_prefilter_cube_resource(alloc, face_size, mip_bytes)
}
fn upload_cube_resource(
alloc: &DeviceAllocator,
face_size: u32,
mip_count: u32,
bytes: &[u8],
) -> Result<PooledTexture, String> {
let face_bytes_mip0 = (face_size as usize) * (face_size as usize) * 16;
let needed = 6 * face_bytes_mip0 * mip_count as usize;
if mip_count == 1 && bytes.len() < needed {
return Err(format!(
"cubemap data too short for face_size {}: {} bytes, need {}",
face_size,
bytes.len(),
needed
));
}
let desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_TEXTURE2D,
Width: face_size as u64,
Height: face_size,
DepthOrArraySize: 6,
MipLevels: mip_count as u16,
Format: DXGI_FORMAT_R32G32B32A32_FLOAT,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
..Default::default()
};
let texture = alloc.alloc_texture(
&desc,
D3D12_HEAP_TYPE_DEFAULT,
D3D12_RESOURCE_STATE_COPY_DEST,
)?;
upload_face_major_into_cube(alloc, &texture, &desc, face_size, mip_count, &[bytes])?;
Ok(texture)
}
fn upload_prefilter_cube_resource(
alloc: &DeviceAllocator,
face_size: u32,
mip_bytes: &[&[u8]],
) -> Result<PooledTexture, String> {
let mip_count = mip_bytes.len() as u32;
let desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_TEXTURE2D,
Width: face_size as u64,
Height: face_size,
DepthOrArraySize: 6,
MipLevels: mip_count as u16,
Format: DXGI_FORMAT_R32G32B32A32_FLOAT,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
..Default::default()
};
let texture = alloc.alloc_texture(
&desc,
D3D12_HEAP_TYPE_DEFAULT,
D3D12_RESOURCE_STATE_COPY_DEST,
)?;
upload_face_major_into_cube(alloc, &texture, &desc, face_size, mip_count, mip_bytes)?;
Ok(texture)
}
fn upload_face_major_into_cube(
alloc: &DeviceAllocator,
texture: &ID3D12Resource,
desc: &D3D12_RESOURCE_DESC,
face_size: u32,
mip_count: u32,
mip_bytes: &[&[u8]],
) -> Result<(), String> {
let device = alloc.device();
let num_subresources = 6 * mip_count;
let mut layouts: Vec<D3D12_PLACED_SUBRESOURCE_FOOTPRINT> =
vec![D3D12_PLACED_SUBRESOURCE_FOOTPRINT::default(); num_subresources as usize];
let mut row_counts: Vec<u32> = vec![0; num_subresources as usize];
let mut row_sizes: Vec<u64> = vec![0; num_subresources as usize];
let mut total_bytes: u64 = 0;
unsafe {
device.GetCopyableFootprints(
desc,
0,
num_subresources,
0,
Some(layouts.as_mut_ptr()),
Some(row_counts.as_mut_ptr()),
Some(row_sizes.as_mut_ptr()),
Some(&mut total_bytes),
);
}
let upload = create_buffer(
alloc,
total_bytes.max(4),
D3D12_HEAP_TYPE_UPLOAD,
D3D12_RESOURCE_STATE_GENERIC_READ,
)?;
let mut map_ptr = std::ptr::null_mut::<std::ffi::c_void>();
unsafe { upload.Map(0, None, Some(&mut map_ptr)) }
.map_err(|e| format!("cube upload map: {e}"))?;
for mip in 0..mip_count {
let mip_face_size = (face_size >> mip).max(1);
let face_bytes = (mip_face_size as usize) * (mip_face_size as usize) * 16;
let slab = mip_bytes[mip as usize];
if slab.len() < 6 * face_bytes {
unsafe { upload.Unmap(0, None) };
return Err(format!(
"cube upload mip {} too short: {} bytes, need {}",
mip,
slab.len(),
6 * face_bytes
));
}
for face in 0..6u32 {
let subres = mip + face * mip_count;
let layout = &layouts[subres as usize];
let row_pitch = layout.Footprint.RowPitch as usize;
let src_row = (mip_face_size as usize) * 16;
let face_src_offset = (face as usize) * face_bytes;
for row in 0..mip_face_size as usize {
let src =
&slab[face_src_offset + row * src_row..face_src_offset + (row + 1) * src_row];
let dst =
unsafe { (map_ptr as *mut u8).add(layout.Offset as usize + row * row_pitch) };
unsafe { std::ptr::copy_nonoverlapping(src.as_ptr(), dst, src_row) };
}
}
}
unsafe { upload.Unmap(0, None) };
one_shot_submit(device, alloc.queue(), |cmd| {
for subres in 0..num_subresources {
let src = D3D12_TEXTURE_COPY_LOCATION {
pResource: com::borrowed(&*upload),
Type: D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT,
Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 {
PlacedFootprint: layouts[subres as usize],
},
};
let dst = D3D12_TEXTURE_COPY_LOCATION {
pResource: com::borrowed(texture),
Type: D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 {
SubresourceIndex: subres,
},
};
unsafe { cmd.CopyTextureRegion(&dst, 0, 0, 0, &src, None) };
}
let barrier = transition_barrier(
texture,
D3D12_RESOURCE_STATE_COPY_DEST,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
);
unsafe { cmd.ResourceBarrier(&[barrier]) };
})?;
Ok(())
}
fn write_lut_srv(
device: &ID3D12Device,
resource: &ID3D12Resource,
srv_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
) {
let srv_desc = D3D12_SHADER_RESOURCE_VIEW_DESC {
Format: DXGI_FORMAT_R8G8B8A8_UNORM,
ViewDimension: D3D12_SRV_DIMENSION_TEXTURE3D,
Shader4ComponentMapping: D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
Anonymous: D3D12_SHADER_RESOURCE_VIEW_DESC_0 {
Texture3D: D3D12_TEX3D_SRV {
MostDetailedMip: 0,
MipLevels: 1,
ResourceMinLODClamp: 0.0,
},
},
};
unsafe { device.CreateShaderResourceView(resource, Some(&srv_desc), srv_cpu) };
}
pub(super) fn upload_color_lut(
alloc: &DeviceAllocator,
size: u32,
data: &[u8],
srv_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
srv_gpu: D3D12_GPU_DESCRIPTOR_HANDLE,
) -> Result<GpuResource, String> {
let device = alloc.device();
let n = size as usize;
let needed = n * n * n * 4;
if data.len() < needed {
return Err(format!(
"color LUT data too short for size {}: {} bytes, need {}",
size,
data.len(),
needed
));
}
let desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_TEXTURE3D,
Width: size as u64,
Height: size,
DepthOrArraySize: size as u16,
MipLevels: 1,
Format: DXGI_FORMAT_R8G8B8A8_UNORM,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
..Default::default()
};
let texture = alloc.alloc_texture(
&desc,
D3D12_HEAP_TYPE_DEFAULT,
D3D12_RESOURCE_STATE_COPY_DEST,
)?;
let mut layout = D3D12_PLACED_SUBRESOURCE_FOOTPRINT::default();
let mut total_size: u64 = 0;
unsafe {
device.GetCopyableFootprints(
&desc,
0,
1,
0,
Some(&mut layout),
None,
None,
Some(&mut total_size),
);
}
let upload = create_buffer(
alloc,
total_size,
D3D12_HEAP_TYPE_UPLOAD,
D3D12_RESOURCE_STATE_GENERIC_READ,
)?;
let mut map_ptr = std::ptr::null_mut::<std::ffi::c_void>();
unsafe { upload.Map(0, None, Some(&mut map_ptr)) }
.map_err(|e| format!("color LUT upload map: {e}"))?;
let src_row = n * 4;
let dst_pitch = layout.Footprint.RowPitch as usize;
let slice_pitch = dst_pitch * n;
for z in 0..n {
for y in 0..n {
let src_off = (z * n + y) * src_row;
let src = &data[src_off..src_off + src_row];
let dst = unsafe {
(map_ptr as *mut u8).add(layout.Offset as usize + z * slice_pitch + y * dst_pitch)
};
unsafe { std::ptr::copy_nonoverlapping(src.as_ptr(), dst, src_row) };
}
}
unsafe { upload.Unmap(0, None) };
one_shot_submit(device, alloc.queue(), |cmd| {
let src = D3D12_TEXTURE_COPY_LOCATION {
pResource: com::borrowed(&*upload),
Type: D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT,
Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 {
PlacedFootprint: layout,
},
};
let dst = D3D12_TEXTURE_COPY_LOCATION {
pResource: com::borrowed(&*texture),
Type: D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 {
SubresourceIndex: 0,
},
};
unsafe {
cmd.CopyTextureRegion(&dst, 0, 0, 0, &src, None);
let barrier = transition_barrier(
&texture,
D3D12_RESOURCE_STATE_COPY_DEST,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
);
cmd.ResourceBarrier(&[barrier]);
}
})?;
write_lut_srv(device, &texture, srv_cpu);
Ok(GpuResource {
resource: texture,
srv_cpu,
srv_gpu,
})
}
pub(super) fn upload_float_lut(
alloc: &DeviceAllocator,
size: u32,
components: u32,
texels: &[f32],
srv_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
srv_gpu: D3D12_GPU_DESCRIPTOR_HANDLE,
) -> Result<GpuResource, String> {
let device = alloc.device();
let n = size as usize;
let comp = components as usize;
let needed = n * n * comp;
if texels.len() < needed {
return Err(format!(
"float LUT data too short for {size}x{size}x{components}: {} floats, need {needed}",
texels.len()
));
}
let format = match components {
4 => DXGI_FORMAT_R32G32B32A32_FLOAT,
2 => DXGI_FORMAT_R32G32_FLOAT,
other => return Err(format!("unsupported float LUT component count {other}")),
};
let desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_TEXTURE2D,
Width: size as u64,
Height: size,
DepthOrArraySize: 1,
MipLevels: 1,
Format: format,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
..Default::default()
};
let texture = alloc.alloc_texture(
&desc,
D3D12_HEAP_TYPE_DEFAULT,
D3D12_RESOURCE_STATE_COPY_DEST,
)?;
let mut layout = D3D12_PLACED_SUBRESOURCE_FOOTPRINT::default();
let mut total_size: u64 = 0;
unsafe {
device.GetCopyableFootprints(
&desc,
0,
1,
0,
Some(&mut layout),
None,
None,
Some(&mut total_size),
);
}
let upload = create_buffer(
alloc,
total_size,
D3D12_HEAP_TYPE_UPLOAD,
D3D12_RESOURCE_STATE_GENERIC_READ,
)?;
let mut map_ptr = std::ptr::null_mut::<std::ffi::c_void>();
unsafe { upload.Map(0, None, Some(&mut map_ptr)) }
.map_err(|e| format!("float LUT upload map: {e}"))?;
let src_row = n * comp;
let dst_pitch = layout.Footprint.RowPitch as usize;
for y in 0..n {
let src = &texels[y * src_row..y * src_row + src_row];
let dst =
unsafe { (map_ptr as *mut u8).add(layout.Offset as usize + y * dst_pitch) as *mut f32 };
unsafe { std::ptr::copy_nonoverlapping(src.as_ptr(), dst, src_row) };
}
unsafe { upload.Unmap(0, None) };
one_shot_submit(device, alloc.queue(), |cmd| {
let src = D3D12_TEXTURE_COPY_LOCATION {
pResource: com::borrowed(&*upload),
Type: D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT,
Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 {
PlacedFootprint: layout,
},
};
let dst = D3D12_TEXTURE_COPY_LOCATION {
pResource: com::borrowed(&*texture),
Type: D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 {
SubresourceIndex: 0,
},
};
unsafe {
cmd.CopyTextureRegion(&dst, 0, 0, 0, &src, None);
let barrier = transition_barrier(
&texture,
D3D12_RESOURCE_STATE_COPY_DEST,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
);
cmd.ResourceBarrier(&[barrier]);
}
})?;
let srv_desc = D3D12_SHADER_RESOURCE_VIEW_DESC {
Format: 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: 1,
..Default::default()
},
},
};
unsafe { device.CreateShaderResourceView(&*texture, Some(&srv_desc), srv_cpu) };
Ok(GpuResource {
resource: texture,
srv_cpu,
srv_gpu,
})
}
pub(super) fn create_fallback_color_lut(
alloc: &DeviceAllocator,
srv_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
srv_gpu: D3D12_GPU_DESCRIPTOR_HANDLE,
) -> Result<GpuResource, String> {
let mut data = Vec::with_capacity(2 * 2 * 2 * 4);
for b in 0..2u8 {
for g in 0..2u8 {
for r in 0..2u8 {
data.extend_from_slice(&[r * 255, g * 255, b * 255, 255]);
}
}
}
upload_color_lut(alloc, 2, &data, srv_cpu, srv_gpu)
}