feat(linux/vulkan-encode): default-on native NV12 capture + RGB true-extent
ci / web (push) Successful in 48s
ci / docs-site (push) Successful in 1m5s
apple / swift (push) Successful in 1m20s
decky / build-publish (push) Successful in 21s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 25s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 42s
ci / bench (push) Successful in 5m59s
apple / screenshots (push) Successful in 6m17s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 6m29s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 5m12s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m16s
deb / build-publish (push) Successful in 12m58s
deb / build-publish-host (push) Successful in 13m40s
windows-host / package (push) Successful in 10m46s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 6m52s
android / android (push) Successful in 16m18s
arch / build-publish (push) Successful in 16m20s
docker / deploy-docs (push) Successful in 30s
ci / rust (push) Successful in 19m25s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 14m19s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 15m19s
ci / web (push) Successful in 48s
ci / docs-site (push) Successful in 1m5s
apple / swift (push) Successful in 1m20s
decky / build-publish (push) Successful in 21s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 25s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 42s
ci / bench (push) Successful in 5m59s
apple / screenshots (push) Successful in 6m17s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 6m29s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 5m12s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m16s
deb / build-publish (push) Successful in 12m58s
deb / build-publish-host (push) Successful in 13m40s
windows-host / package (push) Successful in 10m46s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 6m52s
android / android (push) Successful in 16m18s
arch / build-publish (push) Successful in 16m20s
docker / deploy-docs (push) Successful in 30s
ci / rust (push) Successful in 19m25s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 14m19s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 15m19s
Flip both zero-copy levers from opt-in to default: - The capture negotiation now PREFERS gamescope's producer-side NV12 pod by default (PUNKTFUNK_PIPEWIRE_NV12=0 restores the packed-RGB negotiation). Codec-aware gating rides a new OutputFormat::nv12_native -> ZeroCopyPolicy::native_nv12_session edge resolved by the host facade from the session plan (pf_encode::linux_native_nv12_ok): only H265/AV1 sessions whose backend can open the raw Vulkan Video encoder ever see the NV12 pod -- an H264/Moonlight session (libav VAAPI, which would misread the two-plane buffer) keeps today's BGRx negotiation, as do the GameStream-resolve and portal-mirror paths, PyroWave, and NVENC prefs. - RGB-direct's unaligned modes default to the true-extent direct import (PUNKTFUNK_VULKAN_RGB_TRUE_EXTENT=0 restores the padded-copy staging). Guarded-tested on Van Gogh with the kernel journal watched: clean, and at 5.38 ms p50 the fastest 1080p encode path measured on that hardware. The EFC only exists on Mesa >= 26, where the codedExtent-driven session_init padding is guaranteed (verified back to Mesa 24.2). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -259,6 +259,12 @@ pub struct ZeroCopyPolicy {
|
|||||||
/// passthrough (like the VAAPI backend) instead of the EGL→CUDA import whose payloads only
|
/// passthrough (like the VAAPI backend) instead of the EGL→CUDA import whose payloads only
|
||||||
/// NVENC can consume. Per-session (the codec is negotiated), unlike `backend_is_vaapi`.
|
/// NVENC can consume. Per-session (the codec is negotiated), unlike `backend_is_vaapi`.
|
||||||
pub pyrowave_session: bool,
|
pub pyrowave_session: bool,
|
||||||
|
/// THIS session's encoder can ingest a producer-native NV12 capture (the Linux raw Vulkan
|
||||||
|
/// Video backend on an H265/AV1 session — resolved by the host facade via
|
||||||
|
/// `pf_encode::linux_native_nv12_ok`). Gates whether the negotiation PREFERS gamescope's
|
||||||
|
/// producer-side NV12 pod: libav VAAPI (H264's backend) would misread the two-plane buffer,
|
||||||
|
/// so H264/GameStream/PyroWave sessions must never see NV12 frames.
|
||||||
|
pub native_nv12_session: bool,
|
||||||
/// The PyroWave encoder's Vulkan-importable dmabuf modifiers for the capture's packed-RGB fourcc,
|
/// The PyroWave encoder's Vulkan-importable dmabuf modifiers for the capture's packed-RGB fourcc,
|
||||||
/// resolved when the session encodes PyroWave (the passthrough advertises them so Mutter+NVIDIA,
|
/// resolved when the session encodes PyroWave (the passthrough advertises them so Mutter+NVIDIA,
|
||||||
/// which allocates tiled-only, still negotiates zero-copy). Empty otherwise.
|
/// which allocates tiled-only, still negotiates zero-copy). Empty otherwise.
|
||||||
|
|||||||
@@ -2086,22 +2086,26 @@ mod pipewire {
|
|||||||
// PyroWave session (the wavelet encoder's own Vulkan device, any vendor) → hand the raw
|
// PyroWave session (the wavelet encoder's own Vulkan device, any vendor) → hand the raw
|
||||||
// dmabuf straight to the encoder.
|
// dmabuf straight to the encoder.
|
||||||
let vaapi_passthrough = zerocopy && !force_shm && importer.is_none() && raw_passthrough;
|
let vaapi_passthrough = zerocopy && !force_shm && importer.is_none() && raw_passthrough;
|
||||||
// Bring-up gate for producer-side NV12. Gamescope offers a one-fd LINEAR NV12 image when
|
// Producer-side NV12 (default-on; PUNKTFUNK_PIPEWIRE_NV12=0 escapes): gamescope offers a
|
||||||
// the consumer asks for it; keep this opt-in until the live Vulkan import path is proven.
|
// one-fd LINEAR NV12 image when the consumer asks — its compositor pass does the RGB→YUV,
|
||||||
// Raw passthrough is required because CUDA/PyroWave expect packed RGB, and 4:4:4/HDR must
|
// and the Vulkan Video encoder imports the buffer as its encode source directly (no host
|
||||||
// not be silently subsampled/downconverted.
|
// CSC at all). `native_nv12_session` restricts this to sessions whose encoder can ingest
|
||||||
let prefer_native_nv12 = std::env::var("PUNKTFUNK_PIPEWIRE_NV12").as_deref() == Ok("1")
|
// it (Linux vulkan-encode H265/AV1 — never H264/libav-VAAPI, GameStream-resolve, or
|
||||||
|
// PyroWave, whose Vulkan compute CSC ingests packed RGB only). Raw passthrough is
|
||||||
|
// required because the CUDA importer expects packed RGB, and 4:4:4/HDR must not be
|
||||||
|
// silently subsampled/downconverted. Non-NV12 compositors (KWin/GNOME) simply match the
|
||||||
|
// packed-RGB fallback pod.
|
||||||
|
let prefer_native_nv12 = std::env::var("PUNKTFUNK_PIPEWIRE_NV12").as_deref() != Ok("0")
|
||||||
|
&& policy.native_nv12_session
|
||||||
&& backend_is_vaapi
|
&& backend_is_vaapi
|
||||||
&& vaapi_passthrough
|
&& vaapi_passthrough
|
||||||
// PyroWave rides the same raw passthrough but its Vulkan compute CSC ingests packed
|
|
||||||
// RGB only — native NV12 would feed it an unreadable two-plane image.
|
|
||||||
&& !policy.pyrowave_session
|
&& !policy.pyrowave_session
|
||||||
&& !want_444
|
&& !want_444
|
||||||
&& !want_hdr;
|
&& !want_hdr;
|
||||||
if prefer_native_nv12 {
|
if prefer_native_nv12 {
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
"PUNKTFUNK_PIPEWIRE_NV12=1: preferring gamescope producer-side NV12 LINEAR \
|
"zero-copy: preferring gamescope producer-side NV12 LINEAR DMA-BUF (no host \
|
||||||
DMA-BUF (no host RGB CSC)"
|
RGB CSC; PUNKTFUNK_PIPEWIRE_NV12=0 restores the packed-RGB negotiation)"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Modifiers our import stack handles for BGRx: the EGL-importable (tiled) set, plus LINEAR
|
// Modifiers our import stack handles for BGRx: the EGL-importable (tiled) set, plus LINEAR
|
||||||
|
|||||||
@@ -84,13 +84,14 @@ fn rgb_request() -> Option<bool> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `PUNKTFUNK_VULKAN_RGB_TRUE_EXTENT=1` (bring-up): at unaligned modes, replace RGB-direct's
|
/// True-extent RGB-direct at unaligned modes (default ON; `PUNKTFUNK_VULKAN_RGB_TRUE_EXTENT=0`
|
||||||
/// padded-copy staging with a direct import carrying the TRUE-SIZE source `codedExtent` — RADV
|
/// restores the padded-copy staging): direct-import the visible-size capture with the TRUE-SIZE
|
||||||
/// derives nonzero VCN firmware padding from it, so the EFC is told the source lacks the
|
/// source `codedExtent` — RADV derives nonzero VCN firmware padding from it, so the EFC is told
|
||||||
/// alignment rows (see [`RgbDirect::true_extent`]). Off by default until the EFC front-end is
|
/// the source lacks the alignment rows (see [`RgbDirect::true_extent`]). Guarded-tested on Van
|
||||||
/// proven to honor the padding like the YUV fetch path does.
|
/// Gogh 2026-07-21 (kernel clean, and the fastest 1080p encode path measured); the EFC only
|
||||||
|
/// exists on Mesa ≥ 26, where the `codedExtent`-driven `session_init` is guaranteed.
|
||||||
fn rgb_true_extent_request() -> bool {
|
fn rgb_true_extent_request() -> bool {
|
||||||
std::env::var("PUNKTFUNK_VULKAN_RGB_TRUE_EXTENT").as_deref() == Ok("1")
|
std::env::var("PUNKTFUNK_VULKAN_RGB_TRUE_EXTENT").as_deref() != Ok("0")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Live RGB-direct session config: the chroma-siting bits the session was created with
|
/// Live RGB-direct session config: the chroma-siting bits the session was created with
|
||||||
@@ -105,13 +106,13 @@ struct RgbDirect {
|
|||||||
/// with the edge rows/columns duplicated into the padding (transfer-only, no shader) and
|
/// with the edge rows/columns duplicated into the padding (transfer-only, no shader) and
|
||||||
/// encoded from there. Aligned modes keep the true zero-copy import.
|
/// encoded from there. Aligned modes keep the true zero-copy import.
|
||||||
padded: bool,
|
padded: bool,
|
||||||
/// `PUNKTFUNK_VULKAN_RGB_TRUE_EXTENT=1` bring-up alternative to `padded` at unaligned
|
/// The default unaligned-mode source strategy (`PUNKTFUNK_VULKAN_RGB_TRUE_EXTENT=0` falls
|
||||||
/// modes: direct-import the visible-size buffer and pass the TRUE-SIZE source
|
/// back to `padded`): direct-import the visible-size buffer and pass the TRUE-SIZE source
|
||||||
/// `codedExtent` — RADV then programs nonzero firmware padding from it (Mesa ≥ 24.2
|
/// `codedExtent` — RADV then programs nonzero firmware padding from it (Mesa ≥ 24.2
|
||||||
/// derives `session_init` padding from `srcPictureResource.codedExtent`; see
|
/// derives `session_init` padding from `srcPictureResource.codedExtent`; see
|
||||||
/// [`VulkanVideoEncoder::native_nv12`]), telling the VCN the source lacks the alignment
|
/// [`VulkanVideoEncoder::native_nv12`]), telling the VCN the source lacks the alignment
|
||||||
/// rows. The session/SPS/DPB stay app-aligned. Field-proven for YUV fetches; the EFC RGB
|
/// rows, which the hardware edge-extends internally. The session/SPS/DPB stay app-aligned.
|
||||||
/// front-end honoring the padding is what this gate exists to prove.
|
/// Guarded-tested on Van Gogh (kernel clean; fastest 1080p path measured).
|
||||||
true_extent: bool,
|
true_extent: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -696,7 +697,7 @@ impl VulkanVideoEncoder {
|
|||||||
) =>
|
) =>
|
||||||
"active(true-extent: unaligned mode, direct import with the true-size \
|
"active(true-extent: unaligned mode, direct import with the true-size \
|
||||||
source codedExtent — RADV firmware padding covers the alignment rows; \
|
source codedExtent — RADV firmware padding covers the alignment rows; \
|
||||||
PUNKTFUNK_VULKAN_RGB_TRUE_EXTENT bring-up)",
|
PUNKTFUNK_VULKAN_RGB_TRUE_EXTENT=0 restores the padded copy)",
|
||||||
(_, _, Some(RgbDirect { padded: false, .. })) => "active",
|
(_, _, Some(RgbDirect { padded: false, .. })) => "active",
|
||||||
(_, _, Some(RgbDirect { padded: true, .. })) =>
|
(_, _, Some(RgbDirect { padded: true, .. })) =>
|
||||||
"active(padded-copy: mode is not 64x16-aligned — staging blit + edge \
|
"active(padded-copy: mode is not 64x16-aligned — staging blit + edge \
|
||||||
|
|||||||
@@ -351,8 +351,8 @@ fn open_video_backend(
|
|||||||
Err(e) if format == PixelFormat::Nv12 => {
|
Err(e) if format == PixelFormat::Nv12 => {
|
||||||
return Err(e.context(
|
return Err(e.context(
|
||||||
"Vulkan Video open failed on a native-NV12 capture \
|
"Vulkan Video open failed on a native-NV12 capture \
|
||||||
(PUNKTFUNK_PIPEWIRE_NV12=1) — no VAAPI fallback exists; unset the \
|
— no VAAPI fallback exists; set PUNKTFUNK_PIPEWIRE_NV12=0 to \
|
||||||
flag to restore the packed-RGB pipeline",
|
restore the packed-RGB negotiation",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Err(e) => tracing::warn!(
|
Err(e) => tracing::warn!(
|
||||||
@@ -365,10 +365,10 @@ fn open_video_backend(
|
|||||||
// PUNKTFUNK_VULKAN_ENCODE=0, or a build without the feature).
|
// PUNKTFUNK_VULKAN_ENCODE=0, or a build without the feature).
|
||||||
if format == PixelFormat::Nv12 {
|
if format == PixelFormat::Nv12 {
|
||||||
anyhow::bail!(
|
anyhow::bail!(
|
||||||
"native NV12 capture (PUNKTFUNK_PIPEWIRE_NV12=1) requires the Vulkan Video \
|
"native NV12 capture requires the Vulkan Video encoder (HEVC/AV1 \
|
||||||
encoder (HEVC/AV1 session, --features vulkan-encode, \
|
session, --features vulkan-encode, PUNKTFUNK_VULKAN_ENCODE not 0) — this \
|
||||||
PUNKTFUNK_VULKAN_ENCODE not 0) — this session resolved to libav VAAPI; \
|
session resolved to libav VAAPI; set PUNKTFUNK_PIPEWIRE_NV12=0 to restore \
|
||||||
unset the flag"
|
the packed-RGB negotiation"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
vaapi::VaapiEncoder::open(
|
vaapi::VaapiEncoder::open(
|
||||||
@@ -841,6 +841,33 @@ fn vulkan_encode_enabled() -> bool {
|
|||||||
.unwrap_or(true)
|
.unwrap_or(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether THIS session's encoder can ingest a producer-native NV12 capture: only the raw
|
||||||
|
/// Vulkan Video backend does (libav VAAPI would misread the two-plane buffer as packed RGB —
|
||||||
|
/// [`open_video`] refuses the combination), so the session's codec must be one it encodes and
|
||||||
|
/// the backend must be eligible to open. The host facade threads the verdict into the capture
|
||||||
|
/// negotiation (`OutputFormat::nv12_native` → `ZeroCopyPolicy::native_nv12_session`), which
|
||||||
|
/// then PREFERS gamescope's producer-side NV12 pod (default-on; `PUNKTFUNK_PIPEWIRE_NV12=0`
|
||||||
|
/// escapes at the capture gate).
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub fn linux_native_nv12_ok(codec: Codec) -> bool {
|
||||||
|
#[cfg(feature = "vulkan-encode")]
|
||||||
|
{
|
||||||
|
matches!(codec, Codec::H265 | Codec::Av1)
|
||||||
|
&& vulkan_encode_enabled()
|
||||||
|
// NVENC/PyroWave prefs never open the Vulkan Video backend; every other pref
|
||||||
|
// (auto/vaapi/amd/intel/vulkan) tries it first on AMD/Intel — see [`open_video`].
|
||||||
|
&& !matches!(
|
||||||
|
pf_host_config::config().encoder_pref.as_str(),
|
||||||
|
"nvenc" | "nvidia" | "cuda" | "pyrowave"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "vulkan-encode"))]
|
||||||
|
{
|
||||||
|
let _ = codec;
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Cheap, side-effect-free NVIDIA-presence probe for the `auto` backend selector: the NVIDIA
|
/// Cheap, side-effect-free NVIDIA-presence probe for the `auto` backend selector: the NVIDIA
|
||||||
/// kernel driver exposes these device nodes, AMD/Intel boxes have neither. Deliberately does NOT
|
/// kernel driver exposes these device nodes, AMD/Intel boxes have neither. Deliberately does NOT
|
||||||
/// create a CUDA context (that would allocate GPU state on every host that merely *might* be
|
/// create a CUDA context (that would allocate GPU state on every host that merely *might* be
|
||||||
|
|||||||
@@ -146,6 +146,12 @@ pub struct OutputFormat {
|
|||||||
/// (never BGRA-passthrough / P010). `false` on every non-PyroWave session and on Linux (the
|
/// (never BGRA-passthrough / P010). `false` on every non-PyroWave session and on Linux (the
|
||||||
/// wavelet encoder ingests dmabufs / CPU RGB there, not a D3D11 texture).
|
/// wavelet encoder ingests dmabufs / CPU RGB there, not a D3D11 texture).
|
||||||
pub pyrowave: bool,
|
pub pyrowave: bool,
|
||||||
|
/// THIS session's encoder can ingest a producer-native NV12 capture (Linux raw Vulkan Video
|
||||||
|
/// backend on an H265/AV1 session — see `pf_encode::linux_native_nv12_ok`). The Linux capture
|
||||||
|
/// negotiation only offers gamescope the NV12 pod when this is set: libav VAAPI (the H264
|
||||||
|
/// codec's backend, and the fallback family) would misread the two-plane buffer as packed
|
||||||
|
/// RGB. Always `false` on Windows (the IDD-push capturer owns its own formats).
|
||||||
|
pub nv12_native: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OutputFormat {
|
impl OutputFormat {
|
||||||
@@ -163,6 +169,11 @@ impl OutputFormat {
|
|||||||
chroma_444: false,
|
chroma_444: false,
|
||||||
// GameStream never negotiates PyroWave (native punktfunk/1 only).
|
// GameStream never negotiates PyroWave (native punktfunk/1 only).
|
||||||
pyrowave: false,
|
pyrowave: false,
|
||||||
|
// Conservative: the GameStream + spike paths don't resolve the codec here, and a
|
||||||
|
// Moonlight client may negotiate H264 (whose VAAPI backend can't ingest NV12) — so
|
||||||
|
// they never prefer the producer-native NV12 pod. The punktfunk/1 plane opts in via
|
||||||
|
// `SessionPlan::output_format()`, which knows the codec.
|
||||||
|
nv12_native: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,10 @@ pub use pf_capture::{dxgi, synthetic_nv12};
|
|||||||
/// capture→encode cycle). Resolved here (the host facade) and threaded in, so the edge stays one-way
|
/// capture→encode cycle). Resolved here (the host facade) and threaded in, so the edge stays one-way
|
||||||
/// (plan §2.4 / §W6).
|
/// (plan §2.4 / §W6).
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
fn zero_copy_policy(pyrowave_session: bool) -> pf_capture::ZeroCopyPolicy {
|
fn zero_copy_policy(
|
||||||
|
pyrowave_session: bool,
|
||||||
|
native_nv12_session: bool,
|
||||||
|
) -> pf_capture::ZeroCopyPolicy {
|
||||||
let backend_is_vaapi = crate::encode::linux_zero_copy_is_vaapi();
|
let backend_is_vaapi = crate::encode::linux_zero_copy_is_vaapi();
|
||||||
// The raw-dmabuf passthrough serves a PyroWave session on ANY vendor (the wavelet encoder's
|
// The raw-dmabuf passthrough serves a PyroWave session on ANY vendor (the wavelet encoder's
|
||||||
// own Vulkan device imports the dmabuf) — per-session from the negotiated codec, plus the
|
// own Vulkan device imports the dmabuf) — per-session from the negotiated codec, plus the
|
||||||
@@ -56,6 +59,7 @@ fn zero_copy_policy(pyrowave_session: bool) -> pf_capture::ZeroCopyPolicy {
|
|||||||
backend_is_gpu: crate::encode::resolved_backend_is_gpu(),
|
backend_is_gpu: crate::encode::resolved_backend_is_gpu(),
|
||||||
pyrowave_session,
|
pyrowave_session,
|
||||||
pyrowave_modifiers,
|
pyrowave_modifiers,
|
||||||
|
native_nv12_session,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +74,9 @@ pub fn open_portal_monitor(want_hdr: bool) -> Result<Box<dyn Capturer>> {
|
|||||||
let anchored = crate::inject::default_backend() == crate::inject::Backend::Libei;
|
let anchored = crate::inject::default_backend() == crate::inject::Backend::Libei;
|
||||||
// Monitor mirrors never carry the native PyroWave plane (GameStream protocol) — per-session
|
// Monitor mirrors never carry the native PyroWave plane (GameStream protocol) — per-session
|
||||||
// passthrough is virtual-output-only; the global encoder-pref lever still applies inside.
|
// passthrough is virtual-output-only; the global encoder-pref lever still applies inside.
|
||||||
pf_capture::open_portal_monitor(anchored, want_hdr, zero_copy_policy(false))
|
// Native NV12 stays off too: the mirror path doesn't resolve the codec here, and the desktop
|
||||||
|
// compositors it mirrors (GNOME/KWin) don't produce NV12 anyway.
|
||||||
|
pf_capture::open_portal_monitor(anchored, want_hdr, zero_copy_policy(false, false))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "linux"))]
|
#[cfg(not(target_os = "linux"))]
|
||||||
@@ -101,7 +107,7 @@ pub fn capture_virtual_output(
|
|||||||
vout.keepalive,
|
vout.keepalive,
|
||||||
want.gpu,
|
want.gpu,
|
||||||
want.chroma_444,
|
want.chroma_444,
|
||||||
zero_copy_policy(want.pyrowave),
|
zero_copy_policy(want.pyrowave, want.nv12_native),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -179,6 +179,13 @@ impl SessionPlan {
|
|||||||
// Vulkan device; on Linux the capture facade flips the zero-copy policy to the
|
// Vulkan device; on Linux the capture facade flips the zero-copy policy to the
|
||||||
// raw-dmabuf passthrough (see above).
|
// raw-dmabuf passthrough (see above).
|
||||||
pyrowave: self.codec == crate::encode::Codec::PyroWave,
|
pyrowave: self.codec == crate::encode::Codec::PyroWave,
|
||||||
|
// Producer-native NV12 (gamescope) is consumable only by the Linux Vulkan Video
|
||||||
|
// backend — resolved HERE from the plan's codec so the capturer never reaches back
|
||||||
|
// into encode (the same one-way edge as `gpu` above).
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
nv12_native: crate::encode::linux_native_nv12_ok(self.codec),
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
nv12_native: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user