```cpp // These bindings alias the same descriptors. Depending on the type, the shader will use the correct one. SamplerStatenagaSamplerHeap[2048]:register(s0,space0); SamplerComparisonStatenagaComparisonSamplerHeap[2048]:register(s2048,space1);
// Indexes into group 0 index array staticconstSamplerStatemyLinearSampler=nagaSamplerHeap[nagaGroup0SamplerIndexArray[0]];
// Indexes into group 1 index array staticconstSamplerStatemyAnisoSampler=nagaSamplerHeap[nagaGroup1SamplerIndexArray[0]]; staticconstSamplerComparisonStatemyCompSampler=nagaComparisonSamplerHeap[nagaGroup1SamplerIndexArray[1]]; ```
mod adapter; mod command; mod conv; mod dcomp; mod descriptor; mod device; mod device_creation; mod instance; mod pipeline_desc; mod sampler; mod shader_compilation; mod suballocation; mod types; mod view;
use alloc::{borrow::ToOwned as _, string::String, sync::Arc, vec::Vec}; use core::{ffi, fmt, mem, ops::Deref, sync::atomic::AtomicU64};
use arrayvec::ArrayVec; use hashbrown::HashMap; use parking_lot::{Mutex, RwLock}; use suballocation::Allocator; use windows::{
core::{Free as _, Interface},
Win32::{
Foundation,
Graphics::{
Direct3D,
Direct3D12::{self, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT},
DirectComposition, Dxgi,
},
System::Threading,
},
};
/// Calls D3D12GetInterface to obtain a COM interface by CLSID and IID. /// /// This is used by the Independent Devices API to obtain `ID3D12SDKConfiguration1`. fn get_interface<T: Interface>(
&self,
clsid: &windows_core::GUID,
) -> Result<T, GetInterfaceError> { // Calls windows::Win32::Graphics::Direct3D12::D3D12GetInterface on d3d12.dll type Fun = extern"system"fn(
rclsid: *const windows_core::GUID,
riid: *const windows_core::GUID,
ppvdebug: *mut *mut ffi::c_void,
) -> windows_core::HRESULT; let func: libloading::Symbol<Fun> = unsafe { self.lib.get(c"D3D12GetInterface".to_bytes()) }
.map_err(|_| GetInterfaceError::GetProcAddress)?;
letmut result__: Option<T> = None;
let res = (func)(clsid, &T::IID, <*mut _>::cast(&mut result__));
if res.is_err() { return Err(GetInterfaceError::D3D12GetInterface(res));
}
/// Will error with crate::DeviceError::Unexpected if DXGI 1.3 is not available. pubfn create_factory_media(&self) -> Result<Dxgi::IDXGIFactoryMedia, crate::DeviceError> { // Calls windows::Win32::Graphics::Dxgi::CreateDXGIFactory1 on dxgi.dll type Fun = extern"system"fn(
riid: *const windows_core::GUID,
ppfactory: *mut *mut ffi::c_void,
) -> windows_core::HRESULT; let func: libloading::Symbol<Fun> = unsafe { self.lib.get(c"CreateDXGIFactory1".to_bytes()) }?;
/// Create a temporary "owned" copy inside a [`mem::ManuallyDrop`] without increasing the refcount or /// moving away the source variable. /// /// This is a common pattern when needing to pass interface pointers ("borrows") into Windows /// structs. Moving/cloning ownership is impossible/inconvenient because: /// /// - The caller does _not_ assume ownership (and decrement the refcount at a later time); /// - Unnecessarily increasing and decrementing the refcount; /// - [`Drop`] destructors cannot run inside `union` structures (when the created structure is /// implicitly dropped after a call). /// /// See also <https://github.com/microsoft/windows-rs/pull/2361#discussion_r1150799401> and /// <https://github.com/microsoft/windows-rs/issues/2386>. /// /// # Safety /// Performs a [`mem::transmute_copy()`] on a refcounted [`Interface`] type. The returned /// [`mem::ManuallyDrop`] should _not_ be dropped. pubunsafefn borrow_interface_temporarily<I: Interface>(src: &I) -> mem::ManuallyDrop<Option<I>> { unsafe { mem::transmute_copy(src) }
}
implcrate::Api for Api { const VARIANT: wgt::Backend = wgt::Backend::Dx12;
type Instance = Instance; type Surface = Surface; type Adapter = Adapter; type Device = Device;
type Queue = Queue; type CommandEncoder = CommandEncoder; type CommandBuffer = CommandBuffer;
type Buffer = Buffer; type Texture = Texture; type SurfaceTexture = Texture; type TextureView = TextureView; type Sampler = Sampler; type QuerySet = QuerySet; type Fence = Fence;
type BindGroupLayout = BindGroupLayout; type BindGroup = BindGroup; type PipelineLayout = PipelineLayout; type ShaderModule = ShaderModule; type RenderPipeline = RenderPipeline; type ComputePipeline = ComputePipeline; type PipelineCache = PipelineCache;
type AccelerationStructure = AccelerationStructure;
}
// Limited by D3D12's root signature size of 64. Each element takes 1 or 2 entries. const MAX_ROOT_ELEMENTS: usize = 64; /// See comment in [`Adapter::expose`]. /// You must change the math in the comment before you update this value. const MAX_IMMEDIATE_SIZE: u32 = 128; const MAX_IMMEDIATES: usize = MAX_IMMEDIATE_SIZE as usize / 4; const ZERO_BUFFER_SIZE: wgt::BufferAddress = 256 << 10;
pubstruct Instance {
factory: DxgiFactory,
factory_media: Option<Dxgi::IDXGIFactoryMedia>, // `device_factory` must be dropped before `library` because the COM // object's Release call goes through the d3d12.dll vtable. If // `library` (which unloads d3d12.dll) is dropped first the Release // segfaults.
device_factory: Arc<device_creation::DeviceFactory>,
library: Arc<D3D12Lib>,
dcomp_lib: Arc<DCompLib>,
supports_allow_tearing: bool,
presentation_system: wgt::Dx12SwapchainKind,
_lib_dxgi: DxgiLib,
flags: wgt::InstanceFlags,
memory_budget_thresholds: wgt::MemoryBudgetThresholds,
compiler_container: Arc<shader_compilation::CompilerContainer>,
options: wgt::Dx12BackendOptions,
telemetry: Option<crate::Telemetry>,
}
impl Instance { /// Get the raw DXGI factory associated with this instance. pubunsafefn raw_factory4(&self) -> &Dxgi::IDXGIFactory4 { self.factory.deref()
}
pubunsafefn create_surface_from_visual(&self, visual: *mut ffi::c_void) -> Surface { let visual = unsafe { DirectComposition::IDCompositionVisual::from_raw_borrowed(&visual) }
.expect("COM pointer should not be NULL");
Surface {
factory: self.factory.clone(),
factory_media: self.factory_media.clone(),
target: SurfaceTarget::Visual(visual.to_owned()),
supports_allow_tearing: self.supports_allow_tearing,
swap_chain: RwLock::new(None),
options: self.options.clone(),
}
}
pubunsafefn create_surface_from_surface_handle(
&self,
surface_handle: *mut ffi::c_void,
) -> Surface { // TODO: We're not given ownership, so we shouldn't call HANDLE::free(). This puts an extra burden on the caller to keep it alive. // https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-duplicatehandle could help us, even though DirectComposition is not in the list? // Or we make all these types owned, require an ownership transition, and replace SurfaceTargetUnsafe with SurfaceTarget. let surface_handle = Foundation::HANDLE(surface_handle);
Surface {
factory: self.factory.clone(),
factory_media: self.factory_media.clone(),
target: SurfaceTarget::SurfaceHandle(surface_handle),
supports_allow_tearing: self.supports_allow_tearing,
swap_chain: RwLock::new(None),
options: self.options.clone(),
}
}
pubunsafefn create_surface_from_swap_chain_panel(
&self,
swap_chain_panel: *mut ffi::c_void,
) -> Surface { let swap_chain_panel = unsafe { types::ISwapChainPanelNative::from_raw_borrowed(&swap_chain_panel) }
.expect("COM pointer should not be NULL");
Surface {
factory: self.factory.clone(),
factory_media: self.factory_media.clone(),
target: SurfaceTarget::SwapChainPanel(swap_chain_panel.to_owned()),
supports_allow_tearing: self.supports_allow_tearing,
swap_chain: RwLock::new(None),
options: self.options.clone(),
}
}
}
unsafeimpl Send for Instance {} unsafeimpl Sync for Instance {}
struct SwapChain { // TODO: Drop order frees the SWC before the raw image pointers...?
raw: Dxgi::IDXGISwapChain3, // need to associate raw image pointers with the swapchain so they can be properly released // when the swapchain is destroyed
resources: Vec<Direct3D12::ID3D12Resource>, /// Handle is freed in [`Self::release_resources()`]
waitable: Option<Foundation::HANDLE>,
acquired_count: usize,
present_mode: wgt::PresentMode,
format: wgt::TextureFormat,
size: wgt::Extent3d,
}
/// Returns the waitable handle associated with this swap chain, if any. /// Handle is only valid while the swap chain is alive. pubunsafefn waitable_handle(&self) -> Option<Foundation::HANDLE> { self.swap_chain.read().as_ref()?.waitable
}
}
#[derive(Default, Debug, Copy, Clone)] struct Workarounds { // On WARP 1.0.13+, debug information in shaders in certain situations causes the device // to hang. https://github.com/gfx-rs/wgpu/issues/8368
avoid_shader_debug_info: bool,
}
/// Stage a `ID3D12CommandQueue::Wait(fence, value)` for the next /// [`crate::Queue::submit`]. The wait is enqueued before the /// submit's command lists, so subsequent GPU work observes the /// foreign signal at `value`. pubfn add_wait_fence(&self, fence: Direct3D12::ID3D12Fence, value: u64) { self.pending_waits.lock().push((fence, value));
}
/// Remove `fence` from the pending wait list if it is still present. /// Returns `true` if it was found and removed. pubfn remove_wait_fence(&self, fence: &Direct3D12::ID3D12Fence) -> bool { let target = fence.as_raw(); letmut waits = self.pending_waits.lock(); let before = waits.len();
waits.retain(|(f, _)| f.as_raw() != target);
waits.len() != before
}
/// Stage a `ID3D12CommandQueue::Signal(fence, value)` for the next /// [`crate::Queue::submit`]. The signal is enqueued after the /// submit's command lists complete, so a foreign API waiting on /// `(fence, value)` observes the wgpu work as done. pubfn add_signal_fence(&self, fence: Direct3D12::ID3D12Fence, value: u64) { self.pending_signals.lock().push((fence, value));
}
/// Remove `fence` from the pending signal list if it is still present. /// Returns `true` if it was found and removed. pubfn remove_signal_fence(&self, fence: &Direct3D12::ID3D12Fence) -> bool { let target = fence.as_raw(); letmut signals = self.pending_signals.lock(); let before = signals.len();
signals.retain(|(f, _)| f.as_raw() != target);
signals.len() != before
}
}
unsafeimpl Send for Queue {} unsafeimpl Sync for Queue {}
#[derive(Clone, Copy, Debug, Default, PartialEq)] struct SpecialConstants { /// The first vertex in an indirect draw call, _or_ the `x` of a compute dispatch.
first_vertex_or_x: i32, /// The first instance in an indirect draw call, _or_ the `y` of a compute dispatch.
first_instance_or_y: u32, /// Unused in an indirect draw call, _or_ the `z` of a compute dispatch.
unused_or_z: u32,
}
#[derive(Clone, Copy, Debug)] enum RootElement {
Empty,
Immediates,
SpecialConstants(SpecialConstants),
DescriptorTable(Direct3D12::D3D12_GPU_DESCRIPTOR_HANDLE), /// Descriptor table referring to the entire sampler heap.
SamplerHeapDescriptorTable, /// Root descriptor for a uniform buffer binding that has a dynamic offset.
DynamicUniformBuffer {
address: Direct3D12::D3D12_GPU_DESCRIPTOR_HANDLE,
}, /// Root constants for storage buffer bindings with dynamic offsets. /// /// start..end is the range of values in [`PassState::dynamic_storage_buffer_offsets`] /// that will be used to update the root constants.
DynamicStorageBufferOffsets {
start: usize,
end: usize,
},
}
/// If set, the end of the next render/compute pass will write a timestamp at /// the given pool & location.
end_of_pass_timer_query: Option<(Direct3D12::ID3D12QueryHeap, u32)>,
counters: Arc<wgt::HalCounters>,
}
unsafeimpl Send for CommandEncoder {} unsafeimpl Sync for CommandEncoder {}
unsafeimpl Send for CommandBuffer {} unsafeimpl Sync for CommandBuffer {}
#[derive(Debug)] pubstruct Buffer {
resource: Direct3D12::ID3D12Resource, // While the allocation also has _a_ size, it may not // be the same as the original size of the buffer, // as the allocation size varies for assorted reasons.
size: wgt::BufferAddress,
allocation: suballocation::Allocation,
}
#[derive(Debug)] pubstruct Texture {
resource: Direct3D12::ID3D12Resource,
format: wgt::TextureFormat,
dimension: wgt::TextureDimension,
size: wgt::Extent3d,
mip_level_count: u32,
sample_count: u32,
allocation: suballocation::Allocation, /// Pins `PlaneSlice` for every view and copy derived from this texture, /// overriding the default aspect-based derivation. Used by cross-API /// importers wrapping one plane of a multi-plane DXGI resource as a /// single-plane wgpu texture.
plane_slice_override: Option<u32>,
}
/// Pin the D3D12 plane slice for every view and copy derived from /// this texture. Pass `0` for the luma plane, `1` for chroma. The /// declared `TextureFormat` must be single-plane (e.g. `R8Unorm`, /// `Rg8Unorm`) and match the plane's texel layout. /// /// # Safety / aliasing /// /// The intended use is to wrap one plane of a multi-plane DXGI /// resource (e.g. NV12) as a single-plane wgpu texture, typically /// by calling [`Device::texture_from_raw`](Device::texture_from_raw) /// twice with two clones of the same `ID3D12Resource` and a /// different `plane_slice` on each. wgpu's state tracker treats /// these wrappers as independent textures: per-subresource state, /// hazard tracking, and resource-state barriers all assume /// non-aliased ownership and have no way to know the two wrappers /// alias the same underlying resource. /// /// The caller is responsible for ensuring that the underlying /// resource is not concurrently used by another wgpu texture /// wrapping a different plane in a way that would race or /// invalidate state tracking — in particular, do not submit work /// touching both wrappers in the same submission unless every /// access goes through `COPY_SRC` / `COPY_DST` on the wrapper that /// owns that plane's subresource, and do not destroy the wrappers /// concurrently with in-flight work that uses either of them. pubfn with_plane_slice(mutself, plane_slice: u32) -> Self {
debug_assert!(
!self.format.is_multi_planar_format(), "`with_plane_slice` expects a single-plane format wrapping a \
multi-plane DXGI resource; got planar format `{:?}`", self.format,
); self.plane_slice_override = Some(plane_slice); self
}
unsafeimpl Send for PipelineLayoutSpecialConstants {} unsafeimpl Sync for PipelineLayoutSpecialConstants {}
#[derive(Debug)] pubstruct PipelineLayout {
shared: PipelineLayoutShared, // Storing for each associated bind group, which tables we created // in the root signature. This is required for binding descriptor sets.
bind_group_infos: [Option<BindGroupInfo>; crate::MAX_BIND_GROUPS],
naga_options: naga::back::hlsl::Options,
}
implcrate::DynPipelineLayout for PipelineLayout {}
pub(super) struct ShaderCacheValue { /// This is the value of [`ShaderCache::nr_of_shaders_compiled`] /// at the time the cache entry was last used.
last_used: u32,
shader: CompiledShader,
}
unsafefn configure(
&self,
device: &Device,
config: &crate::SurfaceConfiguration,
) -> Result<(), crate::SurfaceError> { letmut flags = Dxgi::DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT; // We always set ALLOW_TEARING on the swapchain no matter // what kind of swapchain we want because ResizeBuffers // cannot change the swapchain's ALLOW_TEARING flag. // // This does not change the behavior of the swapchain, just // allow present calls to use tearing. ifself.supports_allow_tearing {
flags |= Dxgi::DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
}
// While `configure`s contract ensures that no work on the GPU's main queues // are in flight, we still need to wait for the present queue to be idle. unsafe { device.wait_for_present_queue_idle() }?;
let non_srgb_format = auxil::dxgi::conv::map_texture_format_nosrgb(config.format);
// Nvidia recommends to use 1-2 more buffers than the maximum latency // https://developer.nvidia.com/blog/advanced-api-performance-swap-chains/ // For high latency extra buffers seems excessive, so go with a minimum of 3 and beyond that add 1. let swap_chain_buffer = (config.maximum_frame_latency + 1).min(16);
let swap_chain = matchself.swap_chain.write().take() { //Note: this path doesn't properly re-initialize all of the things
Some(sc) => { let raw = unsafe { sc.release_resources() }; let result = unsafe {
raw.ResizeBuffers(
swap_chain_buffer,
config.extent.width,
config.extent.height,
non_srgb_format,
flags,
)
}; iflet Err(err) = result {
log::error!("ResizeBuffers failed: {err}"); return Err(crate::SurfaceError::Other("window is in use"));
}
raw
}
None => { let desc = Dxgi::DXGI_SWAP_CHAIN_DESC1 {
AlphaMode: auxil::dxgi::conv::map_acomposite_alpha_mode(
config.composite_alpha_mode,
),
Width: config.extent.width,
Height: config.extent.height,
Format: non_srgb_format,
Stereo: false.into(),
SampleDesc: Dxgi::Common::DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
BufferUsage: Dxgi::DXGI_USAGE_RENDER_TARGET_OUTPUT,
BufferCount: swap_chain_buffer,
Scaling: Dxgi::DXGI_SCALING_STRETCH,
SwapEffect: Dxgi::DXGI_SWAP_EFFECT_FLIP_DISCARD,
Flags: flags.0as u32,
}; let swap_chain1 = matchself.target {
SurfaceTarget::Visual(_)
| SurfaceTarget::VisualFromWndHandle { .. }
| SurfaceTarget::SwapChainPanel(_) => {
profiling::scope!("IDXGIFactory2::CreateSwapChainForComposition"); unsafe { self.factory.CreateSwapChainForComposition(
&device.present_queue,
&desc,
None,
)
}
}
SurfaceTarget::SurfaceHandle(handle) => {
profiling::scope!( "IDXGIFactoryMedia::CreateSwapChainForCompositionSurfaceHandle"
); unsafe { self.factory_media
.as_ref()
.ok_or(crate::SurfaceError::Other("IDXGIFactoryMedia not found"))?
.CreateSwapChainForCompositionSurfaceHandle(
&device.present_queue,
Some(handle),
&desc,
None,
)
}
}
SurfaceTarget::WndHandle(hwnd) => {
profiling::scope!("IDXGIFactory2::CreateSwapChainForHwnd"); unsafe { self.factory.CreateSwapChainForHwnd(
&device.present_queue,
hwnd,
&desc,
None,
None,
)
}
}
};
match &self.target {
SurfaceTarget::WndHandle(_) | SurfaceTarget::SurfaceHandle(_) => {}
SurfaceTarget::VisualFromWndHandle {
handle,
dcomp_state,
} => { letmut dcomp_state = dcomp_state.lock(); let dcomp_state = unsafe { dcomp_state.get_or_init(&device.dcomp_lib, handle) }?; // Set the new swap chain as the content for the backing visual // and commit the changes to the composition visual tree.
{
profiling::scope!("IDCompositionVisual::SetContent"); unsafe { dcomp_state.visual.SetContent(&swap_chain1) }.map_err(
|err| {
log::error!("IDCompositionVisual::SetContent failed: {err}"); crate::SurfaceError::Other("IDCompositionVisual::SetContent")
},
)?;
}
swap_chain1.cast::<Dxgi::IDXGISwapChain3>().map_err(|err| {
log::error!("Unable to cast swapchain: {err}"); crate::SurfaceError::Other("swapchain cast to version 3")
})?
}
};
let waitable = match device.options.latency_waitable_object {
wgt::Dx12UseFrameLatencyWaitableObject::None => None,
wgt::Dx12UseFrameLatencyWaitableObject::Wait
| wgt::Dx12UseFrameLatencyWaitableObject::DontWait => {
Some(unsafe { swap_chain.GetFrameLatencyWaitableObject() })
}
};
letmut resources = Vec::with_capacity(swap_chain_buffer as usize); for i in0..swap_chain_buffer { let resource = unsafe { swap_chain.GetBuffer(i) }
.into_device_result("Failed to get swapchain buffer")?;
resources.push(resource);
}
unsafefn unconfigure(&self, device: &Device) { iflet Some(sc) = self.swap_chain.write().take() { unsafe { // While `unconfigure`s contract ensures that no work on the GPU's main queues // are in flight, we still need to wait for the present queue to be idle.
// The major failure mode of this function is device loss, // which if we have lost the device, we should just continue // cleaning up, without error. let _ = device.wait_for_present_queue_idle();
let base_index = unsafe { sc.raw.GetCurrentBackBufferIndex() } as usize; let index = (base_index + sc.acquired_count) % sc.resources.len();
sc.acquired_count += 1;
// Drain caller-staged waits before ExecuteCommandLists so the // GPU queue blocks on each foreign signal before running our // command lists. D3D12 queue commands are FIFO - Wait calls // here gate everything submitted after them.
{ letmut waits = self.pending_waits.lock(); for (fence, value) in waits.drain(..) { unsafe { self.raw.Wait(&fence, value) }.into_device_result("Wait pending fence")?;
}
}
// Drain caller-staged signals after our own Signal so each // additional fence value publishes once the submit completes.
{ letmut signals = self.pending_signals.lock(); for (fence, value) in signals.drain(..) { unsafe { self.raw.Signal(&fence, value) }
.into_device_result("Signal pending fence")?;
}
}
// Note the lack of synchronization here between the main Direct queue // and the dedicated presentation queue. This is automatically handled // by the D3D runtime by detecting uses of resources derived from the // swapchain. This automatic detection is why you cannot use a swapchain // as an UAV in D3D12.
let (interval, flags) = match sc.present_mode { // We only allow immediate if ALLOW_TEARING is valid.
wgt::PresentMode::Immediate => (0, Dxgi::DXGI_PRESENT_ALLOW_TEARING),
wgt::PresentMode::Mailbox => (0, Dxgi::DXGI_PRESENT::default()),
wgt::PresentMode::Fifo => (1, Dxgi::DXGI_PRESENT::default()),
m => unreachable!("Cannot make surface with present mode {m:?}"),
};
unsafefn get_timestamp_period(&self) -> f32 { let frequency = unsafe { self.raw.GetTimestampFrequency() }.expect("GetTimestampFrequency");
(1_000_000_000.0 / frequency as f64) as f32
}
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.