use alloc::borrow::ToOwned; use alloc::{
borrow::Cow,
string::{String, ToString as _},
sync::Arc,
vec::Vec,
}; use arrayvec::ArrayVec; use core::{ffi, num::NonZeroU32, ptr, time::Duration}; use std::time::Instant;
use bytemuck::TransparentWrapper; use parking_lot::Mutex; use windows::{
core::Interface as _,
Win32::{
Foundation,
Graphics::{Direct3D12, Dxgi},
System::Threading,
},
};
// Blocks until the dedicated present queue is finished with all of its work. // // Once this method completes, the surface is able to be resized or deleted. pub(super) unsafefn wait_for_present_queue_idle(&self) -> Result<(), crate::DeviceError> { let cur_value = unsafe { self.idler.fence.GetCompletedValue() }; if cur_value == !0 { return Err(crate::DeviceError::Lost);
}
let event = Event::create(false, false)?;
let value = cur_value + 1; unsafe { self.present_queue.Signal(&self.idler.fence, value) }
.into_device_result("Signal")?; let hr = unsafe { self.idler.fence.SetEventOnCompletion(value, event.0) };
hr.into_device_result("Set event")?; unsafe { Threading::WaitForSingleObject(event.0, Threading::INFINITE) };
Ok(())
}
/// When generating the vertex shader, the fragment stage must be passed if it exists! /// Otherwise, the generated HLSL may be incorrect since the fragment shader inputs are /// allowed to be a subset of the vertex outputs. fn load_shader(
&self,
stage: &crate::ProgrammableStage<super::ShaderModule>,
layout: &super::PipelineLayout,
naga_stage: naga::ShaderStage,
fragment_stage: Option<&crate::ProgrammableStage<super::ShaderModule>>,
) -> Result<super::CompiledShader, crate::PipelineError> { let stage_bit = auxil::map_naga_stage(naga_stage);
{ letmut shader_cache = self.shader_cache.lock();
shader_cache.nr_of_shaders_compiled += 1; let nr_of_shaders_compiled = shader_cache.nr_of_shaders_compiled; let value = ShaderCacheValue {
last_used: nr_of_shaders_compiled,
shader: compiled_shader.clone(),
};
shader_cache.entries.insert(key, value);
// Retain all entries that have been used since we compiled the last 100 shaders. if shader_cache.entries.len() > 200 {
shader_cache
.entries
.retain(|_, v| v.last_used >= nr_of_shaders_compiled - 100);
}
}
unsafefn map_buffer(
&self,
buffer: &super::Buffer,
range: crate::MemoryRange,
) -> Result<crate::BufferMapping, crate::DeviceError> { letmut ptr = ptr::null_mut(); // TODO: 0 for subresource should be fine here until map and unmap buffer is subresource aware? unsafe { buffer.resource.Map(0, None, Some(&mut ptr)) }.into_device_result("Map buffer")?;
Ok(crate::BufferMapping {
ptr: ptr::NonNull::new(unsafe { ptr.offset(range.start as isize).cast::<u8>() })
.unwrap(), //TODO: double-check this. Documentation is a bit misleading - // it implies that Map/Unmap is needed to invalidate/flush memory.
is_coherent: true,
})
}
unsafefn create_pipeline_layout(
&self,
desc: &crate::PipelineLayoutDescriptor<super::BindGroupLayout>,
) -> Result<super::PipelineLayout, crate::DeviceError> { use naga::back::hlsl; // Pipeline layouts are implemented as RootSignature for D3D12. // // Immediates are implemented as root constants. // // Each bind group layout might use one SRV/CBV/UAV descriptor table. // With resources in the bind group layout using: // - 1 CBV per non-dynamic uniform buffer // - 1 SRV per acceleration structure // - 1 SRV for all samplers in a bind group // - 1 SRV per texture // - 1 SRV per read-only storage buffer // - 1 UAV per storage texture // - 1 UAV per read-write storage buffer // - 3 SRVs & 1 CBV per external texture // // Each dynamic uniform buffer takes up a CBV root descriptor. // This is easier than trying to patch up the offset on the shader side. // // Each dynamic storage buffer is an SRV or UAV in the descriptor table // and its dynamic offsets are passed via root constants. // // All samplers go into a single sampler descriptor table. // // 3 additional root constants are used to populate built-in (shader) inputs. // // Root signature layout: // Root Constants: Parameter=0, Space=0 // ... // (bind group [0]) - Space=0 // View descriptor table, if any // Sampler buffer descriptor table, if any // Root descriptors (for dynamic offset buffers) // (bind group [1]) - Space=0 // ... // (bind group [2]) - Space=0 // Special constant buffer: Space=0 // Sampler descriptor tables: Space=0 // SamplerState Array: Space=0, Register=0-2047 // SamplerComparisonState Array: Space=0, Register=2048-4095
// Collect the whole number of bindings we will create upfront. // It allows us to preallocate enough storage to avoid reallocation, // which could cause invalid pointers. letmut total_non_dynamic_entries = 0_usize; letmut sampler_in_any_bind_group = false; for bgl in desc.bind_group_layouts { let Some(bgl) = bgl else { continue;
};
letmut sampler_in_bind_group = false;
for entry in &bgl.entries { match entry.ty {
wgt::BindingType::Buffer {
ty: wgt::BufferBindingType::Uniform,
has_dynamic_offset: true,
..
} => {}
wgt::BindingType::Sampler(_) => sampler_in_bind_group = true, // Three texture planes and one params buffer
wgt::BindingType::ExternalTexture => total_non_dynamic_entries += 4,
_ => total_non_dynamic_entries += 1,
}
}
if sampler_in_bind_group { // One for the sampler buffer
total_non_dynamic_entries += 1;
sampler_in_any_bind_group = true;
}
}
if sampler_in_any_bind_group { // Two for the sampler arrays themselves
total_non_dynamic_entries += 2;
}
// SRV/CBV/UAV descriptor tables let range_base = ranges.len(); for entry in bgl.entries.iter() { let count = entry.count.map_or(1, NonZeroU32::get); iflet wgt::BindingType::ExternalTexture = entry.ty { // External textures need 3 SRVs (a texture for each plane) // and 1 CBV for the parameters buffer. let bind_target = hlsl::ExternalTextureBindTarget {
planes: core::array::from_fn(|_| hlsl::BindTarget {
register: { let register = bind_srv.register;
bind_srv.register += count;
register
},
..bind_srv
}),
params: hlsl::BindTarget {
register: { let register = bind_cbv.register;
bind_cbv.register += count;
register
},
..bind_cbv
},
};
external_texture_binding_map.insert(
naga::ResourceBinding {
group: index as u32,
binding: entry.binding,
},
bind_target,
); for bt in bind_target.planes {
ranges.push(Direct3D12::D3D12_DESCRIPTOR_RANGE {
RangeType: Direct3D12::D3D12_DESCRIPTOR_RANGE_TYPE_SRV,
NumDescriptors: count,
BaseShaderRegister: bt.register,
RegisterSpace: bt.space as u32,
OffsetInDescriptorsFromTableStart:
Direct3D12::D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND,
});
}
ranges.push(Direct3D12::D3D12_DESCRIPTOR_RANGE {
RangeType: Direct3D12::D3D12_DESCRIPTOR_RANGE_TYPE_CBV,
NumDescriptors: count,
BaseShaderRegister: bind_target.params.register,
RegisterSpace: bind_target.params.space as u32,
OffsetInDescriptorsFromTableStart:
Direct3D12::D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND,
});
} else { let (range_ty, has_dynamic_offset) = match entry.ty {
wgt::BindingType::Buffer {
ty,
has_dynamic_offset: true,
..
} => match ty {
wgt::BufferBindingType::Uniform => continue,
wgt::BufferBindingType::Storage { .. } => {
(conv::map_binding_type(&entry.ty), true)
}
}, ref other => (conv::map_binding_type(other), false),
}; let bt = match range_ty {
Direct3D12::D3D12_DESCRIPTOR_RANGE_TYPE_CBV => &mut bind_cbv,
Direct3D12::D3D12_DESCRIPTOR_RANGE_TYPE_SRV => &mut bind_srv,
Direct3D12::D3D12_DESCRIPTOR_RANGE_TYPE_UAV => &mut bind_uav,
Direct3D12::D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER => continue,
_ => todo!(),
};
let binding_array_size = entry.count.map(NonZeroU32::get);
let dynamic_storage_buffer_offsets_index = if has_dynamic_offset {
debug_assert!(
binding_array_size.is_none(), "binding arrays and dynamic buffers are mutually exclusive"
); let ret = Some(dynamic_storage_buffers);
dynamic_storage_buffers += 1;
ret
} else {
None
};
letmut sampler_index_within_bind_group = 0; for entry in bgl.entries.iter() { iflet wgt::BindingType::Sampler(_) = entry.ty {
binding_map.insert(
naga::ResourceBinding {
group: index as u32,
binding: entry.binding,
},
hlsl::BindTarget { // Naga does not use the space field for samplers
space: 255,
register: sampler_index_within_bind_group,
binding_array_size: None,
dynamic_storage_buffer_offsets_index: None,
restrict_indexing: false,
},
);
sampler_index_within_bind_group += 1;
}
}
if sampler_index_within_bind_group != 0 {
sampler_buffer_binding_map.insert(
hlsl::SamplerIndexBufferKey {
group: index as u32,
},
bind_srv,
);
ranges.push(Direct3D12::D3D12_DESCRIPTOR_RANGE {
RangeType: Direct3D12::D3D12_DESCRIPTOR_RANGE_TYPE_SRV,
NumDescriptors: 1,
BaseShaderRegister: bind_srv.register,
RegisterSpace: bind_srv.space as u32,
OffsetInDescriptorsFromTableStart:
Direct3D12::D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND,
});
bind_srv.register += 1;
}
if ranges.len() > range_base { let range = &ranges[range_base..];
parameters.push(Direct3D12::D3D12_ROOT_PARAMETER {
ParameterType: Direct3D12::D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE,
Anonymous: Direct3D12::D3D12_ROOT_PARAMETER_0 {
DescriptorTable: Direct3D12::D3D12_ROOT_DESCRIPTOR_TABLE {
NumDescriptorRanges: range.len() as u32,
pDescriptorRanges: range.as_ptr(),
},
},
ShaderVisibility: conv::map_visibility(visibility_view_static),
});
info.tables |= super::TableTypes::SRV_CBV_UAV;
}
// Root descriptors for dynamic uniform buffers let dynamic_buffers_visibility = conv::map_visibility(visibility_view_dynamic_uniform); for entry in bgl.entries.iter() { match entry.ty {
wgt::BindingType::Buffer {
ty: wgt::BufferBindingType::Uniform,
has_dynamic_offset: true,
..
} => {}
_ => continue,
};
binding_map.insert(
naga::ResourceBinding {
group: index as u32,
binding: entry.binding,
},
hlsl::BindTarget {
binding_array_size: entry.count.map(NonZeroU32::get),
restrict_indexing: true,
..bind_cbv
},
);
letmut sampler_heap_root_index = None; if sampler_in_any_bind_group { // Sampler descriptor tables // // We bind two sampler ranges pointing to the same descriptor heap, using two different register ranges. // // We bind them as normal samplers in registers 0-2047 and comparison samplers in registers 2048-4095. // Tier 2 hardware guarantees that the type of sampler only needs to match if the sampler is actually // accessed in the shader. As such, we can bind the same array of samplers to both registers. // // We do this because HLSL does not allow you to alias registers at all. let range_base = ranges.len(); // Standard samplers, registers 0-2047
ranges.push(Direct3D12::D3D12_DESCRIPTOR_RANGE {
RangeType: Direct3D12::D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER,
NumDescriptors: 2048,
BaseShaderRegister: 0,
RegisterSpace: 0,
OffsetInDescriptorsFromTableStart: 0,
}); // Comparison samplers, registers 2048-4095
ranges.push(Direct3D12::D3D12_DESCRIPTOR_RANGE {
RangeType: Direct3D12::D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER,
NumDescriptors: 2048,
BaseShaderRegister: 2048,
RegisterSpace: 0,
OffsetInDescriptorsFromTableStart: 0,
});
let range = &ranges[range_base..];
sampler_heap_root_index = Some(parameters.len() assuper::RootIndex);
parameters.push(Direct3D12::D3D12_ROOT_PARAMETER {
ParameterType: Direct3D12::D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE,
Anonymous: Direct3D12::D3D12_ROOT_PARAMETER_0 {
DescriptorTable: Direct3D12::D3D12_ROOT_DESCRIPTOR_TABLE {
NumDescriptorRanges: range.len() as u32,
pDescriptorRanges: range.as_ptr(),
},
},
ShaderVisibility: Direct3D12::D3D12_SHADER_VISIBILITY_ALL,
});
}
// Ensure that we didn't reallocate!
debug_assert_eq!(ranges.len(), total_non_dynamic_entries);
let (special_constants_root_index, special_constants_binding) = if desc.flags.intersects( crate::PipelineLayoutFlags::FIRST_VERTEX_INSTANCE
| crate::PipelineLayoutFlags::NUM_WORK_GROUPS,
) { let parameter_index = parameters.len();
parameters.push(Direct3D12::D3D12_ROOT_PARAMETER {
ParameterType: Direct3D12::D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS,
Anonymous: Direct3D12::D3D12_ROOT_PARAMETER_0 {
Constants: Direct3D12::D3D12_ROOT_CONSTANTS {
ShaderRegister: bind_cbv.register,
RegisterSpace: bind_cbv.space as u32,
Num32BitValues: 3, // 0 = first_vertex, 1 = first_instance, 2 = other
},
},
ShaderVisibility: Direct3D12::D3D12_SHADER_VISIBILITY_ALL, // really needed for VS and CS only,
}); let binding = bind_cbv; // This is the last time we use this, but lets increment // it so if we add more later, the value behaves correctly.
// This is an allow as it doesn't trigger on 1.90, hal's MSRV. #[allow(unused_assignments)]
{
bind_cbv.register += 1;
}
(Some(parameter_index as u32), Some(binding))
} else {
(None, None)
};
let blob = self.library.serialize_root_signature(
Direct3D12::D3D_ROOT_SIGNATURE_VERSION_1_0,
¶meters,
&[],
Direct3D12::D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT,
)?;
let raw = unsafe { self.raw
.CreateRootSignature::<Direct3D12::ID3D12RootSignature>(0, blob.as_slice())
}
.into_device_result("Root signature creation")?;
let special_constants = iflet Some(root_index) = special_constants_root_index { let cmd_signatures = if desc
.flags
.contains(crate::PipelineLayoutFlags::INDIRECT_BUILTIN_UPDATE)
{ let constant_indirect_argument_desc = Direct3D12::D3D12_INDIRECT_ARGUMENT_DESC { Type: Direct3D12::D3D12_INDIRECT_ARGUMENT_TYPE_CONSTANT,
Anonymous: Direct3D12::D3D12_INDIRECT_ARGUMENT_DESC_0 {
Constant: Direct3D12::D3D12_INDIRECT_ARGUMENT_DESC_0_1 {
RootParameterIndex: root_index,
DestOffsetIn32BitValues: 0,
Num32BitValuesToSet: 3,
},
},
}; let special_constant_buffer_args_len = size_of::<super::SpecialConstants>();
let layout_and_entry_iter = desc.entries.iter().map(|entry| { let layout = desc
.layout
.entries
.iter()
.find(|layout_entry| layout_entry.binding == entry.binding)
.expect("internal error: no layout entry found with binding slot");
(layout, entry)
}); letmut sampler_indexes: Vec<super::sampler::SamplerIndex> = Vec::new();
for (layout, entry) in layout_and_entry_iter { match layout.ty {
wgt::BindingType::Buffer {
ty,
has_dynamic_offset,
..
} => { let start = entry.resource_index as usize; let end = start + entry.count as usize; for data in &desc.buffers[start..end] { let gpu_address = data.resolve_address(); letmut size = data.resolve_size().try_into().unwrap();
if has_dynamic_offset { match ty {
wgt::BufferBindingType::Uniform => {
dynamic_buffers.push(super::DynamicBuffer::Uniform(
Direct3D12::D3D12_GPU_DESCRIPTOR_HANDLE {
ptr: data.resolve_address(),
},
)); continue;
}
wgt::BufferBindingType::Storage { .. } => {
size = (data.buffer.size - data.offset) as u32;
dynamic_buffers.push(super::DynamicBuffer::Storage);
}
}
}
let inner = cpu_views.as_mut().unwrap(); let cpu_index = inner.stage.len() as u32; let handle = desc.layout.cpu_heap_views.as_ref().unwrap().at(cpu_index); match ty {
wgt::BufferBindingType::Uniform => { let size_mask =
Direct3D12::D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT - 1; let raw_desc = Direct3D12::D3D12_CONSTANT_BUFFER_VIEW_DESC {
BufferLocation: gpu_address,
SizeInBytes: ((size - 1) | size_mask) + 1,
}; unsafe { self.raw.CreateConstantBufferView(Some(&raw_desc), handle)
};
}
wgt::BufferBindingType::Storage { read_only: true } => { let raw_desc = Direct3D12::D3D12_SHADER_RESOURCE_VIEW_DESC {
Format: Dxgi::Common::DXGI_FORMAT_R32_TYPELESS,
Shader4ComponentMapping:
Direct3D12::D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
ViewDimension: Direct3D12::D3D12_SRV_DIMENSION_BUFFER,
Anonymous: Direct3D12::D3D12_SHADER_RESOURCE_VIEW_DESC_0 {
Buffer: Direct3D12::D3D12_BUFFER_SRV {
FirstElement: data.offset / 4,
NumElements: size / 4,
StructureByteStride: 0,
Flags: Direct3D12::D3D12_BUFFER_SRV_FLAG_RAW,
},
},
}; unsafe { self.raw.CreateShaderResourceView(
&data.buffer.resource,
Some(&raw_desc),
handle,
)
};
}
wgt::BufferBindingType::Storage { read_only: false } => { let raw_desc = Direct3D12::D3D12_UNORDERED_ACCESS_VIEW_DESC {
Format: Dxgi::Common::DXGI_FORMAT_R32_TYPELESS,
ViewDimension: Direct3D12::D3D12_UAV_DIMENSION_BUFFER,
Anonymous: Direct3D12::D3D12_UNORDERED_ACCESS_VIEW_DESC_0 {
Buffer: Direct3D12::D3D12_BUFFER_UAV {
FirstElement: data.offset / 4,
NumElements: size / 4,
StructureByteStride: 0,
CounterOffsetInBytes: 0,
Flags: Direct3D12::D3D12_BUFFER_UAV_FLAG_RAW,
},
},
}; unsafe { self.raw.CreateUnorderedAccessView(
&data.buffer.resource,
None,
Some(&raw_desc),
handle,
)
};
}
}
inner.stage.push(handle);
}
}
wgt::BindingType::Texture { .. } => { let start = entry.resource_index as usize; let end = start + entry.count as usize; for data in &desc.textures[start..end] { let handle = data.view.handle_srv.unwrap();
cpu_views.as_mut().unwrap().stage.push(handle.raw);
}
}
wgt::BindingType::StorageTexture { .. } => { let start = entry.resource_index as usize; let end = start + entry.count as usize; for data in &desc.textures[start..end] { let handle = data.view.handle_uav.unwrap();
cpu_views.as_mut().unwrap().stage.push(handle.raw);
}
}
wgt::BindingType::Sampler { .. } => { let start = entry.resource_index as usize; let end = start + entry.count as usize; for &data in &desc.samplers[start..end] {
sampler_indexes.push(data.index);
}
}
wgt::BindingType::AccelerationStructure { .. } => { let start = entry.resource_index as usize; let end = start + entry.count as usize; for data in &desc.acceleration_structures[start..end] { let inner = cpu_views.as_mut().unwrap(); let cpu_index = inner.stage.len() as u32; let handle = desc.layout.cpu_heap_views.as_ref().unwrap().at(cpu_index); let raw_desc = Direct3D12::D3D12_SHADER_RESOURCE_VIEW_DESC {
Format: Dxgi::Common::DXGI_FORMAT_UNKNOWN,
Shader4ComponentMapping:
Direct3D12::D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
ViewDimension:
Direct3D12::D3D12_SRV_DIMENSION_RAYTRACING_ACCELERATION_STRUCTURE,
Anonymous: Direct3D12::D3D12_SHADER_RESOURCE_VIEW_DESC_0 {
RaytracingAccelerationStructure:
Direct3D12::D3D12_RAYTRACING_ACCELERATION_STRUCTURE_SRV {
Location: unsafe { data.resource.GetGPUVirtualAddress() },
},
},
}; unsafe { self.raw
.CreateShaderResourceView(None, Some(&raw_desc), handle)
};
inner.stage.push(handle);
}
}
wgt::BindingType::ExternalTexture => { // We don't yet support binding arrays of external textures. // https://github.com/gfx-rs/wgpu/issues/8027
assert_eq!(entry.count, 1); let external_texture = &desc.external_textures[entry.resource_index as usize]; for plane in &external_texture.planes { let plane_handle = plane.view.handle_srv.unwrap();
cpu_views.as_mut().unwrap().stage.push(plane_handle.raw);
} let gpu_address = external_texture.params.resolve_address(); let size = external_texture.params.resolve_size() as u32; let inner = cpu_views.as_mut().unwrap(); let cpu_index = inner.stage.len() as u32; let params_handle = desc.layout.cpu_heap_views.as_ref().unwrap().at(cpu_index); let size_mask = Direct3D12::D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT - 1; let raw_desc = Direct3D12::D3D12_CONSTANT_BUFFER_VIEW_DESC {
BufferLocation: gpu_address,
SizeInBytes: ((size - 1) | size_mask) + 1,
}; unsafe { self.raw
.CreateConstantBufferView(Some(&raw_desc), params_handle)
};
inner.stage.push(params_handle);
}
}
}
let sampler_index_buffer = if !sampler_indexes.is_empty() { let buffer_size = (sampler_indexes.len() * size_of::<u32>()) as u64;
let label = iflet Some(label) = desc.label {
Cow::Owned(format!("{label} (Internal Sampler Index Buffer)"))
} else {
Cow::Borrowed("Internal Sampler Index Buffer")
};
let buffer_desc = crate::BufferDescriptor {
label: Some(&label),
size: buffer_size,
usage: wgt::BufferUses::STORAGE_READ_ONLY | wgt::BufferUses::MAP_WRITE, // D3D12 backend doesn't care about the memory flags
memory_flags: crate::MemoryFlags::empty(),
};
let (buffer, allocation) =
suballocation::DeviceAllocationContext::from(self).create_buffer(&buffer_desc)?;
let inner = cpu_views.as_mut().unwrap(); let cpu_index = inner.stage.len() as u32; let srv = desc.layout.cpu_heap_views.as_ref().unwrap().at(cpu_index);
let blob_fs = match desc.fragment_stage {
Some(ref stage) => {
shader_stages |= wgt::ShaderStages::FRAGMENT;
Some(self.load_shader(stage, desc.layout, naga::ShaderStage::Fragment, None)?)
}
None => None,
}; let pixel_shader = match &blob_fs {
Some(shader) => shader.create_native_shader(),
None => Direct3D12::D3D12_SHADER_BYTECODE::default(),
}; let stream_output = Direct3D12::D3D12_STREAM_OUTPUT_DESC {
pSODeclaration: ptr::null(),
NumEntries: 0,
pBufferStrides: ptr::null(),
NumStrides: 0,
RasterizedStream: 0,
}; let blend_state = Direct3D12::D3D12_BLEND_DESC {
AlphaToCoverageEnable: windows_core::BOOL::from(
desc.multisample.alpha_to_coverage_enabled,
),
IndependentBlendEnable: true.into(),
RenderTarget: conv::map_render_targets(desc.color_targets),
}; let depth_stencil_state = match desc.depth_stencil {
Some(ref ds) => conv::map_depth_stencil(ds),
None => Default::default(),
}; let dsv_format = desc
.depth_stencil
.as_ref()
.map_or(Dxgi::Common::DXGI_FORMAT_UNKNOWN, |ds| {
auxil::dxgi::conv::map_texture_format(ds.format)
}); let sample_desc = Dxgi::Common::DXGI_SAMPLE_DESC {
Count: desc.multisample.count,
Quality: 0,
}; let cached_pso = Direct3D12::D3D12_CACHED_PIPELINE_STATE {
pCachedBlob: ptr::null(),
CachedBlobSizeInBytes: 0,
}; let flags = Direct3D12::D3D12_PIPELINE_STATE_FLAG_NONE;
letmut view_instancing = ArrayVec::<Direct3D12::D3D12_VIEW_INSTANCE_LOCATION, 32>::new(); iflet Some(mask) = desc.multiview_mask { let mask = mask.get(); // This array is just what _could_ be rendered to. We actually apply the mask at // renderpass creation time. The `view_index` passed to the shader depends on the // view's index in this array, so if we include every view in this array, `view_index` // actually the texture array layer, like in vulkan. for i in0..32 - mask.leading_zeros() {
view_instancing.push(Direct3D12::D3D12_VIEW_INSTANCE_LOCATION {
ViewportArrayIndex: 0,
RenderTargetArrayIndex: i,
});
}
}
// Borrow view instancing slice, so we can be sure that it won't be moved while we have pointers into this buffer. let view_instancing_slice = view_instancing.as_slice();
letmut stream_desc = RenderPipelineStateStreamDesc { // Shared by vertex and mesh pipelines
root_signature: desc.layout.shared.signature.as_ref(),
pixel_shader,
blend_state,
sample_mask: desc.multisample.mask as u32,
rasterizer_state,
depth_stencil_state,
primitive_topology_type: topology_class,
rtv_formats: Direct3D12::D3D12_RT_FORMAT_ARRAY {
RTFormats: rtv_formats,
NumRenderTargets: desc.color_targets.len() as u32,
},
dsv_format,
sample_desc,
node_mask: 0,
cached_pso,
flags,
view_instancing: if !view_instancing_slice.is_empty() {
Some(Direct3D12::D3D12_VIEW_INSTANCING_DESC {
ViewInstanceCount: view_instancing_slice.len() as u32,
pViewInstanceLocations: view_instancing_slice.as_ptr(), // This lets us hide/mask certain values later, at renderpass creation time.
Flags: Direct3D12::D3D12_VIEW_INSTANCING_FLAG_ENABLE_VIEW_INSTANCE_MASKING,
})
} else {
None
},
// Optional data that depends on the pipeline type (vertex vs mesh).
vertex_shader: Default::default(),
input_layout: Default::default(),
index_buffer_strip_cut_value: Default::default(),
stream_output,
task_shader: Default::default(),
mesh_shader: Default::default(),
}; letmut input_element_descs = Vec::new(); let blob_vs; let blob_ts; let blob_ms; letmut vertex_strides = [None; crate::MAX_VERTEX_BUFFERS]; match &desc.vertex_processor {
&crate::VertexProcessor::Standard {
vertex_buffers, ref vertex_stage,
} => {
shader_stages |= wgt::ShaderStages::VERTEX;
blob_vs = Some(self.load_shader(
vertex_stage,
desc.layout,
naga::ShaderStage::Vertex,
desc.fragment_stage.as_ref(),
)?);
for (i, (stride, vbuf)) in vertex_strides.iter_mut().zip(vertex_buffers).enumerate()
{ let Some(vbuf) = vbuf else { continue;
};
*stride = Some(vbuf.array_stride as u32); let (slot_class, step_rate) = match vbuf.step_mode {
wgt::VertexStepMode::Vertex => {
(Direct3D12::D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0)
}
wgt::VertexStepMode::Instance => {
(Direct3D12::D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1)
}
}; for attribute in vbuf.attributes {
input_element_descs.push(Direct3D12::D3D12_INPUT_ELEMENT_DESC {
SemanticName: windows::core::PCSTR(NAGA_LOCATION_SEMANTIC.as_ptr()),
SemanticIndex: attribute.shader_location,
Format: auxil::dxgi::conv::map_vertex_format(attribute.format),
InputSlot: i as u32,
AlignedByteOffset: attribute.offset as u32,
InputSlotClass: slot_class,
InstanceDataStepRate: step_rate,
});
}
}
stream_desc.vertex_shader = blob_vs.as_ref().unwrap().create_native_shader();
stream_desc.input_layout = Direct3D12::D3D12_INPUT_LAYOUT_DESC {
pInputElementDescs: if input_element_descs.is_empty() {
ptr::null()
} else {
input_element_descs.as_ptr()
},
NumElements: input_element_descs.len() as u32,
};
stream_desc.index_buffer_strip_cut_value = match desc.primitive.strip_index_format {
Some(wgt::IndexFormat::Uint16) => {
Direct3D12::D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_0xFFFF
}
Some(wgt::IndexFormat::Uint32) => {
Direct3D12::D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_0xFFFFFFFF
}
None => Direct3D12::D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_DISABLED,
};
stream_desc.stream_output = Direct3D12::D3D12_STREAM_OUTPUT_DESC {
pSODeclaration: ptr::null(),
NumEntries: 0,
pBufferStrides: ptr::null(),
NumStrides: 0,
RasterizedStream: 0,
};
} crate::VertexProcessor::Mesh {
task_stage,
mesh_stage,
} => {
blob_ts = iflet Some(ts) = task_stage {
shader_stages |= wgt::ShaderStages::TASK;
Some(self.load_shader(
ts,
desc.layout,
naga::ShaderStage::Task,
desc.fragment_stage.as_ref(),
)?)
} else {
None
}; let task_shader = iflet Some(ts) = &blob_ts {
ts.create_native_shader()
} else {
Default::default()
};
shader_stages |= wgt::ShaderStages::MESH;
blob_ms = Some(self.load_shader(
mesh_stage,
desc.layout,
naga::ShaderStage::Mesh,
desc.fragment_stage.as_ref(),
)?);
stream_desc.task_shader = task_shader;
stream_desc.mesh_shader = blob_ms.as_ref().unwrap().create_native_shader();
}
}; let raw: Direct3D12::ID3D12PipelineState = // If stream descriptors are available, use them as they are more flexible. iflet Ok(device) = self.raw.cast::<Direct3D12::ID3D12Device2>() { // Prefer stream descs where possible letmut stream = stream_desc.to_stream(); unsafe {
profiling::scope!("ID3D12Device2::CreatePipelineState");
stream.create_pipeline_state(&device).map_err(|err| { crate::PipelineError::Linkage(shader_stages, err.to_string())
})?
}
} else { unsafe { // Safety: `stream_desc` entirely outlives the `desc`. let desc = stream_desc.to_graphics_pipeline_descriptor(); self.raw.CreateGraphicsPipelineState(&desc).map_err(|err| { crate::PipelineError::Linkage(shader_stages, err.to_string())
})?
}
};
// We first check if the fence has already reached the value we're waiting for. letmut fence_value = unsafe { fence.raw.GetCompletedValue() }; if fence_value >= value { return Ok(true);
}
// We need to loop to get correct behavior when timeouts are involved. // // wait(0): // - We set the event from the fence value 0. // - WaitForSingleObject times out, we return false. // // wait(1): // - We set the event from the fence value 1. // - WaitForSingleObject returns. However we do not know if the fence value is 0 or 1, // just that _something_ triggered the event. We check the fence value, and if it is // 1, we return true. Otherwise, we loop and wait again. loop { let elapsed = start_time.elapsed();
// We need to explicitly use checked_sub. Overflow with duration panics, and if the // timing works out just right, we can get a negative remaining wait duration. // // This happens when a previous iteration WaitForSingleObject succeeded with a previous fence value, // right before the timeout would have been hit. let remaining_wait_duration = match timeout.checked_sub(elapsed) {
Some(remaining) => remaining,
None => {
log::trace!("Timeout elapsed in between waits!"); break Ok(false);
}
};
log::trace!("Waiting for fence value {value} for {remaining_wait_duration:?}");
unsafefn get_acceleration_structure_build_sizes<'a>(
&self,
desc: &crate::GetAccelerationStructureBuildSizesDescriptor<'a, super::Buffer>,
) -> crate::AccelerationStructureBuildSizes { letmut geometry_desc; let device5 = self.raw.cast::<Direct3D12::ID3D12Device5>().unwrap(); let ty; let inputs0; let num_desc; match desc.entries {
AccelerationStructureEntries::Instances(instances) => {
ty = Direct3D12::D3D12_RAYTRACING_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL;
inputs0 = Direct3D12::D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_INPUTS_0 {
InstanceDescs: 0,
};
num_desc = instances.count;
}
AccelerationStructureEntries::Triangles(triangles) => {
geometry_desc = Vec::with_capacity(triangles.len()); for triangle in triangles { let index_format = triangle
.indices
.as_ref()
.map_or(Dxgi::Common::DXGI_FORMAT_UNKNOWN, |indices| {
auxil::dxgi::conv::map_index_format(indices.format)
}); let index_count = triangle.indices.as_ref().map_or(0, |indices| indices.count);
let triangle_desc = Direct3D12::D3D12_RAYTRACING_GEOMETRY_TRIANGLES_DESC { // https://learn.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12device5-getraytracingaccelerationstructureprebuildinfo // It may not inspect/dereference any GPU virtual addresses, other than // to check to see if a pointer is NULL or not, such as the optional // transform in D3D12_RAYTRACING_GEOMETRY_TRIANGLES_DESC, without // dereferencing it. // // This suggests we could pass a non-zero invalid address here if fetching the // real address has significant overhead, but we pass the real one to be on the // safe side for now.
Transform3x4: if desc
.flags
.contains(wgt::AccelerationStructureFlags::USE_TRANSFORM)
{ unsafe {
triangle
.transform
.as_ref()
.unwrap()
.buffer
.resource
.GetGPUVirtualAddress()
}
} else { 0
},
IndexFormat: index_format,
VertexFormat: auxil::dxgi::conv::map_vertex_format(triangle.vertex_format),
IndexCount: index_count,
VertexCount: triangle.vertex_count,
IndexBuffer: 0,
VertexBuffer: Direct3D12::D3D12_GPU_VIRTUAL_ADDRESS_AND_STRIDE {
StartAddress: 0,
StrideInBytes: triangle.vertex_stride,
},
};
let info = self
.shared
.adapter
.query_video_memory_info(Dxgi::DXGI_MEMORY_SEGMENT_GROUP_LOCAL)?;
if info.CurrentUsage >= info.Budget / 100 * threshold as u64 { return Err(crate::DeviceError::OutOfMemory);
}
if matches!( self.shared.private_caps.memory_architecture, super::MemoryArchitecture::NonUnified
) { let info = self
.shared
.adapter
.query_video_memory_info(Dxgi::DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL)?;
if info.CurrentUsage >= info.Budget / 100 * threshold as u64 { return Err(crate::DeviceError::OutOfMemory);
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.