perf(encode/nvenc-linux): LN3 — pipelined-retrieve escalation replaces the depth-1 async foot-gun
apple / swift (push) Successful in 1m24s
apple / screenshots (push) Successful in 5m16s
windows-host / package (push) Successful in 10m11s
ci / web (push) Successful in 59s
ci / docs-site (push) Successful in 1m40s
ci / bench (push) Successful in 5m13s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 11s
decky / build-publish (push) Successful in 19s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 10s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 8s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 9s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 10s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 10s
android / android (push) Successful in 17m6s
arch / build-publish (push) Successful in 17m27s
deb / build-publish (push) Successful in 12m39s
deb / build-publish-host (push) Successful in 9m33s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Failing after 7m27s
ci / rust (push) Successful in 27m10s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 15m1s
docker / deploy-docs (push) Successful in 17s
apple / swift (push) Successful in 1m24s
apple / screenshots (push) Successful in 5m16s
windows-host / package (push) Successful in 10m11s
ci / web (push) Successful in 59s
ci / docs-site (push) Successful in 1m40s
ci / bench (push) Successful in 5m13s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 11s
decky / build-publish (push) Successful in 19s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 10s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 8s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 9s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 10s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 10s
android / android (push) Successful in 17m6s
arch / build-publish (push) Successful in 17m27s
deb / build-publish (push) Successful in 12m39s
deb / build-publish-host (push) Successful in 9m33s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Failing after 7m27s
ci / rust (push) Successful in 27m10s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 15m1s
docker / deploy-docs (push) Successful in 17s
PUNKTFUNK_NVENC_ASYNC gains a real tri-state: 1 = always (as before, now documented as ~+1 tick at depth-1), 0 = never (vetoes escalation), unset = ADAPTIVE — off until the session loop's cadence-overrun detector escalates. The host loop's adaptive-depth leaky bucket grows a second stage: once the capturer's depth is maxed (Linux portal is permanently depth-1), it asks the encoder for pipelined retrieve via the new Encoder::set_pipelined hook (asked exactly once; default impl declines, Windows untouched). nvenc_cuda engages at a safe point via a clean session rebuild WITHOUT the IO-stream binding: with input==output stream bound, later stream work waits on prior encode completions and would serialize a pipelined session — stream-ordered submit and two-thread retrieve are mutually exclusive. The ordered gate now also requires async_rt absence (belt-and-braces for the runtime switch). Re-open's first frame is the standard session IDR. On-hardware test: escalate mid-session → retrieve thread live, binding gone, all AUs deliver, first post-escalation AU is the IDR. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1207,6 +1207,9 @@ pub(super) fn virtual_stream(ctx: SessionContext, prepared: Option<PreparedDispl
|
||||
let mut cur_depth: usize = 1;
|
||||
let mut behind_score: u32 = 0;
|
||||
let mut depth_frames: u64 = 0;
|
||||
// Second escalation stage (§7 LN3): once depth is maxed (or was never available — Linux),
|
||||
// ask the encoder for pipelined retrieve exactly once. Latched whether it accepts or not.
|
||||
let mut pipeline_asked = false;
|
||||
// ~20 net behind-frames (≈0.3 s sustained) escalates; a lone hitch decays away. Warmup skips
|
||||
// the first ~1 s so bring-up (display acquire, encoder open) never triggers it.
|
||||
const DEPTH_ESCALATE: u32 = 20;
|
||||
@@ -2056,10 +2059,15 @@ pub(super) fn virtual_stream(ctx: SessionContext, prepared: Option<PreparedDispl
|
||||
// Adaptive-depth escalate signal (measured BEFORE the trailing sleep): "behind" = the
|
||||
// frame's work overran its cadence deadline `next`, so the trailing sleep would be
|
||||
// zero/negative. At depth-1 that means the synchronous poll (encode + WDDM wait) can't
|
||||
// fit a frame interval — the contention case pipelining is for — so escalate to the
|
||||
// capturer's max and hold there. Leaky bucket + warmup skip reject one-off hitches and
|
||||
// bring-up. Once escalated, `cur_depth` stays (no de-escalation in v1).
|
||||
if idd_adaptive_enabled() && cur_depth < max_depth {
|
||||
// fit a frame interval — the contention case pipelining is for — so escalate, and hold
|
||||
// there. Leaky bucket + warmup skip reject one-off hitches and bring-up; no
|
||||
// de-escalation in v1. Two stages: first the CAPTURER's max depth (Windows IDD depth-2
|
||||
// overlap); where depth can't grow (Linux portal is permanently depth-1, §7 LN3), the
|
||||
// ENCODER's pipelined retrieve is the same trade on the other side of submit — the
|
||||
// two-thread lock moves the encode wait off this loop so capture/submit keep cadence,
|
||||
// at ~one tick of AU latency. `enc.set_pipelined` may decline (unsupported backend or
|
||||
// an explicit PUNKTFUNK_NVENC_ASYNC=0); either way it is asked exactly once.
|
||||
if idd_adaptive_enabled() && (cur_depth < max_depth || !pipeline_asked) {
|
||||
depth_frames += 1;
|
||||
if depth_frames > DEPTH_WARMUP_FRAMES {
|
||||
let behind = std::time::Instant::now() >= next;
|
||||
@@ -2069,13 +2077,27 @@ pub(super) fn virtual_stream(ctx: SessionContext, prepared: Option<PreparedDispl
|
||||
behind_score.saturating_sub(1)
|
||||
};
|
||||
if behind_score >= DEPTH_ESCALATE {
|
||||
cur_depth = max_depth;
|
||||
tracing::info!(
|
||||
depth = cur_depth,
|
||||
"IDD pipeline depth escalated — encode can't hold cadence at depth-1 \
|
||||
(GPU contention); pipelining for the rest of the session (latency \
|
||||
trade for throughput)"
|
||||
);
|
||||
if cur_depth < max_depth {
|
||||
cur_depth = max_depth;
|
||||
tracing::info!(
|
||||
depth = cur_depth,
|
||||
"IDD pipeline depth escalated — encode can't hold cadence at depth-1 \
|
||||
(GPU contention); pipelining for the rest of the session (latency \
|
||||
trade for throughput)"
|
||||
);
|
||||
} else {
|
||||
pipeline_asked = true;
|
||||
if enc.set_pipelined(true) {
|
||||
tracing::info!(
|
||||
"encoder pipelined retrieve escalated — encode can't hold \
|
||||
cadence and the capturer has no depth to give; the encode wait \
|
||||
moves off the loop for the rest of the session (latency trade \
|
||||
for throughput)"
|
||||
);
|
||||
}
|
||||
}
|
||||
// Give the action time to take effect before judging again.
|
||||
behind_score = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user