feat(encode/vulkan): probe the device instead of guessing — AV1 10-bit + zero-CSC HDR
Three fixes to the same mistake: deciding what the Vulkan Video backend can do
from a table in our heads rather than from the driver, and routing everything
that didn't fit to libav VAAPI — where a session loses real RFI recovery and
the cursor blend for no reason the hardware asked for.
**Capability probe, per codec AND depth.** `probe_encode_support`'s "is there
an encode queue" boolean becomes `VulkanEncodeCaps { supported, eight_bit,
ten_bit }`, answered by `vkGetPhysicalDeviceVideoCapabilitiesKHR` against the
very profile chain the session open builds. So the dispatcher's prediction
cannot disagree with reality: a capable device keeps the Vulkan path, an
incapable one routes to VAAPI BEFORE burning a failed open, and the
cursor-blend mirror stays honest for free. This is the shape the direct-SDK
NVENC path already uses for its codec GUIDs.
**AV1 10-bit.** It was excluded on a guess about driver coverage; now the
device answers. `color_config()` carries `high_bitdepth` + the BT.2020/PQ CICP
triplet in both the `StdVideoAV1ColorConfig` and the sequence-header OBU we
bit-pack ourselves — they must stay identical or the driver's frame OBUs parse
against a header we didn't write. `high_bitdepth` sits BEFORE the CICP bytes,
so getting it wrong doesn't just mislabel the depth, it puts every following
field one bit out of phase; the new test reads the packed bits back.
**Zero-CSC RGB-direct in HDR.** The EFC probe assumed BT.709 and BGRA. It now
asks for the model this session's colourimetry needs (`MODEL_YCBCR_2020` for
10-bit — the extension has always had it) and for the CAPTURED format as an
encode-source format, and the session create-info selects the matching model.
An HDR session with no pointer to composite therefore hands the captured
buffer straight to the fixed-function front end and runs no host CSC at all.
Sessions that DO composite a pointer keep the compute CSC, unchanged: the EFC
cannot blend, and that rule outranks everything.
Also: `can_encode_10bit` on AMD/Intel now reports the union of VAAPI's and
Vulkan Video's answers instead of VAAPI's alone. `open_amd_intel` tries Vulkan
first and falls back, so either one being able to encode Main10 makes the
session 10-bit-capable — answering `false` because only one of them said yes
stranded encodable HDR sessions at 8 bits.
This commit is contained in:
@@ -27,9 +27,7 @@ the Punktfunk sysext; there's an Arch package, a NixOS option, and a build scrip
|
||||
else. `punktfunk-host hdr-probe` tells you exactly which pieces are in place.
|
||||
|
||||
Opt-in for this release while it soaks: without the knob, or without the extra package, the
|
||||
gamescope path streams SDR exactly as before. One thing to know on AMD and Intel: keep the codec on
|
||||
HEVC — a 10-bit AV1 session falls back to a slower encode path that also loses the on-screen mouse
|
||||
pointer. NVIDIA is unaffected either way.
|
||||
gamescope path streams SDR exactly as before.
|
||||
|
||||
## Fixed: newer KDE Plasma silently fell back to a slower, less reliable way to arrange displays
|
||||
|
||||
@@ -46,8 +44,10 @@ On current KDE Plasma (6.7 and newer), Punktfunk's direct way of talking to the
|
||||
- **KWin's in-process output-management path now also binds `kde_output_device_registry_v2`**, not just the per-output `kde_output_device_v2` globals it originally shipped with — KWin 6.7 stopped advertising the latter, so the module written specifically to avoid shelling out to `kscreen-doctor` (0.19.x) saw zero devices and silently degraded to it on every session. Both models are supported now; registry-sourced devices arrive one Wayland round-trip later, so the handshake gains one conditional extra barrier.
|
||||
- **gamescope HDR is decided statically, before the display exists.** The punktfunk/1 Welcome fixes a session's bit depth up front and cannot take it back (PQ frames on an 8-bit encoder are a deliberate hard error), so the capability answer is the identity of the gamescope binary the host will spawn — a `+pfhdr` marker in its `--version` banner, probed once per boot — never an optimistic negotiation. The capture-side gate became source-aware (`capturer_supports_hdr_for(compositor)`), the HDR negotiation-failure latch became per-source (a wedged monitor mirror no longer disables a gamescope session's HDR, or vice versa), and the keep-alive reuse key gained `hdr` so a display brought up SDR can never be handed to an HDR session. The GameStream plane's live BT.2100 monitor probe is now scoped to the portal source — a headless gamescope box has no monitor to be in HDR mode.
|
||||
- **The gamescope patch mirrors code already in gamescope's tree.** Its PipeWire node additionally offers `xRGB_210LE`/`xBGR_210LE` with MANDATORY SMPTE ST.2084 + BT.2020 properties, mapped to `DRM_FORMAT_XRGB2101010`/`XBGR2101010` — the same 10-bit capture texture the HDR AVIF screenshot path allocates — and `paint_pipewire()` composites into them with the HDR screenshot LUT set and `EOTF_PQ`, which is exactly the `bHDRScreenshot` branch. The new formats are listed last, so every existing consumer keeps negotiating the 8-bit stream bit-for-bit, and the fixed PQ container is deliberately *not* conditional on the focused app being HDR (a stream's colourimetry must not follow what the game happens to render).
|
||||
- **Vulkan Video learned 10-bit** (HEVC Main10), which is what keeps AMD/Intel HDR on the good path: an HDR session opens a Main10 video profile with 10-bit component depths, a `G10X6…3PACK16` picture and DPB, an SPS carrying `bit_depth_*_minus8 = 2` and the BT.2020/PQ CICP triplet, and a new `rgb2yuv10.comp` — the 8-bit BT.709 shader's twin, doing a pure BT.2020 NCL matrix on the already-PQ-encoded samples (there is no transfer function to apply, and applying one would be wrong) and writing 10-bit values into the high bits of `R16`/`RG16` scratch planes that are size-compatible with the picture's. That keeps the two things HDR would otherwise cost on this vendor: real RFI loss recovery, and the compute CSC's cursor blend — the only way a gamescope pointer reaches the stream at all. 10-bit **AV1** still routes to libav VAAPI: encode-side driver coverage for it is far thinner than Main10. A device that can't do Main10 fails the profile query inside the open and lands on the same VAAPI fallback as any other unsupported config.
|
||||
- **AMD/Intel HDR needs no new encoder code on the VAAPI fallback**; NVIDIA gained a zero-copy leg. The VAAPI path already ingested XR30 dmabufs into `format=p010:out_color_matrix=bt2020`. On NVIDIA the packed 10-bit frame now travels LINEAR dmabuf → Vulkan bridge → CUDA → NVENC `ARGB10`/`ABGR10`, letting NVENC do the BT.2020 CSC itself: no host CSC pass, no depth loss, and the cursor-blend compute shader gained two 10-bit modes so the pointer survives. The tiled EGL de-tile blit is still 8-bit and HDR never routes through it. A host without the direct-SDK NVENC backend keeps HDR on the CPU path, since libav's HDR route swscales into a P010 hardware frame that a packed-10-bit CUDA buffer cannot fill.
|
||||
- **Vulkan Video learned 10-bit**, which is what keeps AMD/Intel HDR on the good path: an HDR session opens a 10-bit video profile (HEVC Main10 / AV1 Main at 10 bits) with a `G10X6…3PACK16` picture and DPB, headers carrying the depth and the BT.2020/PQ CICP triplet — an SPS `bit_depth_*_minus8 = 2` for HEVC, `high_bitdepth` plus the matching sequence-header OBU bits for AV1 — and a new `rgb2yuv10.comp`: the 8-bit BT.709 shader's twin, doing a pure BT.2020 NCL matrix on the already-PQ-encoded samples (there is no transfer function to apply, and applying one would be wrong) and writing 10-bit values into the high bits of `R16`/`RG16` scratch planes that are size-compatible with the picture's. That keeps the two things HDR would otherwise cost on this vendor: real RFI loss recovery, and the compute CSC's cursor blend — the only way a gamescope pointer reaches the stream at all.
|
||||
- **The Vulkan encode backend is now capability-probed per codec AND depth**, mirroring what the direct-SDK NVENC path already does with its GUID probe. `vkGetPhysicalDeviceVideoCapabilitiesKHR` is asked against the very profile the session open will build, so the dispatcher's prediction cannot disagree with reality: a device that can encode 10-bit keeps the Vulkan path, one that can't routes to libav VAAPI *before* burning a failed session open, and `can_encode_10bit` reports the union of what VAAPI and Vulkan Video can do rather than VAAPI's answer alone (which was under-reporting 10-bit on hardware that could do it).
|
||||
- **The zero-CSC RGB-direct (EFC) source works in HDR too.** The `VK_VALVE_video_encode_rgb_conversion` probe now asks for the BT.2020 model and the captured 10-bit packed-RGB format instead of assuming BT.709/BGRA, and the session selects the matching model — so an HDR session with no pointer to composite (the GameStream desktop mirror) hands the captured buffer straight to the encoder's fixed-function front end and runs no host CSC at all. Sessions that DO composite a pointer keep the compute CSC, as before: the EFC cannot blend.
|
||||
- **NVIDIA gained a zero-copy HDR leg**, and the VAAPI fallback needed no new encoder code. The VAAPI path already ingested XR30 dmabufs into `format=p010:out_color_matrix=bt2020`. On NVIDIA the packed 10-bit frame now travels LINEAR dmabuf → Vulkan bridge → CUDA → NVENC `ARGB10`/`ABGR10`, letting NVENC do the BT.2020 CSC itself: no host CSC pass, no depth loss, and the cursor-blend compute shader gained two 10-bit modes so the pointer survives. The tiled EGL de-tile blit is still 8-bit and HDR never routes through it. A host without the direct-SDK NVENC backend keeps HDR on the CPU path, since libav's HDR route swscales into a P010 hardware frame that a packed-10-bit CUDA buffer cannot fill.
|
||||
- **CI-only fix:** the winget release-verification step's `envs:` allow-list was a step-level sibling of `with:`/`env:` instead of nested inside `with:`, so `appleboy/ssh-action` never actually received it as an input and the step kept failing on every tag. Moved to match how `REGISTRY_TOKEN` is already forwarded elsewhere.
|
||||
|
||||
See [v0.20.1](v0.20.1.md) for the fixes carried by that release (Windows install/winget, GameStream opt-in default, the gamescope Game Mode takeover hardening, a laptop-panel stall fix, and the PyroWave latency-creep bundle) — all included here too, since this release supersedes it.
|
||||
|
||||
Reference in New Issue
Block a user