diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bcccc723b..6a1f20c769 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,6 +64,27 @@ Available on `CommandEncoder`, `CommandBuffer`, `RenderPass`, and `ComputePass`. By @cwfitzgerald in [#8125](https://github.com/gfx-rs/wgpu/pull/8125). +#### Builtin Support for DXGI swapchains on top of of DirectComposition Visuals in DX12 + +By enabling DirectComposition support, the dx12 backend can now support transparent windows. + +This creates a single `IDCompositionVisual` over the entire window that is used by the mf`Surface`. If a user wants to manage the composition tree themselves, they should create their own device and composition, and pass the relevant visual down into `wgpu` via `SurfaceTargetUnsafe::CompositionVisual`. + +```rust +let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { + backend_options: wgpu::BackendOptions { + dx12: wgpu::Dx12BackendOptions { + presentation_system: wgpu::Dx12SwapchainKind::DxgiFromVisual, + .. + }, + .. + }, + .. +}); +``` + +By @n1ght-hunter in [#7550](https://github.com/gfx-rs/wgpu/pull/7550). + #### `EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE` has been merged into `EXPERIMENTAL_RAY_QUERY` We have merged the acceleration structure feature into the `RayQuery` feature. This is to help work around an AMD driver bug and reduce the feature complexity of ray tracing. In the future when ray tracing pipelines are implemented, if either feature is enabled, acceleration structures will be available. diff --git a/wgpu-hal/src/dx12/adapter.rs b/wgpu-hal/src/dx12/adapter.rs index 1645088afb..7f96bbcbaf 100644 --- a/wgpu-hal/src/dx12/adapter.rs +++ b/wgpu-hal/src/dx12/adapter.rs @@ -919,7 +919,10 @@ impl crate::Adapter for super::Adapter { ) -> Option { let current_extent = { match surface.target { - SurfaceTarget::WndHandle(wnd_handle) => { + SurfaceTarget::WndHandle(wnd_handle) + | SurfaceTarget::VisualFromWndHandle { + handle: wnd_handle, .. + } => { let mut rect = Default::default(); if unsafe { WindowsAndMessaging::GetClientRect(wnd_handle, &mut rect) }.is_ok() { @@ -963,6 +966,7 @@ impl crate::Adapter for super::Adapter { composite_alpha_modes: match surface.target { SurfaceTarget::WndHandle(_) => vec![wgt::CompositeAlphaMode::Opaque], SurfaceTarget::Visual(_) + | SurfaceTarget::VisualFromWndHandle { .. } | SurfaceTarget::SurfaceHandle(_) | SurfaceTarget::SwapChainPanel(_) => vec![ wgt::CompositeAlphaMode::Auto, diff --git a/wgpu-hal/src/dx12/dcomp.rs b/wgpu-hal/src/dx12/dcomp.rs new file mode 100644 index 0000000000..a6bc83352a --- /dev/null +++ b/wgpu-hal/src/dx12/dcomp.rs @@ -0,0 +1,61 @@ +use windows::Win32::{Foundation::HWND, Graphics::DirectComposition}; + +#[derive(Default)] +pub struct DCompState { + inner: Option, +} + +impl DCompState { + /// This will create a DirectComposition device and a target for the window handle if not already initialized. + /// If the device is already initialized, it will return the existing state. + pub unsafe fn get_or_init( + &mut self, + hwnd: &HWND, + ) -> Result<&mut InnerState, crate::SurfaceError> { + if self.inner.is_none() { + self.inner = Some(unsafe { InnerState::init(hwnd) }?); + } + Ok(self.inner.as_mut().unwrap()) + } +} + +pub struct InnerState { + pub visual: DirectComposition::IDCompositionVisual, + pub device: DirectComposition::IDCompositionDevice, + // Must be kept alive but is otherwise unused after initialization. + pub _target: DirectComposition::IDCompositionTarget, +} + +impl InnerState { + /// Creates a DirectComposition device and a target for the given window handle. + pub unsafe fn init(hwnd: &HWND) -> Result { + profiling::scope!("DCompState::init"); + let dcomp_device: DirectComposition::IDCompositionDevice = { + unsafe { DirectComposition::DCompositionCreateDevice2(None) }.map_err(|err| { + log::error!("DirectComposition::DCompositionCreateDevice failed: {err}"); + crate::SurfaceError::Other("DirectComposition::DCompositionCreateDevice") + })? + }; + + let target = unsafe { dcomp_device.CreateTargetForHwnd(*hwnd, false) }.map_err(|err| { + log::error!("IDCompositionDevice::CreateTargetForHwnd failed: {err}"); + crate::SurfaceError::Other("IDCompositionDevice::CreateTargetForHwnd") + })?; + + let visual = unsafe { dcomp_device.CreateVisual() }.map_err(|err| { + log::error!("IDCompositionDevice::CreateVisual failed: {err}"); + crate::SurfaceError::Other("IDCompositionDevice::CreateVisual") + })?; + + unsafe { target.SetRoot(&visual) }.map_err(|err| { + log::error!("IDCompositionTarget::SetRoot failed: {err}"); + crate::SurfaceError::Other("IDCompositionTarget::SetRoot") + })?; + + Ok(InnerState { + visual, + device: dcomp_device, + _target: target, + }) + } +} diff --git a/wgpu-hal/src/dx12/instance.rs b/wgpu-hal/src/dx12/instance.rs index 69bfa77d8e..5f137836f8 100644 --- a/wgpu-hal/src/dx12/instance.rs +++ b/wgpu-hal/src/dx12/instance.rs @@ -104,6 +104,7 @@ impl crate::Instance for super::Instance { factory, factory_media, library: Arc::new(lib_main), + presentation_system: desc.backend_options.dx12.presentation_system, _lib_dxgi: lib_dxgi, supports_allow_tearing, flags: desc.flags, @@ -119,15 +120,26 @@ impl crate::Instance for super::Instance { window_handle: raw_window_handle::RawWindowHandle, ) -> Result { match window_handle { - raw_window_handle::RawWindowHandle::Win32(handle) => Ok(super::Surface { - factory: self.factory.clone(), - factory_media: self.factory_media.clone(), + raw_window_handle::RawWindowHandle::Win32(handle) => { // https://github.com/rust-windowing/raw-window-handle/issues/171 - 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(), - }), + let handle = Foundation::HWND(handle.hwnd.get() as *mut _); + let target = match self.presentation_system { + wgt::Dx12SwapchainKind::DxgiFromHwnd => SurfaceTarget::WndHandle(handle), + wgt::Dx12SwapchainKind::DxgiFromVisual => SurfaceTarget::VisualFromWndHandle { + handle, + dcomp_state: Default::default(), + }, + }; + + Ok(super::Surface { + factory: self.factory.clone(), + factory_media: self.factory_media.clone(), + target, + 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" ))), diff --git a/wgpu-hal/src/dx12/mod.rs b/wgpu-hal/src/dx12/mod.rs index c15f3b81e4..a2570048b9 100644 --- a/wgpu-hal/src/dx12/mod.rs +++ b/wgpu-hal/src/dx12/mod.rs @@ -75,6 +75,7 @@ Otherwise, we pass a range corresponding only to the current bind group. mod adapter; mod command; mod conv; +mod dcomp; mod descriptor; mod device; mod instance; @@ -460,6 +461,7 @@ pub struct Instance { factory_media: Option, library: Arc, supports_allow_tearing: bool, + presentation_system: wgt::Dx12SwapchainKind, _lib_dxgi: DxgiLib, flags: wgt::InstanceFlags, memory_budget_thresholds: wgt::MemoryBudgetThresholds, @@ -542,6 +544,11 @@ struct SwapChain { enum SurfaceTarget { /// Borrowed, lifetime externally managed WndHandle(Foundation::HWND), + /// `handle` is borrowed, lifetime externally managed + VisualFromWndHandle { + handle: Foundation::HWND, + dcomp_state: Mutex, + }, Visual(DirectComposition::IDCompositionVisual), /// Borrowed, lifetime externally managed SurfaceHandle(Foundation::HANDLE), @@ -1297,7 +1304,9 @@ impl crate::Surface for Surface { Flags: flags.0 as u32, }; let swap_chain1 = match self.target { - SurfaceTarget::Visual(_) | SurfaceTarget::SwapChainPanel(_) => { + SurfaceTarget::Visual(_) + | SurfaceTarget::VisualFromWndHandle { .. } + | SurfaceTarget::SwapChainPanel(_) => { profiling::scope!("IDXGIFactory2::CreateSwapChainForComposition"); unsafe { self.factory.CreateSwapChainForComposition( @@ -1344,6 +1353,33 @@ impl crate::Surface for Surface { match &self.target { SurfaceTarget::WndHandle(_) | SurfaceTarget::SurfaceHandle(_) => {} + SurfaceTarget::VisualFromWndHandle { + handle, + dcomp_state, + } => { + let mut dcomp_state = dcomp_state.lock(); + let dcomp_state = unsafe { dcomp_state.get_or_init(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") + }, + )?; + } + + // Commit the changes to the composition device. + { + profiling::scope!("IDCompositionDevice::Commit"); + unsafe { dcomp_state.device.Commit() }.map_err(|err| { + log::error!("IDCompositionDevice::Commit failed: {err}"); + crate::SurfaceError::Other("IDCompositionDevice::Commit") + })?; + } + } SurfaceTarget::Visual(visual) => { if let Err(err) = unsafe { visual.SetContent(&swap_chain1) } { log::error!("Unable to SetContent: {err}"); @@ -1381,6 +1417,7 @@ impl crate::Surface for Surface { .into_device_result("MakeWindowAssociation")?; } SurfaceTarget::Visual(_) + | SurfaceTarget::VisualFromWndHandle { .. } | SurfaceTarget::SurfaceHandle(_) | SurfaceTarget::SwapChainPanel(_) => {} } diff --git a/wgpu-types/src/instance.rs b/wgpu-types/src/instance.rs index c78fccdb66..b19ce6ff93 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, + /// Presentation system to use. + pub presentation_system: Dx12SwapchainKind, /// Whether to wait for the latency waitable object before acquiring the next swapchain image. pub latency_waitable_object: Dx12UseFrameLatencyWaitableObject, } @@ -370,10 +372,12 @@ impl Dx12BackendOptions { #[must_use] pub fn from_env_or_default() -> Self { let compiler = Dx12Compiler::from_env().unwrap_or_default(); + let presentation_system = Dx12SwapchainKind::from_env().unwrap_or_default(); let latency_waitable_object = Dx12UseFrameLatencyWaitableObject::from_env().unwrap_or_default(); Self { shader_compiler: compiler, + presentation_system, latency_waitable_object, } } @@ -384,10 +388,11 @@ impl Dx12BackendOptions { #[must_use] pub fn with_env(self) -> Self { let shader_compiler = self.shader_compiler.with_env(); + let presentation_system = self.presentation_system.with_env(); let latency_waitable_object = self.latency_waitable_object.with_env(); - Self { shader_compiler, + presentation_system, latency_waitable_object, } } @@ -439,6 +444,58 @@ impl NoopBackendOptions { } } +#[derive(Clone, Debug, Default, Copy, PartialEq, Eq)] +/// Selects which kind of swapchain to use on DX12. +pub enum Dx12SwapchainKind { + /// Use a DXGI swapchain made directly from the window's HWND. + /// + /// This does not support transparency but has better support from developer tooling from RenderDoc. + #[default] + DxgiFromHwnd, + /// Use a DXGI swapchain made from a DirectComposition visual made automatically from the window's HWND. + /// + /// This creates a single [`IDCompositionVisual`] over the entire window that is used by the `Surface`. + /// If a user wants to manage the composition tree themselves, they should create their own device and + /// composition, and pass the relevant visual down via [`SurfaceTargetUnsafe::CompositionVisual`][CV]. + /// + /// This supports transparent windows, but does not have support from RenderDoc. + /// + /// [`IDCompositionVisual`]: https://learn.microsoft.com/en-us/windows/win32/api/dcomp/nn-dcomp-idcompositionvisual + /// [CV]: ../wgpu/struct.SurfaceTargetUnsafe.html#variant.CompositionVisual + DxgiFromVisual, +} + +impl Dx12SwapchainKind { + /// Choose which presentation system to use from the environment variable `WGPU_DX12_PRESENTATION_SYSTEM`. + /// + /// Valid values, case insensitive: + /// - `DxgiFromVisual` or `Visual` + /// - `DxgiFromHwnd` or `Hwnd` for [`Self::DxgiFromHwnd`] + #[must_use] + pub fn from_env() -> Option { + let value = crate::env::var("WGPU_DX12_PRESENTATION_SYSTEM") + .as_deref()? + .to_lowercase(); + match value.as_str() { + "dxgifromvisual" | "visual" => Some(Self::DxgiFromVisual), + "dxgifromhwnd" | "hwnd" => Some(Self::DxgiFromHwnd), + _ => None, + } + } + + /// Takes the given presentation system, modifies it based on the `WGPU_DX12_PRESENTATION_SYSTEM` environment variable, and returns the result. + /// + /// See [`from_env`](Self::from_env) for more information. + #[must_use] + pub fn with_env(self) -> Self { + if let Some(presentation_system) = Self::from_env() { + presentation_system + } else { + self + } + } +} + /// DXC shader model. #[derive(Clone, Debug)] #[allow(missing_docs)]