A field 4K60 HDR capture (RTX 4090, gamescope → direct-NVENC HEVC, ABR ~409 Mbps) spent 10.3 ms of a 16.67 ms frame budget in encode with the GPU's second NVENC engine idle. The path was clean — zero drops, 60.0 fps, zero-copy capture — so the encoder was simply never being split.
Root cause: split engaged for exactly one shape — 8-bit above 950 Mpix/s. No HDR session ever split, on any GPU.
What was wrong
Five defects in resolve_split_mode, two of them pure short-circuit accidents:
The 10-bit veto sat above the pixel-rate arm, so 10-bit 4K120 was vetoed by the rule written for 4K120.
It took no codec, so an HEVC-Main10-on-Ada measurement vetoed AV1 10-bit, which has neither the constraint nor any measurement against it.
No bits/frame term, though encode cost tracks bits/frame far more than pixel rate.
NV_ENC_CAPS_NUM_ENCODER_ENGINES was queried nowhere, so a 3-NVENC part ran 2-way and a 1-NVENC part paid a wasted session open.
Default-on sub-frame makes the AUTO arm mean "never split" on the fleet's default config.
What this changes
Use every engine the GPU has. The engine count is now probed and the widest split selected; >3 engines falls back to AUTO_FORCED since the enum can only name three. Operator overrides are clamped — necessary because the driver honours an over-ask and silently encodes narrower: THREE_FORCED on a 2-engine card was accepted and ran identically to TWO_FORCED, so the rejection fallback can't discover the ceiling.
The 10-bit short circuit is gone, and the veto is codec-scoped.
Live split arbitration (opt-in, PUNKTFUNK_NVENC_SPLIT_ARBITRATE=1): the encoder measures both arms on the running session and keeps the winner, caching the verdict per config. Prediction was impossible — the decision depends on bits/frame and an Automatic client's steady-state rate is unknown at open — so it measures instead. Zero extra IDRs, asserted on hardware.
The HEVC sub-frame trade is priced. Forcing split gives up sub-frame readback, whose value is that the send overlaps the encode — a cost the encoder cannot see. The host now reports its paced-send spread and the encoder converts it into a handicap. A unit test runs identical encode numbers under a cheap and an expensive send and asserts the verdict reverses.
One split policy. The libav path had drifted (hard-coded 2-way, no depth rule); it now shares the resolver, which moved to codec.rs so the featureless build can use it, with a parity test pinning the hand-written enum constants against the SDK.
Measurements
The veto's founding datapoint does not reproduce. It rested on 5120×1440@240 Main10 on Ada being 2.7× slower split. Re-run at its own operating point:
Main10
Ada 4090
Blackwell 5070 Ti
3840×2160@60
2.06×
1.70×
5120×1440@240 ← origin config
1.31×
1.19×
3840×2160@120
1.89×
—
Split wins everywhere, both architectures.
The bits/frame curve — no crossover. Previous measurements used driver-zeroed buffers, so rate control had nothing to code (~300 B/AU against an 833 KB quota) and only the pixel-proportional half was exercised. With real content:
actual bits/frame
Ada 4090
Blackwell
0.2–0.3 Mbit
4567 → 2381 µs (1.92×)
5549 → 3552 (1.56×)
~3.3 Mbit
8478 → 4455 µs (1.90×)
9286 → 5862 (1.58×)
~9.6 Mbit
16237 → 8114 µs (2.00×)
16435 → 9275 (1.77×)
Encode time scales 4.6 → 16.2 ms with bits/frame, and split wins at every point. At ~6.8 Mbit/frame these curves put single-engine 4K60 at 10–13 ms — against the field's 10.3 ms, so that number reads as real ASIC time rather than queue-depth inflation.
⚠ Content structure is a real term: the highest-bits/frame row encodes faster than the next one down on both boxes, because pure noise defeats motion estimation. bits/frame is a good axis, not a complete model.
Verification
Hardware across three platform × arch × driver combinations — Linux/CUDA Blackwell 610.57.04, Linux/CUDA Ada 610.43.03, Windows/D3D11 Ada 610.88 — with the in-place switch accepted and zero IDRs on all three.
.21: clippy -D warnings clean with and without the nvenc feature, 65 unit tests, 25/25 NVENC on-hardware.
.181, .158: on-hardware suites green.
.133 + .158: Windows clippy -D warnings clean.
Notes for review
Arbitration is opt-in; default behaviour is unchanged unless the knob is set. Default-on should be a separate decision after a real streaming session — nothing here has been validated outside synthetic content.
GameStream never arbitrates the sub-frame trade: that plane has no paced-send measurement to report, so the gate stays closed (the safe direction).
Windows NVENC test binaries do link given RUSTFLAGS='-L native=C:\Users\Public\nvenc -l nvencodeapi' — previously believed impossible, and it unblocks Windows on-hardware testing generally. Test-local only; production dlopens NVENC by design.
The item-level dead_code trap bit five times here, every one caught by running a Windows build rather than by reasoning. Worth a look at any shared-module item added for one platform's caller.
A field 4K60 HDR capture (RTX 4090, gamescope → direct-NVENC HEVC, ABR ~409 Mbps) spent **10.3 ms of a 16.67 ms frame budget** in `encode` with the GPU's second NVENC engine idle. The path was clean — zero drops, 60.0 fps, zero-copy capture — so the encoder was simply never being split.
Root cause: split engaged for **exactly one shape** — 8-bit above 950 Mpix/s. **No HDR session ever split, on any GPU.**
## What was wrong
Five defects in `resolve_split_mode`, two of them pure short-circuit accidents:
- The **10-bit veto sat above the pixel-rate arm**, so 10-bit 4K120 was vetoed by the rule written for 4K120.
- It **took no `codec`**, so an HEVC-Main10-on-Ada measurement vetoed **AV1 10-bit**, which has neither the constraint nor any measurement against it.
- No bits/frame term, though encode cost tracks bits/frame far more than pixel rate.
- `NV_ENC_CAPS_NUM_ENCODER_ENGINES` was **queried nowhere**, so a 3-NVENC part ran 2-way and a 1-NVENC part paid a wasted session open.
- Default-on sub-frame makes the `AUTO` arm mean "never split" on the fleet's default config.
## What this changes
**Use every engine the GPU has.** The engine count is now probed and the widest split selected; >3 engines falls back to `AUTO_FORCED` since the enum can only *name* three. Operator overrides are clamped — necessary because **the driver honours an over-ask and silently encodes narrower**: `THREE_FORCED` on a 2-engine card was accepted and ran identically to `TWO_FORCED`, so the rejection fallback can't discover the ceiling.
**The 10-bit short circuit is gone**, and the veto is codec-scoped.
**Live split arbitration** (opt-in, `PUNKTFUNK_NVENC_SPLIT_ARBITRATE=1`): the encoder measures both arms on the running session and keeps the winner, caching the verdict per config. Prediction was impossible — the decision depends on bits/frame and an Automatic client's steady-state rate is unknown at open — so it measures instead. **Zero extra IDRs**, asserted on hardware.
**The HEVC sub-frame trade is priced.** Forcing split gives up sub-frame readback, whose value is that the send overlaps the encode — a cost the encoder cannot see. The host now reports its paced-send spread and the encoder converts it into a handicap. A unit test runs *identical* encode numbers under a cheap and an expensive send and asserts the verdict **reverses**.
**One split policy.** The libav path had drifted (hard-coded 2-way, no depth rule); it now shares the resolver, which moved to `codec.rs` so the featureless build can use it, with a parity test pinning the hand-written enum constants against the SDK.
## Measurements
**The veto's founding datapoint does not reproduce.** It rested on 5120×1440@240 Main10 on Ada being 2.7× *slower* split. Re-run at its own operating point:
| Main10 | Ada 4090 | Blackwell 5070 Ti |
|---|---|---|
| 3840×2160@60 | **2.06×** | 1.70× |
| **5120×1440@240** ← origin config | **1.31×** | 1.19× |
| 3840×2160@120 | **1.89×** | — |
Split wins everywhere, both architectures.
**The bits/frame curve — no crossover.** Previous measurements used driver-zeroed buffers, so rate control had nothing to code (~300 B/AU against an 833 KB quota) and only the pixel-proportional half was exercised. With real content:
| actual bits/frame | Ada 4090 | Blackwell |
|---|---|---|
| 0.2–0.3 Mbit | 4567 → 2381 µs (1.92×) | 5549 → 3552 (1.56×) |
| ~3.3 Mbit | 8478 → 4455 µs (1.90×) | 9286 → 5862 (1.58×) |
| ~9.6 Mbit | 16237 → 8114 µs (2.00×) | 16435 → 9275 (1.77×) |
Encode time scales 4.6 → 16.2 ms with bits/frame, and split wins at **every** point. At ~6.8 Mbit/frame these curves put single-engine 4K60 at 10–13 ms — against the field's 10.3 ms, so that number reads as real ASIC time rather than queue-depth inflation.
⚠ Content structure is a real term: the highest-bits/frame row encodes *faster* than the next one down on both boxes, because pure noise defeats motion estimation. bits/frame is a good axis, not a complete model.
## Verification
Hardware across **three platform × arch × driver combinations** — Linux/CUDA Blackwell 610.57.04, Linux/CUDA Ada 610.43.03, Windows/D3D11 Ada 610.88 — with the in-place switch accepted and zero IDRs on all three.
- `.21`: clippy `-D warnings` clean with **and without** the nvenc feature, 65 unit tests, 25/25 NVENC on-hardware.
- `.181`, `.158`: on-hardware suites green.
- `.133` + `.158`: Windows clippy `-D warnings` clean.
## Notes for review
- Arbitration is **opt-in**; default behaviour is unchanged unless the knob is set. Default-on should be a separate decision after a real streaming session — nothing here has been validated outside synthetic content.
- GameStream never arbitrates the sub-frame trade: that plane has no paced-send measurement to report, so the gate stays closed (the safe direction).
- Windows NVENC **test binaries do link** given `RUSTFLAGS='-L native=C:\Users\Public\nvenc -l nvencodeapi'` — previously believed impossible, and it unblocks Windows on-hardware testing generally. Test-local only; production dlopens NVENC by design.
- The item-level `dead_code` trap bit **five times** here, every one caught by running a Windows build rather than by reasoning. Worth a look at any shared-module item added for one platform's caller.
Two on-hardware spikes answering the gate on the split-encode engagement
program (design/nvenc-split-encode-engagement-implementation-plan.md).
S1a `nvenc_cuda_split_reconfigure_in_place`: can splitEncodeMode change via
nvEncReconfigureEncoder with resetEncoder=0, without an IDR? Our "reconfigure
must present the SAME init params as the open" rule (windows/nvenc.rs:620) is
our own invariant and had never been tested against a driver. It reports rather
than asserts the verdict -- both outcomes are legitimate findings -- and only
asserts what would invalidate the measurement (session live, engines >= 2, the
arms actually differ). Sub-frame is pinned off so the driver can't reject for
the wrong reason (HEVC forced-split and sub-frame are mutually unsupported).
S1b `nvenc_cuda_split_reconfigure_takes_effect`: the other half -- a driver that
accepts the parameter and quietly ignores it looks identical to one that honours
it. Three legs at 4K (fresh DISABLE / fresh TWO_FORCED / DISABLE->TWO in place);
if C tracks B and not A, the switch is real.
RESULT on .21 (RTX 5070 Ti, GB203 Blackwell, driver 610.57.04):
NV_ENC_CAPS_NUM_ENCODER_ENGINES = 2
S1a: accepted, ZERO IDRs, both directions.
S1b: A fresh DISABLE 5054 us/frame, B fresh TWO_FORCED 2453,
C switched in place 2419 -- |C-B|=34 vs |C-A|=2635. It takes effect,
and split is a clean ~2x at 4K.
Two limits, both recorded in the test docs rather than the commit only. The
frames come out at 427 B/AU against an 833 KB CBR quota: the driver hands back
zeroed VRAM, so the rotated buffers are identical and rate control skip-codes
everything. So this measures the PIXEL-proportional half of the cost only --
the bits/frame regime the field case lives in is untested here, and the test
prints an explicit INCONCLUSIVE-on-content line when it detects that. And this
is Blackwell 8-bit; the Ada Main10 question is untouched.
Verified on .21: clippy -p pf-encode --features nvenc --all-targets -D warnings
clean, both spikes green, cargo fmt --all --check clean.
S1c `nvenc_cuda_split_subframe_pair_reconfigure`: the leg S1a/S1b excluded. Both
pinned sub-frame OFF to isolate the split variable, but a real HEVC arbitration
cannot -- split and sub-frame are mutually unsupported there, so engaging split
means flipping enableSubFrameWrite in the same breath, a second init param and
the one the reconfigure path deliberately pins. RESULT on .21: the PAIR moves in
place, accepted, ZERO IDRs, both directions.
It also pins the invariant that makes this safe to build on: `subframe_chunks` is
latched ONLY in the init path (~line 1625) and is NOT recomputed by
reconfigure_bitrate, so a caller flipping sub-frame in place must clear it too or
supports_chunked_poll keeps reporting true and poll_chunk busy-polls its whole
budget every AU against a numSlices that never advances. The test performs the
correct sequence and asserts the state stays coherent, so WP3 has a worked
example rather than a warning.
`nvenc_cuda_auto_split_with_subframe`: the D5 confirm -- the one claim in the
design's defect list that was only ever inferred. The driver reports no "mode I
actually chose", so it is settled by timing, at 4K where the gap is ~2x.
RESULT: AUTO (env unset) + sub-frame 4904 us/frame, DISABLE + sub-frame 5062,
TWO_FORCED without sub-frame 3464. AUTO sits 158 us from DISABLE and 1440 from
TWO_FORCED ⇒ D5 CONFIRMED: plain AUTO does not split while sub-frame is on, so
the resolver's AUTO fallthrough reads as "let the driver decide" and means
"never split".
⚠ TRAP, hit on this test's first run and now documented in it: the env knob
CANNOT express plain AUTO. `0` is DISABLE and `1` is AUTO_FORCED, and
resolve_split_subframe counts AUTO_FORCED as forced, so passing `1` silently
disarms sub-frame and measures a different configuration entirely -- which
produced a spurious "D5 REFUTED". Plain AUTO is only reachable as the resolver's
fallthrough with the env unset. The leg now asserts sub-frame resolved TRUE, so
the test can no longer answer the wrong question quietly.
Verified on .21: clippy --features nvenc --all-targets -D warnings clean, all 4
spikes green, the normal 54-test suite unaffected, cargo fmt --all --check clean.
WP1.1 plus the engine-count fix. `resolve_split_mode` forced TWO_FORCED at high
pixel rate regardless of hardware, so a 3-NVENC part (GB202, AD102 workstation)
left a third of its encode silicon idle, and a 1-NVENC part paid a wasted session
open to discover it could not split.
Probes NV_ENC_CAPS_NUM_ENCODER_ENGINES in both direct-SDK backends' query_caps
(the cap is `= 49` in both linux_sys and windows_sys of the vendored SDK 0.4.0 --
the caps enum is cfg-selected per-OS, so that was checked) and latches it on a
backend field. NOT on EncoderCaps: nine backends construct that struct as
exhaustive literals, so a new field would be a 9-site change of which 7 are
unrelated codecs passing a meaningless value, and the only consumer is the
resolver.
New `max_forced_split_mode(engines)`: 1 -> DISABLE, 2 -> TWO, 3 -> THREE, and
>3 -> AUTO_FORCED, because NV_ENC_SPLIT_ENCODE_MODE cannot NAME more than three
(NVENCAPI 12.1; values 4..14 are unallocated, so a future API may extend it) and
AUTO_FORCED = "split, driver picks how many" is measurably a real split (2.01x vs
disabled on .21). 0 = unprobed keeps the historical two-engine assumption.
⚠ WHY THE CLAMP EXISTS, measured on .21 (RTX 5070 Ti, 2 NVENC, 4K HEVC):
requesting THREE_FORCED was HONOURED -- session opened in mode 3 -- and ran at
2303 us/frame, identical to TWO_FORCED's 2308. The driver does not reject an
over-ask; it silently encodes narrower. So the rejection fallback cannot find the
ceiling and PUNKTFUNK_SPLIT_ENCODE=3 on a 2-engine card would have logged a
3-way split over a 2-way encode. Operator overrides are now clamped with a warn.
The ordering trap is covered by a test: on a >3-engine part hw_max is
AUTO_FORCED (1), which is not "narrower than" TWO_FORCED (2) despite comparing
smaller, so a naive min() would collapse a legitimate 3-way request to AUTO.
Also adds `engines` and `subframe` to the Linux session-ready log: split_mode
alone is ambiguous between "used both engines" and "left a third idle", and
since the driver honours an over-wide request the mode cannot be read without
the ceiling it was chosen from. This is the line a field report needs.
--- and a correction to S1b, in the same change ---
Re-running S1b afterwards flipped its verdict to "the driver appears to have
IGNORED the in-place split change", contradicting the isolated runs that produced
the |C-B|=34 figure already written into the design docs. Investigated rather
than re-rolled.
The switched leg was landing MIDWAY between the arms (~3600 us against A~5050,
B~2300) and the nearest-neighbour verdict flipped on noise. Cause: split-encode
does not reach steady state on the first frame -- a FRESH TWO_FORCED session
shows it too (early-half 3280 us vs late-half 1996 in one run), so it is split
warmup generally, not something specific to reconfiguring in place. A single
median over the whole window cannot see that. The test now reports early-half vs
late-half and gives a switched leg SETTLE=16 frames before its window opens,
every leg the same length. With that, 4/4 runs agree: the switched leg reaches
~2030 us against a fresh-split ~2000 and a single-engine ~4900.
⚠ S1b's CONCLUSION stands (the switch does take effect) but the evidence behind
the committed number did not reproduce; the docs are corrected rather than left
implying a cleaner result than the harness could support.
⚠⚠ This is a WP3 REQUIREMENT, not just a test fix: a live-session arbitration
that switches arms and immediately measures will misjudge the arm it just chose,
because the encoder needs ~16 frames to settle. The settle window has to be part
of the arbitration, and it is now a measured number rather than a guess.
Verified on .21: clippy --features nvenc --all-targets -D warnings clean,
57 unit tests (3 new), all 23 NVENC on-hardware tests green, fmt clean. The 3
failing on-hw tests in a full --ignored run are VAAPI (no AMD/Intel GPU on that
box -- their own ignore reason says so), pre-existing and unrelated.
Last change's docs concluded "AUTO never splits, retire the arm" from the
sub-frame-ON measurement alone. Measured the missing leg before implementing it,
and the conclusion was wrong.
On .21 at 4K, plain AUTO (env unset, the resolver's fallthrough):
sub-frame ON -> 5023/5157 us/frame ~= DISABLE 4979/5000 (does NOT split)
sub-frame OFF -> 2401/2352 us/frame ~= TWO_FORCED 2319/2378 (DOES split)
So AUTO is CONDITIONAL, not dead. Retiring it would have silently cost every
sub-frame-off session its second engine -- a regression introduced while
"cleaning up" an arm that looked inert. Split and sub-frame are mutually
unsupported for HEVC, so the driver resolves AUTO to no-split only in that
combination.
Fix is disclosure, not removal:
- resolve_split_subframe debug-logs the inert HEVC + AUTO + sub-frame case, which
is the fleet default shape: "split_mode=AUTO" has meant "no split" for every
default session and nothing said so. Deliberately NOT rewritten to DISABLE --
the mode we pass is what the driver was actually given, and the ceiling-cache
key must keep describing that.
- New unit test `auto_survives_the_arbitration_in_both_subframe_states` pins the
contract so the arm cannot be simplified away later.
- The resolver doc now records both measured legs instead of "AUTO is dead".
Also in this change:
- WP1.6: `resolve_subframe`'s doc said "Windows passes `false`". Stale since the
2026-07-31 .173 A/B flipped Windows to caps-gated default-on. It mattered:
it made the AUTO-plus-sub-frame dead combination look Linux-only when it is
fleet-wide.
- Windows session-ready log parity: split_mode + engines + subframe. The Windows
line had no split_mode at all, so a Windows field report could not answer "did
this session actually split?" -- the question that started this whole thread.
Verified: fmt clean; .21 clippy -p pf-encode --features nvenc --all-targets
-D warnings clean, 58 unit tests (1 new), 22/22 NVENC on-hardware tests green;
.133 Windows clippy --features nvenc --all-targets -D warnings clean (15m cold,
zero errors or warnings) -- the Windows backend is cfg'd out on both macOS and
the Linux box, so that leg needed a real Windows host.
The fix S1 unlocked. Rather than predict the right split mode at open — which
cannot work, because the decision depends on bits/frame and an Automatic client's
steady-state bitrate is unknown at open (ABR climbs in place afterwards) — the
encoder now measures both arms on the live session and keeps the winner. S1
proved nvEncReconfigureEncoder takes a changed splitEncodeMode with
resetEncoder=0, emits no IDR, and actually applies it, so the experiment is
invisible on the wire.
Deliberately measures instead of modelling: hard-coded per-arch constants are
exactly how the rule this replaces went wrong (one 5120x1440@240 Ada datapoint
generalised into a fleet-wide 10-bit veto). A measurement tracks driver updates
for free.
`SplitArbiter` (pure state machine, unit-tested without a GPU): measure incumbent
-> switch -> SETTLE -> measure challenger -> keep the winner, else switch back.
Verdicts cache per (gpu, codec, mode, depth, chroma) so later sessions open
straight into the winning arm; the key is CeilingKey minus split_mode, since the
split mode is the thing being decided.
⚠ SETTLE_FRAMES=16 is load-bearing, not padding: split-encode does not reach
steady state on the first frame (a FRESH TWO_FORCED session measured early-half
3280us vs late-half 1996), so judging an arm right after switching reads the
transient — intermittently, which would then be cached. A unit test feeds exactly
that transient and asserts the arbiter still sees the steady state.
Safety gates, all correctness conditions rather than preferences: opt-in
(PUNKTFUNK_NVENC_SPLIT_ARBITRATE=1) while it earns trust; an operator
PUNKTFUNK_SPLIT_ENCODE pin always wins; skip if a verdict is already cached; sync
depth-1 only (async_rt.is_none(), same gate chunked poll uses — under pipelined
retrieve the submit->AU span includes queue depth and the comparison is noise);
needs >=2 engines; never H.264.
⚠ And the one that bounds this increment: NO SUB-FRAME TRADE. For HEVC, forcing
split gives up sub-frame readback, which costs send/encode overlap the ENCODER
CANNOT SEE — it measures encode time only, so it would reliably prefer split and
silently make end-to-end latency worse. So arbitration runs only where nothing is
traded: sub-frame already off, or AV1 (both features legal). Pricing that trade
needs the host's send cost and is the next work package.
Challenger choice tests the question worth asking — anything not already the
widest forced split is challenged BY the widest ("are we leaving engines idle?").
The naive "challenge whatever we are not" spent the experiment re-proving that
splitting beats not-splitting, while parking the session on the slow arm to do
it, because 4K60 sits on the fallthrough AUTO.
⚠ Every new nvenc_core item is linux-gated: the arbiter is wired into the Linux
backend only for now and nvenc_core compiles on Windows too. Caught by the .133
check, not by reasoning — the first cut failed Windows clippy with 12 dead_code
errors, the exact item-level trap this file already carries a scar from.
Verified .21: clippy --features nvenc --all-targets -D warnings clean, 62 unit
tests (4 new arbiter tests), 23/23 NVENC on-hardware green including a new
end-to-end convergence test asserting ZERO extra IDRs and a cached verdict.
Verified .133: Windows clippy -D warnings clean, zero dead_code. fmt clean.
The named next step after WP3's first increment. That increment deliberately
REFUSED to arbitrate HEVC-with-sub-frame -- the fleet default, and the reported
field case -- because engaging split there gives up sub-frame readback, whose
whole value is that the send overlaps the encode. An encoder measuring only
encode time would see split as ~2x faster, take it, and make end-to-end latency
worse while reporting a win. This supplies the missing number.
The real comparison is encode_1eng + send_of_last_slice against
encode_2eng + send_of_whole_AU, so the challenger owes roughly
spread x (slices-1)/slices. Split across the two sides that can each see half:
- Host: new `Encoder::set_send_spread_us` (defaulted, forwarded by
TrackedEncoder -- same trap class as set_wire_chunking, and unforwarded it
would fail SILENTLY IN THE SAFE DIRECTION, which is the hardest kind to
notice). The send thread is the only place a paced send is observed and the
encode loop the only place the encoder can be touched, so it goes over an
AtomicU32 like encoder_ceiling_kbps, EWMA-smoothed 3:1 per completed AU: one
content spike must not flip a verdict that then gets cached.
- Encoder: turns the raw spread into the handicap, because only it knows
`slices`. SplitArbiter::with_handicap charges it to the challenger before the
comparison. A unit test runs identical encode numbers with a cheap and an
expensive send and asserts the verdict REVERSES -- with an expensive send the
arm that looks twice as fast is a loss end to end, and the incumbent must
hold. That is precisely the regression an encode-only arbiter ships.
Gate now opens for HEVC+sub-frame only when a spread has actually been reported
(and slices >= 2); with no hint it still refuses, so behaviour is unchanged until
the host feeds it.
Two mechanics this needed:
- apply_split_mode became a PAIR flip (split + sub-frame), routed through
resolve_split_subframe and restoring from `subframe_opened_with` so a session
that never had sub-frame can never gain it. It also recomputes
`subframe_chunks`, which reconfigure_bitrate does NOT -- spike S1c's finding;
leave it stale and supports_chunked_poll keeps saying yes while numSlices never
advances, so poll_chunk busy-polls its whole budget every AU.
- The arbiter is now fed from BOTH completion points. A sub-frame session
finishes through poll_chunk, so the incumbent arm of an HEVC experiment would
otherwise never deliver a sample -- only the challenger, with sub-frame
dropped, comes through poll.
Verified .21: clippy -D warnings clean for pf-encode AND punktfunk-host with
nvenc, 63 unit tests (1 new), 23/23 NVENC on-hardware green. Verified .133:
Windows clippy -D warnings clean, zero dead_code. fmt clean.
WP1.3, and the measurement that justifies it. `resolve_split_mode`'s 10-bit rule
sat ABOVE the pixel-rate arm and took no codec, so it (D1) vetoed 10-bit 4K120 --
the very case the pixel-rate arm exists for -- and (D2) applied an HEVC-Main10-on-
Ada result to AV1 10-bit, which has no such measurement. Both fixed: the
pixel-rate arm now comes first, and what remains is codec-scoped to HEVC and only
applies BELOW that bar, where a second engine buys nothing anyway.
The rule rested on one datapoint: 5120x1440@240 Main10 on Ada, forced-2 7.6 ms
vs 2.8 ms single-engine -- split 2.7x SLOWER. Dropping the short circuit flips
that exact configuration's behaviour, so it was re-measured on a 4090 (AD102,
driver 610.43.03), 400 Mbps, sub-frame pinned off, via a new mode-parameterizable
Main10 A/B test (PF_AB_MODE=WxHxFPS reproduces the original operating point).
Ada 4090 single forced-2 ratio
3840x2160@60 4483 us 2178 us 2.06x split WINS
5120x1440@240 3689 us 2813 us 1.31x split WINS <- the veto's origin
3840x2160@120 4148 us 2189 us 1.89x split WINS
Blackwell 5070 Ti
3840x2160@60 4216 us 2477 us 1.70x split WINS
5120x1440@240 4651 us 3894 us 1.19x split WINS
Split wins for Main10 at every mode on BOTH architectures, including the config
the veto came from. The original number does not reproduce.
⚠ Caveats, unchanged from the rest of this work: content is trivial (297-300 B/AU
against an 833 KB CBR quota -- zeroed VRAM), so this is the pixel-proportional
term and the bits/frame regime is still unmeasured; debug build; and the driver
differs from whenever the original was taken.
Also validated on Ada in the same session -- the whole spike set reproduces on a
SECOND architecture and an OLDER driver (610.43.03 vs 610.57.04): S1a in-place
split switch accepted with zero IDRs both directions; S1b takes effect
(|C-B|=12 vs |C-A|=1921, the cleanest run yet); S1c pair flip passes; D5 confirmed
(AUTO+sub-frame 4424 vs DISABLE 4409, 15 us apart -- and AUTO without sub-frame
2310 ~= TWO_FORCED 2314, so the arm stays); engines=2 with THREE_FORCED correctly
clamped to mode 2; arbitration converged with exactly 1 keyframe.
Verified: .21 clippy -D warnings clean + 64 unit tests; .133 Windows clippy
-D warnings clean (the resolver signature grew a `codec` param, so both backends
moved); Ada + Blackwell on-hardware as above. fmt clean.
WP0's real deliverable, and the hole every previous measurement in this
programme had. All prior timings ran against driver-zeroed buffers, so rate
control had nothing to code (~300 B/AU against an 833 KB quota) and only the
PIXEL-proportional half of the encode cost was ever exercised -- while the 4K60
HDR field report was a BITS/FRAME problem at 6.8 Mbit/frame.
Adds `pf_zerocopy::cuda::write_plane_from_host`, the exact mirror of the existing
read_plane_to_host. No new loader entry was needed: cuMemcpy2DAsync_v2 was
already in the table and CUDA_MEMCPY2D just needed the reverse memory types.
Linux-only by construction (pf-zerocopy's `imp` is cfg'd to linux).
⚠ Two harness mistakes found and fixed by looking at bytes/AU rather than
trusting the knob:
- Pure per-pixel noise is INCOMPRESSIBLE, so a low bitrate target does not
produce low bits/frame -- it OVERSHOOTS. At a nominal 50 Mbps the encoder
emitted 719 KB/AU against a 104 KB quota, and the three lowest rows of the
first sweep all sat at the same ~5.7 Mbit/frame. Sweeping nominal bitrate
measures nothing.
- So the sweep moves CONTENT DETAIL (block size) instead, and the x-axis is the
bits/frame the encoder ACTUALLY produced, never the one requested.
4K60 HEVC 8-bit, real content, single-engine vs forced-2:
bits/frame Ada 4090 Blackwell 5070 Ti
0.2-0.3 Mb 4567 -> 2381 1.92x 5549 -> 3552 1.56x
~1.1-1.2 Mb 5060 -> 2626 1.93x 5867 -> 4082 1.44x
~3.3 Mb 8478 -> 4455 1.90x 9286 -> 5862 1.58x
~9.6 Mb 16237 -> 8114 2.00x 16435 -> 9275 1.77x
RESULTS. (1) Encode time scales strongly with bits/frame -- 4.6 ms to 16.2 ms
across the range on Ada -- confirming the hypothesis' core claim. (2) There is NO
CROSSOVER: split wins at every point on both architectures (Ada ~1.9-2.0x and
notably flat, Blackwell 1.44-1.77x). So the arbitration's encode-side answer is
essentially always "split", which makes the sub-frame handicap the only decision
that actually matters -- exactly the part already built and unit-pinned.
(3) It corroborates the field capture: at ~6.8 Mbit/frame these curves put
single-engine 4K60 around 10-13 ms, and the field report was 10.3 ms on a 4090.
That reads as real ASIC time, not the retrieve-queue inflation it might have been.
⚠ Caveat the data itself shows: cost is NOT monotonic in bits/frame alone. The
1px row lands at the HIGHEST bits/frame yet encodes FASTER than the 4px row on
both boxes (Ada 10148 vs 16237 us) -- pure noise defeats motion estimation, which
gives up early, where semi-structured content makes it search hard. Content
structure is a real term, so "bits/frame" is a good axis but not a complete cost
model.
Verified .21: clippy -D warnings clean (pf-encode + pf-zerocopy), 64 unit tests,
25/25 NVENC on-hardware. Curves run on both Ada and Blackwell. fmt clean.
The libav NVENC path carried its own inline copy of the split decision and had
already drifted from the direct-SDK selector: it hard-coded a 2-way split
regardless of engine count, and had no depth rule at all. That is the drift the
shared resolver was extracted to prevent, and the copy quietly reintroduced it.
Routing it through `resolve_split_mode` needed the policy to MOVE. `nvenc_core`
is gated on `feature = "nvenc"`, but the libav path is precisely the build where
that feature is OFF (`PUNKTFUNK_NVENC_DIRECT=0`, and the featureless packages --
the packaging gap this project has been bitten by before). So
resolve_split_mode / max_forced_split_mode / clamp_to_engines, plus a new
`forced_split_width`, now live in `codec.rs`, which is always compiled and
already owned SPLIT_FORCE_PIXEL_RATE.
That means the NV_ENC_SPLIT_ENCODE_MODE values had to be hand-written as plain
constants, since the SDK enum does not exist without the feature. They are
therefore pinned: `nvenc_split_constants_match_the_sdk` (feature-gated, the only
place both are visible at once) asserts all five against the real enum, so the
copies cannot rot.
⚠ Only the FORCED outcomes are actionable on the libav side -- libavcodec's
`split_encode_mode` AVOption is its own vocabulary and our DISABLE is the NVENC
enum's 15, which would be meaningless there. DISABLE/AUTO both map to "leave the
option unset", which is exactly today's behaviour (unset = the driver's auto).
`engines = 0` ("not probed") maps to 2-way, preserving what that site always did;
a 3-NVENC part gets the wider split only on the direct-SDK path, which is the one
that actually probes.
⚠⚠ VERIFICATION GAP: .133 went down mid-change (no ping), so the WINDOWS leg is
UNVERIFIED. This matters more than usual -- the Windows backend imported
resolve_split_mode from nvenc_core and that import had to move too, which a grep
caught rather than a compiler. Re-run before trusting it:
cargo clippy -p pf-encode --features nvenc --all-targets -- -D warnings
Verified .21: clippy -D warnings clean BOTH with and without the nvenc feature
(the featureless build is the whole point of the move) and with
nvenc,vulkan-encode; 65 unit tests incl. the new constant-parity test; 25/25
NVENC on-hardware; punktfunk-host clippy clean. fmt clean.
The verification gap flagged in 01294e3a was real. `.133` came back up and the
WP4 commit failed Windows clippy: `forced_split_width` is used only by the libav
NVENC path (`enc/linux/mod.rs`), but it was added to `codec.rs`, which compiles
everywhere -- so it is dead code on Windows and `-D warnings` rejects it.
Third time this crate has hit the same item-level dead_code trap (see
`subframe_env_forced`, and the arbiter items in `nvenc_core`), and the third time
it was caught by actually running the Windows check rather than by reasoning
about it. The comment on the gate says so, since the pattern is clearly not
self-evident from the code.
Verified .21: clippy -D warnings clean both WITH and WITHOUT the nvenc feature,
65 unit tests. Verified .133: Windows clippy --features nvenc --all-targets
-D warnings clean, zero errors, zero dead_code. fmt clean.
Everything the split-encode programme rests on had been proven only on
Linux/CUDA. The Windows backend drives NV_ENC_DEVICE_TYPE_DIRECTX, so none of it
transferred by assumption -- and if the driver refused an in-place split change
there, Windows arbitration would simply not be buildable.
RESULT on the RTX Windows box (RTX 4090 / AD102, driver 610.88, D3D11):
engines=2, latched by query_caps (WP1.1's probe, validated on Windows
hardware rather than inferred from Linux)
DISABLE -> TWO_FORCED via nvEncReconfigureEncoder, resetEncoder=0: ACCEPTED,
ZERO IDRs, and the reverse likewise.
So the foundation now holds across three platform x arch x driver combinations:
Linux/CUDA Blackwell 610.57.04, Linux/CUDA Ada 610.43.03, Windows/D3D11 Ada
610.88.
⭐ UNBLOCKS ALL FUTURE WINDOWS ON-HARDWARE TESTING. pf-encode's nvenc test
binaries were believed unlinkable on Windows ("NvEncodeAPICreateInstance
unresolved", recorded as pre-existing and worked around by only ever running
clippy there). They link fine given the SDK import library:
RUSTFLAGS='-L native=C:\Users\Public\nvenc -l nvencodeapi'
`-L` alone is not enough -- without a `-l` nothing pulls the archive in, which is
why the earlier attempt still failed. ⚠ This is TEST-BINARY-LOCAL and must stay
that way: production deliberately dlopens NVENC rather than link-loading it, and
an unconditional link-load is the known crash class on non-NVIDIA Windows hosts.
⚠ Box note: the RTX Windows box answers on .158, not the .173 in its memory
entry, and `Administrator@` there resets the connection right after
SSH2_MSG_SERVICE_ACCEPT in a way that reads like the host being down -- the
working login is "Enrico Bühler"@192.168.1.158.
The last coverage gap, and only worth building once S1 proved it possible: the
Windows backend drives NV_ENC_DEVICE_TYPE_DIRECTX, and an in-place splitEncodeMode
change had never been tested there. It works (071358cb), so the arbiter is now
ungated from Linux-only to the union of both direct-SDK backends and wired into
windows/nvenc.rs: the submit stamp, the feed hook on AU completion,
apply_split_mode, split_key, arm_split_arbiter, and set_send_spread_us.
Same gates as Linux, and they are correctness conditions rather than preferences:
opt-in while it earns trust, an operator PUNKTFUNK_SPLIT_ENCODE pin always wins,
a cached verdict short-circuits, >=2 engines, never H.264, and the sub-frame
trade is only entered when the host has actually reported a send spread to price
it with. The one Windows-specific difference is that `async_rt` is a real
possibility here (opt-in two-thread retrieve) and the arbiter refuses it, because
under pipelined retrieve the submit->AU span includes queue depth and the
comparison would be noise.
⚠ Two more instances of the same item-level dead_code trap, caught by the Windows
run and not by reasoning -- that is now 4 and 5:
- `clear_split_verdicts` is called only by the Linux on-hw test, so it is dead on
Windows; gated to `all(test, target_os = "linux")`.
- The arbiter methods first landed inside `impl Encoder` rather than the inherent
impl (the anchor I used, supports_chunked_poll, is a trait method), which the
compiler caught as "not a member of trait Encoder".
Verified .158 (RTX 4090 / Ada, driver 610.88, D3D11): clippy --features nvenc
--all-targets -D warnings clean, and 2 on-hardware NVENC tests green including S1
re-run with the arbitration code in place (engines=2 latched, DISABLE->TWO_FORCED
accepted, zero IDRs, reverse accepted). Verified .21: clippy clean with AND
without the nvenc feature, 65 unit tests, 25/25 NVENC on-hardware. fmt clean.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
A field 4K60 HDR capture (RTX 4090, gamescope → direct-NVENC HEVC, ABR ~409 Mbps) spent 10.3 ms of a 16.67 ms frame budget in
encodewith the GPU's second NVENC engine idle. The path was clean — zero drops, 60.0 fps, zero-copy capture — so the encoder was simply never being split.Root cause: split engaged for exactly one shape — 8-bit above 950 Mpix/s. No HDR session ever split, on any GPU.
What was wrong
Five defects in
resolve_split_mode, two of them pure short-circuit accidents:codec, so an HEVC-Main10-on-Ada measurement vetoed AV1 10-bit, which has neither the constraint nor any measurement against it.NV_ENC_CAPS_NUM_ENCODER_ENGINESwas queried nowhere, so a 3-NVENC part ran 2-way and a 1-NVENC part paid a wasted session open.AUTOarm mean "never split" on the fleet's default config.What this changes
Use every engine the GPU has. The engine count is now probed and the widest split selected; >3 engines falls back to
AUTO_FORCEDsince the enum can only name three. Operator overrides are clamped — necessary because the driver honours an over-ask and silently encodes narrower:THREE_FORCEDon a 2-engine card was accepted and ran identically toTWO_FORCED, so the rejection fallback can't discover the ceiling.The 10-bit short circuit is gone, and the veto is codec-scoped.
Live split arbitration (opt-in,
PUNKTFUNK_NVENC_SPLIT_ARBITRATE=1): the encoder measures both arms on the running session and keeps the winner, caching the verdict per config. Prediction was impossible — the decision depends on bits/frame and an Automatic client's steady-state rate is unknown at open — so it measures instead. Zero extra IDRs, asserted on hardware.The HEVC sub-frame trade is priced. Forcing split gives up sub-frame readback, whose value is that the send overlaps the encode — a cost the encoder cannot see. The host now reports its paced-send spread and the encoder converts it into a handicap. A unit test runs identical encode numbers under a cheap and an expensive send and asserts the verdict reverses.
One split policy. The libav path had drifted (hard-coded 2-way, no depth rule); it now shares the resolver, which moved to
codec.rsso the featureless build can use it, with a parity test pinning the hand-written enum constants against the SDK.Measurements
The veto's founding datapoint does not reproduce. It rested on 5120×1440@240 Main10 on Ada being 2.7× slower split. Re-run at its own operating point:
Split wins everywhere, both architectures.
The bits/frame curve — no crossover. Previous measurements used driver-zeroed buffers, so rate control had nothing to code (~300 B/AU against an 833 KB quota) and only the pixel-proportional half was exercised. With real content:
Encode time scales 4.6 → 16.2 ms with bits/frame, and split wins at every point. At ~6.8 Mbit/frame these curves put single-engine 4K60 at 10–13 ms — against the field's 10.3 ms, so that number reads as real ASIC time rather than queue-depth inflation.
⚠ Content structure is a real term: the highest-bits/frame row encodes faster than the next one down on both boxes, because pure noise defeats motion estimation. bits/frame is a good axis, not a complete model.
Verification
Hardware across three platform × arch × driver combinations — Linux/CUDA Blackwell 610.57.04, Linux/CUDA Ada 610.43.03, Windows/D3D11 Ada 610.88 — with the in-place switch accepted and zero IDRs on all three.
.21: clippy-D warningsclean with and without the nvenc feature, 65 unit tests, 25/25 NVENC on-hardware..181,.158: on-hardware suites green..133+.158: Windows clippy-D warningsclean.Notes for review
RUSTFLAGS='-L native=C:\Users\Public\nvenc -l nvencodeapi'— previously believed impossible, and it unblocks Windows on-hardware testing generally. Test-local only; production dlopens NVENC by design.dead_codetrap bit five times here, every one caught by running a Windows build rather than by reasoning. Worth a look at any shared-module item added for one platform's caller.Two on-hardware spikes answering the gate on the split-encode engagement program (design/nvenc-split-encode-engagement-implementation-plan.md). S1a `nvenc_cuda_split_reconfigure_in_place`: can splitEncodeMode change via nvEncReconfigureEncoder with resetEncoder=0, without an IDR? Our "reconfigure must present the SAME init params as the open" rule (windows/nvenc.rs:620) is our own invariant and had never been tested against a driver. It reports rather than asserts the verdict -- both outcomes are legitimate findings -- and only asserts what would invalidate the measurement (session live, engines >= 2, the arms actually differ). Sub-frame is pinned off so the driver can't reject for the wrong reason (HEVC forced-split and sub-frame are mutually unsupported). S1b `nvenc_cuda_split_reconfigure_takes_effect`: the other half -- a driver that accepts the parameter and quietly ignores it looks identical to one that honours it. Three legs at 4K (fresh DISABLE / fresh TWO_FORCED / DISABLE->TWO in place); if C tracks B and not A, the switch is real. RESULT on .21 (RTX 5070 Ti, GB203 Blackwell, driver 610.57.04): NV_ENC_CAPS_NUM_ENCODER_ENGINES = 2 S1a: accepted, ZERO IDRs, both directions. S1b: A fresh DISABLE 5054 us/frame, B fresh TWO_FORCED 2453, C switched in place 2419 -- |C-B|=34 vs |C-A|=2635. It takes effect, and split is a clean ~2x at 4K. Two limits, both recorded in the test docs rather than the commit only. The frames come out at 427 B/AU against an 833 KB CBR quota: the driver hands back zeroed VRAM, so the rotated buffers are identical and rate control skip-codes everything. So this measures the PIXEL-proportional half of the cost only -- the bits/frame regime the field case lives in is untested here, and the test prints an explicit INCONCLUSIVE-on-content line when it detects that. And this is Blackwell 8-bit; the Ada Main10 question is untouched. Verified on .21: clippy -p pf-encode --features nvenc --all-targets -D warnings clean, both spikes green, cargo fmt --all --check clean.The fix S1 unlocked. Rather than predict the right split mode at open — which cannot work, because the decision depends on bits/frame and an Automatic client's steady-state bitrate is unknown at open (ABR climbs in place afterwards) — the encoder now measures both arms on the live session and keeps the winner. S1 proved nvEncReconfigureEncoder takes a changed splitEncodeMode with resetEncoder=0, emits no IDR, and actually applies it, so the experiment is invisible on the wire. Deliberately measures instead of modelling: hard-coded per-arch constants are exactly how the rule this replaces went wrong (one 5120x1440@240 Ada datapoint generalised into a fleet-wide 10-bit veto). A measurement tracks driver updates for free. `SplitArbiter` (pure state machine, unit-tested without a GPU): measure incumbent -> switch -> SETTLE -> measure challenger -> keep the winner, else switch back. Verdicts cache per (gpu, codec, mode, depth, chroma) so later sessions open straight into the winning arm; the key is CeilingKey minus split_mode, since the split mode is the thing being decided. ⚠ SETTLE_FRAMES=16 is load-bearing, not padding: split-encode does not reach steady state on the first frame (a FRESH TWO_FORCED session measured early-half 3280us vs late-half 1996), so judging an arm right after switching reads the transient — intermittently, which would then be cached. A unit test feeds exactly that transient and asserts the arbiter still sees the steady state. Safety gates, all correctness conditions rather than preferences: opt-in (PUNKTFUNK_NVENC_SPLIT_ARBITRATE=1) while it earns trust; an operator PUNKTFUNK_SPLIT_ENCODE pin always wins; skip if a verdict is already cached; sync depth-1 only (async_rt.is_none(), same gate chunked poll uses — under pipelined retrieve the submit->AU span includes queue depth and the comparison is noise); needs >=2 engines; never H.264. ⚠ And the one that bounds this increment: NO SUB-FRAME TRADE. For HEVC, forcing split gives up sub-frame readback, which costs send/encode overlap the ENCODER CANNOT SEE — it measures encode time only, so it would reliably prefer split and silently make end-to-end latency worse. So arbitration runs only where nothing is traded: sub-frame already off, or AV1 (both features legal). Pricing that trade needs the host's send cost and is the next work package. Challenger choice tests the question worth asking — anything not already the widest forced split is challenged BY the widest ("are we leaving engines idle?"). The naive "challenge whatever we are not" spent the experiment re-proving that splitting beats not-splitting, while parking the session on the slow arm to do it, because 4K60 sits on the fallthrough AUTO. ⚠ Every new nvenc_core item is linux-gated: the arbiter is wired into the Linux backend only for now and nvenc_core compiles on Windows too. Caught by the .133 check, not by reasoning — the first cut failed Windows clippy with 12 dead_code errors, the exact item-level trap this file already carries a scar from. Verified .21: clippy --features nvenc --all-targets -D warnings clean, 62 unit tests (4 new arbiter tests), 23/23 NVENC on-hardware green including a new end-to-end convergence test asserting ZERO extra IDRs and a cached verdict. Verified .133: Windows clippy -D warnings clean, zero dead_code. fmt clean.WP0's real deliverable, and the hole every previous measurement in this programme had. All prior timings ran against driver-zeroed buffers, so rate control had nothing to code (~300 B/AU against an 833 KB quota) and only the PIXEL-proportional half of the encode cost was ever exercised -- while the 4K60 HDR field report was a BITS/FRAME problem at 6.8 Mbit/frame. Adds `pf_zerocopy::cuda::write_plane_from_host`, the exact mirror of the existing read_plane_to_host. No new loader entry was needed: cuMemcpy2DAsync_v2 was already in the table and CUDA_MEMCPY2D just needed the reverse memory types. Linux-only by construction (pf-zerocopy's `imp` is cfg'd to linux). ⚠ Two harness mistakes found and fixed by looking at bytes/AU rather than trusting the knob: - Pure per-pixel noise is INCOMPRESSIBLE, so a low bitrate target does not produce low bits/frame -- it OVERSHOOTS. At a nominal 50 Mbps the encoder emitted 719 KB/AU against a 104 KB quota, and the three lowest rows of the first sweep all sat at the same ~5.7 Mbit/frame. Sweeping nominal bitrate measures nothing. - So the sweep moves CONTENT DETAIL (block size) instead, and the x-axis is the bits/frame the encoder ACTUALLY produced, never the one requested. 4K60 HEVC 8-bit, real content, single-engine vs forced-2: bits/frame Ada 4090 Blackwell 5070 Ti 0.2-0.3 Mb 4567 -> 2381 1.92x 5549 -> 3552 1.56x ~1.1-1.2 Mb 5060 -> 2626 1.93x 5867 -> 4082 1.44x ~3.3 Mb 8478 -> 4455 1.90x 9286 -> 5862 1.58x ~9.6 Mb 16237 -> 8114 2.00x 16435 -> 9275 1.77x RESULTS. (1) Encode time scales strongly with bits/frame -- 4.6 ms to 16.2 ms across the range on Ada -- confirming the hypothesis' core claim. (2) There is NO CROSSOVER: split wins at every point on both architectures (Ada ~1.9-2.0x and notably flat, Blackwell 1.44-1.77x). So the arbitration's encode-side answer is essentially always "split", which makes the sub-frame handicap the only decision that actually matters -- exactly the part already built and unit-pinned. (3) It corroborates the field capture: at ~6.8 Mbit/frame these curves put single-engine 4K60 around 10-13 ms, and the field report was 10.3 ms on a 4090. That reads as real ASIC time, not the retrieve-queue inflation it might have been. ⚠ Caveat the data itself shows: cost is NOT monotonic in bits/frame alone. The 1px row lands at the HIGHEST bits/frame yet encodes FASTER than the 4px row on both boxes (Ada 10148 vs 16237 us) -- pure noise defeats motion estimation, which gives up early, where semi-structured content makes it search hard. Content structure is a real term, so "bits/frame" is a good axis but not a complete cost model. Verified .21: clippy -D warnings clean (pf-encode + pf-zerocopy), 64 unit tests, 25/25 NVENC on-hardware. Curves run on both Ada and Blackwell. fmt clean.The libav NVENC path carried its own inline copy of the split decision and had already drifted from the direct-SDK selector: it hard-coded a 2-way split regardless of engine count, and had no depth rule at all. That is the drift the shared resolver was extracted to prevent, and the copy quietly reintroduced it. Routing it through `resolve_split_mode` needed the policy to MOVE. `nvenc_core` is gated on `feature = "nvenc"`, but the libav path is precisely the build where that feature is OFF (`PUNKTFUNK_NVENC_DIRECT=0`, and the featureless packages -- the packaging gap this project has been bitten by before). So resolve_split_mode / max_forced_split_mode / clamp_to_engines, plus a new `forced_split_width`, now live in `codec.rs`, which is always compiled and already owned SPLIT_FORCE_PIXEL_RATE. That means the NV_ENC_SPLIT_ENCODE_MODE values had to be hand-written as plain constants, since the SDK enum does not exist without the feature. They are therefore pinned: `nvenc_split_constants_match_the_sdk` (feature-gated, the only place both are visible at once) asserts all five against the real enum, so the copies cannot rot. ⚠ Only the FORCED outcomes are actionable on the libav side -- libavcodec's `split_encode_mode` AVOption is its own vocabulary and our DISABLE is the NVENC enum's 15, which would be meaningless there. DISABLE/AUTO both map to "leave the option unset", which is exactly today's behaviour (unset = the driver's auto). `engines = 0` ("not probed") maps to 2-way, preserving what that site always did; a 3-NVENC part gets the wider split only on the direct-SDK path, which is the one that actually probes. ⚠⚠ VERIFICATION GAP: .133 went down mid-change (no ping), so the WINDOWS leg is UNVERIFIED. This matters more than usual -- the Windows backend imported resolve_split_mode from nvenc_core and that import had to move too, which a grep caught rather than a compiler. Re-run before trusting it: cargo clippy -p pf-encode --features nvenc --all-targets -- -D warnings Verified .21: clippy -D warnings clean BOTH with and without the nvenc feature (the featureless build is the whole point of the move) and with nvenc,vulkan-encode; 65 unit tests incl. the new constant-parity test; 25/25 NVENC on-hardware; punktfunk-host clippy clean. fmt clean.Everything the split-encode programme rests on had been proven only on Linux/CUDA. The Windows backend drives NV_ENC_DEVICE_TYPE_DIRECTX, so none of it transferred by assumption -- and if the driver refused an in-place split change there, Windows arbitration would simply not be buildable. RESULT on the RTX Windows box (RTX 4090 / AD102, driver 610.88, D3D11): engines=2, latched by query_caps (WP1.1's probe, validated on Windows hardware rather than inferred from Linux) DISABLE -> TWO_FORCED via nvEncReconfigureEncoder, resetEncoder=0: ACCEPTED, ZERO IDRs, and the reverse likewise. So the foundation now holds across three platform x arch x driver combinations: Linux/CUDA Blackwell 610.57.04, Linux/CUDA Ada 610.43.03, Windows/D3D11 Ada 610.88. ⭐ UNBLOCKS ALL FUTURE WINDOWS ON-HARDWARE TESTING. pf-encode's nvenc test binaries were believed unlinkable on Windows ("NvEncodeAPICreateInstance unresolved", recorded as pre-existing and worked around by only ever running clippy there). They link fine given the SDK import library: RUSTFLAGS='-L native=C:\Users\Public\nvenc -l nvencodeapi' `-L` alone is not enough -- without a `-l` nothing pulls the archive in, which is why the earlier attempt still failed. ⚠ This is TEST-BINARY-LOCAL and must stay that way: production deliberately dlopens NVENC rather than link-loading it, and an unconditional link-load is the known crash class on non-NVIDIA Windows hosts. ⚠ Box note: the RTX Windows box answers on .158, not the .173 in its memory entry, and `Administrator@` there resets the connection right after SSH2_MSG_SERVICE_ACCEPT in a way that reads like the host being down -- the working login is "Enrico Bühler"@192.168.1.158.