Skip to content

Allow disabling waiting for latency waitable object #7400

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: trunk
Choose a base branch
from
1 change: 1 addition & 0 deletions deno_webgpu/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,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(),
Expand Down
1 change: 1 addition & 0 deletions tests/src/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,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") {
Expand Down
1 change: 1 addition & 0 deletions wgpu-hal/examples/ray-traced-triangle/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ impl<A: hal::Api> Example<A> {
backend_options: wgpu_types::BackendOptions {
dx12: Dx12BackendOptions {
shader_compiler: wgpu_types::Dx12Compiler::default_dynamic_dxc(),
..Default::default()
},
..Default::default()
},
Expand Down
3 changes: 3 additions & 0 deletions wgpu-hal/src/dx12/adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ impl super::Adapter {
library: &Arc<D3D12Lib>,
instance_flags: wgt::InstanceFlags,
dxc_container: Option<Arc<shader_compilation::DxcContainer>>,
backend_options: wgt::Dx12BackendOptions,
) -> Option<crate::ExposedAdapter<super::Api>> {
// Create the device so that we can get the capabilities.
let device = {
Expand Down Expand Up @@ -519,6 +520,7 @@ impl super::Adapter {
presentation_timer,
workarounds,
dxc_container,
options: backend_options,
},
info,
features,
Expand Down Expand Up @@ -656,6 +658,7 @@ impl crate::Adapter for super::Adapter {
self.private_caps,
&self.library,
self.dxc_container.clone(),
self.options.clone(),
)?;
Ok(crate::OpenDevice {
device,
Expand Down
2 changes: 2 additions & 0 deletions wgpu-hal/src/dx12/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ impl super::Device {
private_caps: super::PrivateCapabilities,
library: &Arc<D3D12Lib>,
dxc_container: Option<Arc<shader_compilation::DxcContainer>>,
backend_options: wgt::Dx12BackendOptions,
) -> Result<Self, crate::DeviceError> {
if private_caps
.instance_flags
Expand Down Expand Up @@ -192,6 +193,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(),
Expand Down
10 changes: 9 additions & 1 deletion wgpu-hal/src/dx12/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ impl crate::Instance for super::Instance {
supports_allow_tearing,
flags: desc.flags,
dxc_container,
options: desc.backend_options.dx12.clone(),
})
}

Expand All @@ -128,6 +129,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"
Expand All @@ -144,7 +146,13 @@ impl crate::Instance for super::Instance {
adapters
.into_iter()
.filter_map(|raw| {
super::Adapter::expose(raw, &self.library, self.flags, self.dxc_container.clone())
super::Adapter::expose(
raw,
&self.library,
self.flags,
self.dxc_container.clone(),
self.options.clone(),
)
})
.collect()
}
Expand Down
62 changes: 51 additions & 11 deletions wgpu-hal/src/dx12/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,7 @@ pub struct Instance {
supports_allow_tearing: bool,
_lib_dxgi: DxgiLib,
flags: wgt::InstanceFlags,
options: wgt::Dx12BackendOptions,
dxc_container: Option<Arc<shader_compilation::DxcContainer>>,
}

Expand All @@ -472,6 +473,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(),
}
}

Expand All @@ -489,6 +491,7 @@ impl Instance {
target: SurfaceTarget::SurfaceHandle(surface_handle),
supports_allow_tearing: self.supports_allow_tearing,
swap_chain: RwLock::new(None),
options: self.options.clone(),
}
}

Expand All @@ -505,6 +508,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(),
}
}
}
Expand All @@ -519,7 +523,7 @@ struct SwapChain {
// when the swapchain is destroyed
resources: Vec<Direct3D12::ID3D12Resource>,
/// Handle is freed in [`Self::release_resources()`]
waitable: Foundation::HANDLE,
waitable: Option<Foundation::HANDLE>,
acquired_count: usize,
present_mode: wgt::PresentMode,
format: wgt::TextureFormat,
Expand All @@ -541,11 +545,23 @@ pub struct Surface {
target: SurfaceTarget,
supports_allow_tearing: bool,
swap_chain: RwLock<Option<SwapChain>>,
options: wgt::Dx12BackendOptions,
}

unsafe impl Send for Surface {}
unsafe impl Sync for Surface {}

impl Surface {
/// Returns the waitable handle of the swapchain, if it exists.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also define the lifetime of the handle

Copy link
Contributor Author

@marcpabst marcpabst Apr 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Define the lifetime in a SAFETEY comment or as an actual Rust lifetime?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just as a safety comment, saying it's valid until the surface is reconfigured or unconfigured or whatever

pub unsafe fn waitable_handle(&self) -> Option<Foundation::HANDLE> {
let swapchain = self.swap_chain.read();
if let Some(swap_chain) = swapchain.as_ref() {
return swap_chain.waitable.clone();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implies that Handles are refcounted, but they are Copy aliases around *const c_void - we should return it by copy

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so just drop the clone()?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah

}
None
}
}

#[derive(Debug, Clone, Copy)]
enum MemoryArchitecture {
Unified {
Expand Down Expand Up @@ -585,6 +601,7 @@ pub struct Adapter {
#[allow(unused)]
workarounds: Workarounds,
dxc_container: Option<Arc<shader_compilation::DxcContainer>>,
options: wgt::Dx12BackendOptions,
}

unsafe impl Send for Adapter {}
Expand Down Expand Up @@ -636,6 +653,7 @@ pub struct Device {
private_caps: PrivateCapabilities,
features: wgt::Features,
shared: Arc<DeviceShared>,
options: wgt::Dx12BackendOptions,
// CPU only pools
rtv_pool: Mutex<descriptor::CpuPool>,
dsv_pool: Mutex<descriptor::CpuPool>,
Expand Down Expand Up @@ -1118,7 +1136,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
}

Expand All @@ -1130,14 +1150,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{:x?}", other);
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{:x?}", other);
Err(crate::SurfaceError::Lost)
}
}
} else {
Ok(true)
}
}
}
Expand Down Expand Up @@ -1305,7 +1332,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 {
Expand Down Expand Up @@ -1352,7 +1386,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();
Expand Down
60 changes: 59 additions & 1 deletion wgpu-types/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,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 {
Expand All @@ -276,8 +278,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,
}
}

Expand All @@ -287,7 +292,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,
}
}
}

Expand Down Expand Up @@ -429,6 +439,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<Self> {
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.
Expand Down