use super::barriers;
use super::types::{Dx12State, PendingDeletion, TextureState};
use super::utils::{execute_command_lists_and_signal_device, format_to_dxgi, wait_for_fence};
use super::{BufferHandle, DeviceHandle, TextureHandle};
use crate::types::{TextureFlags, TextureFormat, TextureKind};
use anyhow::{Context, Result};
use windows::core::Interface;
use windows::Win32::Graphics::{Direct3D12::*, Dxgi::Common::*};
pub(super) enum TextureUploadSource {
Pooled(super::staging::TextureStagingEntry),
Direct(windows::Win32::Graphics::Direct3D12::ID3D12Resource),
}
impl TextureUploadSource {
fn resource(&self) -> &windows::Win32::Graphics::Direct3D12::ID3D12Resource {
match self {
TextureUploadSource::Pooled(entry) => &entry.resource,
TextureUploadSource::Direct(res) => res,
}
}
}
pub(super) struct StagedTextureUpload {
pub source: TextureUploadSource,
pub footprint: D3D12_PLACED_SUBRESOURCE_FOOTPRINT,
pub texture_handle: TextureHandle,
pub dst_x: u32,
pub dst_y: u32,
pub layout_before: D3D12_BARRIER_LAYOUT,
}
pub(super) struct TextureUploadRegion<'a> {
pub texture_handle: TextureHandle,
pub x: u32,
pub y: u32,
pub width: u32,
pub height: u32,
pub data: &'a [u8],
}
fn access_for_layout(layout: D3D12_BARRIER_LAYOUT) -> D3D12_BARRIER_ACCESS {
if layout == D3D12_BARRIER_LAYOUT_COMMON {
D3D12_BARRIER_ACCESS_COMMON
} else if layout == D3D12_BARRIER_LAYOUT_DIRECT_QUEUE_SHADER_RESOURCE
|| layout == D3D12_BARRIER_LAYOUT_SHADER_RESOURCE
|| layout == D3D12_BARRIER_LAYOUT_COMPUTE_QUEUE_SHADER_RESOURCE
{
D3D12_BARRIER_ACCESS_SHADER_RESOURCE
} else if layout == D3D12_BARRIER_LAYOUT_DIRECT_QUEUE_UNORDERED_ACCESS
|| layout == D3D12_BARRIER_LAYOUT_UNORDERED_ACCESS
|| layout == D3D12_BARRIER_LAYOUT_COMPUTE_QUEUE_UNORDERED_ACCESS
{
D3D12_BARRIER_ACCESS_UNORDERED_ACCESS
} else if layout == D3D12_BARRIER_LAYOUT_COPY_DEST {
D3D12_BARRIER_ACCESS_COPY_DEST
} else if layout == D3D12_BARRIER_LAYOUT_COPY_SOURCE {
D3D12_BARRIER_ACCESS_COPY_SOURCE
} else {
D3D12_BARRIER_ACCESS_COMMON
}
}
fn post_copy_texture_state(is_storage: bool, on_direct_queue: bool) -> (D3D12_BARRIER_ACCESS, D3D12_BARRIER_LAYOUT) {
if is_storage {
(
D3D12_BARRIER_ACCESS_UNORDERED_ACCESS,
super::storage_uav_layout(on_direct_queue),
)
} else {
(
D3D12_BARRIER_ACCESS_SHADER_RESOURCE,
super::shader_resource_layout(on_direct_queue),
)
}
}
pub(super) fn init_storage_texture_uav_layout(
state: &mut Dx12State,
device_handle: DeviceHandle,
resource: &ID3D12Resource,
) -> Result<D3D12_BARRIER_LAYOUT> {
let last_layout = D3D12_BARRIER_LAYOUT_COMMON;
let logical_device = state
.devices
.get(&device_handle)
.context("Invalid device handle for storage texture initial barrier")?;
unsafe { logical_device.command_allocator.Reset() }
.context("Failed to reset command allocator for texture init barrier")?;
let init_cmd: ID3D12GraphicsCommandList = unsafe {
logical_device.device.CreateCommandList(
0,
D3D12_COMMAND_LIST_TYPE_DIRECT,
&logical_device.command_allocator,
None,
)
}
.context("Failed to create init barrier command list")?;
let init_cmd7: ID3D12GraphicsCommandList7 = init_cmd.cast().context("ID3D12GraphicsCommandList7")?;
let after_layout = D3D12_BARRIER_LAYOUT_UNORDERED_ACCESS;
let b = barriers::texture_barrier_full(
resource,
D3D12_BARRIER_SYNC_NONE,
D3D12_BARRIER_SYNC_NONE,
D3D12_BARRIER_ACCESS_NO_ACCESS,
D3D12_BARRIER_ACCESS_NO_ACCESS,
last_layout,
after_layout,
);
unsafe {
barriers::barrier_textures(&init_cmd7, &[b]);
init_cmd.Close()
}
.context("Failed to close init barrier command list")?;
let cmd_list: ID3D12CommandList = init_cmd.cast().context("Failed to cast init command list")?;
let fence_value = execute_command_lists_and_signal_device(logical_device, &[Some(cmd_list)])?;
super::utils::wait_for_fence(&logical_device.fence, fence_value)?;
Ok(after_layout)
}
pub(super) fn create(
state: &mut Dx12State,
device_handle: DeviceHandle,
width: u32,
height: u32,
format: TextureFormat,
access: TextureKind,
flags: TextureFlags,
) -> Result<TextureHandle> {
let is_storage = matches!(access, TextureKind::Direct | TextureKind::DirectInterpolated);
let is_dual_access = matches!(access, TextureKind::DirectInterpolated);
let logical_device = state.devices.get(&device_handle).context("Invalid device handle")?;
let heap_properties = D3D12_HEAP_PROPERTIES {
Type: D3D12_HEAP_TYPE_DEFAULT,
CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
MemoryPoolPreference: D3D12_MEMORY_POOL_UNKNOWN,
CreationNodeMask: 0,
VisibleNodeMask: 0,
};
let resource_flags = if is_storage {
D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS
} else {
D3D12_RESOURCE_FLAG_NONE
};
let resource_desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_TEXTURE2D,
Alignment: 0,
Width: width as u64,
Height: height,
DepthOrArraySize: 1,
MipLevels: 1,
Format: format_to_dxgi(format),
SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 },
Layout: D3D12_TEXTURE_LAYOUT_UNKNOWN,
Flags: resource_flags,
};
let initial_state = D3D12_RESOURCE_STATE_COMMON;
let mut resource: Option<ID3D12Resource> = None;
let hr = unsafe {
logical_device.device.CreateCommittedResource(
&heap_properties,
D3D12_HEAP_FLAG_NONE,
&resource_desc,
initial_state,
None,
&mut resource,
)
};
if hr.is_err() {
crate::signal::push_sync_signal(crate::signal::Signal::Oversubscribed {
reason: crate::signal::OversubscribedReason::TextureHeap,
size_hint: (width as u64) * (height as u64) * 4,
});
}
hr.context("Failed to create texture")?;
let resource = resource.context("CreateCommittedResource returned null")?;
let handle = state.textures.write().unwrap().alloc_handle();
let logical_device = state.devices.get(&device_handle).context("Invalid device handle")?;
let srv_offset = logical_device
.descriptors
.lock()
.unwrap()
.resource_registry
.register_texture(handle);
let srv_desc = D3D12_SHADER_RESOURCE_VIEW_DESC {
Format: format_to_dxgi(format),
ViewDimension: D3D12_SRV_DIMENSION_TEXTURE2D,
Shader4ComponentMapping: D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
Anonymous: D3D12_SHADER_RESOURCE_VIEW_DESC_0 {
Texture2D: D3D12_TEX2D_SRV {
MostDetailedMip: 0,
MipLevels: 1,
PlaneSlice: 0,
ResourceMinLODClamp: 0.0,
},
},
};
let srv_cpu_handle = unsafe {
let mut cpu_handle = logical_device.cbv_srv_uav_heap.GetCPUDescriptorHandleForHeapStart();
cpu_handle.ptr += (srv_offset * logical_device.cbv_srv_uav_descriptor_size) as usize;
cpu_handle
};
unsafe {
logical_device
.device
.CreateShaderResourceView(&resource, Some(&srv_desc), srv_cpu_handle);
}
let bindless_offset = if is_storage {
let uav_offset = logical_device
.descriptors
.lock()
.unwrap()
.resource_registry
.register_texture_uav(handle);
let uav_desc = D3D12_UNORDERED_ACCESS_VIEW_DESC {
Format: format_to_dxgi(format),
ViewDimension: D3D12_UAV_DIMENSION_TEXTURE2D,
Anonymous: D3D12_UNORDERED_ACCESS_VIEW_DESC_0 {
Texture2D: D3D12_TEX2D_UAV {
MipSlice: 0,
PlaneSlice: 0,
},
},
};
let uav_cpu_handle = unsafe {
let mut cpu_handle = logical_device.cbv_srv_uav_heap.GetCPUDescriptorHandleForHeapStart();
cpu_handle.ptr += (uav_offset * logical_device.cbv_srv_uav_descriptor_size) as usize;
cpu_handle
};
unsafe {
logical_device
.device
.CreateUnorderedAccessView(Some(&resource), None, Some(&uav_desc), uav_cpu_handle);
}
Some(uav_offset)
} else {
Some(srv_offset)
};
let sampled_bindless_offset = if is_dual_access {
let logical_device = state.devices.get(&device_handle).context("Invalid device handle")?;
let srv2_offset = logical_device
.descriptors
.lock()
.unwrap()
.resource_registry
.register_texture_srv(handle);
let srv2_cpu_handle = unsafe {
let mut cpu_handle = logical_device.cbv_srv_uav_heap.GetCPUDescriptorHandleForHeapStart();
cpu_handle.ptr += (srv2_offset * logical_device.cbv_srv_uav_descriptor_size) as usize;
cpu_handle
};
let srv2_desc = D3D12_SHADER_RESOURCE_VIEW_DESC {
Format: format_to_dxgi(format),
ViewDimension: D3D12_SRV_DIMENSION_TEXTURE2D,
Shader4ComponentMapping: D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
Anonymous: D3D12_SHADER_RESOURCE_VIEW_DESC_0 {
Texture2D: D3D12_TEX2D_SRV {
MostDetailedMip: 0,
MipLevels: 1,
PlaneSlice: 0,
ResourceMinLODClamp: 0.0,
},
},
};
unsafe {
logical_device
.device
.CreateShaderResourceView(&resource, Some(&srv2_desc), srv2_cpu_handle);
}
Some(srv2_offset)
} else {
None
};
let last_layout = if is_storage {
init_storage_texture_uav_layout(state, device_handle, &resource)?
} else {
D3D12_BARRIER_LAYOUT_COMMON
};
state.textures.write().unwrap().entries.insert(
handle,
TextureState {
device_handle,
width,
height,
format,
resource,
srv_offset,
bindless_offset,
sampled_bindless_offset,
last_layout,
is_storage,
transient_placed: false,
},
);
let _ = flags;
tracing::debug!(
"Created texture {}x{} (handle={}, storage={})",
width,
height,
handle,
is_storage
);
Ok(handle)
}
pub(super) fn stage_texture_upload_full(
devices: &std::collections::HashMap<DeviceHandle, super::types::SharedLogicalDevice>,
textures: &std::collections::HashMap<TextureHandle, super::types::TextureState>,
pool: &mut super::staging::TextureStagingPool,
texture_handle: TextureHandle,
data: &[u8],
width: u32,
height: u32,
) -> Result<StagedTextureUpload> {
let texture = textures.get(&texture_handle).context("Invalid texture handle")?;
if texture.width != width || texture.height != height {
anyhow::bail!("Texture dimensions mismatch");
}
let device_handle = texture.device_handle;
let logical_device = devices.get(&device_handle).context("Invalid device handle")?;
let resource_desc = unsafe { texture.resource.GetDesc() };
let mut footprint = D3D12_PLACED_SUBRESOURCE_FOOTPRINT::default();
let mut num_rows: u32 = 0;
let mut row_size: u64 = 0;
let mut total_bytes: u64 = 0;
unsafe {
logical_device.device.GetCopyableFootprints(
&resource_desc,
0,
1,
0,
Some(&mut footprint),
Some(&mut num_rows),
Some(&mut row_size),
Some(&mut total_bytes),
);
}
let staging_size = total_bytes;
let staging_entry = pool.acquire(logical_device, staging_size)?;
let mapped_ptr = staging_entry.mapped_ptr();
let texture_format = texture.format;
let bytes_per_row = (width * texture_format.bytes_per_pixel()) as usize;
let row_pitch_bytes = footprint.Footprint.RowPitch as usize;
for row in 0..height {
let src_offset = (row as usize) * bytes_per_row;
let dst_offset = (footprint.Offset + row as u64 * row_pitch_bytes as u64) as usize;
unsafe {
std::ptr::copy_nonoverlapping(data.as_ptr().add(src_offset), mapped_ptr.add(dst_offset), bytes_per_row);
}
}
let layout_before = texture.last_layout;
Ok(StagedTextureUpload {
source: TextureUploadSource::Pooled(staging_entry),
footprint,
texture_handle,
dst_x: 0,
dst_y: 0,
layout_before,
})
}
pub(super) fn stage_texture_upload_region(
devices: &std::collections::HashMap<DeviceHandle, super::types::SharedLogicalDevice>,
textures: &std::collections::HashMap<TextureHandle, super::types::TextureState>,
pool: &mut super::staging::TextureStagingPool,
region: TextureUploadRegion<'_>,
) -> Result<StagedTextureUpload> {
let TextureUploadRegion {
texture_handle,
x,
y,
width,
height,
data,
} = region;
let texture = textures.get(&texture_handle).context("Invalid texture handle")?;
if x + width > texture.width || y + height > texture.height {
anyhow::bail!(
"Region out of bounds: {}x{} at ({},{}) exceeds {}x{} texture",
width,
height,
x,
y,
texture.width,
texture.height
);
}
let expected_size = (width * height * texture.format.bytes_per_pixel()) as usize;
if data.len() != expected_size {
anyhow::bail!("Data size mismatch: expected {}, got {}", expected_size, data.len());
}
let device_handle = texture.device_handle;
let logical_device = devices.get(&device_handle).context("Invalid device handle")?;
let region_desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_TEXTURE2D,
Alignment: 0,
Width: width as u64,
Height: height,
DepthOrArraySize: 1,
MipLevels: 1,
Format: format_to_dxgi(texture.format),
SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 },
Layout: D3D12_TEXTURE_LAYOUT_UNKNOWN,
Flags: D3D12_RESOURCE_FLAG_NONE,
};
let mut footprint = D3D12_PLACED_SUBRESOURCE_FOOTPRINT::default();
let mut _num_rows: u32 = 0;
let mut _row_size: u64 = 0;
let mut total_bytes: u64 = 0;
unsafe {
logical_device.device.GetCopyableFootprints(
®ion_desc,
0,
1,
0,
Some(&mut footprint),
Some(&mut _num_rows),
Some(&mut _row_size),
Some(&mut total_bytes),
);
}
let staging_size = total_bytes;
let staging_entry = pool.acquire(logical_device, staging_size)?;
let texture_format = texture.format;
let bytes_per_row = (width * texture_format.bytes_per_pixel()) as usize;
let mapped_ptr = staging_entry.mapped_ptr();
let row_pitch_bytes = footprint.Footprint.RowPitch as usize;
for row in 0..height {
let src_offset = (row as usize) * bytes_per_row;
let dst_offset = (footprint.Offset + row as u64 * row_pitch_bytes as u64) as usize;
unsafe {
std::ptr::copy_nonoverlapping(data.as_ptr().add(src_offset), mapped_ptr.add(dst_offset), bytes_per_row);
}
}
let layout_before = texture.last_layout;
Ok(StagedTextureUpload {
source: TextureUploadSource::Pooled(staging_entry),
footprint,
texture_handle,
dst_x: x,
dst_y: y,
layout_before,
})
}
#[allow(clippy::too_many_arguments)]
pub(super) fn stage_copy_buffer_to_texture_upload(
devices: &std::collections::HashMap<DeviceHandle, super::types::SharedLogicalDevice>,
textures: &std::collections::HashMap<TextureHandle, super::types::TextureState>,
buffers: &std::collections::HashMap<super::super::BufferHandle, super::types::BufferState>,
pool: &mut super::staging::TextureStagingPool,
src: super::super::BufferHandle,
src_offset: u64,
src_row_pitch: u32,
texture_handle: TextureHandle,
x: u32,
y: u32,
width: u32,
height: u32,
) -> Result<StagedTextureUpload> {
let texture = textures.get(&texture_handle).context("Invalid texture handle")?;
if src_row_pitch > 0 {
let buffer_state = buffers
.get(&src)
.context("CopyBufferToTexture: invalid source buffer")?;
let upload_resource = buffer_state
.upload_buffer
.as_ref()
.context("CopyBufferToTexture: source buffer has no upload heap (not CPU_WRITABLE?)")?
.clone();
let layout_before = texture.last_layout;
let footprint = D3D12_PLACED_SUBRESOURCE_FOOTPRINT {
Offset: src_offset,
Footprint: D3D12_SUBRESOURCE_FOOTPRINT {
Format: format_to_dxgi(texture.format),
Width: width,
Height: height,
Depth: 1,
RowPitch: src_row_pitch,
},
};
return Ok(StagedTextureUpload {
source: TextureUploadSource::Direct(upload_resource),
footprint,
texture_handle,
dst_x: x,
dst_y: y,
layout_before,
});
}
let bpp = texture.format.bytes_per_pixel();
let flat_len = (width as usize)
.checked_mul(height as usize)
.and_then(|h| h.checked_mul(bpp as usize))
.context("CopyBufferToTexture: flat byte size overflow")?;
let data = super::buffer::cpu_writable_flat_slice(buffers, src, src_offset, flat_len)?;
if x == 0 && y == 0 && width == texture.width && height == texture.height {
stage_texture_upload_full(devices, textures, pool, texture_handle, data, width, height)
} else {
stage_texture_upload_region(
devices,
textures,
pool,
TextureUploadRegion {
texture_handle,
x,
y,
width,
height,
data,
},
)
}
}
pub(super) fn record_staged_texture_upload(
command_list: &ID3D12GraphicsCommandList,
command_list7: &ID3D12GraphicsCommandList7,
textures: &mut std::collections::HashMap<TextureHandle, TextureState>,
upload: &StagedTextureUpload,
on_direct_queue: bool,
) -> Result<()> {
let texture = textures
.get(&upload.texture_handle)
.context("record_staged_texture_upload: invalid texture")?;
let layout_before = upload.layout_before;
let layout_before_barrier = super::texture_layout_for_command_list(layout_before, on_direct_queue);
let is_storage = texture.is_storage;
let (post_access, after_layout) = post_copy_texture_state(is_storage, on_direct_queue);
let mut b_to_copy = [barriers::texture_barrier_full(
&texture.resource,
D3D12_BARRIER_SYNC_ALL,
D3D12_BARRIER_SYNC_COPY,
access_for_layout(layout_before),
D3D12_BARRIER_ACCESS_COPY_DEST,
layout_before_barrier,
D3D12_BARRIER_LAYOUT_COPY_DEST,
)];
unsafe { barriers::barrier_textures(command_list7, &b_to_copy) };
unsafe { barriers::drop_texture_barriers(&mut b_to_copy) };
let src_location = D3D12_TEXTURE_COPY_LOCATION {
pResource: unsafe { std::mem::transmute_copy(upload.source.resource()) },
Type: D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT,
Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 {
PlacedFootprint: D3D12_PLACED_SUBRESOURCE_FOOTPRINT {
Offset: upload.footprint.Offset,
Footprint: upload.footprint.Footprint,
},
},
};
let dst_location = D3D12_TEXTURE_COPY_LOCATION {
pResource: unsafe { std::mem::transmute_copy(&texture.resource) },
Type: D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 { SubresourceIndex: 0 },
};
unsafe {
command_list.CopyTextureRegion(&dst_location, upload.dst_x, upload.dst_y, 0, &src_location, None);
}
let mut b_to_shader = [barriers::texture_barrier_full(
&texture.resource,
D3D12_BARRIER_SYNC_COPY,
D3D12_BARRIER_SYNC_ALL,
D3D12_BARRIER_ACCESS_COPY_DEST,
post_access,
D3D12_BARRIER_LAYOUT_COPY_DEST,
after_layout,
)];
unsafe { barriers::barrier_textures(command_list7, &b_to_shader) };
unsafe { barriers::drop_texture_barriers(&mut b_to_shader) };
if let Some(tex) = textures.get_mut(&upload.texture_handle) {
tex.last_layout = after_layout;
}
Ok(())
}
pub(super) fn write(
state: &mut Dx12State,
texture_handle: TextureHandle,
data: &[u8],
width: u32,
height: u32,
) -> Result<()> {
let staged = {
let mut pool = super::staging::TextureStagingPool::new();
stage_texture_upload_full(
&state.devices,
&state.textures.read().unwrap().entries,
&mut pool,
texture_handle,
data,
width,
height,
)?
};
execute_staged_uploads_sync(state, vec![staged])?;
tracing::debug!("Wrote {}x{} texture (sync upload)", width, height);
Ok(())
}
pub(super) fn write_region(
state: &mut Dx12State,
texture_handle: TextureHandle,
x: u32,
y: u32,
width: u32,
height: u32,
data: &[u8],
) -> Result<()> {
let staged = {
let mut pool = super::staging::TextureStagingPool::new();
stage_texture_upload_region(
&state.devices,
&state.textures.read().unwrap().entries,
&mut pool,
TextureUploadRegion {
texture_handle,
x,
y,
width,
height,
data,
},
)?
};
execute_staged_uploads_sync(state, vec![staged])?;
tracing::debug!(
"Wrote {}x{} texture region at ({},{}) (sync upload)",
width,
height,
x,
y,
);
Ok(())
}
pub(super) fn execute_staged_uploads_sync(state: &mut Dx12State, uploads: Vec<StagedTextureUpload>) -> Result<()> {
if uploads.is_empty() {
return Ok(());
}
let copies = uploads;
let count = copies.len();
let device_handle = {
let textures_read = state.textures.read().unwrap();
textures_read
.entries
.get(&copies[0].texture_handle)
.context("flush_pending_copies: invalid texture handle")?
.device_handle
};
let logical_device = state
.devices
.get(&device_handle)
.context("flush_pending_copies: invalid device handle")?;
unsafe { logical_device.command_allocator.Reset() }
.context("flush_pending_copies: failed to reset command allocator")?;
let command_list: ID3D12GraphicsCommandList = unsafe {
logical_device.device.CreateCommandList(
0,
D3D12_COMMAND_LIST_TYPE_DIRECT,
&logical_device.command_allocator,
None,
)
}
.context("flush_pending_copies: failed to create command list")?;
let command_list7: ID3D12GraphicsCommandList7 = command_list.cast().context("ID3D12GraphicsCommandList7")?;
let mut texture_order: Vec<TextureHandle> = Vec::new();
let mut groups: std::collections::HashMap<TextureHandle, Vec<usize>> = std::collections::HashMap::new();
for (i, copy) in copies.iter().enumerate() {
groups
.entry(copy.texture_handle)
.or_insert_with(|| {
texture_order.push(copy.texture_handle);
Vec::new()
})
.push(i);
}
let after_layout = D3D12_BARRIER_LAYOUT_SHADER_RESOURCE;
for tex_handle in &texture_order {
let indices = &groups[tex_handle];
let layout_before = copies[indices[0]].layout_before;
let layout_before_barrier = super::texture_layout_for_command_list(layout_before, true);
let resource = {
let textures_read = state.textures.read().unwrap();
textures_read
.entries
.get(tex_handle)
.context("flush_pending_copies: texture disappeared")?
.resource
.clone()
};
let mut b_to_copy = [barriers::texture_barrier_full(
&resource,
D3D12_BARRIER_SYNC_ALL,
D3D12_BARRIER_SYNC_COPY,
access_for_layout(layout_before),
D3D12_BARRIER_ACCESS_COPY_DEST,
layout_before_barrier,
D3D12_BARRIER_LAYOUT_COPY_DEST,
)];
unsafe { barriers::barrier_textures(&command_list7, &b_to_copy) };
unsafe { barriers::drop_texture_barriers(&mut b_to_copy) };
for &idx in indices {
let copy = &copies[idx];
let src_location = D3D12_TEXTURE_COPY_LOCATION {
pResource: unsafe { std::mem::transmute_copy(copy.source.resource()) },
Type: D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT,
Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 {
PlacedFootprint: D3D12_PLACED_SUBRESOURCE_FOOTPRINT {
Offset: copy.footprint.Offset,
Footprint: copy.footprint.Footprint,
},
},
};
let dst_location = D3D12_TEXTURE_COPY_LOCATION {
pResource: unsafe { std::mem::transmute_copy(&resource) },
Type: D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 { SubresourceIndex: 0 },
};
unsafe {
command_list.CopyTextureRegion(&dst_location, copy.dst_x, copy.dst_y, 0, &src_location, None);
}
}
let mut b_to_shader = [barriers::texture_barrier_full(
&resource,
D3D12_BARRIER_SYNC_COPY,
D3D12_BARRIER_SYNC_ALL,
D3D12_BARRIER_ACCESS_COPY_DEST,
D3D12_BARRIER_ACCESS_SHADER_RESOURCE,
D3D12_BARRIER_LAYOUT_COPY_DEST,
after_layout,
)];
unsafe { barriers::barrier_textures(&command_list7, &b_to_shader) };
unsafe { barriers::drop_texture_barriers(&mut b_to_shader) };
{
let mut textures_write = state.textures.write().unwrap();
if let Some(tex) = textures_write.entries.get_mut(tex_handle) {
tex.last_layout = after_layout;
}
}
}
unsafe { command_list.Close() }.context("flush_pending_copies: failed to close command list")?;
let cmd_list: ID3D12CommandList = command_list.cast().context("Failed to cast command list")?;
let logical_device = state.devices.get(&device_handle).unwrap();
let fence_value = execute_command_lists_and_signal_device(logical_device, &[Some(cmd_list)])?;
wait_for_fence(&logical_device.fence, fence_value)?;
for copy in copies {
if let TextureUploadSource::Pooled(entry) = copy.source {
unsafe { entry.destroy() };
}
}
tracing::debug!("Flushed {} pending texture copies in one submission", count);
Ok(())
}
pub(super) fn query_texture_copy_footprint(
state: &Dx12State,
device_handle: DeviceHandle,
width: u32,
height: u32,
format: TextureFormat,
) -> Result<crate::backend::TextureCopyFootprint> {
let logical_device = state.devices.get(&device_handle).context("Invalid device handle")?;
let dxgi_format = format_to_dxgi(format);
let res_desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_TEXTURE2D,
Alignment: 0,
Width: width as u64,
Height: height,
DepthOrArraySize: 1,
MipLevels: 1,
Format: dxgi_format,
SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 },
Layout: D3D12_TEXTURE_LAYOUT_UNKNOWN,
Flags: D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS,
};
let mut footprint = D3D12_PLACED_SUBRESOURCE_FOOTPRINT::default();
let mut num_rows: u32 = 0;
let mut row_size: u64 = 0;
let mut total_bytes: u64 = 0;
unsafe {
logical_device.device.GetCopyableFootprints(
&res_desc,
0,
1,
0,
Some(&mut footprint),
Some(&mut num_rows),
Some(&mut row_size),
Some(&mut total_bytes),
);
}
let logical_bytes = (width as u64) * (height as u64) * (format.bytes_per_pixel() as u64);
let row_pitch = footprint.Footprint.RowPitch;
let footprint_offset = footprint.Offset;
let min_from_pitch = footprint_offset.saturating_add((row_pitch as u64).saturating_mul(height as u64));
Ok(crate::backend::TextureCopyFootprint {
width,
height,
format,
logical_bytes,
staging_bytes: total_bytes.max(min_from_pitch).max(logical_bytes),
row_pitch,
footprint_offset,
})
}
#[allow(clippy::too_many_arguments)]
pub(super) fn record_copy_texture_to_readback(
command_list: &ID3D12GraphicsCommandList,
command_list7: &ID3D12GraphicsCommandList7,
textures: &mut std::collections::HashMap<TextureHandle, TextureState>,
buffers: &std::collections::HashMap<BufferHandle, super::types::BufferState>,
src: TextureHandle,
dst: BufferHandle,
layout: crate::backend::TextureCopyFootprint,
on_direct_queue: bool,
) -> Result<()> {
let (src_resource, dst_resource, layout_before, is_storage, dxgi_format) = {
let texture = textures
.get(&src)
.context("CopyTextureToReadback: src texture not found")?;
let dst_buf = buffers
.get(&dst)
.context("CopyTextureToReadback: dst buffer not found")?;
(
texture.resource.clone(),
dst_buf.resource.clone(),
texture.last_layout,
texture.is_storage,
format_to_dxgi(layout.format),
)
};
let layout_before_barrier = super::texture_layout_for_command_list(layout_before, on_direct_queue);
let b_to_src = barriers::texture_barrier_full(
&src_resource,
D3D12_BARRIER_SYNC_ALL,
D3D12_BARRIER_SYNC_COPY,
access_for_layout(layout_before),
D3D12_BARRIER_ACCESS_COPY_SOURCE,
layout_before_barrier,
D3D12_BARRIER_LAYOUT_COPY_SOURCE,
);
unsafe { barriers::barrier_textures(command_list7, &[b_to_src]) };
let src_location = D3D12_TEXTURE_COPY_LOCATION {
pResource: unsafe { std::mem::transmute_copy(&src_resource) },
Type: D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 { SubresourceIndex: 0 },
};
let dst_location = D3D12_TEXTURE_COPY_LOCATION {
pResource: unsafe { std::mem::transmute_copy(&dst_resource) },
Type: D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT,
Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 {
PlacedFootprint: D3D12_PLACED_SUBRESOURCE_FOOTPRINT {
Offset: layout.footprint_offset,
Footprint: D3D12_SUBRESOURCE_FOOTPRINT {
Format: dxgi_format,
Width: layout.width,
Height: layout.height,
Depth: 1,
RowPitch: layout.row_pitch,
},
},
},
};
unsafe { command_list.CopyTextureRegion(&dst_location, 0, 0, 0, &src_location, None) };
let (post_access, post_layout) = post_copy_texture_state(is_storage, on_direct_queue);
let b_back = barriers::texture_barrier_full(
&src_resource,
D3D12_BARRIER_SYNC_COPY,
D3D12_BARRIER_SYNC_ALL,
D3D12_BARRIER_ACCESS_COPY_SOURCE,
post_access,
D3D12_BARRIER_LAYOUT_COPY_SOURCE,
post_layout,
);
unsafe { barriers::barrier_textures(command_list7, &[b_back]) };
if let Some(tex) = textures.get_mut(&src) {
tex.last_layout = post_layout;
}
Ok(())
}
pub(super) fn destroy(state: &mut Dx12State, texture_handle: TextureHandle) {
if let Some(tex) = state.textures.write().unwrap().entries.remove(&texture_handle) {
if let Some(dev) = state.devices.get(&tex.device_handle) {
let slots = dev.descriptors.lock().unwrap().texture_slot_keys(texture_handle);
super::compute::evict_retained_graphs_using_slots(state, tex.device_handle, &slots);
if tex.transient_placed {
dev.descriptors.lock().unwrap().reclaim_texture_slots(texture_handle);
return;
}
let ctx_h = super::context::destroy_attribution_context(state, tex.device_handle);
let base = super::context::reclamation_requirements(state, tex.device_handle, ctx_h);
let requirements = {
let registry = dev.descriptors.lock().unwrap();
registry.bindless_retirement_requirements_for_texture(texture_handle, base)
};
dev.deletion_queue.lock().unwrap().queue(
requirements,
PendingDeletion::Texture {
texture_handle,
resource: tex.resource,
},
);
}
}
}
pub(super) fn bindless_index(state: &Dx12State, texture_handle: TextureHandle) -> Option<u32> {
state
.textures
.read()
.unwrap()
.entries
.get(&texture_handle)
.and_then(|t| t.bindless_offset)
}
pub(super) fn bindless_sampled_index(state: &Dx12State, texture_handle: TextureHandle) -> Option<u32> {
state
.textures
.read()
.unwrap()
.entries
.get(&texture_handle)
.and_then(|t| t.sampled_bindless_offset)
}