From e7cdfc436a6980e5a1ae3844767dd435ca902b46 Mon Sep 17 00:00:00 2001 From: Marc Pabst <2624210+marcpabst@users.noreply.github.com> Date: Sun, 10 Aug 2025 11:01:25 +0100 Subject: [PATCH] Allow disabling waiting for latency waitable object (#7400) --- CHANGELOG.md | 7 ++- deno_webgpu/lib.rs | 1 + tests/src/init.rs | 1 + wgpu-hal/examples/ray-traced-triangle/main.rs | 1 + wgpu-hal/src/dx12/adapter.rs | 3 + wgpu-hal/src/dx12/device.rs | 2 + wgpu-hal/src/dx12/instance.rs | 3 + wgpu-hal/src/dx12/mod.rs | 57 ++++++++++++++---- wgpu-types/src/instance.rs | 60 ++++++++++++++++++- 9 files changed, 122 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a8a963cf..d04d74205 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,7 +73,7 @@ By @Vecvec in [#7913](https://github.com/gfx-rs/wgpu/pull/7913). #### DX12 -- Fixed a bug where access to matrices with 2 rows would not work in some cases. By @andyleiserson in [#7438](https://github.com/gfx-rs/wgpu/pull/7438). +- Allow disabling waiting for latency waitable object. By @marcpabst in [#7400](https://github.com/gfx-rs/wgpu/pull/7400) ### Bug Fixes @@ -85,6 +85,11 @@ By @Vecvec in [#7913](https://github.com/gfx-rs/wgpu/pull/7913). - Fix `STATUS_HEAP_CORRUPTION` crash when concurrently calling `create_sampler`. By @atlv24 in [#8043](https://github.com/gfx-rs/wgpu/pull/8043). +##### DX12 + +- Fixed a bug where access to matrices with 2 rows would not work in some cases. By @andyleiserson in [#7438](https://github.com/gfx-rs/wgpu/pull/7438). + + ## v26.0.2 (2025-07-23) ### Bug Fixes diff --git a/deno_webgpu/lib.rs b/deno_webgpu/lib.rs index 6ce31a29c..8b39160b2 100644 --- a/deno_webgpu/lib.rs +++ b/deno_webgpu/lib.rs @@ -158,6 +158,7 @@ impl GPU { backend_options: wgpu_types::BackendOptions { dx12: wgpu_types::Dx12BackendOptions { shader_compiler: wgpu_types::Dx12Compiler::Fxc, + ..Default::default() }, gl: wgpu_types::GlBackendOptions::default(), noop: wgpu_types::NoopBackendOptions::default(), diff --git a/tests/src/init.rs b/tests/src/init.rs index bee5af689..0a33f969f 100644 --- a/tests/src/init.rs +++ b/tests/src/init.rs @@ -50,6 +50,7 @@ pub fn initialize_instance(backends: wgpu::Backends, params: &TestParameters) -> backend_options: wgpu::BackendOptions { dx12: wgpu::Dx12BackendOptions { shader_compiler: dx12_shader_compiler, + ..Default::default() }, gl: wgpu::GlBackendOptions { fence_behavior: if cfg!(target_family = "wasm") { diff --git a/wgpu-hal/examples/ray-traced-triangle/main.rs b/wgpu-hal/examples/ray-traced-triangle/main.rs index 2947d1a60..43cb405ef 100644 --- a/wgpu-hal/examples/ray-traced-triangle/main.rs +++ b/wgpu-hal/examples/ray-traced-triangle/main.rs @@ -242,6 +242,7 @@ impl Example { backend_options: wgpu_types::BackendOptions { dx12: Dx12BackendOptions { shader_compiler: wgpu_types::Dx12Compiler::default_dynamic_dxc(), + ..Default::default() }, ..Default::default() }, diff --git a/wgpu-hal/src/dx12/adapter.rs b/wgpu-hal/src/dx12/adapter.rs index 3a0ece692..b8b908279 100644 --- a/wgpu-hal/src/dx12/adapter.rs +++ b/wgpu-hal/src/dx12/adapter.rs @@ -58,6 +58,7 @@ impl super::Adapter { instance_flags: wgt::InstanceFlags, memory_budget_thresholds: wgt::MemoryBudgetThresholds, compiler_container: Arc, + backend_options: wgt::Dx12BackendOptions, ) -> Option> { // Create the device so that we can get the capabilities. let device = { @@ -534,6 +535,7 @@ impl super::Adapter { workarounds, memory_budget_thresholds, compiler_container, + options: backend_options, }, info, features, @@ -697,6 +699,7 @@ impl crate::Adapter for super::Adapter { &self.library, self.memory_budget_thresholds, self.compiler_container.clone(), + self.options.clone(), )?; Ok(crate::OpenDevice { device, diff --git a/wgpu-hal/src/dx12/device.rs b/wgpu-hal/src/dx12/device.rs index 8eb4bb886..d62fc5c67 100644 --- a/wgpu-hal/src/dx12/device.rs +++ b/wgpu-hal/src/dx12/device.rs @@ -48,6 +48,7 @@ impl super::Device { library: &Arc, memory_budget_thresholds: wgt::MemoryBudgetThresholds, compiler_container: Arc, + backend_options: wgt::Dx12BackendOptions, ) -> Result { if private_caps .instance_flags @@ -198,6 +199,7 @@ impl super::Device { raw.clone(), Direct3D12::D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, )), + options: backend_options, library: Arc::clone(library), #[cfg(feature = "renderdoc")] render_doc: Default::default(), diff --git a/wgpu-hal/src/dx12/instance.rs b/wgpu-hal/src/dx12/instance.rs index 9055b5407..69bfa77d8 100644 --- a/wgpu-hal/src/dx12/instance.rs +++ b/wgpu-hal/src/dx12/instance.rs @@ -109,6 +109,7 @@ impl crate::Instance for super::Instance { flags: desc.flags, memory_budget_thresholds: desc.memory_budget_thresholds, compiler_container: Arc::new(compiler_container), + options: desc.backend_options.dx12.clone(), }) } @@ -125,6 +126,7 @@ impl crate::Instance for super::Instance { target: SurfaceTarget::WndHandle(Foundation::HWND(handle.hwnd.get() as *mut _)), supports_allow_tearing: self.supports_allow_tearing, swap_chain: RwLock::new(None), + options: self.options.clone(), }), _ => Err(crate::InstanceError::new(format!( "window handle {window_handle:?} is not a Win32 handle" @@ -147,6 +149,7 @@ impl crate::Instance for super::Instance { self.flags, self.memory_budget_thresholds, self.compiler_container.clone(), + self.options.clone(), ) }) .collect() diff --git a/wgpu-hal/src/dx12/mod.rs b/wgpu-hal/src/dx12/mod.rs index a102006ac..c15f3b81e 100644 --- a/wgpu-hal/src/dx12/mod.rs +++ b/wgpu-hal/src/dx12/mod.rs @@ -464,6 +464,7 @@ pub struct Instance { flags: wgt::InstanceFlags, memory_budget_thresholds: wgt::MemoryBudgetThresholds, compiler_container: Arc, + options: wgt::Dx12BackendOptions, } impl Instance { @@ -481,6 +482,7 @@ impl Instance { target: SurfaceTarget::Visual(visual.to_owned()), supports_allow_tearing: self.supports_allow_tearing, swap_chain: RwLock::new(None), + options: self.options.clone(), } } @@ -498,6 +500,7 @@ impl Instance { target: SurfaceTarget::SurfaceHandle(surface_handle), supports_allow_tearing: self.supports_allow_tearing, swap_chain: RwLock::new(None), + options: self.options.clone(), } } @@ -514,6 +517,7 @@ impl Instance { target: SurfaceTarget::SwapChainPanel(swap_chain_panel.to_owned()), supports_allow_tearing: self.supports_allow_tearing, swap_chain: RwLock::new(None), + options: self.options.clone(), } } } @@ -528,7 +532,7 @@ struct SwapChain { // when the swapchain is destroyed resources: Vec, /// Handle is freed in [`Self::release_resources()`] - waitable: Foundation::HANDLE, + waitable: Option, acquired_count: usize, present_mode: wgt::PresentMode, format: wgt::TextureFormat, @@ -550,6 +554,7 @@ pub struct Surface { target: SurfaceTarget, supports_allow_tearing: bool, swap_chain: RwLock>, + options: wgt::Dx12BackendOptions, } unsafe impl Send for Surface {} @@ -559,6 +564,12 @@ impl Surface { pub fn swap_chain(&self) -> Option { Some(self.swap_chain.read().as_ref()?.raw.clone()) } + + /// Returns the waitable handle associated with this swap chain, if any. + /// Handle is only valid while the swap chain is alive. + pub unsafe fn waitable_handle(&self) -> Option { + self.swap_chain.read().as_ref()?.waitable + } } #[derive(Debug, Clone, Copy)] @@ -601,6 +612,7 @@ pub struct Adapter { workarounds: Workarounds, memory_budget_thresholds: wgt::MemoryBudgetThresholds, compiler_container: Arc, + options: wgt::Dx12BackendOptions, } unsafe impl Send for Adapter {} @@ -659,6 +671,7 @@ pub struct Device { idler: Idler, features: wgt::Features, shared: Arc, + options: wgt::Dx12BackendOptions, // CPU only pools rtv_pool: Arc>, dsv_pool: Mutex, @@ -1178,7 +1191,9 @@ impl crate::DynAccelerationStructure for AccelerationStructure {} impl SwapChain { unsafe fn release_resources(mut self) -> Dxgi::IDXGISwapChain3 { - unsafe { Foundation::HANDLE::free(&mut self.waitable) }; + if let Some(mut waitable) = self.waitable.take() { + unsafe { Foundation::HANDLE::free(&mut waitable) }; + } self.raw } @@ -1190,14 +1205,21 @@ impl SwapChain { Some(duration) => duration.as_millis() as u32, None => Threading::INFINITE, }; - match unsafe { Threading::WaitForSingleObject(self.waitable, timeout_ms) } { - Foundation::WAIT_ABANDONED | Foundation::WAIT_FAILED => Err(crate::SurfaceError::Lost), - Foundation::WAIT_OBJECT_0 => Ok(true), - Foundation::WAIT_TIMEOUT => Ok(false), - other => { - log::error!("Unexpected wait status: 0x{other:x?}"); - Err(crate::SurfaceError::Lost) + + if let Some(waitable) = self.waitable { + match unsafe { Threading::WaitForSingleObject(waitable, timeout_ms) } { + Foundation::WAIT_ABANDONED | Foundation::WAIT_FAILED => { + Err(crate::SurfaceError::Lost) + } + Foundation::WAIT_OBJECT_0 => Ok(true), + Foundation::WAIT_TIMEOUT => Ok(false), + other => { + log::error!("Unexpected wait status: 0x{other:x?}"); + Err(crate::SurfaceError::Lost) + } } + } else { + Ok(true) } } } @@ -1365,7 +1387,14 @@ impl crate::Surface for Surface { unsafe { swap_chain.SetMaximumFrameLatency(config.maximum_frame_latency) } .into_device_result("SetMaximumFrameLatency")?; - let waitable = unsafe { swap_chain.GetFrameLatencyWaitableObject() }; + + let waitable = match device.options.latency_waitable_object { + wgt::Dx12UseFrameLatencyWaitableObject::None => None, + wgt::Dx12UseFrameLatencyWaitableObject::Wait + | wgt::Dx12UseFrameLatencyWaitableObject::DontWait => { + Some(unsafe { swap_chain.GetFrameLatencyWaitableObject() }) + } + }; let mut resources = Vec::with_capacity(swap_chain_buffer as usize); for i in 0..swap_chain_buffer { @@ -1412,7 +1441,13 @@ impl crate::Surface for Surface { let mut swapchain = self.swap_chain.write(); let sc = swapchain.as_mut().unwrap(); - unsafe { sc.wait(timeout) }?; + match self.options.latency_waitable_object { + wgt::Dx12UseFrameLatencyWaitableObject::None + | wgt::Dx12UseFrameLatencyWaitableObject::DontWait => {} + wgt::Dx12UseFrameLatencyWaitableObject::Wait => { + unsafe { sc.wait(timeout) }?; + } + } let base_index = unsafe { sc.raw.GetCurrentBackBufferIndex() } as usize; let index = (base_index + sc.acquired_count) % sc.resources.len(); diff --git a/wgpu-types/src/instance.rs b/wgpu-types/src/instance.rs index 79e3f7b25..c78fccdb6 100644 --- a/wgpu-types/src/instance.rs +++ b/wgpu-types/src/instance.rs @@ -359,6 +359,8 @@ impl GlBackendOptions { pub struct Dx12BackendOptions { /// Which DX12 shader compiler to use. pub shader_compiler: Dx12Compiler, + /// Whether to wait for the latency waitable object before acquiring the next swapchain image. + pub latency_waitable_object: Dx12UseFrameLatencyWaitableObject, } impl Dx12BackendOptions { @@ -368,8 +370,11 @@ impl Dx12BackendOptions { #[must_use] pub fn from_env_or_default() -> Self { let compiler = Dx12Compiler::from_env().unwrap_or_default(); + let latency_waitable_object = + Dx12UseFrameLatencyWaitableObject::from_env().unwrap_or_default(); Self { shader_compiler: compiler, + latency_waitable_object, } } @@ -379,7 +384,12 @@ impl Dx12BackendOptions { #[must_use] pub fn with_env(self) -> Self { let shader_compiler = self.shader_compiler.with_env(); - Self { shader_compiler } + let latency_waitable_object = self.latency_waitable_object.with_env(); + + Self { + shader_compiler, + latency_waitable_object, + } } } @@ -515,6 +525,54 @@ impl Dx12Compiler { } } +/// Whether and how to use a waitable handle obtained from `GetFrameLatencyWaitableObject`. +#[derive(Clone, Debug, Default)] +pub enum Dx12UseFrameLatencyWaitableObject { + /// Do not obtain a waitable handle and do not wait for it. The swapchain will + /// be created without the `DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT` flag. + None, + /// Obtain a waitable handle and wait for it before acquiring the next swapchain image. + #[default] + Wait, + /// Create the swapchain with the `DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT` flag and + /// obtain a waitable handle, but do not wait for it before acquiring the next swapchain image. + /// This is useful if the application wants to wait for the waitable object itself. + DontWait, +} + +impl Dx12UseFrameLatencyWaitableObject { + /// Choose whether to use a frame latency waitable object from the environment variable `WGPU_DX12_USE_FRAME_LATENCY_WAITABLE_OBJECT`. + /// + /// Valid values, case insensitive: + /// - `None` + /// - `Wait` + /// - `DontWait` + #[must_use] + pub fn from_env() -> Option { + let value = crate::env::var("WGPU_DX12_USE_FRAME_LATENCY_WAITABLE_OBJECT") + .as_deref()? + .to_lowercase(); + match value.as_str() { + "none" => Some(Self::None), + "wait" => Some(Self::Wait), + "dontwait" => Some(Self::DontWait), + _ => None, + } + } + + /// Takes the given setting, modifies it based on the `WGPU_DX12_USE_FRAME_LATENCY_WAITABLE_OBJECT` environment variable, and returns the result. + /// + /// See `from_env` for more information. + #[must_use] + pub fn with_env(self) -> Self { + if let Some(compiler) = Self::from_env() { + compiler + } else { + self + } + } +} + /// Selects which OpenGL ES 3 minor version to request. /// /// When using ANGLE as an OpenGL ES/EGL implementation, explicitly requesting `Version1` can provide a non-conformant ES 3.1 on APIs like D3D11.