Skip to content

Commit 28452b4

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 99c7644 commit 28452b4

13 files changed

Lines changed: 1542 additions & 23 deletions

File tree

CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,29 @@ A new standalone example, `examples/standalone/03_hdr_surface`, prints a surface
143143

144144
By @stuartparmenter in [#9658](https://github.com/gfx-rs/wgpu/pull/9658).
145145

146+
#### DXGI flip-model swapchain for Vulkan on Windows
147+
148+
The Vulkan backend can now present on Windows through a DXGI flip-model swapchain that wgpu drives directly, rather than the native `VK_KHR_swapchain`. The native swapchain is implemented by the driver on its own DXGI swapchain, which wgpu cannot configure and which often paces frames poorly. Driving the flip-model swapchain ourselves gives the same presentation control the DX12 backend has, so frame pacing is steadier and present latency is lower. This is opt-in; the native swapchain stays the default.
149+
150+
Turn it on at instance creation, or set `WGPU_VULKAN_SWAPCHAIN_KIND` to `native`, `dxgi-hwnd`, or `dxgi-visual`:
151+
152+
```diff
153+
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
154+
backends: wgpu::Backends::VULKAN,
155+
+ backend_options: wgpu::BackendOptions {
156+
+ vulkan: wgpu::VulkanBackendOptions {
157+
+ swapchain_kind: wgpu::VulkanSwapchainKind::DxgiFromHwnd,
158+
+ },
159+
+ ..Default::default()
160+
+ },
161+
..Default::default()
162+
});
163+
```
164+
165+
A DXGI kind needs a device LUID, timeline semaphores, and `VK_KHR_external_memory_win32` / `VK_KHR_external_semaphore_win32`; adapters without them cannot present under it. The interop D3D12 device honors the same Agility SDK configuration as the DX12 backend, via `VulkanBackendOptions::agility_sdk`.
166+
167+
By @cwfitzgerald in [#8388](https://github.com/gfx-rs/wgpu/pull/8388).
168+
146169
### Added/New Features
147170

148171
#### General

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/vulkan/adapter.rs

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

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

1347+
// Optional `VK_KHR_external_semaphore_win32`
1348+
if self.supports_extension(khr::external_semaphore_win32::NAME) {
1349+
extensions.push(khr::external_semaphore_win32::NAME);
1350+
}
1351+
13431352
// Optional `VK_KHR_external_memory_fd`
13441353
if self.supports_extension(khr::external_memory_fd::NAME) {
13451354
extensions.push(khr::external_memory_fd::NAME);
@@ -1973,6 +1982,13 @@ impl super::InstanceShared {
19731982
properties2 = properties2.push_next(next);
19741983
}
19751984

1985+
if capabilities.device_api_version >= vk::API_VERSION_1_1 {
1986+
let next = capabilities
1987+
.id
1988+
.insert(vk::PhysicalDeviceIDProperties::default());
1989+
properties2 = properties2.push_next(next);
1990+
}
1991+
19761992
unsafe {
19771993
get_device_properties.get_physical_device_properties2(phd, &mut properties2)
19781994
};
@@ -2422,6 +2438,9 @@ impl super::Instance {
24222438
max_draw_indirect_count: phd_capabilities.properties.limits.max_draw_indirect_count,
24232439
non_coherent_map_mask: phd_capabilities.properties.limits.non_coherent_atom_size - 1,
24242440
can_present: true,
2441+
device_luid: phd_capabilities
2442+
.id
2443+
.and_then(|id| (id.device_luid_valid == vk::TRUE).then_some(id.device_luid)),
24252444
//TODO: make configurable
24262445
robust_buffer_access: phd_features.core.robust_buffer_access != 0,
24272446
robust_image_access: match phd_features.robustness2 {
@@ -2662,6 +2681,15 @@ impl super::Adapter {
26622681
} else {
26632682
None
26642683
};
2684+
let external_semaphore_win32_fn =
2685+
if enabled_extensions.contains(&khr::external_semaphore_win32::NAME) {
2686+
Some(khr::external_semaphore_win32::Device::new(
2687+
&self.instance.raw,
2688+
&raw_device,
2689+
))
2690+
} else {
2691+
None
2692+
};
26652693

26662694
let naga_options = {
26672695
use naga::back::spv;
@@ -2917,6 +2945,7 @@ impl super::Adapter {
29172945
ray_tracing_pipelines: ray_tracing_pipeline_fns,
29182946
mesh_shading: mesh_shading_fns,
29192947
external_memory_fd: external_memory_fd_fn,
2948+
external_semaphore_win32: external_semaphore_win32_fn,
29202949
},
29212950
pipeline_cache_validation_key,
29222951
vendor_id: self.phd_capabilities.properties.vendor_id,
@@ -2933,6 +2962,8 @@ impl super::Adapter {
29332962
texture_identity_factory: super::ResourceIdentityFactory::new(),
29342963
texture_view_identity_factory: super::ResourceIdentityFactory::new(),
29352964
empty_descriptor_set_layout,
2965+
#[cfg(windows)]
2966+
dxgi_interop: once_cell::sync::OnceCell::new(),
29362967
});
29372968

29382969
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
@@ -247,10 +258,10 @@ impl crate::CommandEncoder for super::CommandEncoder {
247258
&self.device.private_caps,
248259
);
249260
let (src_stage, src_access) = conv::map_texture_usage_to_barrier(bar.usage.from);
250-
let src_layout = conv::derive_image_layout(bar.usage.from, bar.texture.format);
261+
let src_layout = barrier_image_layout(bar.texture, bar.usage.from);
251262
src_stages |= src_stage;
252263
let (dst_stage, dst_access) = conv::map_texture_usage_to_barrier(bar.usage.to);
253-
let dst_layout = conv::derive_image_layout(bar.usage.to, bar.texture.format);
264+
let dst_layout = barrier_image_layout(bar.texture, bar.usage.to);
254265
dst_stages |= dst_stage;
255266

256267
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,

wgpu-hal/src/vulkan/instance.rs

Lines changed: 73 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,7 @@ impl super::Instance {
325325
memory_budget_thresholds: wgt::MemoryBudgetThresholds,
326326
has_nv_optimus: bool,
327327
swapchain_kind: wgt::VulkanSwapchainKind,
328+
agility_sdk: Option<wgt::Dx12AgilitySDK>,
328329
drop_callback: Option<crate::DropCallback>,
329330
) -> Result<Self, crate::InstanceError> {
330331
log::debug!("Instance version: 0x{instance_api_version:x}");
@@ -368,6 +369,23 @@ impl super::Instance {
368369

369370
let drop_guard = crate::DropGuard::from_option(drop_callback);
370371

372+
// Resolve the DXGI swapchain backend. A requested DXGI kind falls back to `Native` if DXGI
373+
// is unavailable on the system.
374+
#[cfg(windows)]
375+
let (swapchain_kind, dxgi_instance) =
376+
super::swapchain::init_dxgi_instance(swapchain_kind, agility_sdk.as_ref(), flags);
377+
#[cfg(not(windows))]
378+
let swapchain_kind = if swapchain_kind == wgt::VulkanSwapchainKind::Native {
379+
let _ = &agility_sdk;
380+
swapchain_kind
381+
} else {
382+
log::warn!(
383+
"VulkanSwapchainKind::{swapchain_kind:?} is only supported on Windows; \
384+
using the native Vulkan swapchain"
385+
);
386+
wgt::VulkanSwapchainKind::Native
387+
};
388+
371389
Ok(Self {
372390
shared: Arc::new(super::InstanceShared {
373391
raw: raw_instance,
@@ -382,6 +400,8 @@ impl super::Instance {
382400
instance_api_version,
383401
android_sdk_version,
384402
swapchain_kind,
403+
#[cfg(windows)]
404+
dxgi_instance,
385405
}),
386406
})
387407
}
@@ -490,13 +510,31 @@ impl super::Instance {
490510
hinstance: vk::HINSTANCE,
491511
hwnd: vk::HWND,
492512
) -> Result<super::Surface, crate::InstanceError> {
493-
// Scaffold: the DXGI swapchain surface does not exist yet, so a requested DXGI kind
494-
// falls back to the native surface. Removed once `DxgiSurface` lands.
495-
if self.shared.swapchain_kind != wgt::VulkanSwapchainKind::Native {
496-
log::warn!(
497-
"VulkanSwapchainKind::{:?} is not yet implemented; using the native swapchain",
498-
self.shared.swapchain_kind
499-
);
513+
// The swapchain backend is fixed here and immutable for the surface's lifetime.
514+
#[cfg(windows)]
515+
match self.shared.swapchain_kind {
516+
wgt::VulkanSwapchainKind::Native => {}
517+
kind @ (wgt::VulkanSwapchainKind::DxgiFromHwnd
518+
| wgt::VulkanSwapchainKind::DxgiFromVisual) => {
519+
use windows::Win32::Foundation::HWND;
520+
let hwnd = HWND(hwnd as _);
521+
let target = match kind {
522+
wgt::VulkanSwapchainKind::DxgiFromHwnd => {
523+
crate::auxil::dxgi::swapchain::SurfaceTarget::WndHandle(hwnd)
524+
}
525+
_ => crate::auxil::dxgi::swapchain::SurfaceTarget::VisualFromWndHandle {
526+
handle: hwnd,
527+
dcomp_state: parking_lot::Mutex::new(Default::default()),
528+
},
529+
};
530+
return Ok(super::Surface {
531+
swapchain: RwLock::new(None),
532+
inner: Box::new(super::swapchain::DxgiSurface::new(
533+
Arc::clone(&self.shared),
534+
target,
535+
)),
536+
});
537+
}
500538
}
501539

502540
if !self.shared.extensions.contains(&khr::win32_surface::NAME) {
@@ -854,6 +892,7 @@ impl super::Instance {
854892
};
855893

856894
let swapchain_kind = desc.backend_options.vulkan.swapchain_kind;
895+
let agility_sdk = desc.backend_options.vulkan.agility_sdk.clone();
857896

858897
unsafe {
859898
Self::from_raw(
@@ -867,6 +906,7 @@ impl super::Instance {
867906
desc.memory_budget_thresholds,
868907
has_nv_optimus,
869908
swapchain_kind,
909+
agility_sdk,
870910
None,
871911
)
872912
}
@@ -1008,6 +1048,32 @@ impl crate::Instance for super::Instance {
10081048
}
10091049
}
10101050

1051+
// When a DXGI swapchain is selected, an adapter that lacks the interop prerequisites
1052+
// (a valid LUID, timeline semaphores, and both external-memory/semaphore win32 extensions)
1053+
// cannot present through the DXGI path, and there is no per-surface native fallback.
1054+
#[cfg(windows)]
1055+
if self.shared.swapchain_kind != wgt::VulkanSwapchainKind::Native {
1056+
for exposed in exposed_adapters.iter_mut() {
1057+
let interop_capable = exposed.adapter.private_caps.device_luid.is_some()
1058+
&& exposed.adapter.private_caps.timeline_semaphores
1059+
&& exposed
1060+
.adapter
1061+
.phd_capabilities
1062+
.supports_extension(khr::external_memory_win32::NAME)
1063+
&& exposed
1064+
.adapter
1065+
.phd_capabilities
1066+
.supports_extension(khr::external_semaphore_win32::NAME);
1067+
if !interop_capable {
1068+
log::warn!(
1069+
"Disabling presentation on '{}': missing DXGI interop prerequisites",
1070+
exposed.info.name
1071+
);
1072+
exposed.adapter.private_caps.can_present = false;
1073+
}
1074+
}
1075+
}
1076+
10111077
exposed_adapters
10121078
}
10131079
}

0 commit comments

Comments
 (0)