Files
punktfunk/crates/pf-presenter/src/d3d11.rs
T
enricobuehler 4839c0e6f6
apple / swift (push) Successful in 1m16s
windows-host / package (push) Successful in 8m34s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 1m49s
release / apple (push) Successful in 9m12s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 1m45s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 1m22s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 1m49s
apple / screenshots (push) Successful in 5m42s
android / android (push) Successful in 4m53s
arch / build-publish (push) Successful in 7m9s
ci / web (push) Successful in 1m9s
ci / docs-site (push) Successful in 1m12s
ci / rust (push) Successful in 5m28s
deb / build-publish (push) Successful in 4m9s
decky / build-publish (push) Successful in 15s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 6s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 6s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 6s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 4s
ci / bench (push) Successful in 6m13s
flatpak / build-publish (push) Failing after 5m1s
docker / deploy-docs (push) Successful in 16s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 12m30s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 12m36s
fix(ci): windows clippy + rustfmt debt from the D3D11VA push
The windows workflow has been red since a69a83b5: clippy 1.96 rejects the two
field-reassign-with-default view-desc initializers in video_d3d11.rs (now struct
literals), and with clippy failing first, the rustfmt step never ran — cargo fmt
--all had genuine misses queued up in video_d3d11.rs / pf-presenter d3d11.rs +
vk.rs / core abr.rs + client.rs (plus this session's config.rs). Formatting only
beyond the two initializers; no behaviour change.

Verified: clippy -p pf-client-core --all-targets -D warnings clean on the RTX
Windows box, cargo fmt --all --check clean, core lib tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 11:47:06 +02:00

188 lines
7.9 KiB
Rust

//! 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,
})
}