Add support for builtin DComp swapchains (#7550)

Co-authored-by: Connor Fitzgerald <connorwadefitzgerald@gmail.com>
Co-authored-by: Marijn Suijten <marijns95@gmail.com>
This commit is contained in:
Night_Hunter 2025-09-11 12:19:42 +12:00 committed by GitHub
parent 1791064a30
commit 3902b0c11c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 203 additions and 11 deletions

View File

@ -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.

View File

@ -919,7 +919,10 @@ impl crate::Adapter for super::Adapter {
) -> Option<crate::SurfaceCapabilities> {
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,

View File

@ -0,0 +1,61 @@
use windows::Win32::{Foundation::HWND, Graphics::DirectComposition};
#[derive(Default)]
pub struct DCompState {
inner: Option<InnerState>,
}
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<Self, crate::SurfaceError> {
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,
})
}
}

View File

@ -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<super::Surface, crate::InstanceError> {
match window_handle {
raw_window_handle::RawWindowHandle::Win32(handle) => Ok(super::Surface {
raw_window_handle::RawWindowHandle::Win32(handle) => {
// https://github.com/rust-windowing/raw-window-handle/issues/171
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(),
// https://github.com/rust-windowing/raw-window-handle/issues/171
target: SurfaceTarget::WndHandle(Foundation::HWND(handle.hwnd.get() as *mut _)),
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"
))),

View File

@ -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<Dxgi::IDXGIFactoryMedia>,
library: Arc<D3D12Lib>,
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<dcomp::DCompState>,
},
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(_) => {}
}

View File

@ -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<Self> {
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)]