feat(client): HDR pass-through on the D3D11VA path
A PQ stream on the D3D11VA backend was always tone-mapped to sRGB by the
VideoProcessor — with D3D11VA now auto's first choice on Intel (40030e90),
Intel Windows users would have lost HDR entirely. When the presenter can
import an RGB10A2 D3D11 texture AND offers an HDR10 swapchain (the new
VulkanDecodeDevice::d3d11_hdr10 probe), the hand-off ring switches to
RGB10A2 and the VideoProcessor does a pure colorspace conversion (YCbCr
G2084 -> RGB G2084, no tone mapping); the emitted frame carries PQ/BT.2020
color, so the presenter flips its HDR10 swapchain and video image exactly
as it does for Vulkan Video PQ frames, and the blit passes the PQ values
through untouched. SDR-only paths keep the tonemap-to-sRGB BGRA8 ring;
in-band PQ flips rebuild the ring like a resize (generation-bumped).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -273,6 +273,9 @@ pub struct Decoder {
|
||||
/// rebuild lands on the SAME GPU.
|
||||
#[cfg(windows)]
|
||||
adapter_luid: Option<[u8; 8]>,
|
||||
/// [`VulkanDecodeDevice::d3d11_hdr10`], for the same demotion rebuild.
|
||||
#[cfg(windows)]
|
||||
d3d11_hdr10: bool,
|
||||
}
|
||||
|
||||
/// Demote a hardware backend (Vulkan→VAAPI/D3D11VA, VAAPI/D3D11VA→software) only after
|
||||
@@ -373,9 +376,10 @@ impl Decoder {
|
||||
.filter(|v| !v.is_empty())
|
||||
.unwrap_or_else(|| pref.to_string());
|
||||
#[cfg(windows)]
|
||||
let (d3d11_import, adapter_luid) = (
|
||||
let (d3d11_import, adapter_luid, d3d11_hdr10) = (
|
||||
vk.is_some_and(|v| v.d3d11_import),
|
||||
vk.and_then(|v| v.adapter_luid),
|
||||
vk.is_some_and(|v| v.d3d11_hdr10),
|
||||
);
|
||||
let done = |backend| {
|
||||
Ok(Decoder {
|
||||
@@ -388,6 +392,8 @@ impl Decoder {
|
||||
d3d11_import,
|
||||
#[cfg(windows)]
|
||||
adapter_luid,
|
||||
#[cfg(windows)]
|
||||
d3d11_hdr10,
|
||||
})
|
||||
};
|
||||
// Linux `auto`: try VAAPI FIRST unless this device is one where Vulkan Video is
|
||||
@@ -434,7 +440,11 @@ impl Decoder {
|
||||
{
|
||||
if let Some(v) = vk.filter(|v| v.d3d11_import) {
|
||||
d3d11_tried = true;
|
||||
match crate::video_d3d11::D3d11vaDecoder::new(codec_id, v.adapter_luid) {
|
||||
match crate::video_d3d11::D3d11vaDecoder::new(
|
||||
codec_id,
|
||||
v.adapter_luid,
|
||||
v.d3d11_hdr10,
|
||||
) {
|
||||
Ok(d) => {
|
||||
tracing::info!(
|
||||
?codec_id,
|
||||
@@ -508,7 +518,11 @@ impl Decoder {
|
||||
if choice != "software" && choice != "vulkan" && !d3d11_tried {
|
||||
match vk.filter(|v| v.d3d11_import) {
|
||||
Some(v) => {
|
||||
match crate::video_d3d11::D3d11vaDecoder::new(codec_id, v.adapter_luid) {
|
||||
match crate::video_d3d11::D3d11vaDecoder::new(
|
||||
codec_id,
|
||||
v.adapter_luid,
|
||||
v.d3d11_hdr10,
|
||||
) {
|
||||
Ok(d) => {
|
||||
tracing::info!(
|
||||
?codec_id,
|
||||
@@ -696,6 +710,7 @@ impl Decoder {
|
||||
match crate::video_d3d11::D3d11vaDecoder::new(
|
||||
self.codec_id,
|
||||
self.adapter_luid,
|
||||
self.d3d11_hdr10,
|
||||
) {
|
||||
Ok(d) => {
|
||||
tracing::warn!(error = %e, fails = self.vaapi_fails,
|
||||
@@ -866,6 +881,10 @@ pub struct VulkanDecodeDevice {
|
||||
/// The presenter enabled `VK_KHR_external_memory_win32` + `VK_KHR_win32_keyed_mutex`:
|
||||
/// D3D11 shared-texture frames can reach the screen. Always `false` off Windows.
|
||||
pub d3d11_import: bool,
|
||||
/// The presenter can also import the RGB10A2 hand-off texture AND offers an HDR10
|
||||
/// swapchain — the D3D11VA backend emits its HDR (RGB10 PQ pass-through) ring flavor
|
||||
/// for PQ streams instead of tone-mapping to sRGB. Always `false` off Windows.
|
||||
pub d3d11_hdr10: bool,
|
||||
/// `VkPhysicalDeviceIDProperties::deviceLUID` when the driver reports one — the D3D11VA
|
||||
/// backend creates its decode device on the SAME adapter so shared textures never cross
|
||||
/// GPUs. `None` when not reported (or off Windows, where it's unused).
|
||||
@@ -950,6 +969,7 @@ mod tests {
|
||||
pyrowave_decode: false,
|
||||
video_decode: true,
|
||||
d3d11_import: false,
|
||||
d3d11_hdr10: false,
|
||||
adapter_luid: None,
|
||||
queue_lock: std::sync::Arc::new(QueueLock::new()),
|
||||
}
|
||||
|
||||
@@ -27,9 +27,11 @@
|
||||
//! (`VK_KHR_win32_keyed_mutex`); both sides take and release it with **key 0**: a frame the
|
||||
//! presenter drops (arrival-paced, newest wins) is simply never acquired, which a
|
||||
//! key-ping-pong protocol would deadlock on.
|
||||
//! * An HDR (PQ/BT.2020) stream is tone-mapped to SDR by the video processor (input colour
|
||||
//! space `G2084_P2020`, output sRGB): correct picture, no HDR presentation on this backend —
|
||||
//! its targets (Intel iGPU laptops) are SDR panels; HDR-first boxes take Vulkan Video.
|
||||
//! * An HDR (PQ/BT.2020) stream passes through when the presenter can take it (RGB10A2
|
||||
//! import + an HDR10 swapchain — [`crate::video::VulkanDecodeDevice::d3d11_hdr10`]): the
|
||||
//! video processor converts YCbCr G2084 → RGB G2084 into an RGB10A2 ring, colorspace
|
||||
//! only, no tone mapping. On an SDR-only path it tone-maps to sRGB instead (input
|
||||
//! `G2084_P2020`, output sRGB) — correct picture, no HDR presentation.
|
||||
//!
|
||||
//! The decode device is created on the **presenter's adapter** (matched by the Vulkan device's
|
||||
//! LUID) so the shared textures never cross GPUs on a multi-adapter box.
|
||||
@@ -55,11 +57,12 @@ use windows::Win32::Graphics::Direct3D11::{
|
||||
D3D11_VPOV_DIMENSION_TEXTURE2D,
|
||||
};
|
||||
use windows::Win32::Graphics::Dxgi::Common::{
|
||||
DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709, DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P2020,
|
||||
DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P601, DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P709,
|
||||
DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_LEFT_P2020, DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P2020,
|
||||
DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P601, DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P709,
|
||||
DXGI_FORMAT, DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_NV12, DXGI_FORMAT_P010, DXGI_RATIONAL,
|
||||
DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020, DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709,
|
||||
DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P2020, DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P601,
|
||||
DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P709, DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_LEFT_P2020,
|
||||
DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P2020, DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P601,
|
||||
DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P709, DXGI_FORMAT, DXGI_FORMAT_B8G8R8A8_UNORM,
|
||||
DXGI_FORMAT_NV12, DXGI_FORMAT_P010, DXGI_FORMAT_R10G10B10A2_UNORM, DXGI_RATIONAL,
|
||||
DXGI_SAMPLE_DESC,
|
||||
};
|
||||
use windows::Win32::Graphics::Dxgi::{
|
||||
@@ -98,10 +101,14 @@ const PROFILE_AV1_VLD_PROFILE0: GUID = GUID::from_u128(0xb8be4ccb_cf53_46ba_8d59
|
||||
pub struct D3d11Frame {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
/// What the ring slot actually CONTAINS after the video processor's conversion: sRGB
|
||||
/// BT.709 full-range RGB — regardless of the stream's own CICP (a PQ stream was
|
||||
/// tone-mapped). The presenter keys SDR/HDR handling off this, so it always reads SDR.
|
||||
/// What the ring slot actually CONTAINS after the video processor's conversion:
|
||||
/// sRGB BT.709 full-range RGB normally (a PQ stream was tone-mapped), or PQ BT.2020
|
||||
/// full-range RGB when the HDR pass-through ring is active (`rgb10`) — the presenter
|
||||
/// keys its SDR/HDR handling off this.
|
||||
pub color: ColorDesc,
|
||||
/// The ring slot's texture format: `false` = BGRA8, `true` = RGB10A2 (the HDR PQ
|
||||
/// pass-through flavor) — the presenter's Vulkan import must match it exactly.
|
||||
pub rgb10: bool,
|
||||
/// Intra keyframe (IDR/I) — the pump's post-loss re-anchor signal. See
|
||||
/// `crate::video::VkVideoFrame`.
|
||||
pub keyframe: bool,
|
||||
@@ -334,6 +341,9 @@ struct SharedRing {
|
||||
height: u32,
|
||||
next: usize,
|
||||
generation: u32,
|
||||
/// HDR flavor: RGB10A2 slots the processor fills with PQ BT.2020 RGB (colorspace
|
||||
/// conversion only — both sides G2084, no tone mapping). `false` = BGRA8 sRGB.
|
||||
pq_out: bool,
|
||||
}
|
||||
|
||||
impl SharedRing {
|
||||
@@ -343,6 +353,7 @@ impl SharedRing {
|
||||
width: u32,
|
||||
height: u32,
|
||||
generation: u32,
|
||||
pq_out: bool,
|
||||
) -> Result<SharedRing> {
|
||||
// The video processor: NV12/P010 in, BGRA8 out, 1:1 (no scaling — the Vulkan side
|
||||
// scales at composite time like every other path). Frame rates are advisory.
|
||||
@@ -372,10 +383,15 @@ impl SharedRing {
|
||||
Height: height,
|
||||
MipLevels: 1,
|
||||
ArraySize: 1,
|
||||
// Single-plane BGRA8: the ONLY hand-off format whose Vulkan import is a
|
||||
// Single-plane RGB: the ONLY hand-off family whose Vulkan import is a
|
||||
// universally exercised driver path (see the module docs — NV12 import TDRs
|
||||
// on NVIDIA despite being advertised).
|
||||
Format: DXGI_FORMAT_B8G8R8A8_UNORM,
|
||||
// on NVIDIA despite being advertised). RGB10A2 for the HDR pass-through
|
||||
// flavor (gated on the presenter's probe), BGRA8 otherwise.
|
||||
Format: if pq_out {
|
||||
DXGI_FORMAT_R10G10B10A2_UNORM
|
||||
} else {
|
||||
DXGI_FORMAT_B8G8R8A8_UNORM
|
||||
},
|
||||
SampleDesc: DXGI_SAMPLE_DESC {
|
||||
Count: 1,
|
||||
Quality: 0,
|
||||
@@ -433,7 +449,8 @@ impl SharedRing {
|
||||
height,
|
||||
slots = RING_SLOTS,
|
||||
generation,
|
||||
"D3D11 shared hand-off ring built (VideoProcessor → BGRA8)"
|
||||
hdr = pq_out,
|
||||
"D3D11 shared hand-off ring built (VideoProcessor → RGB)"
|
||||
);
|
||||
Ok(SharedRing {
|
||||
slots,
|
||||
@@ -443,6 +460,7 @@ impl SharedRing {
|
||||
height,
|
||||
next: 0,
|
||||
generation,
|
||||
pq_out,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -460,6 +478,10 @@ pub(crate) struct D3d11vaDecoder {
|
||||
/// setters (Win10 1703+, universally present — init fails to software without it).
|
||||
video_context1: ID3D11VideoContext1,
|
||||
ring: Option<SharedRing>,
|
||||
/// The presenter can import RGB10A2 AND offers an HDR10 swapchain
|
||||
/// ([`crate::video::VulkanDecodeDevice::d3d11_hdr10`]) — PQ streams get the HDR
|
||||
/// pass-through ring; without it they keep the tonemap-to-sRGB ring.
|
||||
hdr10_out: bool,
|
||||
}
|
||||
|
||||
// Single-owner pointers + COM interfaces, only touched from the session pump thread (the
|
||||
@@ -470,6 +492,7 @@ impl D3d11vaDecoder {
|
||||
pub(crate) fn new(
|
||||
codec_id: ffmpeg::codec::Id,
|
||||
luid: Option<[u8; 8]>,
|
||||
hdr10_out: bool,
|
||||
) -> Result<D3d11vaDecoder> {
|
||||
use ffmpeg::ffi;
|
||||
let (device, context) = create_device(luid)?;
|
||||
@@ -540,6 +563,7 @@ impl D3d11vaDecoder {
|
||||
video_device,
|
||||
video_context1,
|
||||
ring: None,
|
||||
hdr10_out,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -594,12 +618,15 @@ impl D3d11vaDecoder {
|
||||
let video_device = self.video_device.clone();
|
||||
let video_context1 = self.video_context1.clone();
|
||||
let context = self.context.clone();
|
||||
// (Re)build the ring + video processor on first use or a stream size change (the
|
||||
// hand-off is BGRA8 regardless of the stream's bit depth, so depth never rebuilds).
|
||||
// (Re)build the ring + video processor on first use, a stream size change, or a
|
||||
// flavor change (the host flips PQ in-band; SDR↔HDR swaps the slot format, so
|
||||
// it rebuilds like a resize — bit DEPTH alone still never rebuilds: an SDR
|
||||
// 10-bit stream and an 8-bit one share the same output flavor).
|
||||
let pq_out = self.hdr10_out && color.is_pq();
|
||||
let rebuild = self
|
||||
.ring
|
||||
.as_ref()
|
||||
.is_none_or(|r| r.width != width || r.height != height);
|
||||
.is_none_or(|r| r.width != width || r.height != height || r.pq_out != pq_out);
|
||||
if rebuild {
|
||||
let generation = self.ring.as_ref().map_or(0, |r| r.generation + 1);
|
||||
self.ring = Some(SharedRing::build(
|
||||
@@ -608,6 +635,7 @@ impl D3d11vaDecoder {
|
||||
width,
|
||||
height,
|
||||
generation,
|
||||
pq_out,
|
||||
)?);
|
||||
}
|
||||
let ring = self.ring.as_mut().expect("ring built above");
|
||||
@@ -653,7 +681,14 @@ impl D3d11vaDecoder {
|
||||
video_context1.VideoProcessorSetStreamColorSpace1(&ring.vp, 0, in_cs);
|
||||
video_context1.VideoProcessorSetOutputColorSpace1(
|
||||
&ring.vp,
|
||||
DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709,
|
||||
// HDR ring: PQ in, PQ out — a pure colorspace conversion (YCbCr→RGB),
|
||||
// no tone mapping; the presenter passes the values through to its HDR10
|
||||
// swapchain. SDR ring: sRGB out (a PQ stream is tone-mapped here).
|
||||
if ring.pq_out {
|
||||
DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020
|
||||
} else {
|
||||
DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709
|
||||
},
|
||||
);
|
||||
|
||||
let stream = D3D11_VIDEO_PROCESSOR_STREAM {
|
||||
@@ -691,13 +726,25 @@ impl D3d11vaDecoder {
|
||||
Ok(D3d11Frame {
|
||||
width,
|
||||
height,
|
||||
// What the slot now CONTAINS: sRGB BT.709 full-range RGB (PQ was tone-mapped).
|
||||
color: ColorDesc {
|
||||
primaries: 1,
|
||||
transfer: 13, // sRGB (H.273)
|
||||
matrix: 0, // identity — RGB
|
||||
full_range: true,
|
||||
// What the slot now CONTAINS. HDR ring: PQ BT.2020 full-range RGB (the
|
||||
// presenter reads is_pq() and flips its HDR10 swapchain). SDR ring: sRGB
|
||||
// BT.709 full-range RGB (PQ was tone-mapped above).
|
||||
color: if ring.pq_out {
|
||||
ColorDesc {
|
||||
primaries: 9,
|
||||
transfer: 16, // PQ / SMPTE ST.2084
|
||||
matrix: 0, // identity — RGB
|
||||
full_range: true,
|
||||
}
|
||||
} else {
|
||||
ColorDesc {
|
||||
primaries: 1,
|
||||
transfer: 13, // sRGB (H.273)
|
||||
matrix: 0, // identity — RGB
|
||||
full_range: true,
|
||||
}
|
||||
},
|
||||
rgb10: ring.pq_out,
|
||||
// SAFETY: `self.frame` is the live decoded AVFrame for this call.
|
||||
keyframe: crate::video::frame_is_keyframe(self.frame),
|
||||
handle,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! 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
|
||||
//! handle of a shareable single-plane RGB texture — **BGRA8** sRGB normally, **RGB10A2**
|
||||
//! PQ for the HDR pass-through flavor (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
|
||||
@@ -28,31 +29,41 @@ pub const DEVICE_EXTENSIONS: [&std::ffi::CStr; 2] = [
|
||||
ash::khr::win32_keyed_mutex::NAME,
|
||||
];
|
||||
|
||||
/// Can this device import a D3D11 BGRA8 texture as a blit source? The spec-required
|
||||
/// Can this device import a D3D11 texture of `format` 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 {
|
||||
fn format_importable(
|
||||
instance: &ash::Instance,
|
||||
pdev: vk::PhysicalDevice,
|
||||
format: vk::Format,
|
||||
) -> 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)
|
||||
.format(format)
|
||||
.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()
|
||||
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
|
||||
.contains(vk::ExternalMemoryFeatureFlags::IMPORTABLE)
|
||||
}
|
||||
|
||||
/// The two hand-off flavors' import support: `.0` = BGRA8 (the SDR ring — gates the whole
|
||||
/// D3D11VA path), `.1` = RGB10A2 (the HDR PQ ring — gates only the pass-through flavor;
|
||||
/// without it a PQ stream keeps the decoder-side tonemap to BGRA8).
|
||||
pub fn import_supported(instance: &ash::Instance, pdev: vk::PhysicalDevice) -> (bool, bool) {
|
||||
let bgra8 = format_importable(instance, pdev, vk::Format::B8G8R8A8_UNORM);
|
||||
let rgb10 = format_importable(instance, pdev, vk::Format::A2B10G10R10_UNORM_PACK32);
|
||||
tracing::info!(bgra8, rgb10, "D3D11 texture → Vulkan import support");
|
||||
(bgra8, rgb10)
|
||||
}
|
||||
|
||||
/// One imported frame: the BGRA8 image over the shared texture and its imported
|
||||
@@ -98,7 +109,13 @@ pub fn import(
|
||||
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;
|
||||
// DXGI R10G10B10A2 and Vulkan A2B10G10R10_PACK32 are the same bit layout (R in the
|
||||
// low bits) — the standard interop pairing, same as BGRA8 ↔ B8G8R8A8.
|
||||
let mp_format = if frame.rgb10 {
|
||||
vk::Format::A2B10G10R10_UNORM_PACK32
|
||||
} else {
|
||||
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
|
||||
|
||||
@@ -239,11 +239,12 @@ impl Presenter {
|
||||
);
|
||||
}
|
||||
|
||||
// D3D11 frame: acquire the imported BGRA texture from the external "queue
|
||||
// D3D11 frame: acquire the imported RGB 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.
|
||||
// blit it into the video image — the frame arrives as ready RGB from the
|
||||
// decoder's VideoProcessor (sRGB BGRA8, or PQ RGB10A2 on the HDR ring —
|
||||
// matching the HDR-mode video image), so there is no CSC pass; the blit
|
||||
// converts 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);
|
||||
|
||||
@@ -94,8 +94,9 @@ impl Presenter {
|
||||
// (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);
|
||||
let (import_bgra8, import_rgb10) = crate::d3d11::import_supported(&instance, pdev);
|
||||
#[cfg(windows)]
|
||||
let win_capable = crate::d3d11::DEVICE_EXTENSIONS.iter().all(|n| has(n)) && import_bgra8;
|
||||
#[cfg(windows)]
|
||||
if win_capable {
|
||||
dev_exts.extend(crate::d3d11::DEVICE_EXTENSIONS.iter().map(|n| n.as_ptr()));
|
||||
@@ -392,14 +393,25 @@ impl Presenter {
|
||||
d3d11_import: win_capable,
|
||||
#[cfg(not(windows))]
|
||||
d3d11_import: false,
|
||||
// Filled in below — the HDR10 surface facts arrive with pick_formats.
|
||||
d3d11_hdr10: false,
|
||||
adapter_luid,
|
||||
queue_lock: queue_lock.clone(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
#[cfg(windows)]
|
||||
let mut video_export = video_export;
|
||||
|
||||
let (format, hdr10_format) = pick_formats(&surface_i, pdev, surface, has_colorspace_ext)?;
|
||||
// The D3D11VA backend may emit its HDR (RGB10 PQ) ring only when this device can
|
||||
// import the 10-bit texture AND the surface offers an HDR10 swapchain to pass it
|
||||
// through to; otherwise a PQ stream keeps the decoder-side tonemap to sRGB.
|
||||
#[cfg(windows)]
|
||||
if let Some(v) = video_export.as_mut() {
|
||||
v.d3d11_hdr10 = win_capable && import_rgb10 && hdr10_format.is_some();
|
||||
}
|
||||
let present_mode = pick_present_mode(&surface_i, pdev, surface)?;
|
||||
tracing::info!(
|
||||
?format,
|
||||
|
||||
Reference in New Issue
Block a user