feat(clients/windows): D3D11VA hardware decode in the session client — Vulkan chain becomes vulkan → d3d11va → software

The vendor-agnostic DXVA path for GPUs without Vulkan Video (Intel's Windows driver foremost,
which previously landed on CPU decode). Ported from the retired WinUI presenter's decoder with
its Intel-safe discipline intact (decode pool stays libavcodec-derived — a hand-built pool
broke Intel at the first SubmitDecoderBuffers), on a decode device LUID-matched to the
presenter's adapter.

Hand-off is a ring of shareable BGRA8 textures (SHARED_NTHANDLE | SHARED_KEYEDMUTEX) filled by
the fixed-function ID3D11VideoProcessor (NV12/P010 → BGRA8, colour spaces from the per-frame
CICP; PQ is tone-mapped to SDR by the processor — HDR-first boxes take Vulkan Video). BGRA is
deliberate: importing a multiplanar NV12 D3D11 texture device-losts on NVIDIA however it is
consumed (plane-view sampling and DMA copy both validation-clean, both TDR — bisected), while
single-plane RGBA D3D11↔Vulkan interop is the path Chromium/ANGLE exercise on every driver.
The presenter imports a slot's NT handle per frame (VK_KHR_external_memory_win32, gated on the
spec-required external-format probe) and blits it into the video image — no CSC pass; the DXGI
keyed mutex (key 0 both sides, drop-tolerant) is the cross-API lock and visibility barrier.

Verified live vs a real host at 5120x1440@240 HEVC on an RTX 4090: 240 fps, e2e 2.7/3.0 ms
p50/p95 under the Khronos validation layer — parity with Vulkan Video (2.6 ms); auto still
resolves vulkan on NVIDIA. PUNKTFUNK_DECODER=d3d11va forces it; import/present failures demote
to software on the existing streak contract.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-09 10:57:10 +02:00
parent d0fa8bd3ee
commit a69a83b545
10 changed files with 1231 additions and 16 deletions
+182
View File
@@ -0,0 +1,182 @@
//! D3D11 shared-texture → Vulkan import (Windows): the presenter half of the D3D11VA
//! decode path (`pf_client_core::video_d3d11`). Each decoded frame arrives as the NT
//! handle of a shareable **BGRA8** texture (the decoder's VideoProcessor already did
//! YUV→RGB); we import it as a single-plane VkImage (`VK_KHR_external_memory_win32`,
//! dedicated allocation) and the presenter blits it straight into its video image — no
//! CSC pass. Single-plane RGBA is deliberate: importing the earlier multiplanar NV12
//! hand-off device-lost on NVIDIA however it was consumed (sampling or DMA copy, both
//! validation-clean — bisected 2026-07-09), while RGBA D3D11↔Vulkan interop is the path
//! Chromium/ANGLE exercise on every Windows driver.
//!
//! Synchronization is the texture's DXGI **keyed mutex** (`VK_KHR_win32_keyed_mutex`),
//! key 0 on both sides: the submit chains an acquire(0)/release(0) pair, so the GPU
//! waits for the decoder's conversion to complete before reading and the decoder's next
//! `AcquireSync(0)` on that ring slot blocks until our reads are done. Import is
//! per-frame (same discipline as the dmabuf path — parked in `Retired` until the fence
//! proves the GPU past it); NT-handle ownership stays with the decoder ring, the import
//! only references the payload.
use anyhow::{bail, Context as _, Result};
use ash::vk;
use pf_client_core::video::{ColorDesc, D3d11Frame};
/// The two device extensions this path needs; queried at device creation. Broadly present
/// on every Windows driver (NVIDIA/AMD/Intel) — a device without them just reports
/// `supports_d3d11() == false` and the decoder chain skips D3D11VA.
pub const DEVICE_EXTENSIONS: [&std::ffi::CStr; 2] = [
ash::khr::external_memory_win32::NAME,
ash::khr::win32_keyed_mutex::NAME,
];
/// Can this device import a D3D11 BGRA8 texture as a blit source? The spec-required
/// capability probe for the exact image the import path creates — creating an external
/// image the driver doesn't support is undefined behavior (observed as
/// `VK_ERROR_DEVICE_LOST` at the first submits with the old NV12 hand-off).
pub fn import_supported(instance: &ash::Instance, pdev: vk::PhysicalDevice) -> bool {
let mut ext_info = vk::PhysicalDeviceExternalImageFormatInfo::default()
.handle_type(vk::ExternalMemoryHandleTypeFlags::D3D11_TEXTURE);
let fmt_info = vk::PhysicalDeviceImageFormatInfo2::default()
.format(vk::Format::B8G8R8A8_UNORM)
.ty(vk::ImageType::TYPE_2D)
.tiling(vk::ImageTiling::OPTIMAL)
.usage(vk::ImageUsageFlags::TRANSFER_SRC)
.push_next(&mut ext_info);
let mut ext_props = vk::ExternalImageFormatProperties::default();
let mut props = vk::ImageFormatProperties2::default().push_next(&mut ext_props);
let ok = unsafe {
instance.get_physical_device_image_format_properties2(pdev, &fmt_info, &mut props)
}
.is_ok()
&& ext_props
.external_memory_properties
.external_memory_features
.contains(vk::ExternalMemoryFeatureFlags::IMPORTABLE);
tracing::info!(bgra8 = ok, "D3D11 texture → Vulkan import support");
ok
}
/// One imported frame: the BGRA8 image over the shared texture and its imported
/// (dedicated) memory — a blit source, nothing more. Parked until the in-flight fence
/// proves the GPU past the blit, then [`HwFrame::destroy`]ed — the memory is what the
/// keyed-mutex info on the submit references.
pub struct HwFrame {
pub color: ColorDesc,
pub width: u32,
pub height: u32,
image: vk::Image,
memory: vk::DeviceMemory,
}
impl HwFrame {
/// The imported image — the acquire barrier + copy source.
pub fn image(&self) -> vk::Image {
self.image
}
/// The imported memory object — the submit's keyed-mutex acquire/release info needs it.
pub fn memory(&self) -> vk::DeviceMemory {
self.memory
}
pub fn destroy(self, device: &ash::Device) {
unsafe {
device.destroy_image(self.image, None);
device.free_memory(self.memory, None);
}
}
}
/// Import one hand-off frame. Fails cleanly (the caller demotes to software after a
/// streak) on anything the driver rejects: unsupported multiplanar external format,
/// import refusal, no matching memory type.
pub fn import(
device: &ash::Device,
ext_mem_win32: &ash::khr::external_memory_win32::Device,
frame: &D3d11Frame,
) -> Result<HwFrame> {
// The demotion test hook — same contract as the dmabuf path's.
if std::env::var_os("PUNKTFUNK_HW_FAULT").is_some_and(|v| v == "import") {
bail!("injected import failure (PUNKTFUNK_HW_FAULT=import)");
}
let mp_format = vk::Format::B8G8R8A8_UNORM;
let handle_type = vk::ExternalMemoryHandleTypeFlags::D3D11_TEXTURE;
// One single-plane image over the whole texture, transfer-source only — the blit is
// the whole job. Kept maximally "identical" to the D3D11 resource (no view-format
// aliasing, no extra usages).
let mut external_info = vk::ExternalMemoryImageCreateInfo::default().handle_types(handle_type);
let image = unsafe {
device.create_image(
&vk::ImageCreateInfo::default()
.push_next(&mut external_info)
.image_type(vk::ImageType::TYPE_2D)
.format(mp_format)
.extent(vk::Extent3D {
width: frame.width,
height: frame.height,
depth: 1,
})
.mip_levels(1)
.array_layers(1)
.samples(vk::SampleCountFlags::TYPE_1)
.tiling(vk::ImageTiling::OPTIMAL)
.usage(vk::ImageUsageFlags::TRANSFER_SRC)
.initial_layout(vk::ImageLayout::UNDEFINED),
None,
)
}
.with_context(|| format!("create {}x{} {mp_format:?} external image", frame.width, frame.height))?;
let result = (|| {
// The handle's importable memory types, intersected with the image's requirement.
let handle = frame.handle as vk::HANDLE;
let mut handle_props = vk::MemoryWin32HandlePropertiesKHR::default();
unsafe {
ext_mem_win32.get_memory_win32_handle_properties(handle_type, handle, &mut handle_props)
}
.context("vkGetMemoryWin32HandlePropertiesKHR")?;
let reqs = unsafe { device.get_image_memory_requirements(image) };
let bits = reqs.memory_type_bits & handle_props.memory_type_bits;
let type_index = (0..32u32)
.find(|i| bits & (1 << i) != 0)
.context("no importable memory type for the D3D11 texture")?;
// Import does NOT take handle ownership (NT handle rule): the decoder ring keeps
// closing its own handle; this allocation references the payload independently.
let mut import_info = vk::ImportMemoryWin32HandleInfoKHR::default()
.handle_type(handle_type)
.handle(handle);
let mut dedicated = vk::MemoryDedicatedAllocateInfo::default().image(image);
let memory = unsafe {
device.allocate_memory(
&vk::MemoryAllocateInfo::default()
.push_next(&mut import_info)
.push_next(&mut dedicated)
.allocation_size(reqs.size)
.memory_type_index(type_index),
None,
)
}
.context("import D3D11 texture memory")?;
if let Err(e) = unsafe { device.bind_image_memory(image, memory, 0) } {
unsafe { device.free_memory(memory, None) };
return Err(e).context("bind imported memory");
}
Ok(memory)
})();
let memory = match result {
Ok(m) => m,
Err(e) => {
unsafe { device.destroy_image(image, None) };
return Err(e);
}
};
Ok(HwFrame {
color: frame.color,
width: frame.width,
height: frame.height,
image,
memory,
})
}
+5 -2
View File
@@ -10,11 +10,14 @@
//! import/present failure streak, demote the decoder to software via the session pump's
//! `force_software` contract, same as the GTK presenter.
//!
//! Builds on Linux AND Windows; `dmabuf` is the one Linux-only module (DRM-PRIME does
//! not exist on Windows — the decode chain there is Vulkan → software).
//! Builds on Linux AND Windows; `dmabuf` is Linux-only (DRM-PRIME does not exist on
//! Windows) and `d3d11` is its Windows counterpart (D3D11VA shared-texture import) —
//! the decode chain there is Vulkan → D3D11VA → software.
#[cfg(any(target_os = "linux", windows))]
pub mod csc;
#[cfg(windows)]
pub mod d3d11;
#[cfg(target_os = "linux")]
pub mod dmabuf;
#[cfg(any(target_os = "linux", windows))]
+41
View File
@@ -817,6 +817,47 @@ fn run_inner(mut opts: SessionOpts, mut mode: ModeCtl) -> Result<Option<Outcome>
}
false
}
// D3D11VA: shared-texture import, same gate + failure-streak
// demotion contract as the dmabuf path.
#[cfg(windows)]
DecodedImage::D3d11(d) if presenter.supports_d3d11() && !st.dmabuf_demoted => {
st.hdr = d.color.is_pq();
match presenter.present(
&window,
FrameInput::D3d11(d),
overlay_frame.as_ref(),
) {
Ok(p) => {
st.hw_fails = 0;
p
}
Err(e) => {
st.hw_fails += 1;
tracing::warn!(error = %format!("{e:#}"), fails = st.hw_fails,
"hardware present failed");
if st.hw_fails >= 3 && !st.dmabuf_demoted {
st.dmabuf_demoted = true;
tracing::warn!("demoting the decoder to software");
st.force_software.store(true, Ordering::Relaxed);
}
false
}
}
}
#[cfg(windows)]
DecodedImage::D3d11(_) => {
// No import extensions on this device (or already demoted) — the
// pump rebuilds the decoder as software; frames flow again soon.
if !st.dmabuf_demoted {
st.dmabuf_demoted = true;
tracing::warn!(
"no win32 external-memory import on this device — demoting \
the decoder to software"
);
st.force_software.store(true, Ordering::Relaxed);
}
false
}
// Vulkan-Video: decoded on the presenter's own device — present is
// views + CSC, no import step to gate on. Same failure-streak
// demotion contract as the dmabuf path.
+217 -7
View File
@@ -37,6 +37,9 @@ pub enum FrameInput<'a> {
Dmabuf(DmabufFrame),
/// FFmpeg Vulkan Video output — a VkImage already on THIS device (zero copy).
VkFrame(VkVideoFrame),
/// D3D11VA hand-off — a shareable NT-handle texture to import (`d3d11.rs`).
#[cfg(windows)]
D3d11(pf_client_core::video::D3d11Frame),
}
/// The dmabuf/CSC machinery, present only when the device carries the import extensions.
@@ -45,12 +48,22 @@ struct HwCtx {
ext_mem_fd: ash::khr::external_memory_fd::Device,
}
/// The D3D11 shared-texture import machinery, present only when the device carries
/// `VK_KHR_external_memory_win32` + `VK_KHR_win32_keyed_mutex`.
#[cfg(windows)]
struct HwCtxWin {
ext_mem_win32: ash::khr::external_memory_win32::Device,
}
/// A submitted hardware frame parked until the in-flight fence proves the GPU reads
/// done: imported dmabuf planes, or a Vulkan-Video frame (FFmpeg's image — we own only
/// the plane views; dropping the frame's guard releases the AVFrame back to the pool).
enum Retired {
#[cfg(target_os = "linux")]
Dmabuf(HwFrame),
#[cfg(windows)]
D3d11(crate::d3d11::HwFrame),
Vk {
frame: VkVideoFrame,
views: [vk::ImageView; 2],
@@ -62,6 +75,8 @@ impl Retired {
match self {
#[cfg(target_os = "linux")]
Retired::Dmabuf(f) => f.destroy(device),
#[cfg(windows)]
Retired::D3d11(f) => f.destroy(device),
Retired::Vk { frame, views } => {
unsafe {
for v in views {
@@ -333,6 +348,10 @@ pub struct Presenter {
/// pass itself is unconditional: Vulkan-Video frames need it everywhere).
#[cfg(target_os = "linux")]
hw: Option<HwCtx>,
/// D3D11 shared-texture import — `None` when the device lacks the win32 external
/// memory / keyed-mutex extensions.
#[cfg(windows)]
hw_win: Option<HwCtxWin>,
csc: CscPass,
/// FFmpeg Vulkan Video decode handles — `None` when the stack can't do it.
video_export: Option<pf_client_core::video::VulkanDecodeDevice>,
@@ -451,6 +470,31 @@ impl Presenter {
unavailable"
);
}
// D3D11 shared-texture import (the D3D11VA decode hand-off) — optional exactly
// like the dmabuf set; a device without it keeps Vulkan-Video/software decode.
// Extensions alone aren't the whole gate: the driver must also report the
// multiplanar NV12 image as IMPORTABLE from a D3D11 texture handle
// (vkGetPhysicalDeviceImageFormatProperties2 — creating an unsupported external
// image is UB, observed as VK_ERROR_DEVICE_LOST at the first submits on NVIDIA).
#[cfg(windows)]
let win_capable = crate::d3d11::DEVICE_EXTENSIONS.iter().all(|n| has(n))
&& crate::d3d11::import_supported(&instance, pdev);
#[cfg(windows)]
if win_capable {
dev_exts.extend(crate::d3d11::DEVICE_EXTENSIONS.iter().map(|n| n.as_ptr()));
} else {
tracing::info!(
"device lacks the win32 external-memory/keyed-mutex extensions — D3D11VA \
hardware frames unavailable"
);
}
// The adapter LUID (for the D3D11VA backend to create its decode device on the
// SAME adapter). Core 1.1 query; valid on effectively every Windows driver.
let mut id_props = vk::PhysicalDeviceIDProperties::default();
let mut props2 = vk::PhysicalDeviceProperties2::default().push_next(&mut id_props);
unsafe { instance.get_physical_device_properties2(pdev, &mut props2) };
let adapter_luid: Option<[u8; 8]> =
(id_props.device_luid_valid == vk::TRUE).then_some(id_props.device_luid);
// Static HDR metadata (ST.2086 mastering + CLL) to the presentation engine.
// Compositors key their "this app is HDR" signaling on the client pushing
// metadata via vkSetHdrMetadataEXT in addition to picking the HDR10 colorspace
@@ -596,12 +640,22 @@ impl Presenter {
} else {
None
};
#[cfg(windows)]
let hw_win = win_capable.then(|| HwCtxWin {
ext_mem_win32: ash::khr::external_memory_win32::Device::new(&instance, &device),
});
let csc = CscPass::new(&device, vk::Format::R8G8B8A8_UNORM)?;
// The exported handle bundle for FFmpeg's Vulkan Video decoder (None = the
// decoder chain skips straight to VAAPI/software). Extension lists must mirror
// creation exactly — FFmpeg keys its code paths off the strings.
let video_export = if video_ok {
// The exported handle bundle: FFmpeg Vulkan Video handles when the device can
// decode, AND (Windows) the D3D11-interop facts — so it's built whenever EITHER
// consumer needs it; `video_decode`/`d3d11_import` tell the decoder chain which
// paths are real. Extension lists must mirror creation exactly — FFmpeg keys its
// code paths off the strings.
#[cfg(windows)]
let export_worthy = video_ok || win_capable;
#[cfg(not(windows))]
let export_worthy = video_ok;
let video_export = if export_worthy {
let qf_props = unsafe { instance.get_physical_device_queue_family_properties(pdev) };
let mut device_extensions: Vec<CString> =
vec![CString::from(ash::khr::swapchain::NAME)];
@@ -610,6 +664,11 @@ impl Presenter {
device_extensions
.extend(dmabuf::DEVICE_EXTENSIONS.iter().map(|n| CString::from(*n)));
}
#[cfg(windows)]
if win_capable {
device_extensions
.extend(crate::d3d11::DEVICE_EXTENSIONS.iter().map(|n| CString::from(*n)));
}
if has_hdr_metadata {
device_extensions.push(CString::from(ash::ext::hdr_metadata::NAME));
}
@@ -628,9 +687,15 @@ impl Presenter {
.map(|e| CString::new(e.as_str()).unwrap())
.collect(),
device_extensions,
f_sampler_ycbcr: true,
f_timeline_semaphore: true,
f_synchronization2: true,
f_sampler_ycbcr: have_f11.sampler_ycbcr_conversion == vk::TRUE,
f_timeline_semaphore: have_f12.timeline_semaphore == vk::TRUE,
f_synchronization2: have_f13.synchronization2 == vk::TRUE,
video_decode: video_ok,
#[cfg(windows)]
d3d11_import: win_capable,
#[cfg(not(windows))]
d3d11_import: false,
adapter_luid,
})
} else {
None
@@ -685,6 +750,8 @@ impl Presenter {
qfi,
#[cfg(target_os = "linux")]
hw,
#[cfg(windows)]
hw_win,
csc,
video_export,
overlay_pipe,
@@ -881,6 +948,13 @@ impl Presenter {
self.hw.is_some()
}
/// Whether the D3D11 shared-texture path exists on this device — callers keep the
/// decoder on software when it doesn't.
#[cfg(windows)]
pub fn supports_d3d11(&self) -> bool {
self.hw_win.is_some()
}
/// The FFmpeg Vulkan Video decode handle bundle — `None` when this stack can't
/// (device < 1.3, missing video extensions/queue/features). The decoder chain
/// falls back to VAAPI/software then.
@@ -980,6 +1054,8 @@ impl Presenter {
#[cfg(target_os = "linux")]
FrameInput::Dmabuf(d) => Some(d.color.is_pq()),
FrameInput::VkFrame(v) => Some(v.color.is_pq()),
#[cfg(windows)]
FrameInput::D3d11(d) => Some(d.color.is_pq()),
};
if let Some(pq) = frame_pq {
let want = pq && self.hdr10_format.is_some();
@@ -992,6 +1068,8 @@ impl Presenter {
// semaphore.
#[cfg(target_os = "linux")]
let mut hw_frame: Option<HwFrame> = None;
#[cfg(windows)]
let mut win_frame: Option<crate::d3d11::HwFrame> = None;
let mut vk_frame: Option<(VkVideoFrame, [vk::ImageView; 2])> = None;
let cpu_frame = match input {
FrameInput::Redraw => None,
@@ -1005,6 +1083,15 @@ impl Presenter {
hw_frame = Some(dmabuf::import(&self.device, &hw.ext_mem_fd, d)?);
None
}
#[cfg(windows)]
FrameInput::D3d11(d) => {
let hw = self
.hw_win
.as_ref()
.context("D3D11 frame without win32 import support")?;
win_frame = Some(crate::d3d11::import(&self.device, &hw.ext_mem_win32, &d)?);
None
}
FrameInput::VkFrame(v) => {
let views = self.vkframe_plane_views(&v)?;
vk_frame = Some((v, views));
@@ -1047,6 +1134,17 @@ impl Presenter {
self.csc
.bind_planes(&self.device, f.luma_view, f.chroma_view);
}
#[cfg(windows)]
if let Some(f) = &win_frame {
if self
.video
.as_ref()
.is_none_or(|v| v.width != f.width || v.height != f.height)
{
self.rebuild_video_image(f.width, f.height)?;
tracing::info!(width = f.width, height = f.height, "video image (re)built");
}
}
if let Some((f, views)) = &vk_frame {
if self
.video
@@ -1086,6 +1184,10 @@ impl Presenter {
if let Some(f) = hw_frame {
f.destroy(&self.device);
}
#[cfg(windows)]
if let Some(f) = win_frame {
f.destroy(&self.device);
}
self.recreate_swapchain(window)?;
return Ok(false);
}
@@ -1122,6 +1224,49 @@ impl Presenter {
);
}
// D3D11 frame: acquire the imported BGRA texture from the external "queue
// family" (the keyed mutex on the submit is the actual cross-API sync) and
// blit it into the video image — the frame arrives as ready sRGB from the
// decoder's VideoProcessor, so there is no CSC pass; the blit converts the
// BGRA→RGBA component order. Same layout dance as the CPU staging path.
#[cfg(windows)]
if let (Some(f), Some(v)) = (&win_frame, &self.video) {
external_acquire_barrier(&self.device, self.cmd_buf, f.image(), self.qfi);
barrier(
&self.device,
self.cmd_buf,
v.image,
vk::ImageLayout::UNDEFINED,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
);
let extent = vk::Offset3D {
x: v.width as i32,
y: v.height as i32,
z: 1,
};
let blit = vk::ImageBlit::default()
.src_subresource(subresource_layers())
.src_offsets([vk::Offset3D::default(), extent])
.dst_subresource(subresource_layers())
.dst_offsets([vk::Offset3D::default(), extent]);
self.device.cmd_blit_image(
self.cmd_buf,
f.image(),
vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
v.image,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
&[blit],
vk::Filter::NEAREST, // 1:1 — the composite blit below does the scaling
);
barrier(
&self.device,
self.cmd_buf,
v.image,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
);
}
// Vulkan-Video frame: the decoded image is already on THIS device. Read the
// live sync state under the frames lock (held through submission — the
// AVVulkanFramesContext contract), acquire from the decode queue family,
@@ -1331,6 +1476,33 @@ impl Presenter {
if vk_sync.is_some() {
submit = submit.push_next(&mut timeline);
}
// D3D11 frame: bracket the submit in the shared texture's keyed mutex, key 0
// both ways (the decode side copies under acquire(0)/release(0) too) — the
// GPU-side acquire is what orders our sampling after the decoder's copy, and
// our completion release is what unblocks the ring slot's reuse.
#[cfg(windows)]
let keyed_mem;
#[cfg(windows)]
let keyed_keys = [0u64];
#[cfg(windows)]
let keyed_timeouts = [2000u32];
#[cfg(windows)]
let mut keyed_info;
#[cfg(windows)]
if let Some(f) = &win_frame {
// Bisect knob: PUNKTFUNK_D3D11_NO_MUTEX=1 skips the acquire/release pair
// (torn frames possible — debugging only).
if std::env::var_os("PUNKTFUNK_D3D11_NO_MUTEX").is_none() {
keyed_mem = [f.memory()];
keyed_info = vk::Win32KeyedMutexAcquireReleaseInfoKHR::default()
.acquire_syncs(&keyed_mem)
.acquire_keys(&keyed_keys)
.acquire_timeouts(&keyed_timeouts)
.release_syncs(&keyed_mem)
.release_keys(&keyed_keys);
submit = submit.push_next(&mut keyed_info);
}
}
let submitted = self.device.queue_submit(self.queue, &[submit], self.fence);
// Write the new sync state back and release the frames lock REGARDLESS of
// the submit outcome (an abandoned lock would wedge the decoder).
@@ -1358,6 +1530,10 @@ impl Presenter {
if let Some(f) = hw_frame.take() {
self.retired_hw = Some(Retired::Dmabuf(f));
}
#[cfg(windows)]
if let Some(f) = win_frame.take() {
self.retired_hw = Some(Retired::D3d11(f));
}
let swapchains = [self.swapchain];
let indices = [index];
@@ -2002,6 +2178,40 @@ fn vkframe_acquire_barrier(
}
}
/// Acquire an imported D3D11 texture from the EXTERNAL queue family as a copy source.
/// The keyed mutex on the submit is the actual cross-API ordering; per the
/// external-memory rules an UNDEFINED-old-layout transition on externally-bound memory
/// preserves the contents (unlike ordinary images), so this is purely the
/// layout/ownership hop.
#[cfg(windows)]
fn external_acquire_barrier(
device: &ash::Device,
cmd: vk::CommandBuffer,
image: vk::Image,
qfi: u32,
) {
let b = vk::ImageMemoryBarrier::default()
.src_access_mask(vk::AccessFlags::empty())
.dst_access_mask(vk::AccessFlags::TRANSFER_READ)
.old_layout(vk::ImageLayout::UNDEFINED)
.new_layout(vk::ImageLayout::TRANSFER_SRC_OPTIMAL)
.src_queue_family_index(vk::QUEUE_FAMILY_EXTERNAL)
.dst_queue_family_index(qfi)
.image(image)
.subresource_range(subresource_range());
unsafe {
device.cmd_pipeline_barrier(
cmd,
vk::PipelineStageFlags::TOP_OF_PIPE,
vk::PipelineStageFlags::TRANSFER,
vk::DependencyFlags::empty(),
&[],
&[],
&[b],
);
}
}
/// Acquire a dmabuf plane image from its foreign owner (the VAAPI decoder): queue-family
/// transfer FOREIGN → ours, UNDEFINED → SHADER_READ_ONLY (content is preserved across
/// the transfer regardless of the UNDEFINED old-layout, per the external-memory rules).