Skip to content

Commit 7b15171

Browse files
committed
vulkan: add DXGI flip-model interop swapchain via D3D12 direct back-buffer sharing (Windows)
Vulkan renders directly into the DXGI flip-model back buffers, which are created on a LUID-matched interop D3D12 device and shared into Vulkan as external-memory images (no copy). A shared D3D12 fence orders Vulkan rendering ahead of a no-op present command list that binds each back buffer and transitions it to PRESENT. Opt-in via VulkanSwapchainKind / WGPU_VULKAN_SWAPCHAIN_KIND; the native VK_KHR_swapchain path stays the default.
1 parent ffa9e38 commit 7b15171

15 files changed

Lines changed: 1425 additions & 23 deletions

File tree

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,31 @@ Bottom level categories:
4242

4343
## Unreleased
4444

45+
### Major changes
46+
47+
#### DXGI swapchain for Vulkan on Windows
48+
49+
The Vulkan backend can now present via a D3D12 swapchain on Windows. D3D12 swapchains generally perform more consistently, give more control over frame pacing, and may use optimizations that are not available to native vulkan swapchains. For now this is default-off feature, though in the future it may be enabled by default.
50+
51+
Turn it on at instance creation, or if your program is using wgpu environment variables set `WGPU_VULKAN_SWAPCHAIN_KIND` to `native`, `dxgi-hwnd`, or `dxgi-visual`:
52+
53+
```diff
54+
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
55+
backends: wgpu::Backends::VULKAN,
56+
+ backend_options: wgpu::BackendOptions {
57+
+ vulkan: wgpu::VulkanBackendOptions {
58+
+ swapchain_kind: wgpu::VulkanSwapchainKind::DxgiFromHwnd,
59+
+ },
60+
+ ..Default::default()
61+
+ },
62+
..Default::default()
63+
});
64+
```
65+
66+
While all graphics debuggers still broadly function, Nsight and Radeon GPU Profiler work as expected, but RenderDoc will only capture the D3D12 work, unless you explicitly use `Device::start_graphics_debugger_capture` on the wgpu device which will capture the vulkan work. The interop D3D12 device honors the same Agility SDK configuration as the DX12 backend, via `VulkanBackendOptions::agility_sdk`.
67+
68+
By @cwfitzgerald in [#8388](https://github.com/gfx-rs/wgpu/pull/8388).
69+
4570
### Added/New Features
4671

4772
#### Hal

wgpu-hal/src/auxil/dxgi/conv.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,8 @@ pub fn map_texture_format_nosrgb(format: wgt::TextureFormat) -> Dxgi::Common::DX
120120
/// The typeless DXGI format family for `format`, for the color formats wgpu may view as both their
121121
/// sRGB and non-sRGB form. Returns `None` for formats with no such castable typeless family.
122122
///
123-
/// Shared by the DX12 resource-creation path ([`map_texture_format_for_resource`]) and the Windows
124-
/// Vulkan DXGI interop swapchain, which creates its shared interop textures typeless so the imported
125-
/// Vulkan image can be viewed through either form.
123+
/// Used by the DX12 resource-creation path ([`map_texture_format_for_resource`]).
124+
#[cfg(dx12)]
126125
pub fn map_texture_format_typeless(
127126
format: wgt::TextureFormat,
128127
) -> Option<Dxgi::Common::DXGI_FORMAT> {

wgpu-hal/src/auxil/dxgi/handles.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
use windows::Win32::Foundation::{CloseHandle, HANDLE};
2+
3+
/// An owned Win32 NT handle that closes itself on drop.
4+
pub struct OwnedHandle(pub HANDLE);
5+
6+
// The handle is just an opaque OS resource; sharing it across threads is sound.
7+
unsafe impl Send for OwnedHandle {}
8+
unsafe impl Sync for OwnedHandle {}
9+
10+
impl Drop for OwnedHandle {
11+
fn drop(&mut self) {
12+
if !self.0.is_invalid() {
13+
let _ = unsafe { CloseHandle(self.0) };
14+
}
15+
}
16+
}

wgpu-hal/src/auxil/dxgi/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pub mod device_factory;
77
#[cfg(dx12)]
88
pub mod exception;
99
pub mod factory;
10+
pub mod handles;
1011
pub mod hdr;
1112
pub mod library;
1213
#[cfg(dx12)]

wgpu-hal/src/vulkan/adapter.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1186,6 +1186,10 @@ pub struct PhysicalDeviceProperties {
11861186
/// `VK_EXT_pci_bus_info` extension.
11871187
pci_bus_info: Option<vk::PhysicalDevicePCIBusInfoPropertiesEXT<'static>>,
11881188

1189+
/// Device identity properties from Vulkan 1.1, including the device LUID used to
1190+
/// match the Vulkan physical device against a DXGI adapter for interop.
1191+
id: Option<vk::PhysicalDeviceIDProperties<'static>>,
1192+
11891193
/// The device API version.
11901194
///
11911195
/// Which is the version of Vulkan supported for device-level functionality.
@@ -1341,6 +1345,11 @@ impl PhysicalDeviceProperties {
13411345
extensions.push(khr::external_memory_win32::NAME);
13421346
}
13431347

1348+
// Optional `VK_KHR_external_semaphore_win32`
1349+
if self.supports_extension(khr::external_semaphore_win32::NAME) {
1350+
extensions.push(khr::external_semaphore_win32::NAME);
1351+
}
1352+
13441353
// Optional `VK_KHR_external_memory_fd`
13451354
if self.supports_extension(khr::external_memory_fd::NAME) {
13461355
extensions.push(khr::external_memory_fd::NAME);
@@ -1975,6 +1984,13 @@ impl super::InstanceShared {
19751984
properties2 = properties2.push_next(next);
19761985
}
19771986

1987+
if capabilities.device_api_version >= vk::API_VERSION_1_1 {
1988+
let next = capabilities
1989+
.id
1990+
.insert(vk::PhysicalDeviceIDProperties::default());
1991+
properties2 = properties2.push_next(next);
1992+
}
1993+
19781994
unsafe {
19791995
get_device_properties.get_physical_device_properties2(phd, &mut properties2)
19801996
};
@@ -2424,6 +2440,9 @@ impl super::Instance {
24242440
max_draw_indirect_count: phd_capabilities.properties.limits.max_draw_indirect_count,
24252441
non_coherent_map_mask: phd_capabilities.properties.limits.non_coherent_atom_size - 1,
24262442
can_present: true,
2443+
device_luid: phd_capabilities
2444+
.id
2445+
.and_then(|id| (id.device_luid_valid == vk::TRUE).then_some(id.device_luid)),
24272446
//TODO: make configurable
24282447
robust_buffer_access: phd_features.core.robust_buffer_access != 0,
24292448
robust_image_access: match phd_features.robustness2 {
@@ -2664,6 +2683,15 @@ impl super::Adapter {
26642683
} else {
26652684
None
26662685
};
2686+
let external_semaphore_win32_fn =
2687+
if enabled_extensions.contains(&khr::external_semaphore_win32::NAME) {
2688+
Some(khr::external_semaphore_win32::Device::new(
2689+
&self.instance.raw,
2690+
&raw_device,
2691+
))
2692+
} else {
2693+
None
2694+
};
26672695

26682696
let naga_options = {
26692697
use naga::back::spv;
@@ -2919,6 +2947,7 @@ impl super::Adapter {
29192947
ray_tracing_pipelines: ray_tracing_pipeline_fns,
29202948
mesh_shading: mesh_shading_fns,
29212949
external_memory_fd: external_memory_fd_fn,
2950+
external_semaphore_win32: external_semaphore_win32_fn,
29222951
},
29232952
pipeline_cache_validation_key,
29242953
vendor_id: self.phd_capabilities.properties.vendor_id,
@@ -2935,6 +2964,8 @@ impl super::Adapter {
29352964
texture_identity_factory: super::ResourceIdentityFactory::new(),
29362965
texture_view_identity_factory: super::ResourceIdentityFactory::new(),
29372966
empty_descriptor_set_layout,
2967+
#[cfg(windows)]
2968+
dxgi_interop: once_cell::sync::OnceCell::new(),
29382969
});
29392970

29402971
let relay_semaphores = super::RelaySemaphores::new(&shared)?;

wgpu-hal/src/vulkan/command.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,17 @@ use hashbrown::hash_map::Entry;
77
const ALLOCATION_GRANULARITY: u32 = 16;
88
const DST_IMAGE_LAYOUT: vk::ImageLayout = vk::ImageLayout::TRANSFER_DST_OPTIMAL;
99

10+
/// The layout for `usage`, honoring a texture's overridden present layout. Identical to
11+
/// [`conv::derive_image_layout`] except that `TextureUses::PRESENT` resolves to the texture's own
12+
/// `present_layout` (`GENERAL` for DXGI interop images rather than `PRESENT_SRC_KHR`).
13+
fn barrier_image_layout(texture: &super::Texture, usage: wgt::TextureUses) -> vk::ImageLayout {
14+
if usage == wgt::TextureUses::PRESENT {
15+
texture.present_layout
16+
} else {
17+
conv::derive_image_layout(usage, texture.format)
18+
}
19+
}
20+
1021
impl super::Texture {
1122
fn map_buffer_copies<T>(&self, regions: T) -> impl Iterator<Item = vk::BufferImageCopy>
1223
where
@@ -248,10 +259,10 @@ impl crate::CommandEncoder for super::CommandEncoder {
248259
&self.device.private_caps,
249260
);
250261
let (src_stage, src_access) = conv::map_texture_usage_to_barrier(bar.usage.from);
251-
let src_layout = conv::derive_image_layout(bar.usage.from, bar.texture.format);
262+
let src_layout = barrier_image_layout(bar.texture, bar.usage.from);
252263
src_stages |= src_stage;
253264
let (dst_stage, dst_access) = conv::map_texture_usage_to_barrier(bar.usage.to);
254-
let dst_layout = conv::derive_image_layout(bar.usage.to, bar.texture.format);
265+
let dst_layout = barrier_image_layout(bar.texture, bar.usage.to);
255266
dst_stages |= dst_stage;
256267

257268
vk_barriers.push(

wgpu-hal/src/vulkan/device.rs

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,7 @@ impl super::Device {
314314
format: desc.format,
315315
copy_size: desc.copy_extent(),
316316
identity,
317+
present_layout: vk::ImageLayout::PRESENT_SRC_KHR,
317318
}
318319
}
319320

@@ -444,28 +445,34 @@ impl super::Device {
444445
})
445446
}
446447

448+
/// Create a [`super::Texture`] backed by a D3D11 texture or D3D12 resource shared via an NT
449+
/// handle.
450+
///
451+
/// `handle_type` selects how `handle` is interpreted: `D3D11_TEXTURE` for an `ID3D11Texture2D`
452+
/// shared handle, or `D3D12_RESOURCE` for an `ID3D12Resource` shared handle.
453+
///
447454
/// # Safety
448455
///
449-
/// - Vulkan (with VK_KHR_external_memory_win32)
450-
/// - The `d3d11_shared_handle` must be valid and respecting `desc`
451-
/// - `VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_TEXTURE_BIT` flag is used because we need to hold a reference to the handle
456+
/// - The texture must be imported on the same physical device that owns the shared resource.
457+
/// - `handle` must be a valid shared NT handle whose resource matches `desc` and `handle_type`.
452458
#[cfg(windows)]
453-
pub unsafe fn texture_from_d3d11_shared_handle(
459+
pub unsafe fn texture_from_shared_handle(
454460
&self,
455-
d3d11_shared_handle: windows::Win32::Foundation::HANDLE,
461+
handle: windows::Win32::Foundation::HANDLE,
462+
handle_type: vk::ExternalMemoryHandleTypeFlags,
456463
desc: &crate::TextureDescriptor,
457464
) -> Result<super::Texture, crate::DeviceError> {
458465
if !self
459466
.shared
460-
.features
461-
.contains(wgt::Features::VULKAN_EXTERNAL_MEMORY_WIN32)
467+
.enabled_extensions
468+
.contains(&ash::khr::external_memory_win32::NAME)
462469
{
463470
log::error!("Vulkan driver does not support VK_KHR_external_memory_win32");
464471
return Err(crate::DeviceError::Unexpected);
465472
}
466473

467-
let mut external_memory_image_info = vk::ExternalMemoryImageCreateInfo::default()
468-
.handle_types(vk::ExternalMemoryHandleTypeFlags::D3D11_TEXTURE);
474+
let mut external_memory_image_info =
475+
vk::ExternalMemoryImageCreateInfo::default().handle_types(handle_type);
469476

470477
let image =
471478
self.create_image_without_memory(desc, Some(&mut external_memory_image_info))?;
@@ -476,8 +483,8 @@ impl super::Device {
476483
vk::MemoryDedicatedAllocateInfo::default().image(image.raw);
477484

478485
let mut import_memory_info = vk::ImportMemoryWin32HandleInfoKHR::default()
479-
.handle_type(vk::ExternalMemoryHandleTypeFlags::D3D11_TEXTURE)
480-
.handle(d3d11_shared_handle.0 as _);
486+
.handle_type(handle_type)
487+
.handle(handle.0 as _);
481488
// TODO: We should use `push_next` instead, but currently ash does not provide this method for the `ImportMemoryWin32HandleInfoKHR` type.
482489
#[allow(clippy::unnecessary_mut_passed)]
483490
{
@@ -3055,6 +3062,37 @@ impl super::DeviceShared {
30553062
Ok(semaphore)
30563063
}
30573064

3065+
/// Imports a shared D3D11/D3D12 fence (an `ID3D11Fence`/`ID3D12Fence` NT handle) into an
3066+
/// existing timeline `semaphore`, so the same monotonic counter is visible to both APIs.
3067+
///
3068+
/// # Safety
3069+
///
3070+
/// - `handle` must be a valid shared NT handle to an `ID3D11Fence`/`ID3D12Fence`.
3071+
/// - `semaphore` must be a timeline semaphore with no pending operations.
3072+
#[cfg(windows)]
3073+
pub(super) unsafe fn import_timeline_semaphore_d3d12_fence(
3074+
&self,
3075+
semaphore: vk::Semaphore,
3076+
handle: windows::Win32::Foundation::HANDLE,
3077+
) -> Result<(), crate::DeviceError> {
3078+
let ext = self
3079+
.extension_fns
3080+
.external_semaphore_win32
3081+
.as_ref()
3082+
.ok_or_else(|| {
3083+
log::error!("Vulkan driver does not support VK_KHR_external_semaphore_win32");
3084+
crate::DeviceError::Unexpected
3085+
})?;
3086+
3087+
let import_info = vk::ImportSemaphoreWin32HandleInfoKHR::default()
3088+
.semaphore(semaphore)
3089+
.handle_type(vk::ExternalSemaphoreHandleTypeFlags::D3D12_FENCE)
3090+
.handle(handle.0 as _);
3091+
3092+
unsafe { ext.import_semaphore_win32_handle(&import_info) }
3093+
.map_err(super::map_host_device_oom_err)?;
3094+
Ok(())
3095+
}
30583096
pub(super) fn wait_for_fence(
30593097
&self,
30603098
fence: &super::Fence,

0 commit comments

Comments
 (0)