Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e6d7e5f
add dx12 dcomp option to backend options
n1ght-hunter Apr 13, 2025
c328699
impl dcomp for the dx12 backend
n1ght-hunter Apr 13, 2025
3f91a00
Merge branch 'gfx-rs:trunk' into dev/windows-os-transparent
n1ght-hunter Apr 13, 2025
b8131bf
Merge branch 'gfx-rs:trunk' into dev/windows-os-transparent
n1ght-hunter Apr 16, 2025
1f70a9a
Add Dcomp support to DX12 backend in changelog
n1ght-hunter Apr 16, 2025
60a747c
move decomp to another module
n1ght-hunter Apr 23, 2025
8b012f3
refactor: replace use_dcomp with presentation_system in Dx12BackendOp…
n1ght-hunter Apr 23, 2025
91d98af
refactor: enhance Dx12PresentationSystem with Copy, PartialEq, and Eq…
n1ght-hunter Apr 23, 2025
bae60c3
refactor: replace use_dcomp with presentation_system in Dx12 backend …
n1ght-hunter Apr 23, 2025
194a685
Merge branch 'trunk' into dev/windows-os-transparent
n1ght-hunter Apr 23, 2025
32ce523
refactor: update Dx12BackendOptions to use presentation_system instea…
n1ght-hunter Apr 23, 2025
157c1c6
Merge branch 'dev/windows-os-transparent' of https://github.com/n1ght…
n1ght-hunter Apr 23, 2025
cd41a6e
Merge branch 'trunk' into dev/windows-os-transparent
n1ght-hunter Apr 27, 2025
73579d3
refactor(dx12): update DirectComposition device creation to use DComp…
n1ght-hunter Aug 1, 2025
cae67ca
Merge branch 'trunk' into dev/windows-os-transparent
n1ght-hunter Aug 1, 2025
830bf2b
cleanup dcomp create device types
n1ght-hunter Aug 1, 2025
f40d9b5
clean dcomp code
n1ght-hunter Aug 1, 2025
2302705
refactor(dx12): remove unused Direct3D 11 dependencies from Cargo.toml
n1ght-hunter Aug 1, 2025
504dfa1
refactor(dx12): remove unnecessary device parameter from DirectCompos…
n1ght-hunter Aug 1, 2025
62758fe
Update wgpu-types/src/instance.rs
n1ght-hunter Aug 14, 2025
25f68fa
Update wgpu-types/src/instance.rs
n1ght-hunter Aug 14, 2025
7b5d124
Update wgpu-types/src/instance.rs
n1ght-hunter Aug 14, 2025
eab2de6
Update wgpu-types/src/instance.rs
n1ght-hunter Aug 14, 2025
a9ddf45
Update CHANGELOG.md
n1ght-hunter Aug 14, 2025
55659a1
Update CHANGELOG.md
n1ght-hunter Aug 14, 2025
92afbbc
Merge branch 'trunk' into dev/windows-os-transparent
n1ght-hunter Aug 14, 2025
783e730
fmt
n1ght-hunter Aug 14, 2025
1a9ff41
Update CHANGELOG.md
n1ght-hunter Aug 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,19 @@ Bottom level categories:

### Major Changes

#### Builtin Support for DirectComposition/DXGI swapchains in DX12

By selecting DirectComposition, the compositor cannot optimize your Swapchain as much, but you can support transparent windows.

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 into `wgpu` via `IDCompositionTarget::CompositionVisual` or `IDCompositionTarget::SurfaceHandle`.

```diff
-pub struct Dx12BackendOptions { pub shader_compiler: Dx12Compiler }
+pub struct Dx12BackendOptions { pub shader_compiler: Dx12Compiler, pub presentation_system: Dx12SwapchainKind }
```

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.
Expand Down
6 changes: 5 additions & 1 deletion wgpu-hal/src/dx12/adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -920,7 +920,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()
{
Expand Down Expand Up @@ -964,6 +967,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,
Expand Down
70 changes: 70 additions & 0 deletions wgpu-hal/src/dx12/dcomp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
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> {
let dcomp_device: DirectComposition::IDCompositionDevice = {
profiling::scope!("DirectComposition::DCompositionCreateDevice");
Copy link
Contributor

Choose a reason for hiding this comment

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

Just wondering, is it normal to wrap every external API call - especially these that are well off the hot path - in a profiling scope?

Copy link
Author

Choose a reason for hiding this comment

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

perhaps just a single profile scope for the whole init scope?

Copy link
Member

Choose a reason for hiding this comment

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

Yeah lets do a single scope - generally the decision for what to scope depends on how long it takes, so if there's a single item that takes a long time, scope that, but for processes that only really take a while in aggregate, we can use a single scope.

unsafe { DirectComposition::DCompositionCreateDevice2(None) }.map_err(|err| {
log::error!("DirectComposition::DCompositionCreateDevice failed: {err}");
crate::SurfaceError::Other("DirectComposition::DCompositionCreateDevice")
Copy link
Contributor

Choose a reason for hiding this comment

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

It's surprising that SurfaceError doesn't have an inner error like InstanceError.

})?
};

let target = {
profiling::scope!("IDCompositionDevice::CreateTargetForHwnd");
unsafe { dcomp_device.CreateTargetForHwnd(*hwnd, false) }.map_err(|err| {
log::error!("IDCompositionDevice::CreateTargetForHwnd failed: {err}");
crate::SurfaceError::Other("IDCompositionDevice::CreateTargetForHwnd")
})?
};

let visual = {
profiling::scope!("IDCompositionDevice::CreateVisual");
unsafe { dcomp_device.CreateVisual() }.map_err(|err| {
log::error!("IDCompositionDevice::CreateVisual failed: {err}");
crate::SurfaceError::Other("IDCompositionDevice::CreateVisual")
})?
};

{
profiling::scope!("IDCompositionTarget::SetRoot");
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,
})
}
}
30 changes: 22 additions & 8 deletions wgpu-hal/src/dx12/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -119,15 +120,28 @@ 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 {
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::Dxgi => SurfaceTarget::WndHandle(handle),
wgt::Dx12SwapchainKind::DirectComposition => {
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"
))),
Expand Down
39 changes: 38 additions & 1 deletion wgpu-hal/src/dx12/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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}");
Expand Down Expand Up @@ -1381,6 +1417,7 @@ impl crate::Surface for Surface {
.into_device_result("MakeWindowAssociation")?;
}
SurfaceTarget::Visual(_)
| SurfaceTarget::VisualFromWndHandle { .. }
| SurfaceTarget::SurfaceHandle(_)
| SurfaceTarget::SwapChainPanel(_) => {}
}
Expand Down
52 changes: 51 additions & 1 deletion wgpu-types/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand All @@ -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,
}
}
Expand All @@ -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,
}
}
Expand Down Expand Up @@ -439,6 +444,51 @@ 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 supports fullscreen optimization, making borderless windows just as efficient as exclusive fullscreen. It does not support transparent windows.
#[default]
Dxgi,
/// Use a DXGI swapchain made from a DirectComposition visual made from the window.
///
/// This supports transparent windows, but does not support fullscreen optimization.
DirectComposition,
}

impl Dx12SwapchainKind {
/// Choose which presentation system to use from the environment variable `WGPU_DX12_PRESENTATION_SYSTEM`.
///
/// Valid values, case insensitive:
/// - `Dxgi`
/// - `DirectComposition`
#[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() {
"dcomp" => Some(Self::DirectComposition),
Copy link
Contributor

Choose a reason for hiding this comment

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

Also update the argument name?

Copy link
Author

@n1ght-hunter n1ght-hunter Aug 14, 2025

Choose a reason for hiding this comment

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

oh ya was gonna ask we wanted to update that. though i could see leaving it so its short in the cli

Copy link
Member

Choose a reason for hiding this comment

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

I think having both would be reasonable as well.

"dxgi" => Some(Self::Dxgi),
_ => 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)]
Expand Down
Loading