feat(presenter): VAAPI dmabuf → Vulkan zero-copy import + CSC pass (phase 2)

The decoder's NV12 dmabuf imports per-plane (R8 + GR88, explicit DRM
format modifier, dedicated dup'd-fd import, FOREIGN→graphics acquire)
and a fullscreen-triangle render pass converts it into the presenter's
video image with the CICP-driven coefficients ported from video_gl.rs
(same tests, plus a rows-vs-matrix agreement check). SPIR-V is committed
(shaders/build.sh regenerates) so builds and CI need no toolchain. The
import extension set is probed at device creation; unsupported boxes and
3-failure streaks demote the decoder to software via the existing
force_software contract. The session binary now honors the Settings
decoder preference instead of forcing software.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-07 18:12:16 +02:00
parent 89d45f2a55
commit a02d0a2e9f
12 changed files with 1091 additions and 56 deletions
+10
View File
@@ -0,0 +1,10 @@
#!/usr/bin/env bash
# Recompile the presenter's shaders to the committed .spv blobs. The blobs are committed
# so neither developer builds nor CI need a SPIR-V toolchain (glslc is a dev-time tool);
# rerun this after editing a .vert/.frag and commit both.
set -euo pipefail
cd "$(dirname "$0")"
for f in *.vert *.frag; do
glslc -O "$f" -o "$f.spv"
echo "compiled $f -> $f.spv"
done
@@ -0,0 +1,14 @@
// The bufferless fullscreen triangle (same trick as the GL presenter's gl_VertexID
// geometry). Vulkan clip space: y=-1 is the TOP, so uv (0,0) lands on the top-left —
// which is exactly where the video's row 0 lives. No flip anywhere.
//
// Regenerate: shaders/build.sh (committed .spv, no build-time toolchain).
#version 450
layout(location = 0) out vec2 v_uv;
void main() {
vec2 uv = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
v_uv = uv;
gl_Position = vec4(uv * 2.0 - 1.0, 0.0, 1.0);
}
Binary file not shown.
+36
View File
@@ -0,0 +1,36 @@
// NV12 → RGBA with the stream's CICP signaling — the Vulkan port of the GL presenter's
// fragment shader (video_gl.rs). The YUV→RGB matrix + range expansion arrive as three
// push-constant rows precomputed on the CPU (csc.rs `csc_rows`): rgb[i] = dot(r_i.xyz,
// yuv) + r_i.w — one shader for BT.601/709/2020, full/limited. The chroma plane is
// half-res; the linear sampler interpolates, same as the GL path.
//
// Regenerate: shaders/build.sh (committed .spv, no build-time toolchain).
#version 450
layout(location = 0) in vec2 v_uv;
layout(location = 0) out vec4 frag;
layout(set = 0, binding = 0) uniform sampler2D u_y;
layout(set = 0, binding = 1) uniform sampler2D u_c;
layout(push_constant) uniform Csc {
vec4 r0;
vec4 r1;
vec4 r2;
} pc;
void main() {
vec3 yuv = vec3(texture(u_y, v_uv).r, texture(u_c, v_uv).rg);
frag = vec4(
clamp(
vec3(
dot(pc.r0.xyz, yuv) + pc.r0.w,
dot(pc.r1.xyz, yuv) + pc.r1.w,
dot(pc.r2.xyz, yuv) + pc.r2.w
),
0.0,
1.0
),
1.0
);
}
Binary file not shown.