Rust performance evidence #
Documentation · Performance overview · Features
This is the dated measurement record. For a guided comparison, start with the performance overview. Each section below describes the source, version and fixture used at that time.
| Find a result | Recorded evidence |
|---|---|
| Rubylight 2.2.9 refresh | October 12 released-package encoder probes |
| Radeon compute off/on | 1080p60 comparison |
| D3D11 4:2:0 fallback | October 12 single-pass conversion |
| Real laptop client over Wi-Fi | 2.2.9 current receiver · Render to received, rc.24 |
| Other Sunshine hosts | Vibepollo 2.0 baseline · rc.24 on the same GPU |
| WGC capture and pacing | Guarded comparisons |
| Native HEVC / AV1 HDR | 2.2.9 headline streams · Final rc.10 pixels and repeat runs |
| PyroWave HDR 4:4:4 | Transport and decoding |
| CPU error correction | C++ FEC comparison |
| Reproduction and scope | Probes · Hardware covered |
Initial measurement environment #
Measured locally on 2026-10-01: Ryzen 7 5800X3D (8 cores/16 threads), RX 7900 XT, AMD driver 32.0.31041.1004, Windows x64, Rust 1.98.1 release builds. The initial HEVC/AV1 runs captured a 1968×2184 HDR desktop. The later 2.0 candidate runs captured a 2560×1440 SDR desktop and converted/scaled it to the requested format; physical HDR remained disabled. Neither source establishes native 4K capture performance. Hosts ran on loopback with isolated configurations and display changes disabled. The installed production service was preserved; it was stopped during the 2.0 candidate tests.
The measured reason to switch is video FEC generation: the Rust implementation is 1.26–1.40× faster than the original C++ implementation on representative video blocks, using 21–29% less CPU time for identical parity bytes. GPU-resident HDR processing, bounded texture/encoder queues and a Rust-rendered console are additional implementation benefits.
Capture and encoder wakeups on Windows #
On 2026-10-02, the installed candidate's short condition-variable timeouts were measured on the same Windows machine. A requested 250 µs encoder poll could sleep for a scheduler tick. Capture consumers now wait on their own unnamed event together with the worker's high-resolution timer. A capture arriving before the wait remains signaled; one client resetting its event cannot consume another client's notification. The wait does not spin or change the system timer resolution. Windows documents this combination in waitable timers and wait functions.
| Requested wait | Condition-variable mean / p95 | Capture event + timer mean / p95 |
|---|---|---|
| 250 µs | 15.293 / 16.309 ms | 0.615 / 1.010 ms |
| 500 µs | 15.206 / 16.289 ms | 0.971 / 1.052 ms |
| 16.667 ms | 30.775 / 32.101 ms | 16.798 / 17.010 ms |
Each case uses 40 actual waits in the same release process. These measurements establish lower timeout overshoot on this Windows installation, not a 25× whole-stream speedup. Reproduce with cargo run -p butterpollo-windows --example wait_performance --release --locked from rust in the SDK environment used for the build.
An unchanged desktop image also no longer consumes the next encode slot. Fresh content arriving after a waiting deadline can be submitted immediately, while encoding submissions retain the configured cadence. Resuming after a longer static interval starts a new cadence without a catch-up burst. Tests cover scheduler overshoot, static resumes, capture arriving before a wait, independent capture consumers and timer reuse.
These October 2 session API and five-second host log samples reported capture-to-packetization host processing separately from encoder latency. Current native AMF streams instead reconstruct a claim timestamp from the encoder's measured duration: before capacity checking and GPU conversion submission through collection of the matching QueryOutput result. That duration includes unfinished capture-copy and conversion dependencies, but excludes subsequent output-buffer copying and packet sending. Host processing runs from the reconstructed claim to the inline sender's sample or the separate sender's dequeue; the latter includes sender-capacity and queue waiting. The current loopback runs used inline sending. Only the frame-header value is rounded to 0.1 ms; capture age is separate. Neither definition includes client scanout or input latency; compare only measurements using the same definition. Full-stream idle-desktop comparisons are recorded below; a dynamic game and the customer's client remain separate acceptance checks.
Independent encrypted Moonlight decoding captured the existing native 1968×2184 HDR display, with HEVC Main10, a requested 40 Mbps, 20-second requested runs and the default 20 FPS static repeat target. The actual negotiated encoder bitrate was 30,988 kbps. Display changes were disabled in the isolated loopback profiles; the installed service stayed running without a client during these comparisons. Both builds decoded all four runs without a reported failure.
| Requested stream rate | Installed candidate host mean / p95 | Revised host mean / p95 |
|---|---|---|
| 60 FPS | 4.548 / 4.700 ms | 4.183 / 4.200 ms |
| 120 FPS | 4.187 / 4.200 ms | 4.120 / 4.200 ms |
Measured host steady rates rose from 16.83/18.15 FPS to approximately 20 FPS for the static desktop.
Customer regression checks on 2026-10-02 #
The connected phone reported missing audio, sluggish mouse input and approximately 11 ms average / 20 ms maximum host processing at 1968×2184/120 AV1 HDR. Its actual host log confirmed WGC was being opened even though the requested backend was ddx, and virtual speaker routing failed with 0x88890008. The revised build recognizes the old capture alias, composes DDX's separately supplied cursor in the GPU color pass, and restores the previous PCM/float speaker format choices, including valid 24-bit samples in a 32-bit container. Input polling uses the same high-resolution timer as capture.
A same-process input wait comparison (40 waits per case) measured the previous requested 1 ms thread sleep at 1.534 ms mean / 1.576 ms p95, versus 1.022 / 1.030 ms with the timer. This is a polling-wait improvement, not a measured mouse-to-display latency reduction.
The service-context fixture runs as LocalSystem in the signed-in desktop session, uses a separate loopback profile and cancels if the phone connects to the installed host. It creates an actual 1968×2184 HDR virtual display and streams 120 FPS with an 80 Mbps requested bitrate. Desktop Duplication is explicitly logged. HEVC strict independent decoding passes with zero failures, including nonzero audio from a quiet 960 Hz tone rendered to Steam Streaming Speakers. WASAPI captures the tone; encrypted transport and independent Opus decoding produce a peak of 0.053 and RMS of 0.032. Teardown restores the audio journal. This covers real routing rather than merely successful decoding of silent packets.
| Actual virtual-display stream | Steady host FPS | Steady host mean / p95 | Steady maximum |
|---|---|---|---|
| HEVC HDR 1968×2184 | 119.72 | 3.735 / 3.831 ms | 4.653 ms |
| HEVC HDR, next five-second sample | 119.72 | 3.768 / 3.940 ms | 6.738 ms |
| AV1 HDR 1968×2184, final five-second sample | 119.67 | 3.192 / 3.243 ms | 3.470 ms |
The HEVC client's whole-run mean is 5.733 ms and p95 is 3.900 ms: startup outliers raise the mean. Its 99.35 decoded FPS includes a two-second first-frame delay in the requested 12-second run, so that total is not a steady streaming rate. The AV1 run is not an interoperability pass: it decodes 1984×2186 rather than the requested 1968×2184, and every decoded frame fails the unchanged dimension check. Its first steady sample has 10.439 ms p95; the later sample above is lower. AMD's AV1 alignment contract permits padded output for unaligned dimensions. The host does not silently change codec, resolution or quality to pass this check. Both privileged runs log a Windows layout-restoration warning; their separate recovery helpers subsequently clear the journals and restore the physical-only desktop. This is not a clean immediate-restoration acceptance pass.
The paced AV1 encoder probe, with actual DDX capture kept active, returns 120 FPS and 3.216 ms mean / 3.614 ms p95 from submission to observed output. A repeat of the earlier WGC-backed probe also measures about 3.3 ms; these probes show no encoder-only advantage from changing capture backend.
Reproduce the active-capture component probe with butterpollo-performance.exe --capture ddx --live-capture --encoder amf --codec av1 --hdr --width 1968 --height 2184 --fps 120 --bitrate 80000 --seconds 6 --paced. The independent stream fixture accepts BUTTERPOLLO_TEST_MATCH_DISPLAY=1 to request the matching display mode and BUTTERPOLLO_TEST_AUDIO_TONE=1 to require a nonzero decoded tone. The latter requires a separate renderer such as windows/examples/audio_probe.rs; setting the variable alone fails a silent stream. Privileged virtual displays require the installed service's security context; administrator elevation alone is not sufficient for this driver's access policy. The optional session_command example reproduces that context from a test-owned LocalSystem task without changing driver permissions.
Video packet processing after the first 2.0 candidate #
Compared with Rust candidate 118d0ef133ae2d63898fb61512d25b6253d9c79b, the revised packet path uses 11–16% less CPU time for encrypted frames in these workloads. It reserves the encryption envelope in the final shard allocation, excludes that envelope from FEC, reuses AES-GCM setup within each frame and encrypts the shard in place. Source data goes directly into its shards, avoiding a full packed-frame copy. Windows UDP batches also use a bounded stack buffer for their descriptors.
| Data shards, 20% FEC | Encryption | Previous Rust median | Revised median | CPU time saved |
|---|---|---|---|---|
| 32 | Off | 8.87 µs | 7.57 µs | 15% |
| 32 | AES-GCM | 47.73 µs | 40.12 µs | 16% |
| 192 | Off | 160.61 µs | 151.44 µs | 6% |
| 192 | AES-GCM | 393.96 µs | 344.61 µs | 13% |
| 576 | Off | 492.40 µs | 457.43 µs | 7% |
| 576 | AES-GCM | 1185.92 µs | 1045.23 µs | 12% |
Each case uses seven 250 ms rounds on the same Ryzen/Rust release environment, with prebuilt identical payloads of 44,024, 264,184 and 792,568 bytes. Timing includes header construction, allocations/copies, FEC, optional encryption and packet release; it excludes capture, encoding and UDP sending. Both builds produce the same first-frame SHA-256, packet counts and wire byte totals in all six cases. Tests separately compare every encrypted shard against independent per-packet sealing through FEC, partial tails and sequence/frame/nonce boundaries.
The package includes butterpollo-video-packet-performance.exe. Run it without arguments to print the workloads, timing samples and packet fingerprints. The source is core/examples/video_packet_performance.rs; use that same harness in the previous checkout when reproducing the comparison.
October 2 latency work #
The candidate removes synchronous display renewal and monitor enumeration from the lock used by the capture and encoder workers. Those workers now read a published output/generation pair; maintenance publishes a new identity after native work finishes. A twenty-sample, read-only probe on the physical display measured monitor enumeration at 1.558 ms mean, 2.605 ms p95 and 4.333 ms maximum. This establishes a potentially expensive dependency in the old frame path, not a measured full-stream improvement from removing it. HDR metadata reads were negligible in this probe and remain on their existing polling schedule.
Same-size GPU color conversion now loads each source pixel once, preserving
the existing resize, HDR transfer, chroma and cursor behavior. At 1968×2184
FP16-to-P010, 64 D3D11 timestamp samples after 16 warmups measured 0.335 ms mean
and 0.337 ms p95 before, versus 0.170 ms mean and 0.175 ms p95 after. This is an
approximately 49% reduction in this GPU component's elapsed time. It excludes
capture, encoding, transport and decoding. The opt-in
gpu_color::tests::gpu_conversion_timing test reproduces the measurement.
Pointer-only Desktop Duplication updates reuse immutable owned desktop pixels and update the detached cursor snapshot. A missed new desktop copy invalidates the cache, preventing stale pixels after texture-pool exhaustion. Native readback checks cover retained images, the full bounded pool and recovery. Per-device GPU priority and maximum-frame-latency hints now match the previous host, independently of whether privileged process scheduling is available.
Diagnostics add p99, an explicitly labelled capture-age estimate, and frame send-completion intervals. The original source timestamps and Moonlight processing durations remain intact.
A normal-user isolated HEVC Main10 stream passed encrypted pairing/permissions, exact 1968×2184 output, BT.2020/PQ, and independent Opus decoding of a quiet known tone. It requested 120 fps and 80 Mbps. After a five-second warmup, 900 frames delivered at 119.993 fps with no intervals above 1.5 frame periods. Host processing mean/p95/p99/max was 5.517/5.9/6.3/6.4 ms. Arrival intervals had 9.198 ms p99 and 9.375 ms maximum. This test captured an unchanged 2560×1440 SDR desktop and converted/scaled it; it excludes native HDR motion acceptance and is not a whole-host comparison with Vibepollo.
The independent AV1 geometry gate still fails on this RX 7900 XT. Requested
1920×1080, 1968×2184 and 2184×1968 decode as 1920×1082, 1984×2186 and 2240×1968,
respectively, in both SDR/HDR with tested alignment modes 3 (NO_RESTRICTIONS)
and 4 (8X2_ONLY). Requested component dimensions and alignment read back correctly, but
bitstream traces contain enlarged dimensions without a render-size correction.
The same customer-size failure occurs in the current C++ baseline. Do not
count these as exact-resolution passes or change the strict decoder gate.
AMD's corresponding bug report.
Dynamic loopback tests additionally render a changing barcode containing source sequence/QPC values, then timestamp independent low-delay decoding. This picture-age measurement includes rendering, DWM, capture, encoding, loopback and software decode; it excludes remote display scanout. Source refresh must be measured and matched, not inferred from a requested virtual-display mode. The attempted 240 Hz C++ fixture actually presented at 120 Hz; comparisons with the 240 Hz Rust fixture are therefore not accepted. The next elevated matched benchmark launch was rejected by automatic approval review.
October 4: capture copies and conversion beside a game #
On an RX 7900 XT (AMF 1.5.2), AV1 HDR at 1968×2184 already encodes at the
VCN's floor: 2.8-3.3 ms from submission to bitstream when idle. Tiles,
multi-VCN, pre-encode, CDEF, AQ, the lowest-latency mode and QueryTimeout 0
measured no gain; the driver already applies them under ultra-low latency.
The cost is elsewhere: examples/gpu_load.rs draws a game-like load
(≈5.6 ms GPU frames), and beside it every D3D11 step waits behind the game on
the graphics engine (conversion 7.2 ms instead of 0.9 ms, a plain copy 8.4 ms
instead of 0.75 ms). D3D11 GPU priorities, process scheduling classes and a
high-priority D3D12 direct queue did not change that. A D3D12 compute queue
did the same conversion in 0.22-0.29 ms under the load (d3d12_probe,
copy_probe).
Captured frames are now copied into shared textures on one compute queue and
converted on another, and AMF encodes from D3D12 (windows/src/compute.rs).
Two synchronisation details decided correctness:
- Desktop Duplication returns a frame while DWM's copy into it may still be
running; the D3D11 device waits for it through the keyed mutex. A compute
copy without that wait differed from the D3D11 copy in 159 of 788 frames
idle and 316 of 325 beside the load (
ddx_sync_probe verify-nosync). The D3D11 context signals a shared fence after acquiring, the copy waits for it on the GPU, and the D3D11 context waits for the copy before the frame is released: 0 of 1161 idle and 0 of 267 under load differed. A D3D11 fence signal holding no frame completes in 0.016 ms beside the load; holding a frame it completes when DWM's copy does (0.15 ms idle, 7-10 ms under load). - AMF signals the fence it is given again after reading a D3D12 texture. With one fence for all conversions, that signal released the next texture before its conversion ran: a third of the streamed pictures repeated. Each output texture now has its own fence.
Synthetic moving frames, AV1 HDR 1968×2184 paced at 120 fps
(performance --synthetic 16 --paced, load_matrix.py):
| Case | Graphics queue | Compute queues |
|---|---|---|
| Idle | 3.23 ms mean, 3.55 p95 | 2.92 ms mean, 3.04 p95 |
| Beside the load | 14.32 ms mean, 32.55 p95, 90 fps | 2.79 ms mean, 2.85 p95, 120 fps |
Full encrypted streams from the isolated host, AV1 HDR 1968×2184 at 120 fps
and 80 Mbps from a 240 Hz virtual display, decoded by an independent client
that times a moving barcode from render to decoded picture (run-motion.py
virtual-motion; same binary, gpu_compute_conversion off and on):
| Case | Picture age mean / p95 | New pictures/s | Host mean |
|---|---|---|---|
| Idle, graphics queue | 12.98 / 13.89 ms | 120.6 | 3.45 ms |
| Idle, compute queues | 12.37 / 13.29 ms | 120.1 | 2.86 ms |
| Load, graphics queue | 52.87 / 71.02 ms | 44.6 | 21.2 ms |
| Load, compute queues | 45.45 / 62.26 ms | 50.1 | 10.6 ms |
Under the load DWM's own composition dominates (its copy into the duplication surface finishes 7-10 ms after the frame is handed over), so the host's share falls but the picture stays late. Host time under compute includes that wait, because frames are published before DWM's copy finishes and the encoder waits for it on the GPU. These runs exclude network transport and a remote display.
1080p at 60 fps #
The same comparisons at the most common stream setting: HEVC 10-bit HDR, 1920×1080 at 60 fps and 20 Mbps, from a 120 Hz virtual display, with the same game-like load (the game ran at 174 fps in both paths). Two runs each.
| Synthetic encode | Graphics queue | Compute queues |
|---|---|---|
| Idle, mean | 2.26 / 2.40 ms | 2.14 / 2.03 ms |
| Beside the load, mean | 21.99 / 20.93 ms | 1.98 / 1.95 ms |
| Beside the load, p95 | 37.04 / 37.91 ms | 2.28 / 2.29 ms |
| Beside the load, encoded fps | 58.6 / 59.1 | 60.1 / 60.1 |
| Full stream | Graphics queue | Compute queues |
|---|---|---|
| Idle, picture age mean / p95 | 14.05 / 15.03, 13.98 / 14.72 ms | 14.05 / 14.63, 14.04 / 14.69 ms |
| Load, picture age mean / p95 | 40.92 / 54.04, 41.13 / 54.69 ms | 33.48 / 42.20, 33.46 / 42.46 ms |
| Load, new pictures per second | 56.8, 56.6 | 58.3, 57.8 |
| Load, present to send (host counter) | 16.39, 16.10 ms | 11.40, 11.07 ms |
Idle, both paths deliver the picture at the same time; the compute path's
fence handoffs cost about 0.15 ms of host time at this size, where the
D3D11 copy and conversion are small. Beside the game it delivers the
picture 7.5 ms sooner on average and 12 ms sooner at the 95th percentile.
Artifacts: day-work-20261004\load-1080p60, day-work-20261002\p1080-*.
Against Vibepollo 2.0 #
Vibepollo 2.0 and Rubylight 2.0.0-rc.2 alternated in one batch on the
same fixture. Vibepollo is a Release build of the 2.0.0 tag (8a8c4b03a)
with two startup-only patches for the isolated fixture (skip machine-wide
recovery, log the test display's name); no per-frame code changed. Both
hosts ran HEVC 10-bit HDR at 1080p60, 20 Mbps requested (14,988 kbps after
FEC and audio on both), native AMF at ultra-low latency and speed with
VBAQ and an input queue of 4, Desktop Duplication, realtime GPU priority,
and a 120 Hz virtual display (Vibepollo set to
frame_limiter_auto_virtual_framegen = legacy, its 2x mode; its default is
4x). Three runs each. The game-like load ran at 176.7-177.4 fps beside
Vibepollo and 173.8-174.9 fps beside Rubylight, which streamed about
twice as many frames. "Host latency" is the per-frame value Moonlight
reports from the host; both hosts measure it from the moment Desktop
Duplication hands over the frame to the moment the packet is sent.
| Case | Vibepollo 2.0 | Rubylight 2.0 |
|---|---|---|
| Idle, picture age mean / p95 | 16.03 / 16.62, 15.80 / 16.40, 16.05 / 16.93 ms | 13.88 / 14.58, 13.70 / 14.44, 13.73 / 14.52 ms |
| Idle, host latency | 2.73, 2.71, 2.80 ms | 1.96, 1.96, 1.97 ms |
| Load, picture age mean / p95 | 93.99 / 132.54, 92.04 / 129.45, 103.28 / 148.96 ms | 42.27 / 55.98, 42.31 / 56.81, 42.70 / 56.69 ms |
| Load, new pictures per second | 24.2, 25.0, 22.5 | 52.0, 51.0, 51.1 |
| Load, host latency | 59.09, 57.30, 65.28 ms | 9.11, 9.14, 8.80 ms |
Beside the load, Vibepollo handed AMF about 24 frames a second: its own
encoder output has not caught up lines count 60 submitted frames every
2.4-2.6 s. Rubylight's graphics-queue path delivered about 57 new pictures
a second beside the same load in an earlier batch, so sharing the graphics
queue alone does not explain the gap; where Vibepollo loses the frames has
not been traced. The probe window rendered 50-59 fps beside the load with
Vibepollo and 59 fps with Rubylight. Absolute load numbers move between
batches (Rubylight measured 33.5 ms in an earlier batch without Vibepollo
runs), so only compare rows measured together. Artifacts:
day-work-20261002\hh1080-*.
Both hosts' virtual-display policy also turns on an RTSS 60 fps limit
during a stream (Vibepollo logs it). A second batch, beside the load only,
ran each host with the limit and with frame_limiter_provider = none:
| Beside the load | Vibepollo 2.0 | Rubylight 2.0 |
|---|---|---|
| RTSS limit on, picture age mean / p95 | 124.84 / 156.05 ms | 42.68 / 57.60 ms |
| RTSS limit off, picture age mean / p95 | 125.81 / 158.30, 120.95 / 154.31 ms | 43.32 / 57.33 ms |
| New pictures per second, on / off | 18.6 / 18.5, 19.2 | 51.2 / 50.2 |
| Host latency, on / off | 79.7 / 79.8, 77.2 ms | 9.0 / 9.6 ms |
The limit changes neither host. One Rubylight run with the limit off is
left out: its probe window did not start. Vibepollo was slower in this
batch than in the first (about 124 against 96 ms) and the probe rendered
only 40 fps beside it, while Rubylight stayed at 42-43 ms. Artifacts:
day-work-20261002\hh1080n-*.
HDR colour accuracy #
tests/colour_check.py reads frame 900 of each stream above as decoded by
the client (tests/moonlight_client.c with BUTTERPOLLO_TEST_FRAME_DUMP),
recomputes the probe's scRGB picture for that frame, converts it as BT.2100
PQ with BT.2020 primaries in limited range, and compares. The
Vibepollo runs beside the load sent too few frames to reach frame 900, so
its column has the three idle runs; Rubylight's has all six.
| Vibepollo 2.0 | Rubylight 2.0 | |
|---|---|---|
| Black / 100-nit white patch (expected 64.0 / 509.08) | 64.0 / 509.0 | 64.0 / 509.0 |
| Luma error, mean absolute (10-bit codes) | 0.44 | 0.38-0.44 |
| Contrast slope (1 is exact) | 1.000 | 0.998-0.9995 |
| Saturation (decoded / expected chroma) | 97.9-98.0% | 100.2-100.6% |
| Chroma error, mean absolute | 0.96 | 0.30-0.59 |
Both streams also carry the same HDR10 metadata (BT.2020 primaries, D65, the virtual display's peak luminance). Rubylight's decoded pictures match the expected values within half a 10-bit code on average, with no lifted black and no lost saturation in these reference frames. This validates the tested host conversion and encoding path for this content and setup.
Controlled comparison with the original C++ FEC #
The benchmark loads the original C++ host's Reed–Solomon wrapper from baseline commit f23ee0c9e7857887be7f774de6ac5153500a7e53, with its pinned nanors implementation at 19f07b513e924e471cadd141943c1ec4adc8d0e0. The source verification script checks every compiled reference file against its pinned SHA-256. GCC 16.1.0 builds the reference with -O3 -ftree-vectorize -funroll-loops; Rust uses the release profile. Both select AVX2 on this Ryzen 7 5800X3D. The original runtime ISA dispatch is retained.
| Data + parity shards, 1416 bytes each | Original C++ median | Rust median | Speedup | CPU time saved |
|---|---|---|---|---|
| 32 + 7 | 6.96 µs | 5.51 µs | 1.26× | 21% |
| 96 + 20 | 63.03 µs | 45.87 µs | 1.37× | 27% |
| 192 + 39 | 250.01 µs | 178.72 µs | 1.40× | 29% |
Each result is the median of seven half-second runs, with Rust/C++ order alternated. Caller shard buffers and pointer views are allocated outside the timing; the original per-frame matrix allocation, encoding and release remain inside it. Every parity byte is compared before and after each round. A small generic Cauchy case (4 + 2 shards of 144 bytes) measures 0.139 µs in C++ and 0.133 µs in Rust, effectively equal; it is not the fixed Moonlight audio FEC matrix.
The Rust changes precompute finite-field inverses and reuse AVX2 input loads across four parity rows. SSSE3 and scalar fallbacks preserve the wire format. Tests cover every field coefficient, short and unaligned payloads, row-group tails and the 255-shard boundary. This is a component comparison against the previous implementation, with reproducible source inputs, rather than a comparison against an earlier slow Rust prototype.
# Initialize third-party/nanors in a source checkout first.
pwsh -NoProfile -ExecutionPolicy Bypass -File .\rust\tests\build-fec-reference.ps1 -ArtifactDirectory C:\path\to\artifacts
cargo build -p butterpollo-core --example protocol_performance --release --locked
.\target\release\examples\protocol_performance.exe C:\path\to\artifacts\cpp-nanors-reference\nanors-reference.dll > fec.json
The release package includes the same probe as butterpollo-protocol-performance.exe. The reference DLL is a test fixture built separately; it is not shipped or linked into the Rust host. Building or running this comparison does not start either host or change display settings.
Repeat-frame encoder capacity #
The 2.0 candidate PyroWave record path completed 961 frames in 8.003 seconds at 3840×2160/120 HDR, or 120.09 fps, at an 800 Mbps target. Submission-to-observed-output time averaged 2.17 ms, with 3.21 ms p95. GPU conversion uses shared D3D11/Vulkan planar inputs and reads back only the encoded bitstream. This repeated-frame test covers conversion, encoding and framing; it excludes live capture, packetization/FEC, transport, decoding and display latency. It is a capacity measurement, not a whole-host comparison against C++.
.\butterpollo-performance.exe --codec pyrowave --records --hdr --width 3840 --height 2160 --fps 120 --bitrate 800000 --seconds 8 --paced
The same real captured frame was repeatedly converted and encoded, with 20 warmup submissions followed by an eight-second unpaced run. Counts refer to returned encoded frames. Capture occurs before the timed loop; network, client decoding and display latency are excluded. Requested frame rate was 120; bitrates were 20/40/80 Mbps at 1080p/1440p/4K respectively.
| HEVC HDR output | Earlier Rust CPU path, fps | Rust GPU path, fps | GPU encode-call mean |
|---|---|---|---|
| 1920×1080 | 6.41 | 689.08 | 1.45 ms |
| 2560×1440 | 3.77 | 420.75 | 2.38 ms |
| 3840×2160 | 1.69 | 202.68 | 4.93 ms |
The baseline is commit 319a3833b37119c6dac37339c7363bedc0bc0bee, before this change. Both runs used the same capture dimensions, GPU and driver; the desktop content was not frozen across the runs. This is a repeated-frame capacity comparison against the earlier Rust implementation, not a controlled comparison against C++ and not a live-stream frame rate. Encode-call duration also excludes asynchronous codec completion; use completed-output latency or full-stream measurements to assess latency.
After setting AMF's AV1 alignment mode to allow unaligned input dimensions, the packaged probe produced 849.62 fps with 1080p HDR/120 input in a separate repeat-frame run. Previously initialization failed because AMF's default alignment excludes a 1080-line image. However, the full-stream decoder found a coded height of 1082, and the strict dimension check rejected that run. AMD documents two padded rows on this hardware for 1080p AV1; use HEVC for exact 1080p output or an aligned AV1 resolution such as 1440p/4K. This is not a validated exact-resolution 1080p AV1 result. AMF's alignment documentation
That capacity run's submission-to-observed-output mean was 10.40 ms under an intentionally saturated queue; it is not a latency-oriented workload.
Full encrypted Moonlight streams #
The independent C fixture uses Moonlight-common-c for encrypted RTSP, control and UDP video/audio transport, then FFmpeg and Opus to decode the results. It rejects codec fallback and verifies the decoded dimensions, ten-bit HDR, BT.2020 primaries and PQ transfer. Each performance run lasts 20 seconds after connection initialization. Client frame rates include startup and buffered decoder frames; host steady rate uses session counter deltas after two seconds.
| Output and requested rate | Decoder threads | Host steady fps | Decoded fps | Completed encode mean / p95 |
|---|---|---|---|---|
| H.264 SDR 1080p/120 | 4 | 120.01 | 118.11 | 2.29 / 2.60 ms |
| HEVC HDR 1080p/120 | 1 | 120.03 | 118.70 | 2.27 / 2.60 ms |
| HEVC HDR 4K/120 | 1 | 119.89 | 105.47 | 6.22 / 6.50 ms |
| HEVC HDR 4K/120 | 4 | 120.02 | 117.98 | 6.33 / 6.70 ms |
| AV1 HDR 4K/120 | 4 | 120.04 | 117.96 | 5.43 / 5.80 ms |
All five runs reported zero codec/Opus decoding errors. The single-threaded 4K decoder took 9.33 ms per submitted packet, exceeding the 8.33 ms frame budget; its receive queue eventually dropped packets and requested keyframes. The three latest four-thread runs enable wgc_slot_aligned_publish=true and amd_ltr_frames=4. HEVC 4K/120 received 2371 complete frames and decoded 2368 in 20.072 seconds; three frames remained buffered in the decoder. AV1 4K/120 received 2514 complete frames and decoded 2512 in 21.295 seconds. These client totals include startup/teardown, while host steady rates use counter deltas. Completed encode latency covers conversion/submission until the host observes encoded output, including asynchronous work. It excludes capture age, FEC/packetization, networking, decoding and display scanout.
Final 2.0 candidate standard-codec checks #
The final candidate repeated the three standard-codec checks with a 2560×1440 SDR capture source, a requested 120 fps and four software decoder threads. Each encrypted stream ran for 20 seconds and passed strict dimensions/color, permissions and client hooks with zero video/audio decode errors.
| Output | Host steady fps | Decoded fps | Host processing mean / p95 |
|---|---|---|---|
| H.264 SDR 1080p | 120.03 | 118.13 | 3.37 / 6.70 ms |
| HEVC HDR 4K | 120.05 | 118.20 | 7.05 / 12.50 ms |
| AV1 HDR 4K | 120.04 | 118.26 | 7.14 / 13.00 ms |
The final wire-header processing metric includes capture age and completed encoding; PyroWave also includes its sender wait. It excludes packetization after the header, network transit, decoding and scanout. Earlier completed-encode measurements above exclude capture age, so their latency columns are not directly comparable. Client frame rates include startup/teardown.
Vibepollo 2.0 PyroWave transport #
The pinned Nonary Moonlight-common-c transport at
d6a11bc685b41037b352a96f29d08276fe5359ba receives/decrypts/FEC-recovers
record packets, then the independent vendor decoder renders to a CPU buffer.
Four 20-second encrypted streams at 1920×1080/120 and 200 Mbps pass with zero
video/audio decode failures or partial frames. The source is a 2560×1440 SDR
desktop.
| Profile | Host steady fps | Decoded fps | Host processing mean / p95 |
|---|---|---|---|
| SDR 4:2:0 | 119.57 | 116.46 | 3.08 / 8.40 ms |
| HDR 4:2:0 | 119.95 | 116.51 | 3.08 / 9.60 ms |
| SDR 4:4:4 | 119.96 | 117.23 | 2.66 / 5.30 ms |
| HDR 4:4:4 | 119.97 | 117.44 | 1.88 / 2.40 ms |
Host processing includes capture age, completed encoding and waiting for the PyroWave sender, as recorded in Moonlight's wire header. It excludes packetization after the header, network transit, decoding and scanout. The console's encode p95 measures the encoder separately. Client rates include startup/teardown. The HDR 4:4:4 row is the final repeat after enforcing a minimum one-tick advance on the 90 kHz RTP clock; it decoded all 2,357 received frames with no partial frames.
At an 800 Mbps target, the same loopback sender is the limiting stage: a 30-second 1080p run delivers 101.49 steady host fps and decodes 2,993 frames (99.40 fps including startup). Host processing averages 8.10 ms with 14.30 ms p95. Older pending intra frames are replaced; they do not accumulate in an unbounded queue. This verifies continued decoding across many RTP sequence wraps and the visible backpressure counter, not 800 Mbps playback at 120 fps or a physical-network stress test.
The fixture requires MSYS2 UCRT64 GCC/CMake/Ninja, OpenSSL/Opus development libraries and the pinned patched PyroWave SDK. It is separate from the shipped Rust host:
pwsh -NoProfile -ExecutionPolicy Bypass -File rust/tests/build-pyrowave-client.ps1 -ArtifactDirectory C:\tests\pyrowave -PyrowaveRoot C:\path\to\pyrowave-186f0393
$env:BUTTERPOLLO_TEST_CLIENT_EXE = 'C:\tests\pyrowave\moonlight-pyrowave-client.exe'
$env:PATH = 'C:\msys64\ucrt64\bin;' + $env:PATH
python rust/tests/interop.py C:\tests\pyrowave pyrowave-hdr-444 1920 1080 120 20 200000 4
October 7: PyroWave recovery feedback #
The rc.19 report describes about 3.3 idr_requests per second at several
PyroWave bitrates. That counter alone cannot identify the incoming message:
it included explicit IDR requests, reference invalidations that fell back to
IDR, and host requests such as encoder startup. The exact reporting client
build and its control trace were not available for this investigation.
The pinned Nonary control
implementation
uses 0x0301 for reference invalidation and 0x0302 for an explicit IDR
request. Its older 0x0201 loss report runs every 50 ms; modern Sunshine
connections instead send 0x0200 pings every 100 ms and queued 0x5502 FEC
status reports. Rubylight ignores those statistics messages for recovery. This
client's decoder-capability check enables RFI only for H.264, HEVC and AV1;
PyroWave transport loss requests an IDR instead. Neither this client transport
nor the host has a 300 ms recovery timer. The Qt PyroWave
decoder
also returns DR_OK after rejecting a PyroWave picture: the next independent
picture replaces it.
There was a host-side feedback bug. PyroWave reference invalidation reached
an unsupported encoder method, requested an IDR and increased idr_requests.
Both pending invalidation and the IDR latch could bypass the capture cadence,
including encoding a repeated picture early. The PyroWave encoder itself
ignores the IDR argument, so it never produces a larger recovery keyframe.
Critical FEC uses the record layout and configured percentage; detail FEC uses
frame cadence, record stability and the available wire budget. Neither reads
these control messages. Extra early pictures could indirectly change cadence
and bandwidth, but feedback does not directly increase parity.
PyroWave feedback now leaves the cadence latches alone. Reference invalidations
have their own reference_invalidations counter in session statistics and
the periodic timing log, and cannot become PyroWave IDR requests. Explicit
0x0302 requests remain visible in idr_requests, including for PyroWave;
the existing host startup/fallback accounting is retained. This follows
Vibepollo 2.0's handlers,
which separately count IDR/RFI messages and ignore both for PyroWave encoding.
Unit tests cover valid and malformed PyroWave invalidations, explicit requests,
and unchanged H.264/HEVC/AV1 range merging and IDR fallback.
The framing comparison found the same eight-byte short header, IDR frame type
2, exact final payload length, critical-prefix packet count, 0x80 record-start
flag, RTP/24-bit stream sequence progression and up-to-four-block FEC layout.
The first FEC block protects the critical records at 20% by default, with at
least two parity packets, when it fits the 255-shard Reed-Solomon limit;
otherwise the planner uses unprotected blocks. The critical-packet count is not a required ratio of
the whole frame; low-bitrate pictures can devote a larger share to coarse
records. No framing or FEC changes were justified by the comparison.
Before changing the host, the independent pinned receiver streamed record-framed
PyroWave over loopback at 2560×720/60, with 10/200/800 Mbps requested and
9,308/198,988/798,988 kbps negotiated. It decoded all 682/683/715 received
pictures, with zero partial frames and zero decode failures. Each run kept
idr_requests=1 from startup through teardown; the 3.3-per-second symptom did
not reproduce. These are transport/vendor-decoder checks, not validation of
the reporting client's renderer. The release e2e wrapper correctly reports
missing motion/audio-continuity measurements for this PyroWave receiver; its
interoperability check passes, but it is not a full release-e2e pass.
Radeon LTR recommendation (evaluation only) #
Keep amd_ltr_frames=0 as the default for now. AMF supports explicit LTR
selection for HEVC
and AV1.
Rubylight can already recover a valid reference-invalidation range from an
older retained anchor, reducing the need for an IDR. The existing strict
FFmpeg omission fixtures above decoded all 56 retained pictures and exercised
two LTR recoveries each for HEVC and AV1. That is correctness evidence, not a
measured Wi-Fi or RDNA4 improvement.
LTR cannot replace explicit decoder-reset IDRs, a lost initial anchor, invalid ranges or recovery after all usable anchors are gone. It also needs a client decoder that supports reference invalidation and enough negotiated references for the anchors plus the rolling reference. The current policy respects that budget, disables LTR with intra refresh, checks driver capabilities/readback, and falls back on rejected surface properties.
For a supported Radeon/client pair, opt-in LTR is worth testing with controlled loss, delayed feedback and decoder resets while measuring recovery bytes, latency and sustained decoding. Test RDNA4 separately: the documented RX 9000 H.264/HEVC freeze report Do not enable LTR globally to mask congestion. No encoder policy or AMF code was changed by this investigation.
October 7: Wi-Fi and unknown-route pacing #
H.264, HEVC and AV1 default to twice the negotiated encoder bitrate only on confirmed Wi-Fi (interface type 71) or mobile broadband (237, 243, 244) host routes, bounded to 1–800 Mbps. Ethernet, loopback, VPN, Tailscale and unknown routes retain the rc.19 default of 800 Mbps. A known physical Ethernet link caps that at 80% of its reported speed. Virtual Ethernet adapters, including Hyper-V vSwitches, use the physical adapter's type and speed when Windows' interface stack exposes an unambiguous binding. Missing or ambiguous bindings keep the wired default. This corrects the initial policy that treated every route without a hardware Ethernet speed as wireless. The first frame resolves the route before selecting its rate; subsequent lookups retain the two-second refresh.
Positive overrides retain their existing 110% stream-bitrate floor and 80% physical-link cap. PyroWave's automatic sender is unchanged: 95% of a known Ethernet link, or per-frame wire demand and its bitrate floor otherwise. Its independent-frame bandwidth must not inherit an 800 Mbps ceiling or a conventional-codec multiplier. The rc.19 two-frame encoder gate is unchanged.
Wire-time estimate, not a measurement: a 1,000,000-byte keyframe takes 80 ms at 2×50 Mbps versus 10 ms at 800 Mbps, before packet overhead and send cost. Classification tests cover wireless, mobile, virtual, unknown and loopback routes plus resolved, missing and ambiguous physical bindings. No live stream or physical-route performance test was run for this correction.
Measured loopback cost #
These runs used the release rust/release/e2e.py fixture, WGC and an RX 7900 XT
with driver 32.0.31041.1004. The installed service log was checked before
each run and showed no active stream. The portable host used its own package,
configuration and ports. Other agents could still use the GPU, so settings
and builds were alternated, with two runs per case; ranges below are the two
observations, not confidence intervals. Baseline was commit 4ea3b058.
Raw logs, receiver results and sampled statistics are in this worktree's
target/network/measure; target/network/measurements.json contains the
summary. No physical Wi-Fi path was measured.
The first six runs compared pacing settings on the baseline executable at
2560×720/60, with 60,000 kbps requested and 46,988 kbps at the encoder.
Overrides of 93,976 and 70,482 kbps modelled 2× and 1.5× on loopback.
The moving-strip scene actually sent only 9.8–11.2 Mbps of UDP video, including
FEC; these timings are not a sustained 60 Mbps traffic test. Send cost below
is the mean of sampled present_to_send_mean_ms - frame_age_mean_ms - host_processing_mean_ms after three seconds, including packetization and
send waits. It is not encode time or client latency.
| Baseline pacing | Send cost per frame | Receiver steady FPS | Release e2e results |
|---|---|---|---|
| 800 Mbps | 0.16–0.17 ms | 60.57–60.70 | Both passed |
| 2× encoder bitrate | 0.65–0.81 ms | 60.58–60.77 | One passed; one audio-continuity failure |
| 1.5× encoder bitrate | 1.84–1.90 ms | 60.57–60.60 | One passed; one audio-continuity failure |
All six decoded every delivered picture without a decode failure. The lower send cost and larger FEC/scheduling margin favour 2× over 1.5×.
A separate alternating baseline batch at 5120×1440/240, 150,000 kbps requested and 118,988 kbps encoded, exposed the cost of applying 2× unnecessarily to a fast route. Host throughput fell from 215.61–216.01 FPS at 800 Mbps to 206.43–207.63 FPS at 237,976 kbps, about 4%. This is why loopback retains its old default. The software receiver could not sustain the requested rate, so these runs failed the receiver cadence/motion gates; they are host throughput measurements, not successful 240 FPS playback.
The final baseline/candidate/baseline/candidate batches gave:
| Workload | Baseline host FPS | Candidate host FPS | Baseline / candidate send cost |
|---|---|---|---|
| HEVC 2560×720/60, 800 Mbps versus 2× override | 60.54–60.60 | 60.59–60.61 | 0.12–0.14 / 0.52–0.61 ms |
| HEVC 5120×1440/240, automatic loopback | 216.82–218.14 | 217.73–218.05 | 0.32–0.43 / 0.32–0.33 ms |
| PyroWave 2560×720/120, 800 Mbps requested, automatic | 87.97–88.02 | 88.10–91.50 | 11.26–11.36 / 10.87–11.26 ms |
All four normal HEVC runs passed the complete release fixture, decoded all 927–938 pictures, and retained continuous audio. Receiver steady FPS was 60.58–60.61; arrival-interval p99 was 18.47–22.95 ms. Candidate mean presentation-to-send time was 3.43–3.60 ms versus baseline 3.10–3.73 ms. The saturated HEVC runs again failed receiver cadence/motion gates: the software receiver achieved only 36.05–55.97 FPS and requested recovery frames. All had continuous audio and zero decode failures among delivered pictures. There was no host throughput regression in these loopback observations.
The PyroWave receiver decoded all 4,369 pictures across the four runs, with
zero partial frames, zero decode failures and idr_requests=1 throughout;
candidate reference_invalidations stayed zero. Actual UDP video was 581–611
Mbps on the candidate versus 586–592 Mbps on baseline. These runs passed the
independent receiver's interoperability check, but the release wrapper reports
its missing motion/audio metrics, as described above.
Estimated burst size #
For a hypothetical 60 Mbps encoded stream at 60 FPS, each average picture is 125,000 bytes. With a 1,392-byte packet setting, encryption, IPv4 and 20% FEC, that is 91 data packets plus 19 parity packets: 165,660 modelled wire bytes. Serialization takes about 11.04 ms at 120 Mbps (2×), 14.73 ms at 90 Mbps (1.5×), or 1.66 ms at 800 Mbps, within a 16.67 ms frame period. This explains why 1.5× leaves little allowance for larger pictures or scheduling delays.
With the default 64 KiB batch limit, the sender's two-millisecond budget reduces an initial encrypted UDP burst from 45 packets / 64,800 bytes at 800 Mbps to 20 packets / 28,800 bytes at 120 Mbps.
CPU fallback conversion #
This comparison used exactly the same synthetic 1968×2184 FP16 scRGB image, with patterned RGB data, resized to each output dimension. Each version ran for at least three seconds. The new CPU implementation uses lookup tables, precomputed resize columns and at most eight Rayon workers, with a serial fallback if worker creation fails.
| Output | Earlier Rust mean | New Rust mean | Speedup | Maximum 16-bit output difference |
|---|---|---|---|---|
| 1920×1080 | 134.41 ms | 8.89 ms | 15.1× | 0 |
| 2560×1440 | 240.34 ms | 14.67 ms | 16.4× | 0 |
| 3840×2160 | 539.96 ms | 34.26 ms | 15.8× | 0 |
These timings cover RGB HDR conversion/scaling only, not GPU upload or encoding. Unit tests separately check FP16 decoding, PQ reference luminances, linear-light resizing and already-PQ ten-bit input.
Color and ownership checks #
The Rust GPU converter keeps absolute luminance through 10,000 nits. A separate HEVC decode of grayscale patches at 0/80/1000/10000 nits returned limited-range P010 luma codes 64/490/723/940 and neutral chroma 512/512. Hardware tests compare primaries and grayscale against a CPU reference within two ten-bit codes, check a linear-light resize and verify that frames retained by the codec are not overwritten when the bounded pool is reused. New 4:4:4 tests verify independent adjacent chroma and planar ten-bit HDR codes. The packed AYUV shader is checked through its compatible RGBA render-target view on AMD; actual AYUV resource allocation and NVIDIA CUDA interop require NVIDIA hardware. The abandoned vendor conversion path clipped the 10,000-nit patch; that path is not used.
Native Winsock checks delivered eight separate datagrams in two send calls on both IPv4 and IPv6, then verified the ordinary-send fallback and a short trailing datagram. Video batches respect the previous 16/32/64 KiB setting, a 64-packet/65,507-byte ceiling and a two-millisecond wire budget.
Independent strict FFmpeg loss fixtures encode 64 frames, omit frames 5–8 and 17–20, and decode all 56 retained frames for H.264, HEVC and AV1. HEVC/AV1 use two LTR recoveries; H.264 uses one LTR recovery and an IDR fallback at its reference-counter wrap. Actual Opus round trips cover 21 stereo/5.1/7.1/custom-layout, quality and packet-duration combinations, preserve every channel and stay inside the transport packet budget. These checks establish recovery/audio correctness, not an end-to-end latency improvement.
Reproduce on another machine #
The release ZIP includes butterpollo-performance.exe, which captures one frame and prints JSON. It does not start a service or change display configuration. A functioning local display/capture session and the relevant codec/GPU driver are required.
.\butterpollo-performance.exe --encoder amf --codec hevc --hdr --width 3840 --height 2160 --fps 120 --bitrate 80000 --seconds 8
.\butterpollo-performance.exe --encoder amf --codec hevc --hdr --width 3840 --height 2160 --fps 120 --bitrate 80000 --seconds 8 --paced
.\butterpollo-performance.exe --encoder amf --codec hevc --hdr --width 3840 --height 2160 --fps 120 --bitrate 80000 --seconds 8 --cpu
Unpaced mode measures capacity with a bounded queue; paced mode measures completed-output latency at the requested cadence. --cpu reads back the captured frame once and measures the CPU compatibility encoding path. JSON reports source dimensions, adapter, returned-frame count, call time and submission-to-observed-output time. The source probe is windows/examples/performance.rs; cargo build -p butterpollo-windows --example performance --release --locked builds it with the SDK environment from the main build.
For full streams, use an isolated host configured with base port 48123, the fixture credentials test / rust-smoke-only and a Desktop app. These are test-only credentials for a loopback fixture. tests/build-moonlight-client.ps1 builds the independent C client against the initialized Moonlight-common-c submodule. The MSYS2 UCRT64 environment additionally needs FFmpeg decoding development libraries and OpenSSL (mingw-w64-ucrt-x86_64-ffmpeg and mingw-w64-ucrt-x86_64-openssl), alongside Opus, CMake, Ninja and GCC. Python needs requests and cryptography. The probe creates a client certificate, performs real PIN pairing, grants only its fixture launch permissions and cancels/unpairs that fixture afterward.
$env:BUTTERPOLLO_TEST_PORT = '48123'
.\rust\tests\build-moonlight-client.ps1 -ArtifactDirectory C:\path\to\artifacts
python rust/tests/interop.py C:\path\to\artifacts hevc-hdr 1920 1080 120 20 20000 1
python rust/tests/interop.py C:\path\to\artifacts hevc-hdr 3840 2160 120 20 80000 4
python rust/tests/interop.py C:\path\to\artifacts av1-hdr 3840 2160 120 20 80000 4
The positional arguments after the artifact directory are codec, width, height, requested fps, duration, requested bitrate in kbps and software decoder thread count. Reports contain per-half-second host counters and the derived host steady rate. Requested Moonlight bitrates can be adjusted during negotiation; the actual encoder bitrate is in each session sample.
October 5: WGC startup and notification experiment #
The OpenCode investigation ended with a strict WGC smoke test succeeding as the
interactive user but failing as SYSTEM in that user's session:
CreateForMonitor returned 0x80070424. The Rust service runs its host under
that SYSTEM token. Capture recovery already tried Desktop Duplication when WGC
could not open; initial stream startup did not. Startup and recovery now share
the fallback, log the actual backend and retain both Windows errors if neither
backend opens. Strict probes still fail rather than substitute DDX.
This is a fallback, not service-mode WGC support. A capture helper running as the signed-in user is still needed. DDX fallback is not evidence of equivalent VRR or game-frame-generation behavior.
The next experiment used WGC's
frame-arrival callback on the pool's worker thread
to wake capture, replacing 500 us polling with notification waits bounded by a
100 ms housekeeping timer. Eight ten-second runs used the same release probe,
in poll/notify/notify/poll order for each GPU condition. The source was the
existing physical desktop, with approximately 35 changing pictures per second;
the first second was excluded from latency samples. GPU load was the existing
offscreen gpu_load 55 1000 0 200 fixture. The installed host remained idle.
| Condition | Wake method | Frames sampled | Mean detection | Per-run p95 | Empty pool checks per ten seconds |
|---|---|---|---|---|---|
| Idle | 500 us polling | 632 | 0.566 ms | 1.247 / 1.487 ms | 18,884 / 18,898 |
| Idle | Notifications | 641 | 0.391 ms | 0.889 / 1.006 ms | 360 / 346 |
| GPU load | 500 us polling | 645 | 4.527 ms | 9.647 / 9.655 ms | 18,938 / 18,941 |
| GPU load | Notifications | 657 | 5.133 ms | 11.678 / 12.070 ms | 393 / 349 |
Detection means WGC's SystemRelativeTime to the host's snapshot acquisition,
including WGC's own delivery delay. The means above are weighted by frame
count; p95 values belong to individual runs. These are capture-component
measurements on changing desktop content, not a controlled game or an
end-to-end WGC/DDX comparison.
Notifications reduced idle detection by 0.175 ms and almost eliminated empty
polls, but increased loaded detection by 0.606 ms in this batch. Therefore
production capture keeps polling. Callback registration and waits are
opt-in for windows/examples/wgc_arrival_probe.rs, not enabled by normal
streams. An exploratory hybrid run had zero updated frames after warmup and
overlapped part of a build; it is excluded and establishes no latency result.
The callback experiment also exposed a Windows teardown trap: revoking
FrameArrived after closing the pool aborts in GraphicsCapture.dll instead
of returning an error. The probe's registration is revoked before closure and
only once; the event remains owned until in-flight callbacks return. Sixteen
native reconnect/COM-teardown cycles pass with this order. Workspace tests
and warnings-as-errors checks pass too.
The final release build also passed an isolated encrypted user-mode WGC stream:
1920x1080 HEVC SDR at 60 FPS, 20 Mbps requested, 12 seconds. The independent
client decoded all 715 received frames with zero failures and decoded 2,222
audio packets with a nonzero test tone. The host log explicitly reports
requested=wgc backend="wgc"; capture was the existing 5120x1440 SDR physical
desktop, with display changes disabled. This establishes functional
capture/encode/transport/audio interoperability, not a motion latency
improvement or service-mode WGC acceptance.
Local raw results, guards and validation logs are in
%USERPROFILE%\.codex\artifacts\butterpollo-wgc-20261005.
The stream's full logs are in the earlier fixture's
day-work-20261002/codex-20261005-wgc-startup directory. The final host SHA-256
is c10eeb65724d0598b7869dab6f6a6ace7715d2b46da06e0ab85640339eb3e0c2.
The measured prototype's SHA-256 is
b2b7e36667f7317864c0f8a31a01f2f5d4077691d02a69de398988a2121e68e9.
For further investigation on a changing desktop, build the release
wgc_arrival_probe example and run wgc_arrival_probe DISPLAY 10 poll,
wgc_arrival_probe DISPLAY 10 notify, or wgc_arrival_probe DISPLAY 10 hybrid.
An empty display argument selects the primary output. A report with zero
post-warmup samples has no valid detection-latency comparison.
October 5: LAN pacing and encoder follow-up #
The reporter identifies an RX 9070 XT, the latest driver and Wi-Fi. Their rc.2 log contains one HEVC hardware instance, repeated DDX access-loss recovery, and transient UDP errors 10055 and 10035. This workstation has an RX 7900 XT with two reported HEVC instances; disabling multi-instance encoding does not reproduce the reporter's GPU.
The independent receiver at 192.168.4.10 is an i5-8259U / Iris Plus 655 NUC
running Debian 13 on gigabit Ethernet. The Windows sender uses 2.5 Gb Ethernet.
The receiver uses Moonlight-common-c
2600beaf13f18bfa43453609cf5e3b84a4227760, FFmpeg and Opus in an isolated
Docker image. No host packages, network settings or installed service profile
were changed. Streaming profiles disable display mode/HDR changes; the later
motion-fixture refresh correction is documented below. A guard stops test-owned processes if an installed
stream or application becomes active.
Packet pacing #
Video pacing now measures its next wait from completion of the preceding batch. A delayed socket call or wakeup can no longer accumulate credit for a catch-up burst. Pacing includes Ethernet/IP/UDP wire overhead. Known physical Ethernet routes cap the burst rate at 80% of link speed; unknown routes retain the existing 800 Mbps default. This measures the sender's local Ethernet link, not end-to-end capacity. It does not infer a Wi-Fi client's capacity through a wired access point. Explicit pacing limits remain useful for that case, and the stream bitrate and picture settings are unchanged.
The UDP probe sends 46,042 deterministic 1,400-byte datagrams: 600 frames at 60 FPS, 50 Mbps payload, with two keyframes eight times larger. The independent Linux receiver records kernel timestamps, integrity and socket-overflow counts. Its effective receive buffer is 425,984 bytes; Python processing and this buffer are part of the stress fixture, not Moonlight's normal receiver.
| Pacing case | Actual receiver overflows | Complete frames | Mean frame send span |
|---|---|---|---|
| Previous 800 Mbps, two runs | 344 / 400 | 598 / 599 | 0.693 / 0.693 ms |
| Revised 800 Mbps | 129 | 599 | 0.779 ms |
| Revised 80 Mbps | 0 | 600 | 10.728 ms |
The slower cap delivered every datagram without corruption, at the cost of a
longer send span. In a separate modeled 100 Mbps / 128 KiB bottleneck,
adding a 6 ms sender stall raised peak queued bytes to 93,357 with previous 80
Mbps pacing, versus 39,694 with completion-based pacing. Both corresponding
real receiver runs delivered every datagram. No sender-side 10055/10035 error
was reproduced. Raw results: udp-results.json; probe:
windows/examples/udp_pacing_probe.rs.
Encoder comparisons and rejected output-wait change #
The controlled encoder fixture uses eight deterministic moving input textures, HEVC 3840x2160 at 60 FPS / 50 Mbps, ULL/speed, VBAQ enabled and preanalysis disabled. Each run encodes 480 pictures; comparisons use two runs per setting in reverse order, separately idle and under the same offscreen GPU load.
| Existing conversion path | Idle mean | Loaded mean | Loaded per-run p95 |
|---|---|---|---|
| Compute, current default | 6.339 ms | 5.955 ms | 6.209 / 6.203 ms |
| Graphics | 6.223 ms | 8.512 ms | 10.948 / 10.978 ms |
| Compute, multi-instance disabled | 6.320 ms | 5.948 ms | 6.178 / 6.183 ms |
This confirms the benefit of Opus's existing compute path under load on this GPU; it is not a newly implemented speedup or a whole-stream comparison.
Removing an extra timer wait after AMF's own blocking output query reduced
loaded component mean latency from 5.958 to 5.831 ms. It also reduced idle
1080p60 LAN stream host mean from 2.279 to 2.201 ms and p95 from 2.6 to 2.3 ms.
However, the loaded LAN comparison regressed from 6.102 to 6.501 ms mean, with
per-run late-interval counts 89/76 before versus 83/97 after. All eight runs
decoded successfully and sustained approximately 60 FPS. The source was the
existing desktop scaled from 5120x1440, not controlled full-screen motion.
The production output-wait change was reverted. Component gains alone did
not justify the loaded stream regression. Raw results are in
encoder-controlled, encoder-polling and lan-poll2-results.json.
Independent receiver limits and reproduction #
A native DDX snapshot test failed twice with no initial image. The physical
display's existing idle timeout is 180 seconds. Holding a temporary display
power request made the same unchanged test pass once in 0.36 seconds, but later
repeats failed even with that request and a moving test window. Unlike the C++
host's capture loop, the Rust capture worker had no request to prevent display
sleep, which is a separate missing behavior. Capture now holds
ES_DISPLAY_REQUIRED | ES_CONTINUOUS for its lifetime, preserving prior thread
requirements and restoring them at teardown. The guard cannot move between
threads. The snapshot fixture uses the same guard.
Windows documents the request and restoration semantics here.
This is a capture-liveness correction, not evidence that display sleep caused
the reporter's access-loss events or remaining encoder-latency difference.
Two 230-second 1080p60 HEVC LAN runs crossed the existing 180-second display timeout. Input activity was recorded; the last input occurred at connection startup, with more than 230 seconds idle by each run's end. In the control, all ten five-second samples after 180 seconds had no fresh capture claims; the revised worker continued recording fresh claims in all ten. Sending repeated pictures can hide this problem behind an apparently healthy FPS.
| Display request | Received / decoded pictures | Steady FPS | Host mean / p95 | Late arrival intervals |
|---|---|---|---|---|
| Previous behavior | 13,743 / 13,743 | 60.139 | 2.290 / 2.6 ms | 51 |
| Held during capture | 13,838 / 13,838 | 60.561 | 2.270 / 2.6 ms | 44 |
Both runs decoded nonzero audio with zero codec failures. Both still needed
two DDX restarts during startup and initially received some blank pictures;
the awake request does not resolve that startup issue. This single long pair
validates continued fresh claims in this idle environment, not a general
latency improvement. Reports: lan-power-results.json and each run's
capture-freshness.json. The native power-state test separately checks prior
requirements, nested guards and restoration.
Every hardware-decoded picture is read back before checking exact geometry,
bit depth, HDR signalling and pixel contrast. Audio validation requires a
decoded test tone. A 1080p60 HEVC run decoded 1,203/1,203 pictures, sustained
60.597 FPS after warmup and had zero intervals above 1.5 frame periods.
The 4K60 HEVC run decoded 538/538 received pictures without errors, but the
NUC's decoder plus readback averaged 35.023 ms and delivered only 27.588 FPS.
That run is not a performance pass, despite correct pictures and audio.
The receiver now supports BUTTERPOLLO_TEST_MIN_FPS to fail such runs directly.
The initial separately named comparison binary was unreachable from the NUC;
it produced no stream and is excluded. Subsequent comparisons used the same
test executable path and saved each binary's hash.
Final 18-second runs of the retained changes passed the minimum-rate gate, exact dimensions, visible pixel contrast on every decoded picture, and the test tone. The source remained the existing SDR desktop; the HDR row checks SDR-to-HDR conversion and HDR signalling, not native HDR capture.
| Backend / codec | Stream size | Received and decoded | Steady FPS | Decode failures |
|---|---|---|---|---|
| DDX / H.264 | 1920x1080 | 1,082 | 60.591 | 0 |
| DDX / HEVC Main10 HDR | 1280x720 | 1,082 | 60.582 | 0 |
| DDX / AV1 | 1280x720 | 1,083 | 60.614 | 0 |
| WGC / HEVC | 1920x1080 | 1,088 | 60.601 | 0 |
AV1 used software decoding; the other rows used Intel VAAPI plus readback. The
WGC row checks the actual opened backend, so fallback cannot pass it as a WGC
result. Reports are in lan-final-results.json. That earlier workspace run
passed 198 tests, including 18 native checks, with the desktop active. The
earlier inactive-desktop DDX failures remain recorded; a later pass does not
close that condition. The excluded AV1 geometry check independently fails all
twelve requested SDR/HDR/alignment combinations.
The Windows independent receiver also passed 1280x720 HEVC (718/718 pictures,
60.649 steady FPS), and correctly rejected an intentionally impossible
1,000 FPS requirement while still decoding 718 pictures without codec errors.
The final rebuilt host passed a local 3840x2160 HEVC / 60 FPS / 50 Mbps stream:
985/985 received pictures decoded, 60.585 steady FPS, 6.269 ms mean / 6.6 ms
p95 host time, and nonzero audio. The whole-run rate was only 54.56 FPS because
startup recovery consumed part of the 18-second run; 200 initial pictures had
no sampled luma contrast. Keep that startup defect visible. This source was
the existing 5120x1440 SDR desktop scaled to 4K, not native 4K motion capture.
Reports: local-final-hevc, local-rate-gate-negative, local-final-4k-hevc.
Build tests/build-moonlight-client.sh ARTIFACT_DIRECTORY MOONLIGHT_SOURCE
on Linux with CMake, C/C++ compilers and OpenSSL, FFmpeg and Opus development
packages. Use BUTTERPOLLO_TEST_HOST and BUTTERPOLLO_TEST_PORT with
tests/interop.py against a test-owned host profile. Optional variables are
BUTTERPOLLO_TEST_HW_DECODER=vaapi,
BUTTERPOLLO_TEST_HW_DEVICE=/dev/dri/renderD128,
BUTTERPOLLO_TEST_REQUIRE_PICTURE=1, BUTTERPOLLO_TEST_AUDIO_TONE=1,
BUTTERPOLLO_TEST_WARMUP_SECONDS=5 and BUTTERPOLLO_TEST_MIN_FPS=58.2
for a 60 FPS run. The profile must repeat static pictures at the requested
rate, or a static desktop's deliberately reduced rate will fail this gate.
Remote clocks are independent, so remote measurements do not subtract the
sender's QPC timestamps or claim source-to-display latency.
The remaining RX 9070 XT acceptance comparison needs that GPU and client: keep the same 3840x2160 / 60 FPS HEVC profile, bitrate, range and game scene on both hosts; record the actual AMD driver version, not just "latest". Compare idle and loaded runs in alternating order after warmup. On Rust, compare compute conversion enabled and disabled without changing quality. Repeat the same client once on Ethernet, then on Wi-Fi, to separate encoding from delivery. Preserve capture-restart and UDP-drop logs alongside host processing, frame age and delivery intervals. No setting from a faster local GPU or a wired fixture closes that acceptance check by itself.
October 5 WGC compute copy and capture startup follow-up #
WGC can use the same fenced D3D12 copy and AMF conversion as DDX on supported
AMD devices with wgc_compute_copy=true. It remains opt-in after the production
repeat-rate comparison below found a loaded cadence tradeoff.
gpu_compute_conversion=false disables compute globally. WGC's graphics-copy
and polling defaults remain unchanged. Failure to initialize
compute preserves WGC on D3D11, and a texture-sharing failure now copies the
same frame on D3D11 instead of waiting for another desktop update.
A native regression test failed before that fallback fix and passes after it.
Before measuring latency, 120 WGC frames at idle and 120 under GPU load were compared byte for byte with D3D11 readback of the same source frame. Every comparison passed; each run contained 119 actual content changes. The candidate was read first so reading the reference could not hide a missing synchronization fence. A retained snapshot also remained unchanged after capture teardown. These were SDR desktop captures on the local RX 7900 XT.
Eight initial 22-second HEVC streams used graphics/compute/compute/graphics
order both at idle and beside gpu_load 45 1000 0 200. The motion fixture
temporarily requested 60 Hz on the 5120×1440 SDR display, with a deterministic
128-pixel moving strip; the stream was 2560×720 at 60 FPS and 20 Mbps. These
initial runs forced minimum_fps_target=60. Scaling by exactly one
half preserved the timestamp barcode. The first five seconds were excluded.
All received pictures decoded correctly, with 100% barcode coverage, nonzero
test-tone audio, and no capture restart. Picture age uses the same PC's QPC
clock and includes decoding; it excludes scanout and input latency.
| GPU condition | WGC copy/conversion | Host mean | Decoded picture-age mean | Distinct pictures/second |
|---|---|---|---|---|
| Idle | Graphics | 2.361 ms | 31.432 ms | 46.758 |
| Idle | Compute | 2.210 ms | 31.835 ms | 50.601 |
| Loaded | Graphics | 8.557 ms | 49.052 ms | 49.704 |
| Loaded | Compute | 1.953 ms | 40.630 ms | 50.357 |
Values are the mean of two runs per cell. Under load the host mean fell by 77% and decoded picture age by 17%; both reversed-order runs agreed. Idle picture age did not improve. Transport FPS was 60.425–60.645, which must not be confused with distinct-picture FPS. The producer itself slowed to about 56.7 FPS with the graphics path and 58.8 FPS with compute under this load. This is a controlled strip plus offscreen load, not a full game or native 4K.
An initial two-run pilot found fewer distinct frames with compute (50.386
versus 45.726 FPS). The full alternating comparison above did not reproduce
that ordering. The deliberately strict 50-distinct-FPS gate still failed on
both idle graphics runs and one idle compute run, and on both loaded graphics
runs. Artifacts: wgc-compute-verification, wgc-compute-abba, and
wgc-compute-abba2 under the October 5 artifact directory.
Fixture correction and production repeat rate #
The original motion probe changed physical refresh when passed a numeric rate. It now paces animation with a timer and never changes display mode. Read-only checks before, during and after its 60-FPS animation confirmed 5120×1440 at the saved 240 Hz; the three-second probe produced 60.322 FPS. The longer fixture produced 7,501 pictures over 125.000 seconds. The Windows QPC frequency was independently confirmed as 10 MHz. No restoration was needed: current and saved display modes already agreed at the audit.
Four 18-second WGC compute runs, in 60/20/20/60 repeat-rate order, isolated
the difference between the fixture's forced repeats and production's existing
minimum_fps_target=20. With 60-FPS motion on the unchanged 240-Hz display,
forced repeats delivered 44.085/45.608 distinct FPS and 21.333/21.094 ms mean
picture age. Production's repeat rate delivered 59.925/60.002 distinct FPS
and 11.997/11.805 ms. All pictures decoded, barcode coverage was 100%, and
there were no capture restarts or late intervals. Thus the earlier repeated
pictures are partly a fixture artifact. The production repeat default was
already correct and remains unchanged. Report: repeat-cadence.
Final workspace/native validation passes 200 checks, including 20 native
checks, with the corrected moving fixture on an active desktop. The two
excluded checks are unavailable NVIDIA execution and the separately reproduced
AMD AV1 geometry failure. The new same-frame fallback regression and
WGC compute synchronization test both pass. Report: capture-native-final.
Eight further 18-second streams repeated graphics/compute/compute/graphics at
idle and under the same GPU load, using production's minimum_fps_target=20
and the corrected 60-FPS animation on the unchanged 240-Hz monitor. Each cell
below averages two runs; five warmup seconds are excluded. Every picture
decoded, with 100% barcode coverage, nonzero test-tone audio, no capture
restart and no compute fallback.
| GPU condition | WGC copy/conversion | Host mean | Decoded picture-age mean | Distinct FPS | Transport FPS |
|---|---|---|---|---|---|
| Idle | Graphics | 2.533 ms | 14.789 ms | 59.965 | 59.965 |
| Idle | Compute | 2.247 ms | 11.455 ms | 60.001 | 60.001 |
| Loaded | Graphics | 15.326 ms | 52.859 ms | 51.423 | 56.331 |
| Loaded | Compute | 1.986 ms | 34.603 ms | 49.955 | 55.714 |
The idle runs all pass. All four loaded runs fail the unchanged 58.2-FPS
transport and freshness gates; decoded-picture correctness alone does not
make them performance passes. Compute reduces loaded picture age by 35%,
but distinct delivery also falls by about 3%. Its default activation was
therefore reverted. The implementation remains available for explicit testing;
the final default preserves the previous graphics-copy path. This is the third
rejected default, alongside notifications and the shorter encoder-output wait.
Do not choose only the favorable host-latency counter. Report:
wgc-compute-abba3/results.json, host SHA-256
405f29d01c6ded76877ac8e4c2a3b0917a3172cf2c761201e3d1dbfd414259a9.
The final build differs by disabling the default and making the native test
opt in explicitly. The report's per-run configuration identifies each path.
The retained release build has SHA-256
c8341cefcb1cdfc50f8038e735c412175571222a743da5f0ba6ec6a388366959.
All workspace binaries build; all-target Clippy with warnings denied,
formatting and diff checks pass. Its final workspace/native run again passes
200 checks, including 20 native checks, with the same two exclusions above.
Report: retained-native-final.
Final wired Intel VAAPI/readback runs use 60-FPS motion, the production repeat floor and 2560×720 at 60 FPS / 20 Mbps. Every received picture decodes with exact geometry, 100% barcode coverage, nonzero audio and no capture restart. HDR remains conversion from an SDR desktop; it is not native HDR capture.
| WGC setting / codec | Decoded pictures | Transport FPS | Distinct FPS | Decode failures |
|---|---|---|---|---|
| Default / HEVC | 1,077 | 60.000 | 59.922 | 0 |
| Compute opt-in / HEVC | 1,069 | 60.011 | 60.011 | 0 |
| Compute opt-in / HEVC Main10 HDR | 1,070 | 60.000 | 60.000 | 0 |
Removing the motion fixture deliberately fails motion validation despite
724/724 decoded pictures, zero codec errors and 60.606 transport FPS. The
receiver therefore cannot count a successful static decode as this motion
test's success. No remote picture-age result is emitted because clocks are
independent. Report: retained-lan-final. Test-owned processes and receiver
containers are stopped; the installed rc.2 service and its profile are intact.
Startup diagnosis #
The old ddx_arrival_probe inserted a zero latency sample when it captured
nothing, incorrectly printing one frame. It now reports actual acquisitions
and separate presentation samples. ddx_startup_probe compares raw BGRA-first,
FP16-first and legacy duplication with plain snapshots, configured graphics
and compute capture, and WGC. It records metadata and sparse pixel ranges,
without saving desktop pictures or changing display modes.
With Windows explicitly reporting the display off, all six DDX paths returned zero frames; WGC returned one cached image. Continuous display requests, including a separate system-plus-display request, did not wake this already off output. The active-desktop DDX check passes. This narrows the earlier standalone failure to an off-display condition on this machine; the request still prevents sleep during an already active stream, as measured above.
During a cold stream, a separate raw DDX observer saw the output switch from 5120×1440 BGRA to 3840×2160 FP16 and back, with an access-loss event at each switch. The configuration had display mode/HDR changes disabled. The trace identifies the transitions, but not what initiated them. A subsequent warm stream had no restart. DDX now logs its actual dimensions, format and API at every open, and preserves the modern-API failure when legacy fallback occurs. No fixed startup delay or black-pixel heuristic was added.
The Linux receiver also verifies the barcode's increasing frame sequence. It deliberately omits absolute picture age because the remote clock is not synchronized with Windows QPC.
October 5 rc.3 selection and capture recovery #
The user selected AMD WGC compute as the rc.3 default after reviewing the
production-repeat comparison above: about 35% lower loaded picture age with
about 3% fewer fresh pictures. The historical default-reversion record above
is retained. rc.3 enables wgc_compute_copy by default, retains the independent
off-switch and same-frame graphics fallback, and separates shared captures
when their compute settings differ. This does not change the backend selection
policy or make WGC available under SYSTEM.
Capture recovery previously slept 150 ms before every reopen, assuming all streams had released the old GPU device. It now publishes a reset generation, wakes consumers, and waits for acknowledgements after encoder, image and filter teardown. Departing subscriptions stop blocking recovery; new subscriptions own no old resources. Reopening starts once all owners release, with 150 ms backoff only on failed open attempts and a 30-second deadline. Tests cover multiple consumers, a departing/joining consumer, successive resets and reset notifications arriving before or during a wait.
The tester can run a candidate when available, but the RX 9070 XT itself is
not remotely accessible. Keep the 4.7 versus 3.9 ms report open until matched
measurements arrive. The local fixture is RX 7900 XT and the LAN receiver is
a wired Intel NUC, not the reported Wi-Fi system. rc.3 artifacts are under
%USERPROFILE%\.codex\artifacts\butterpollo-rc3-20261005.
October 5 AV1 idle recheck #
The rc.5 follow-up initially recorded 56.6–57.5 fresh AV1 pictures per second
at a 60 FPS target. A subsequent GPU check found Warhammer 3 using 92–99%
of the graphics engine. After the user closed it, the same released rc.5
executable (9c3bb28832e68cada56d637142b2e8b2527b745c6e9c6224db29cdc5500806a8)
passed the idle motion check: 59.843 fresh FPS, 60.004 steady received FPS,
and all 1,047 received frames decoded without errors. Steady decoded picture
age averaged 10.562 ms (p95 11.512 ms, p99 12.620 ms); host time averaged
1.907 ms. The earlier loaded measurements remain valid observations, but
are not evidence of an idle AV1 regression or an RTSS cap failure.
This local check used an RX 7900 XT, WGC's user helper with both compute
paths enabled, a 2560×1440 physical desktop at 120 Hz, a 60 FPS moving strip,
and a 1280×720 AV1 stream at 20 Mbps. Overlay process lifetime was not recorded
throughout this check. A low-priority, two-job build was also running. The updated harness
lets the source renderer finish and retains its frame timestamps alongside
the receiver's barcode and timing records. Picture age stops at decode,
excluding client scanout and input latency. Evidence is under
%USERPROFILE%\.codex\artifacts\butterpollo-monitor-av1-20261005\rc5-idle-av1-strip.
The rc.6 runtime delivered 59.925 fresh FPS in the same idle strip check, decoding all 1,116 received frames. With RTSS explicitly started and verified alive through a separate rc.6 check, all 1,054 received frames decoded; the steady window contained 752 distinct frames with no repeats or skips, at 60.001 fresh FPS. Decoded picture age averaged 13.795 ms (p95 14.383 ms), and host time averaged 1.796 ms. The existing RTSS global profile remained at 120/1 FPS with SyncLimiter=1; the test requested no limiter changes. RTSS was stopped afterward to restore its prior process state. Optional codec detection also completed with the same capability flags as rc.5. These checks do not claim an rc.6 latency gain.
October 5 rc.8 capture polling and RTSS audit #
The WGC freshness predictor now caches its median and polling window when a new frame updates the history. Polling no longer sorts two identical arrays between frames. A regression test compares the cached decisions against the previous calculation across stable and changing cadence, short intervals, duplicate timestamps and capture resets.
Seven alternating local release-mode microbenchmarks each ran two million
polls, with one observation every eight polls and identical output checksums.
Median time fell from 84.79 ms to 14.32 ms (5.92 times faster for this small
calculation). Evidence and both implementations are under
%USERPROFILE%\.codex\artifacts\butterpollo-rtss-autostart-20261005\qa.
The first rc.8 candidate was installed through the normal update transaction. Settings, pairings and app-library hashes remained unchanged. With RTSS fully closed, the service retried Windows error 740 using the signed-in administrator, applied 2997/50 FPS, and restored 120/1 FPS on disconnect while Desktop remained retained. A 120 FPS renderer measured 59.941 FPS during the cap and 119.996 FPS afterward. WGC compute remained active and all 831 AV1 frames decoded. Warhammer 3 was running, so these are functional checks with a background game, not an idle comparison. A preceding run recovered from two display-resolution changes; its disrupted timing result was rejected.
Fault injection also reproduced a reconnect bug: after failed SDK restoration,
replacing a retained process owner with None killed RTSS during the next lease.
The owner now survives a successful recovery and reacquisition. A separate
fixture reproduced missed process detection for a directory ending in \.;
directory identity is now normalized before comparison.
October 5 rc.8 idle physical-display investigation #
After Warhammer closed, the desktop was 5120×1440 SDR at a configured 240 Hz. These checks stream 2560×720 at 20 Mbps through WGC's user helper and compute, with independent local Moonlight/FFmpeg decoding and source frame barcodes. They are not directly comparable to the earlier 1280×720 checks on a smaller desktop. No compiler runs alongside the motion checks. The source logs and DXGI presentation statistics distinguish requested refresh, actual presented frames and distinct pictures reaching the decoder.
An initial alternating AV1 strip comparison gave 59.978 and 60.002 fresh FPS for the first rc.8 build, versus 60.005 and 54.415 for the audited build. Three subsequent audited runs delivered 59.912–59.938 fresh FPS; HEVC and H.264 checks gave 59.920 and 59.866. The failed run remains part of the evidence. A full-screen pattern reproduced the shortfall in both builds: 50.092 and 49.437 fresh FPS, respectively. Source submission and displayed-present counters confirmed approximately 60 FPS in the separate presentation probe. The frame gaps appeared before encoding. Configured 240 Hz alone is not evidence that every image was scanned out at 240 Hz or that VRR caused the gaps.
The C++ host sets WGC's minimum update interval to 1 ms; the Rust port had left it at Windows' default. A firsthand Windows capture report describes a similar ceiling near 60 FPS with values below 1 ms. The MinUpdateInterval property is available on newer Windows builds. Local Windows 11 build 26200 accepted the setting. An unconditional experiment measured:
| WGC configuration and source | Received FPS | Fresh FPS | Host processing mean | Decoded picture age mean |
|---|---|---|---|---|
| Windows default, full-screen 120 FPS | 59.758 | 59.758 | 1.884 ms | 17.805 ms |
| 1 ms interval, full-screen 120 FPS | 100.326 | 94.180 | 1.853 ms | 10.171 ms |
| 1 ms, compute disabled, full-screen 120 FPS | 98.832 | 91.705 | 2.161 ms | 11.167 ms |
| 1 ms interval, 60 FPS strip | 60.516 | 54.120 | 1.874 ms | 18.920 ms |
Every received frame decoded without errors in these cases, but none passed
the strict fresh-picture target. The 1 ms experiment also failed a full-screen
60 FPS check (43.951 fresh FPS). It is therefore not enabled globally. The
first rate-aware candidate requested it only when the negotiated stream rate
exceeded 60 FPS, preserved the previous 59.94/60 FPS behavior, and kept compute enabled. The effective
policy follows the user helper and capture-sharing key. An explicit
wgc_high_rate_capture=true or false overrides the automatic decision;
unsupported Windows versions retain their default. The improvement at 120
FPS removes an observed ceiling, not all capture loss.
The final rate-aware executable was then compared with its own override disabled, avoiding a binary-version confound. At a 120 FPS request it delivered 90.598 fresh FPS with the automatic 1 ms request, versus 59.959 with the override disabled. Decoded picture age averaged 14.640 versus 10.247 ms; this pair establishes higher delivery rate, not a uniform latency gain. At 60 FPS the unchanged default delivered 57.178 and 47.846 fresh FPS, and the preceding audited executable then delivered 47.567 under the same strip test. Forcing 1 ms at 60 FPS gave 55.417 fresh FPS; combining it with fixed grid pacing gave 56.521, with higher picture age (25.701 and 26.533 ms). Neither diagnostic passed the 58.2 fresh FPS acceptance gate, and neither became a 60 FPS default. No failed run was removed to claim success.
The source's displayed-present counter remained near 60 FPS in the strip checks, while its reported refresh-counter rate differed (approximately 240 in the first run and 100 in the later revised/old pair). The physical display mode, driver settings and the user's applications were not changed by these tests.
H.264 and HEVC HDR output from the SDR source reproduced the shortfall (48.893 and 46.342 fresh FPS). Both decoded every received frame with correct geometry and nonzero decoded audio. Disabling the optional DXGI statistics in the source renderer still gave 48.528 fresh AV1 FPS, so removing that instrumentation did not resolve this occurrence. The ordinary workspace suite passed 206 tests (23 hardware/network tests ignored), all-target Clippy passed with warnings denied, and the release workspace built successfully. These code checks and successful decoding do not override the failed fresh-picture acceptance results.
During diagnosis, an allocator-reused image address exposed a stale
first_seen trace entry. Clearing it after submission fixes the diagnostic;
it does not change pacing. motion_probe now optionally records DXGI
presentation statistics with BUTTERPOLLO_TEST_PRESENT_STATS=1.
wgc_arrival_probe adds an unregistered polling mode for comparison with
registered notifications; no production notification policy was changed.
All original runs, including failed comparisons, are preserved in
%USERPROFILE%\.codex\artifacts\butterpollo-monitor-av1-20261005. The
consolidated CAPTURE_QA.json is under
%USERPROFILE%\.codex\artifacts\butterpollo-rtss-autostart-20261005\qa.
October 5 rc.8 installer firewall repair #
The two manual installation attempts at 22:19–22:20 Berlin time copied the
rate-aware host successfully, then failed when netsh rejected the
\\?\C:\Program Files\ButterpolloRust\butterpollo.exe application path.
The preceding automatic updater had persisted its canonical filesystem path
in the Windows installation entry; the manual installer reused that path.
This failure happened after the motion benchmarks and cannot explain their
loopback fresh-frame loss. An existing private/domain, local-subnet allowance
remained, and the wired receiver at 192.168.4.10 could still reach serverinfo.
Setup now converts conventional drive/UNC paths at Windows command and registration boundaries while retaining canonical filesystem identity checks inside the updater. Unsupported namespaces and names that would change meaning are rejected before firewall operations. Existing Rubylight rules are updated in place; a missing rule is added. No existing rule is deleted on a failed replacement, and legacy-rule cleanup follows a successful Rubylight rule. Regression tests cover canonical paths with spaces and Unicode, UNC paths, real-file identity, existing/fresh rules, and failure without deletion.
October 5 rc.8 explicit WGC interval correction #
A native background probe isolated WGC from the encoder, texture copies and
network. Each case opened a new capture session, left the first second out,
and read MinUpdateInterval plus frame timestamps. Warhammer remained open;
the probe created no visible window, changed no display/input/RTSS settings,
and saved no pictures. On Windows build 26200, the untouched property returned
160,000 100-ns units: 16 ms, not zero.
| Fresh session setting | Property value | Native capture updates/sec |
|---|---|---|
| Untouched, first control | 16 ms | 55.384 |
| Explicit zero | 0 ms | 216.968 |
| Explicit 1 ms | 1 ms | 216.569 |
| Untouched, final control | 16 ms | 57.493 |
A separate comparison rebuilt the previous capture implementation from
ba784a5dba6b063605f22dc1691028ae2e76307e and the corrected source with the
same build command. Two alternating three-second checks of direct WGC plus
the production GPU-copy path measured 59.489/58.000 capture updates/sec before
and 246.923/234.999 after, excluding each first second. These short checks ran
against the existing game picture without a test window or encoder. They
confirm removal of the capture ceiling, not a whole-stream FPS improvement.
The prior saved probe lacked source provenance and was excluded as a baseline;
its measurements remain in the artifacts. Exact-source results and binary
hashes are in qa\explicit-zero-exact-source-comparison\results.json.
This identifies a throttle before encoding and explains why simply omitting
the API call can miss a 60 FPS target. The low-rate path now explicitly sets
zero; the higher-rate path retains its 1 ms request. Both direct capture and
the user helper use the same constructor. Unsupported API versions keep the
existing nonfatal fallback. Capture sharing and compute-copy policy are
unchanged. wgc_high_rate_capture=false now selects explicit zero, rather than
leaving the property untouched.
Earlier failed barcode checks remain failed historical evidence. A controlled
motion check of this revised executable is still required before claiming the
full smoothness acceptance gate passes. The probe and timestamp records are
under
%USERPROFILE%\.codex\artifacts\butterpollo-rtss-autostart-20261005\qa\native-cadence-fresh-session.json.
Two further eight-second background AV1 checks used the real user-helper and
compute path at 2560x720, 60 FPS and 20 Mbps, with an independent local decoder.
They captured the existing game picture without a test window, audio tone,
input, display changes or frame limiter. After the five-second warmup, the
short measured windows were 2.102/2.146 seconds. The previous build delivered
58.039 FPS and missed the unchanged 58.2 FPS delivery gate; the corrected build
delivered 60.585 FPS and passed. All 412/435 received frames decoded, with zero
errors. Intervals over 1.5 frame periods fell from one to zero in those windows.
The final host samples reported mean source-frame ages of 0.681/3.419 ms and
mean present-to-send times of 2.526/5.252 ms; this is not evidence of reduced
latency. The game was uncontrolled and the sample was brief. Distinct-picture
cadence and sustained latency still require the controlled motion check.
The raw cases are butterpollo-monitor-av1-20261005\wgc-zero-background-before
and wgc-zero-background-after; the comparison is preserved separately from
the failed historical motion checks.
Verification of this revision: 208 ordinary tests passed, 24 environment tests were skipped by default, the new native WGC interval regression passed when selected explicitly, Clippy passed with warnings denied, and the release workspace built successfully. The running game's process/start time and all three installed host profile hashes were unchanged after the background tests. After these measurements, the user installed the final candidate. Read-only checks confirmed the tested host hash, service version 2.0.0-rc.8, successful setup completion, a conventional installation path and an enabled inbound firewall allowance for that executable. This confirms installation and service startup; it does not substitute for a complete limiter lifecycle, controlled motion check or automatic-update handoff.
October 6 rc.9 WGC-first test candidate #
An unset, blank or Automatic capture setting now resolves to WGC before the stream opens capture. This uses the existing signed-in user helper when running as a service, together with the existing Desktop Duplication fallback at startup and recovery. Explicit capture choices retain their previous normalized value. The initial candidate preserved compute-copy defaults, capture intervals, stream rates, display refresh policy and frame limiting. The follow-up below removes its automatic high-rate WGC interval limit.
The selection regression matrix covers physical/virtual displays, fractional and integer frame rates, VRR, frame generation and legacy capture aliases. The fallback regression starts from the default policy and injects a WGC open failure, verifying that Desktop Duplication is attempted next. These are policy and error-path checks, not new hardware or performance measurements.
Initial-candidate verification: 210 ordinary tests passed; the default run skipped 23 hardware checks and one network check. Formatting, Clippy with warnings denied, the release workspace build, and the web build passed. The web type check reported zero errors and warnings. These checks ran without starting capture or changing the installed service.
After the game closed and the user made the screen available, eight controlled full-screen motion cases compared Automatic WGC/helper/compute with explicit DDX on the unchanged 5120x1440, 240 Hz display. Each independently decoded AV1 stream requested 2560x720 at 20 Mbps for 20 seconds, with a five-second warmup and 14.3-14.8 measured seconds. The fixture rendered at the requested stream rate; its recorded cadence was approximately 60 or 120 FPS. The test used isolated loopback profiles, without display/RTSS changes or an audio tone.
| Stream target | Capture | Delivered FPS, two runs | Distinct-picture FPS, two runs |
|---|---|---|---|
| 60 | Automatic WGC | 60.602 / 60.586 | 56.203 / 57.495 |
| 60 | DDX | 60.594 / 60.591 | 54.454 / 53.844 |
| 120 | Automatic WGC, 1 ms | 107.272 / 109.162 | 100.344 / 101.478 |
| 120 | DDX | 121.196 / 121.206 | 113.908 / 113.850 |
All frames decoded without errors, all barcodes were readable, and the expected backend opened without capture recovery or compute fallback. None of these eight cases met the unchanged 97% distinct-picture gate. Fixed-grid WGC pacing made 120 FPS delivery worse (99.271 FPS, 93.097 distinct); direct WGC without the helper also failed (106.636 delivered, 100.157 distinct). These results do not justify changing the default pacing mode or blaming the user helper.
Two follow-ups changed only the isolated profile's WGC interval override to explicit zero. The helper/compute path then delivered 121.213/121.190 FPS, with no intervals above 1.5 frame periods, versus 232/227 long intervals in the default 1 ms cases. Distinct-picture rates rose to 116.274/116.663 FPS: one narrowly missed the unchanged 116.4 FPS gate, and one passed. Mean decoded picture age was 9.301/9.255 ms versus 8.983/9.230 ms with 1 ms, so this is a delivery improvement, not evidence of reduced picture age. Mean host processing remained about 1.85 ms. The revised default uses explicit zero at every rate; an explicit wgc_high_rate_capture=true retains the 1 ms diagnostic option.
The candidate remains unpublished and is not installed over the running service. The rebuilt executable was then checked twice without an interval override: 121.186/121.193 delivered FPS, no long intervals and no decoding errors. Distinct-picture rates were 116.289/116.595 FPS, so one still narrowly missed 116.4 while the other passed. Mean decoded picture age was 9.084/9.339 ms; mean host processing was 1.864/1.865 ms. The test binary's SHA-256 is f4a04b4634a690a86727b5b9c9a6da27ff558e275251fae2fc2a23400ef4c82b. The revised source passed all 210 ordinary tests, with the same 24 environment checks skipped, plus formatting, Clippy with warnings denied and the release workspace build. Frontend sources did not change after their successful checks. No failed case is relabelled as a pass. Logs, renderer reports, independent per-frame CSVs and comparison summaries are retained under D:\CodexArtifacts\butterpollo-wgc-default-20261006 alongside the initial candidate package and its exact-source hashes.
October 6 offline latency audit #
Analysis of the same final two 120 FPS WGC runs, without new capture or changes to the installed host, separates the latency counter from picture age:
| Stage or outcome | Run A / Run B |
|---|---|
| Host processing (claim to before packetization) | 1.864 / 1.865 ms mean |
| Capture timestamp to encoder claim | 1.378 / 1.548 ms mean; 4.616 / 5.119 ms p95 |
| Independent software decoder callback | 3.254 / 3.371 ms mean |
| Render timestamp to decoded picture | 9.084 / 9.339 ms mean |
| Distinct-picture rate | 116.289 / 116.595 FPS |
The software decoder cost is specific to the test fixture, not Moonlight Qt's hardware decoder. These values exclude client display scanout and input latency. At 60 FPS the WGC frame-claim wait averages roughly 4 ms, making frame selection and pacing a stronger next investigation than further encoder preset changes. Shortening that wait is not automatically beneficial: selecting an older picture can worsen picture age even while lowering a host counter.
All fourteen source fixtures recorded render submissions, but none enabled the existing optional DXGI actual-presentation statistics. The next comparison should enable those statistics and trace picture identity through capture and encoding before attributing the remaining repeated pictures to either stage. Compute versus graphics copies also need a comparison using the corrected zero interval. The exact workload and distinct-picture acceptance threshold should stay fixed.
Static review found two additional hypotheses, with no measured gain yet: drain the two-buffer native WGC pool to the newest frame before copying after a stall; and compare the helper's process scheduling settings with the host's streaming scope under load. The helper already uses MMCSS and a high-resolution timer. No capture, pacing, encoder or scheduling defaults were changed by this audit.
October 6 capture phase and actual-presentation investigation #
The follow-up measurements use a different desktop configuration: 2560x1440 at 120 Hz, streaming 1280x720 AV1 at 20 Mbps on the local RX 7900 XT. The display was already in this mode when testing resumed. These ages must not be compared directly with the earlier 5120x1440/240 Hz, 2560x720 results. Each controlled case requested 20 seconds with a five-second warmup, WGC through the signed-in user helper, compute conversion and the explicit zero capture interval. The receiver independently decodes pictures in software on loopback; this is not a Moonlight hardware-decoder or input-to-display measurement.
The fixture now records actual DXGI presentation statistics as well as render
submissions. Healthy source windows maintained the intended presentation rate
and a constant one-frame in-flight count. An additional presentation-to-decoder
estimate uses
SyncQPCTime + (PresentRefreshCount - SyncRefreshCount) * QPCfrequency / refreshHz
to estimate display presentation, then subtracts render-to-presentation delay
from the original decoded picture age. The fixture's present-call and picture
IDs are checked against the actual presentation counters before using that
mapping. The DXGI statistics definitions
and present-call counter
are distinct. This estimate supplements the original render-age metric; it does
not replace raw acceptance or include scanout, network Wi-Fi delay or input.
Trace-level capture records identify each publication, including publications replaced before an encoder claim. Current instrumentation scopes IDs by source and records generation, capture/publication timestamps and matching claims. Weak identity records do not keep image textures alive, and tracing disabled avoids populating those records.
Actual presentation was healthy in the initial four compute/graphics cases, while the later 60 FPS diagnostic cases published roughly 70-78 capture updates per second. Every decoded repeat in those cases had a new capture publication ID. Thus a new captured image could contain the same source picture and consume pacing credit immediately before the next useful update. In the baseline trace, 252 publications were replaced before claim, 85 claimed pictures repeated and 76 source picture IDs were absent. Disabling prediction reduced repeats to eight and omitted source IDs to zero, but increased estimated presentation-to-decoder age from 12.334 to 15.438 ms. A fixed-grid comparison likewise reached 60 distinct FPS but increased that age to 19.275 ms. These alternatives do not justify a global pacing change.
The opt-in frame_pacing_source_phase=true diagnostic learns a dominant phase
from capture timestamps observed by the streaming thread. It does not consume
fixture barcodes, render IDs or DXGI statistics. It requires a concentrated
rolling history and a recent source interval close to the stream interval;
irregular, faster and slower sources fall back to ordinary arrival pacing.
Recovery, timestamp discontinuities and persistent phase shifts reset history.
Anticipation remains bounded rather than adding a full source period. The
ordinary arrival policy and frame_pacing_source_phase=false remained defaults
during the initial diagnostics below.
Nine cases used the same runtime SHA-256
1551d2f33a927023c5319f45d770cb07c3962c0ebed5374685f1066be6306c4a.
All decoded without errors. Source and stream rates are independent in the last
four cases. Same-rate gates remain 58.2 distinct FPS at 60 and 116.4 at 120.
The explicitly planned 120-to-60 and 30-to-60 workloads use
min(source FPS, stream FPS) * 97%, recorded as receiver thresholds 58.2 and
29.1 respectively. A 30 FPS source cannot supply 60 distinct pictures. These
new mixed-rate cases have their own declared workload; historical failures and
same-rate gates remain unchanged.
| Case | Source to stream FPS | Delivered FPS | Distinct FPS | Original render age, mean ms | Estimated presentation age, mean / p95 ms | Wire gaps | Fresh-picture gaps |
|---|---|---|---|---|---|---|---|
| phase-60-a | 60 to 60 | 60.392 | 59.906 | 12.007 | 9.281 / 17.259 | 0 | 7 |
| base-60-b | 60 to 60 | 60.479 | 54.232 | 17.533 | 13.067 / 25.075 | 13 | 104 |
| phase-120-a | 120 to 120 | 120.133 | 119.927 | 14.315 | 6.043 / 7.795 | 0 | 3 |
| base-120-b | 120 to 120 | 120.823 | 119.930 | 14.904 | 6.633 / 9.954 | 0 | 13 |
| phase-60-b | 60 to 60 | 60.137 | 59.931 | 16.082 | 7.875 / 11.700 | 0 | 3 |
| phase-fast-source | 120 to 60 | 60.478 | 60.478 | 13.408 | 5.135 / 7.527 | 0 | 0 |
| base-fast-source | 120 to 60 | 60.577 | 60.577 | 13.308 | 5.034 / 7.237 | 0 | 0 |
| phase-slow-source | 30 to 60 | 49.109 | 29.973 | 17.652 | 15.269 / 32.405 | 233 | 0 |
| base-slow-source | 30 to 60 | 45.803 | 30.009 | 21.152 | 13.520 / 31.483 | 260 | 0 |
Wire gaps exceed 1.5 stream periods; fresh-picture gaps exceed 1.5 periods at the available distinct-picture rate. The 30-to-60 cases have zero fresh-picture gaps above 50 ms despite many wire gaps above 25 ms. Omitted source IDs for 120-to-60 can represent expected downsampling and are not alone a loss diagnosis.
Both 60 FPS phase runs passed their freshness gate and reduced estimated presentation age by 3.785/5.192 ms relative to the intervening baseline, which still failed freshness. The 120 FPS pair retained about 119.93 distinct FPS and improved estimated age by 0.590 ms. The faster-source pair was effectively unchanged, with phase 0.100 ms slower in this sample. The slower-source pair was 1.749 ms slower after presentation with phase, despite a lower original render age: the latter was influenced by different render-to-vsync phase. Both retained all 438 distinct source pictures. Exact-estimator offline replays of each saved slow trace selected identical capture IDs and times with phase on/off across three encoder-busy assumptions, and observed claim/deadline analysis found no changed phase deadline in those slow windows. Phase had more surplus capture publications and repeated pictures.
Initial synthetic-load and DDX follow-ups used runtime SHA-256
12bf588d1517a3ce4545366567bdcd11c0a55af4710cc0f43fd4becd572a1294.
The four loaded cases kept the same source/stream rates and freshness gates;
a separate GPU workload rendered at roughly 120 FPS with about 5.1 ms median
and 5.9 ms p95 render work. The DDX pair used the controlled source without that
extra load. All six decoded without errors and maintained the intended average
source presentation rate. The loaded 60 FPS phase source occasionally alternated
one/three display refreshes around its intended two-refresh cadence; its baseline
source was uniform. Both loaded 120 FPS sources were uniform. These are synthetic
stress samples, not gameplay validation.
| Case | Distinct FPS | Original render age, mean ms | Estimated presentation age, mean / p95 ms | Wire gaps | Fresh-picture gaps |
|---|---|---|---|---|---|
| load-60-base | 57.089 | 18.278 | 13.959 / 21.480 | 0 | 51 |
| load-60-phase | 59.310 | 22.708 | 14.390 / 23.263 | 1 | 18 |
| load-120-phase | 118.751 | 19.930 | 11.667 / 15.832 | 13 | 45 |
| load-120-base | 119.178 | 16.626 | 8.354 / 12.111 | 0 | 28 |
| ddx-60-phase | 58.284 | 17.155 | 12.600 / 21.747 | 0 | 34 |
| ddx-60-base | 55.083 | 20.413 | 12.304 / 21.415 | 0 | 81 |
Phase improved 60 FPS freshness under synthetic load and with DDX, but their estimated presentation ages increased by 0.432 and 0.296 ms respectively. The loaded 120 FPS phase run was 3.313 ms slower and had more long intervals than baseline, although both passed freshness. Its source render-to-presentation phase matched baseline, so that difference is not removed by presentation normalization. That phase run had 103 capture publication intervals above 12.5 ms versus zero in baseline, even though actual source presentation was uniform. Five of its 13 long wire intervals claimed a late publication immediately; eight used the ordinary prediction deadline. None directly matched a phase override on the claimed frame, but 76 other first deadlines did. The optional capture publication alignment to the claim grid was disabled in these profiles; shared GPU scheduling can still couple capture and encoder work.
A subsequent guard restricts phase overrides to sustained surplus: at least 32 observed timestamps, using up to 64, must average at least 110% of the requested stream rate. It counts timestamp intervals, resets with phase history and falls back when the surplus ends. Fixed-trace replay preserved the phase choices of the successful 60 FPS runs while selecting ordinary pacing on the idle 120 FPS traces and loaded 120 FPS baseline trace. It reduced, but did not eliminate, activation on the prior loaded 120 FPS phase trace. This replay uses modeled newest-seen observations and several encoder-busy assumptions; it does not reproduce shared GPU scheduling or optional claim-grid feedback to capture. That replay alone did not establish runtime acceptance; the following group measures the guarded implementation with explicit on/off settings.
Guarded A/B, stress and compatibility checks #
All thirteen cases in guard-plan.json used runtime SHA-256
f0974b404dcc613cbeab0a19857bab5e8328c4c7aaeb1a919954c5fae63fabab,
the same 2560x1440/120 Hz desktop and 1280x720/20 Mbps AV1 workload. The loaded
120 FPS group and 30-to-60 group use baseline/phase/phase/baseline order. The
final pair deliberately removes the synthetic GPU workload's rate cap; it
renders about 183.6-183.7 FPS with 5.4 ms median GPU work, leaving very little
headroom. The freshness gates remain 58.2, 116.4 and the explicitly planned
29.1 for the 30 FPS source. All thirteen cases decoded without errors.
| Case | Phase | Source / stream FPS | Distinct FPS | Original render age, mean ms | Estimated presentation age, mean / p95 ms | Wire / fresh / publication gaps |
|---|---|---|---|---|---|---|
| guard-idle60-1 | on | 60 / 60 | 60.000 | 12.628 | 7.902 / 12.358 | 0 / 3 / 0 |
| guard-idle60-2 | off | 60 / 60 | 57.650 | 19.269 | 11.847 / 19.194 | 0 / 43 / 0 |
| guard-idle60-3 | on | 60 / 60 | 59.862 | 16.671 | 8.221 / 15.647 | 0 / 4 / 0 |
| guard-load120-1 | off | 120 / 120 | 118.986 | 19.223 | 10.952 / 14.779 | 0 / 32 / 0 |
| guard-load120-2 | on | 120 / 120 | 119.118 | 16.813 | 8.541 / 12.392 | 0 / 29 / 0 |
| guard-load120-3 | on | 120 / 120 | 119.789 | 14.934 | 6.660 / 9.963 | 0 / 16 / 0 |
| guard-load120-4 | off | 120 / 120 | 118.593 | 19.455 | 11.186 / 15.003 | 0 / 38 / 0 |
| guard-slow-1 | off | 30 / 60 | 30.057 | 19.705 | 14.350 / 31.642 | 223 / 0 / 292 |
| guard-slow-2 | on | 30 / 60 | 29.992 | 19.548 | 14.655 / 30.423 | 172 / 0 / 245 |
| guard-slow-3 | on | 30 / 60 | 30.038 | 19.535 | 13.158 / 31.095 | 237 / 0 / 315 |
| guard-slow-4 | off | 30 / 60 | 30.025 | 19.266 | 14.187 / 31.573 | 270 / 0 / 329 |
| guard-saturated60-1 | on | 60 / 60 | 53.955 | 31.880 | unavailable | 27 / 110 / 64 |
| guard-saturated60-2 | off | 60 / 60 | 53.019 | 31.676 | unavailable | 28 / 120 / 61 |
Wire and publication gaps exceed 1.5 stream periods; fresh gaps use 1.5 periods of available distinct content. Thus slow-source wire/publication intervals above 25 ms are kept in the table, while fresh content has no gaps above 50 ms. The saturated cases no longer pass the presentation estimator's steady-source-rate validation, so their adjusted ages are withheld. Their raw render ages and failed freshness gates are retained. Actual source presentation averaged 59.691/59.140 FPS and varied between one and four display refreshes, rather than a uniform two.
The two guarded idle 60 FPS runs pass freshness and reduce estimated presentation age by 3.945/3.625 ms versus the intervening baseline, or 3.785 ms averaging those two run differences. The baseline still fails freshness. First-deadline trace reconstruction confirms phase-specific decisions in both guarded runs. The loaded 120 FPS group reproduces neither the earlier long wire intervals nor long capture publication intervals. However, neither enabled loaded run has a reconstructed surplus activation in its steady window, so their lower measured ages must not be credited as a new 120 FPS algorithmic latency gain. These are descriptive short runs, not a statistical guarantee against all scheduling variation.
All four slow-source runs retain every available source picture and use ordinary pacing in the reconstructed steady windows. Their mean estimated presentation ages average 14.269 ms baseline and 13.907 ms guarded. Both saturated runs fail the unchanged 58.2 FPS gate. Phase has no learned stable center in those reconstructed windows, so it falls back, while host processing remains about 1.80 ms. Pacing cannot be claimed to restore 60 distinct FPS under this uncapped workload, and no game performance or remote RX 9070 XT/Wi-Fi conclusion follows from it.
The current candidate now enables guarded source-phase pacing for WGC-selected
streams; frame_pacing_source_phase=false preserves the previous behavior.
Explicit DDX keeps its previous default, since this guarded runtime group did not
validate DDX. This selects the measured 60 FPS improvement while retaining the
rate, concentration and recovery fallbacks. The default-path and recovery checks
below follow this decision; packaging is separate. These measurements do not
assert installation over the service or publication.
Runtime-4 verification passed 242 ordinary tests with 26 environment-dependent
tests ignored by default, and Clippy with warnings denied. Four selected native
checks separately passed across native-3 and native-4: WGC reconnect/COM
teardown, H.264's negotiated one-reference budget, native H.264/HEVC/AV1 reference
recovery after dropped packets, and exact localized GPU texture comparison.
The one-reference H.264 check decoded all 64 frames with SPS and VUI reference
budgets of one and no LTR recovery; the unrestricted recovery test retained its
separate LTR coverage. The first default-selection follow-up repeated all 242
ordinary passes, 26 default skips, Clippy and the release build successfully.
Official Windows Moonlight Qt 6.2.0 also passed ten local hardware-decoder and
D3D11-renderer connections against runtime-3, SHA-256
12bf588d1517a3ce4545366567bdcd11c0a55af4710cc0f43fd4becd572a1294: two each
for H.264, HEVC, AV1, HEVC HDR negotiation and AV1 HDR negotiation at
1280x720/60 FPS. Requested formats matched negotiation, client logs reported
about 60 decoded/rendered FPS and no decoder errors, and each connection and
reconnect closed cleanly. Host cancellation returned the host to its free
state.
Repeat deadlines and current candidate #
The first default-enabled executable, SHA-256
cbc837071d1b27a02050a3db1f16ec18aa444e54b4ecacd49c33b24534491ca6,
passed the no-override AV1 60/120 FPS cases with 59.977/119.858 distinct FPS,
zero long wire intervals and zero decode errors. Original render ages were
15.407/14.791 ms mean, and estimated presentation ages were 8.185/6.520 ms.
A deliberately terminated WGC helper reopened in 309 ms after releasing encoder resources in 31 ms. The HEVC stream resumed without decoder errors or a new client connection. This is a functional recovery pass, not a smoothness pass: its whole steady window produced 51.551 distinct FPS and failed the normal 58.2 gate. The recovery runner's explicitly recorded 50 FPS delivery threshold did not replace that freshness gate. The shortfall already existed before failure (51.189 distinct FPS in the preceding steady interval) and remained after the first two recovery seconds were excluded (53.804). The outage itself skipped 19 source pictures, rather than explaining the entire deficit.
That profile requested minimum_fps_target=60. Frequent source presentation
intervals of one/three refreshes around the intended two caused old images to
reach their 16.667 ms repeat deadline before the useful update arrived. Repeating
them spent pacing credit. The old trace logged only new-image submissions, so
its 1,062 claim records could not be joined ordinally to 1,167 received frames.
Clock-aligned matching associated 81 of 111 steady repeated pictures with outputs
without a fresh-image claim record. Separate HEVC on/off/on cases then achieved
60.004/53.533/59.963 distinct FPS and estimated presentation ages of
7.312/11.840/7.417 ms, establishing that this was not a general HEVC decoding or
phase-pacing regression. The failed recovery smoothness result remains retained.
The follow-up gives a repeat a bounded opportunity to yield to an imminent new
capture when source-phase pacing is enabled and the observed source interval is
near the stream period. Extra wait cannot exceed one quarter of the stream
period plus 0.5 ms. Slower sources, disabled phase policy and non-arrival pacing
retain their existing repeat policy. Tracing now includes every submission with
an explicit fresh flag, allowing same-image repeats to be separated from a
newly captured image containing the same pixels.
Eight cases in repeat-plan.json compare the revised executable, SHA-256
d22e446dd103592c09cd9bf11da3a76d355ab1783a18862d5d3b9f8b31a3237a,
with the preceding executable at the same HEVC settings. All seven revised cases
pass their declared freshness gates and decode without errors; the old HEVC case
still fails 58.2. Source presentation is uniform within each measured window.
The normal-minimum cases use the actual default minimum_fps_target=20; the
high-minimum and slow-source cases are explicitly different workloads.
| Case | Codec | Source / stream / minimum FPS | Distinct FPS | Original render age, mean ms | Estimated presentation age, mean / p95 ms | Same-image repeats | Wire / fresh gaps |
|---|---|---|---|---|---|---|---|
| repeat-hevc-on1 | HEVC | 60 / 60 / 60 | 59.793 | 11.292 | 7.343 / 11.288 | 0 | 0 / 6 |
| repeat-hevc-old | HEVC | 60 / 60 / 60 | 57.700 | 16.713 | 13.269 / 22.959 | 42 inferred | 0 / 42 |
| repeat-hevc-on2 | HEVC | 60 / 60 / 60 | 59.860 | 10.317 | 7.210 / 11.157 | 0 | 0 / 5 |
| repeat-av1-60 | AV1 | 60 / 60 / 60 | 60.000 | 13.428 | 7.321 / 9.092 | 0 | 0 / 0 |
| repeat-av1-120 | AV1 | 120 / 120 / 120 | 119.603 | 15.608 | 7.334 / 11.421 | 1 | 0 / 23 |
| repeat-slow | HEVC | 30 / 60 / 60 | 30.025 | 29.907 | 22.907 / 37.708 | 305 | 1 / 5 |
| repeat-normal60 | AV1 | 60 / 60 / 20 | 59.976 | 12.522 | 8.027 / 12.570 | 0 | 0 / 4 |
| repeat-normal120 | AV1 | 120 / 120 / 20 | 120.006 | 14.906 | 6.633 / 10.219 | 0 | 0 / 10 |
The revised HEVC runs remove the same-image repeats observed in the matching old case and lower estimated presentation age by 5.926/6.059 ms. This overlaps with the pacing improvements above and must not be added to them as a separate end-to-end gain. Repeated pixels in new capture publications still occur. The 30 FPS source with a forced 60 FPS repeat minimum delivers 60.600 encoded FPS, retains every source picture, and has five fresh-picture intervals above 50 ms; it is not a zero-jitter pass. Its age cannot be compared directly with the earlier 30 FPS tests using minimum 20. No same-image submission in the new traces decoded as a different picture, supporting the trace's identity distinction.
The revised source passed 246 ordinary tests, with 26 environment checks ignored by default, plus Clippy with warnings denied and the release build. New tests cover bounded repeat waits and fallback conditions. Subsequent direct-WGC and helper-failure checks on this same executable completed as follows; installation and publication remain separate steps.
Direct WGC without the user helper passed 59.930/120.001 distinct FPS at requested 60/120 FPS, with zero decoder errors, long wire intervals, capture restarts or compute fallbacks. Original render ages averaged 14.310/14.315 ms and estimated presentation ages 7.738/6.041 ms. These cases retain the normal minimum of 20 FPS.
The final HEVC helper-failure check repeated the original explicit 60 FPS
minimum. Resource release took 31 ms and WGC reopened in 317 ms; the client
remained connected and decoded all 1,157 received frames without errors.
Including the forced outage, the whole steady window delivered 58.691 FPS and
58.348 distinct FPS, passing the unchanged normal 58.2 freshness gate. It
contains one 379.469 ms wire gap, five repeated pictures and 24 skipped source
pictures. Nineteen skipped pictures span the outage itself; five occur during
immediate encoder recovery. The preceding steady segment produced 59.343
distinct FPS; after the first recovery second, 59.996 distinct FPS resumed with
no omitted source pictures. Whole-window render age averaged 9.781 ms and
estimated presentation age 7.224 ms. Actual source presentation was uniform in
this run. Exact records are direct-results.json,
final-helper-recovery2/recovery-result.json and
final-recovery-analysis-20261006/RECOVERY2_SEGMENTS.json.
Three further official Qt 6.2.0 checks against the preceding
cbc837071d1b27a02050a3db1f16ec18aa444e54b4ecacd49c33b24534491ca6 executable
verified the client's AV1 hardware-decoder, cropping and D3D11-renderer
metadata path: 1920x1080 at 8-bit and 10-bit, and 1968x2184 at 8-bit. The
client reported cropping coded 1920x1082 to 1920x1080 and 1984x2186 to
1968x2184, with no decoder errors and decoded/rendered rates near the requested
60 FPS. This shows the official client's handling of padding; it does not
change the failed raw elementary-bitstream geometry gate. Pixel readback, edge
correctness, native HDR color and display accuracy were not checked.
Raw plans, per-frame receiver CSVs, source presentation reports, acceptance
results and runtime provenance are under
D:\CodexArtifacts\butterpollo-latency-compat-20261006. The
phase-case-analysis-20261006/RESULTS.json and COMPARISON.json retain all nine
cases and raw gates; stress-phase-review-20261006/RESULTS.json retains the six
follow-ups. guard-case-analysis-20261006 retains the complete thirteen-case
comparison and deadline reconstruction; qt62/cases/official-fixed-3/summary.json
retains the exact-client compatibility matrix. repeat-case-analysis-20261006
and final-recovery-analysis-20261006 preserve repeat and recovery attribution.
trace-review-20261006 and slow-phase-review-20261006 preserve earlier trace
attribution and replay limitations. No result here validates the remote
RX 9070 XT/Wi-Fi report, sustained gameplay, native HDR accuracy or client
scanout. Recovery coverage is limited to the specific checks described above;
later codec fixes require their own exact-binary validation.
October 6 follow-up: saturation, HDR state and compatibility #
These follow-ups use evidence under D:\CodexArtifacts\butterpollo-remaining-20261006. The first rc.10 test host is SHA-256 93a681c26f8e767212e3580260d641658e1d871257b64aadd97b511e0e31bcbf. Its ordinary suite passed 257 tests with 27 environment-dependent tests ignored by default; Clippy with warnings denied and the release build passed.
The final hotplug-protection revision passed 263 ordinary tests with 27 excluded by default, including six new hotplug tests. Formatting, Clippy with warnings denied and release builds passed. Its host SHA-256 is 3b6d6e3299cc57383c4ca5aa373a0bfbb721218afb44114c81dbbdd61a9bd89c. The earlier executable hash and SDR result below identify their own tested build; final package and installation provenance are recorded separately.
Saturation diagnosis and queue-drain rejection #
Four serial cases ran the unchanged rc.9 executable d22e446dd103592c09cd9bf11da3a76d355ab1783a18862d5d3b9f8b31a3237a in baseline/drain/drain/baseline order. The only case setting changed was wgc_drain_to_newest=false/true/true/false. All used the WGC helper and compute copy, implicit default source-phase pacing, AV1 1280x720 at 60 FPS and 20 Mbps, the 2560x1440 physical desktop at 120 Hz, minimum_fps_target=20, a 20-second stream with five-second warmup, and the same bounded uncapped synthetic GPU workload. The workload rendered at 184.5–184.6 FPS in all four cases. The freshness floor remained 58.2 FPS.
| Case | Actual source presentation FPS | Distinct FPS | Original render age, mean ms | Estimated presentation age, mean ms | Wire gaps above 25 ms | Host processing, mean ms |
|---|---|---|---|---|---|---|
| rc9-saturated-base-a | 58.727 | 54.063 | 31.034 | 17.251 | 52 | 1.760 |
| rc9-saturated-drain-a | 59.518 | 52.632 | 28.355 | 15.252 | 79 | 1.757 |
| rc9-saturated-drain-b | 59.689 | 54.101 | 27.212 | 15.405 | 58 | 1.761 |
| rc9-saturated-base-b | 59.691 | 54.808 | 29.457 | 16.888 | 34 | 1.760 |
All four decode without errors, capture restart or compute fallback, but all fail the unchanged distinct-picture floor. Drain-a also fails the delivery floor at 57.304 FPS. Draining reduces picture age by roughly 1.5–2.0 ms while losing more fresh pictures and increasing long wire intervals. It remains disabled by default. The result must not be represented as a saturation fix.
The newer timing analysis checks DXGI presentation identity separately from uniform requested cadence. Source frame IDs match Present call IDs, and actual presentation remains one frame behind the calls throughout each measured window. Those checks permit the same presentation-QPC estimate described above even though the source's frame intervals are irregular. The older table's withheld saturated estimates are retained as the original analysis; a separate reanalysis estimates 17.860/17.757 ms for guard-saturated60-1/2. Neither the new estimate nor this reanalysis changes a raw age or acceptance gate. This approximation excludes input and display scanout and has roughly 0.1 ms claim-clock alignment precision.
The measured encoder/host processing stays near 1.8 ms. Under saturation, both capture publication delay and frame-selection delay rise. In the current baseline cases, all 68/72 omitted source pictures have an estimated matching capture publication. With drainage, 73 of 100 and 51 of 80 omitted pictures no longer have an estimated matching publication. This is inferred identity for unclaimed images: selecting the most recent actual presentation at least 0.25–1 ms before the capture timestamp agrees with every known claimed picture in these four runs, but unclaimed pixels were not read back.
Fixed-publication replay does not support disabling prediction globally or shortening the minimum claim spacing as a default fix. An ideal content deduplication oracle improves modeled baseline freshness from 54.057/55.066 to 56.646/58.287 FPS at the existing spacing, but assumes zero GPU comparison cost and uses picture identity unavailable to production. It retains source irregularity and does not pass every case. Exact GPU comparison therefore remains a diagnostic candidate, not a default capture optimization.
Raw cases and unchanged acceptance are in saturation/saturation-results.json. RC9_ABBA_REPORT.md and RC9_ABBA_DIAGNOSIS.json in that directory retain stage timing and provenance; RC9_ORACLE_AND_LIFETIMES.json retains the model's assumptions. No game, display mode, installed profile or service was changed by these four tests; the installed host was checked idle and Warhammer absent before each run.
HDR-state correction and current SDR validation #
The candidate reads the explicit modern HDR capability/request bits and active color mode, using the dedicated HDR setter rather than treating any Windows Advanced Color state as HDR. Its ABI is checked against Microsoft's SDK definitions. Legacy query/set behavior is retained for operating systems without the modern query; a failed modern setter does not fall back to changing WCG. A successful request must settle in both requested and active state. Temporary query errors are retried; a timed-out owned request is rolled back while preserving an observed competing request. Unit checks include pending-state cancellation, failed disable rollback and explicit rollback failure.
The actual physical display path exposed modern flags 69 with HDR-support bit 4 clear and active mode SDR, while the legacy value 5 reported general Advanced Color/WCG capability. The corrected probe refuses to claim native HDR from this state. Earlier attempted physical HDR color results that used BGRA8 capture are not native HDR validation.
One rc.10 AV1 SDR check, rc10-sdr-av1-720p60-1, passed on the unchanged physical desktop using default WGC/helper/source-phase behavior and minimum 20 FPS. All 1,172 received frames decoded without errors. Its steady window delivered 60.002 FPS and 871 consecutive distinct source pictures, with no repeats, omitted IDs or wire gaps above 25 ms. Host processing averaged 1.815 ms and original render-start-to-decoder age averaged 9.508 ms. DXGI presentation counters remained zero in this smoke, so actual presentation timing is unavailable; the raw age must not be relabeled as presentation-to-decoder latency. The decoder barcode gate is independent of those missing counters. Physical settings and 120 Hz refresh were unchanged after cleanup.
The final host repeated this default-path SDR check in rc10-sdr-av1-720p60-2. All 1,169 received frames decoded; the steady window delivered 59.999 distinct FPS with 868 consecutive unique picture IDs, no repeats, omitted IDs or long wire gaps. The physical mode, 120 Hz refresh and SDR state were unchanged. DXGI statistics again supplied no usable presentation-ID/QPC pairs, so this is freshness and compatibility evidence without an actual presentation-to-decoder latency claim. The original 58.2 FPS floor was retained.
The initial native virtual HDR attempts below failed before pixel validation. The reviewed isolated harness uses one owned extended HDR display, full-window native 1280x720 motion, independent frame readback and the original color thresholds: luma/chroma mean absolute error at most three 10-bit code values, contrast 0.98–1.02 and saturation 0.95–1.05. The first normally elevated attempt failed the VDD access preflight with AccessDenied before starting its private host, creating a display, pairing, capture or encoding. Its failure is not a codec or HDR pixel failure. No native HDR pass or relaxed threshold follows from it. A later native HDR result requires its own recorded capture and pixel evidence.
Subsequent isolated service-context attempts did open WGC with RgbaF16 source pixels. One stopped because ANSI log formatting obscured the harness's readiness check; the corrected reader preserves all acceptance tokens and thresholds. The next attempt detected a second newly active display, the previously dormant HISENSE, alongside the owned HDR virtual display. The exact physical/topology gate rejected it before motion began. The original Odyssey settings were unchanged, and teardown restored the original topology in 2.327 seconds. Neither attempt provides a native HDR color-roundtrip pass.
The source fix captures available inactive targets immediately before owned VDD creation/recreation and protects those same identities through HDR startup. It prunes only reactivated dormant targets from a fresh active topology, keeping the supplied modes, path priority and clone relationships. Strict temporary SetDisplayConfig flags neither save the topology nor allow Windows to retime the supplied modes. The owned display is followed by driver target and monitor path, so GDI display-name reuse cannot redirect its HDR setup or recovery. A second semantic snapshot check rejects an observed concurrent layout change. The final settle phase requires 500 ms of quiet within one 1.5-second deadline, retained across retries; expiry prevents further mutation. Protection ends after startup. Windows has no atomic topology compare-and-set, so a narrow last-call race remains, and intentional activation of the same dormant target during this short window cannot be distinguished from automatic topology recall. Six pure tests cover the selection, clone/mode, identity, race and deadline rules. The reporter's phone behavior has not been reproduced by these local tests; final native pixel-validation evidence follows separately.
Final native virtual HDR pixels, excluding physical-panel calibration #
Two isolated service-context runs used the final host above, each with one owned extended 1280x720 HDR virtual display, full-window 60 FPS motion, WGC helper capture and AMF at 40 Mbps. The stream ran for 22 seconds with a five-second warmup and minimum repeat target 20 FPS. Both reported active modern HDR and RgbaF16 capture; independent software decoding supplied a ten-bit frame dump at received frame 480. No capture fallback or restart occurred. These are native HDR capture/conversion/codec pixel checks, not physical-panel calibration, client HDR rendering, scanout or game-content validation.
| Codec | Received/decoded frames | Steady distinct FPS | Unique pictures in steady window | Repeats / skipped IDs | Wire intervals above 25 ms |
|---|---|---|---|---|---|
| HEVC Main10 HDR | 1,281 / 1,281 | 60.001 | 985 / 985 | 0 / 0 | 0 |
| AV1 Main10 HDR | 1,302 / 1,302 | 60.026 | 1,000 / 1,000 | 0 / 0 | 31 |
| Codec | Luma MAE, 10-bit codes | Chroma MAE, 10-bit codes | Contrast slope | Saturation ratio |
|---|---|---|---|---|
| HEVC Main10 HDR | 0.393 | 0.404 | 0.9995 | 1.0059 |
| AV1 Main10 HDR | 0.387 | 0.428 | 0.9983 | 0.9976 |
Both black patches decoded to code 64 and white patches to 509 against 509.08 expected. The original limits were retained: black error at most two codes, white error at most three, luma/chroma MAE at most three, contrast 0.98–1.02 and saturation 0.95–1.05. Both passed the unchanged 58.2 distinct-FPS floor and decoded without errors. No latency improvement is claimed from these two HDR runs.
Each run passed 11 during-stream topology samples and a mandatory check before cancellation, with the original physical settings intact and only the owned virtual display added. Logs in both runs show the guard restoring the dormant HISENSE target after creation and again after HDR settings. This validates correction of that reproduced startup recall, not prevention of every transient activation or the remote phone report. Final teardown removed the virtual display and restored the original topology; the installed service stayed running and its configuration hash was unchanged. The temporary dispatcher tasks were removed. HEVC/AV1 complete fixture durations were 35.493/35.280 seconds, including 0.401/0.439 seconds of cleanup.
Full results are system-context/hdr-9dae55fb791f48f2b23414231da970dc/ coordinator-result.json (HEVC) and system-context/hdr-4edd39d9a9d441bb8f179768b3ee1d8f/coordinator-result.json (AV1). Their matching hdr-virtual case directories retain raw host/receiver logs, pixel dumps, color results, topology samples and before/after snapshots.
Repeat HDR runs and four-run totals #
Two further runs repeated AV1 and then HEVC with the same final executable, 1280x720/60 native FP16 virtual display, 22-second duration, five-second warmup and unchanged freshness/color gates. Together with the original pair above, the recorded order is HEVC/AV1/AV1/HEVC.
| Repeat codec | Received/decoded frames | Steady distinct FPS | Unique pictures in steady window | Repeats / skipped IDs | Wire intervals above 25 ms |
|---|---|---|---|---|---|
| AV1 Main10 HDR | 1,288 / 1,288 | 59.999 | 989 / 989 | 0 / 0 | 0 |
| HEVC Main10 HDR | 1,302 / 1,302 | 60.001 | 999 / 999 | 0 / 0 | 0 |
| Repeat codec | Luma MAE, 10-bit codes | Chroma MAE, 10-bit codes | Contrast slope | Saturation ratio |
|---|---|---|---|---|
| AV1 Main10 HDR | 0.401 | 0.503 | 0.9984 | 0.9976 |
| HEVC Main10 HDR | 0.374 | 0.434 | 0.9995 | 1.0057 |
The AV1 repeat's decoded reference frame also verifies black and a 100-nit white patch. The source renders white as 1.25 in linear scRGB, where 1.0 represents 80 nits. These are mean limited-range ten-bit PQ luma codes from received frame 480 (source frame 468):
| Source patch | Expected Y′ | Decoded Y′ |
|---|---|---|
| Black | 64.00 | 64.00 |
| 100-nit white | 509.08 | 509.00 |
The values come from the AV1 repeat's colour-result.json and describe one
reference frame. The four-run frame total below counts successful decoding.
Both repeats passed their independent pixel checks, all 11 during-stream topology samples, the pre-cancel topology check and complete restoration. The installed service remained running and its configuration hash was unchanged. Across all four HDR runs, 5,173 received frames decoded successfully with zero decoder errors, and 44 during-stream topology samples passed, plus four pre-cancel checks. Adding the final SDR check's 1,169 decoded frames gives 6,342 clean decodes across five rc.10 runs. These are whole-run decode totals; the fresh-picture counts in the tables cover only the steady windows.
The four independently decoded reference frames have luma MAE 0.374–0.401 and chroma MAE 0.404–0.503 in 10-bit code values. Both recorded mean-error metrics stay below 0.51 code values; this describes those four pixel dumps, not a per-pixel maximum or a color analysis of all 5,173 frames.
The first AV1 run's 31 long arrival intervals remain in the original table. The repeat adds an observation with zero intervals above 25 ms without replacing that uneven run or establishing universally even cadence. All source, scope and physical-panel limitations above remain unchanged.
The rc.10 validation record (VALIDATION.json of the rc.10 release, since removed) contains all four cases in current_validation.native_hdr.fixtures. Repeat raw results are system-context/hdr-88d67d5ef35c450da7e4badb41d069d5/ coordinator-result.json (AV1) and system-context/hdr-f87e34f8d65c44cfa13e2c8d4b7d5d52/coordinator-result.json (HEVC), with full evidence in their matching hdr-virtual case directories.
Native controller and Moonlight 6.2 command checks #
One explicitly selected native HID test passed with the installed signed libvirtualgamepad 0.1.0.39 driver. It sequentially created an owned neutral DualShock 4 and DualSense and read their newly enumerated HID reports through the production packet decoder and Gamepads::apply path. It verified two independent primary-pad contacts, stable movement identity, release/reuse, cancel-all, rejection of a third occupied contact, and removal on drop. Secondary-surface events with matching pointer IDs did not alter either primary contact. The first probe's VID/PID pathname assumption was corrected to inspect HID attributes; production input behavior did not change.
This validates the existing fallback and primary-pad behavior, not a second native touch surface. The signed driver's protocol has a contact index but no independent surface index. A genuine second surface needs a compatible device profile, versioned driver protocol and signed package; it cannot be represented by the second finger of the primary pad. No live physical two-pad controller, network-delivered touch event or game-specific mapping was tested. Evidence is touchpad/INVESTIGATION.json and touchpad/native-test-attempt2.log; the selected test executable SHA-256 is 5b01008db084c9aad9ed1cfa886b2b6c2a41772230b7187d4403420be9b031b0.
Further checks of the unchanged official Windows Moonlight Qt 6.2.0 executable resolve part of the earlier command-line uncertainty. Against a private rc.9 software-only host, plain listing and cached CSV listing exited naturally with code zero in 0.204/0.207 seconds. Three running-app quit cases exited naturally in 3.847–3.849 seconds, and idle quit in 0.811 seconds. The fixture used a verified private Windows desktop with normal GUI startup behavior: the earlier hidden-startup flag suppressed the client's GUI polling, and its half-second post-cancel wait was too short. No physical desktop switch or user input was needed.
An unmodified official client still hangs on a cold-cache CSV listing in the controlled mock case; the 65.143-second observation was stopped and is retained as a failure. Source/debugger evidence associates it with asynchronous artwork workers during Qt thread-pool shutdown. Plain listing or a normally populated artwork cache avoids that tested path. No host protocol workaround or official client patch was shipped. These command checks contain no RTSP stream and do not replace the earlier codec matrix or validate the new candidate's codecs. cli/SUMMARY.json preserves exact client/host hashes, mock controls and six passing pure lifecycle tests.
An optional Moonlight 6.2.0 source patch now makes CSV listing read existing artwork without starting background artwork downloads. Eight focused regression cases pass against the actual patched BoxArtManager translation unit with real Qt 6.4.2 on Linux; three negative controls expose the original background-worker behavior. This is source-only validation with stubbed HTTP/computer and cache boundaries, not a full Windows client build or an end-to-end patched-client result. It does not replace the official executable or erase its recorded cold-cache failure.
The read-only environment collector now records OS, GPU/driver, physical adapter/link counters, host version/hash and service state without changing settings or uploading data. Its local output is environmental evidence only. Neither it nor these local tests reproduces the remote RX 9070 XT over Wi-Fi report, establishes NVIDIA execution, or supplies physical-panel calibration and client-scanout validation.
October 7: rc.17 against rc.2 on the October 4 fixture #
The 1080p at 60 fps fixture again (run-motion.py
from day-work-20261002, copied with only its tool paths changed): RX 7900
XT, HEVC 10-bit HDR at 1920×1080 and 60 fps, 20 Mbps requested, native AMF
at ultra-low latency with speed, compute conversion, a 120 Hz virtual
display, and the same game-like load (gpu_load 45 1000 0 200) on the load
rows. The rc.2 binary is the one measured on October 4 (5747318fae0a4581),
rc.17 the installed release (a8f491c3257431fe). Each run started as SYSTEM
in the signed-in session, as the service starts the host: the virtual
display driver now refuses an administrator. Rows alternated in one batch,
three runs each. The DDX rows capture with Desktop Duplication as on
October 4; the WGC row is rc.17's default capture.
| Picture age, mean / p95 (ms) | rc.2, DDX | rc.17, DDX | rc.17, WGC |
|---|---|---|---|
| Idle | 14.69 / 15.77, 14.91 / 17.16, 14.62 / 15.47 | 14.78 / 16.00, 14.66 / 16.04, 14.69 / 15.93 | 14.93 / 16.22, 14.80 / 15.98, 14.84 / 15.91 |
| Load | 35.64 / 44.80, 35.19 / 44.59, 36.10 / 46.53 | 35.72 / 46.31, 35.01 / 43.63, 35.34 / 44.38 | 33.15 / 43.06, 33.34 / 43.70, 33.77 / 44.82 |
| Load, new pictures per second | 57.6, 58.8, 57.6 | 58.2, 59.7, 59.2 | 58.3, 58.1, 58.4 |
| Load, host latency | 5.79, 5.49, 5.68 ms | 5.20, 5.70, 5.53 ms | 1.87, 1.85, 1.86 ms |
On the same capture path rc.17 delivers the picture when rc.2 did, idle (14.7 ms both) and beside the load (35.4 against 35.6 ms, within the spread of the runs). With its default WGC capture it delivers it 2.2 ms sooner beside the load than rc.2 (33.4 against 35.6 ms; 43.9 against 45.3 ms at the 95th percentile), and the host latency Moonlight reports falls from 5.7 to 1.9 ms. Idle, all three are equal; every idle run delivered 60 new pictures a second without a repeat.
rc.2 measured 35.6 ms beside the load here against 42.4 ms on October 4,
more than the difference between the versions: absolute values move
between batches, so only compare rows measured together. Beside the load
the probe rendered at 60-66 Hz instead of 120, as on October 4, so the
fixture's source-rate check fails on load rows as it did then; their
picture-age statistics are complete. One rc.2 idle run's probe exited
with an error after the stream had ended. Artifacts: bench-rc17\cmp-*.
rc.19 (88e8fd322e8b1a6a), three idle runs with WGC later the same day on
the same fixture: 14.95 / 16.49, 14.96 / 16.14 and 14.74 / 15.94 ms, 60.4
new pictures a second without a repeat, as rc.17. Artifacts:
bench-rc17\rc19wgc-*.
October 7: PyroWave conversion on the compute queue #
PyroWave's colour conversion (RGB to its Y, Cb and Cr planes) ran as three
D3D11 draws on the graphics queue. Beside the game-like load
(gpu_load 30 1000 0 200), examples/performance.rs --codec pyrowave --hdr --yuv444 --records at 1080p, 120 fps and 400 Mbps measured 5.6-5.8 ms per
frame against 0.47 ms idle. PyroWave's own GPU work, from
pyrowave_device_report_performance_stats, was 0.11 ms idle and 0.19 ms
under the load; a CPU wait on the conversion's fence showed where the rest
went: 5.1 ms under the load against 0.15 ms idle.
The planes are now written by one compute shader pass on the D3D12 compute
queue the AMF path uses (pyro_cs, high priority), into the same textures
Vulkan imports, with the shared fence ordering the two queues. The planes
are byte-identical to the graphics pass for HDR and SDR, 4:4:4 and 4:2:0,
three output sizes and a composited pointer
(compute_planes_match_the_pyrowave_graphics_planes).
| Per frame, RX 7900 XT | Graphics queue | Compute queue |
|---|---|---|
| Idle, repeat-frame throughput | 0.47 ms | 0.47 ms |
| Beside the load, repeat-frame throughput | 5.62, 5.75 ms | 0.57, 0.54 ms |
| Beside the load, paced at 120 fps | 4.62 ms | 0.71 ms |
| Paced, bytes per frame idle / beside the load | 155,780 / 155,960 | 155,802 / 155,834 |
The fixture client does not decode PyroWave, so no PyroWave stream was played end to end; equal planes and equal frame sizes show the encoder reads the same pictures.
October 7: two frames in the encoder #
The stream claimed a picture whenever the pacer allowed one, and AMF only pushed back at eight frames in flight. When one encode takes longer than a frame interval, up to eight frames queued and each was that much older when it came out, without any more frames per second for it. Since rc.19 a claim waits while two frames are in the encoder, enough to keep both of a Radeon's encoder instances busy, and the host takes the encoder's output first.
5120×1440 HEVC at 240 fps and 150 Mbps on the RX 7900 XT, whose encoder manages 220 fps there; two alternating runs each:
| Encoder saturated | rc.18 | rc.19 |
|---|---|---|
| Present to send, mean | 42.7 ms | 11.1 ms |
| Present to send, p99 | 45.9 ms | 13.3 ms |
| Host latency | 39.6 ms | 8.9 ms |
| Frames per second | 220 | 220 |
Streams the encoder keeps up with are unchanged: H.264, HEVC, AV1 and HEVC VRR at 2560×720 and 60 fps pass the release e2e.
October 7: AMF low-latency mode and AV1 latency mode #
amd_lowlatency_mode (AMF's LowLatencyInternal, H.264 and HEVC) and
amd_av1_latency_mode (Av1EncodingLatencyMode) default to auto, which
leaves the property unset. Whether forcing them on is worth a default was
measured with examples/performance.rs --synthetic 16 --paced at 2560×1440
and 120 fps, 20 Mbps, speed, vbr_latency, on the RX 7900 XT with driver
32.0.31041.1004. The probe reports submission to output; the settings line
the host logs shows what the driver applied.
With the default usage, ultra-low latency, the driver already applies both:
LowLatencyInternal=1 and Av1EncodingLatencyMode=3 (lowest) with the
properties unset. Forcing them changed nothing. Three alternating runs each:
| Ultra-low latency, mean / p95 (ms) | auto |
forced on |
|---|---|---|
| H.264 | 3.12 / 3.58, 3.13 / 3.50, 3.08 / 3.40 | 3.19 / 3.64, 3.10 / 3.37, 3.09 / 3.46 |
| HEVC | 3.13 / 3.62, 3.17 / 3.64, 3.17 / 3.64 | 3.12 / 3.48, 3.16 / 3.62, 3.31 / 3.69 |
| AV1 | 2.61 / 3.11, 2.62 / 3.11, 2.62 / 3.12 | 2.66 / 3.11, 2.64 / 3.12, 2.65 / 3.13 |
Every forced run produced the same number of bytes as its auto pair, to
the byte. The other usages, one run each:
| Usage | Codec | Driver's choice | auto, mean / p95 |
Forced on |
|---|---|---|---|---|
| Low latency | HEVC | LowLatencyInternal=0 |
3.59 / 3.92 ms | 3.16 / 3.63 ms |
| Low latency | AV1 | mode 0 (none) | 3.89 / 4.28 ms | 2.64 / 3.13 ms |
| Low latency, high quality | HEVC | LowLatencyInternal=1 |
3.50 / 3.87 ms | 3.58 / 3.83 ms |
| Low latency, high quality | AV1 | mode 3 (lowest) | 3.48 / 3.76 ms | 3.51 / 3.83 ms |
| Transcoding | HEVC | LowLatencyInternal=0 |
3.43 / 3.76 ms | 3.04 / 3.34 ms |
| Transcoding | AV1 | mode 0 (none) | 3.92 / 4.22 ms | 2.60 / 2.95 ms |
Forcing them helps only with a usage that leaves them off: about 0.4 ms per
HEVC frame and 1.3 ms per AV1 frame here, at the same output size. Both stay
auto. A default of on would change nothing for the default usage and
override a user's deliberate choice of another one, and writing
LowLatencyInternal explicitly froze HEVC encoding on RX 9000 cards with
Adrenalin 26.5 (video stalls while audio plays), the reason the native AMF
encoder stopped forcing it. The setting descriptions now say which usages
already have them on. The fallback console page saved the low-latency switch
as amd_low_latency_internal, a key nothing reads; it now saves
amd_lowlatency_mode and offers the AV1 latency mode too.
October 7: how much bitrate PyroWave needs #
A user streamed PyroWave at 720p60 with Moonlight's bitrate at 10 Mbps and
saw blurred grey blocks: a fifth of a bit per pixel per frame leaves room for
only the coarsest brightness layers. To see what the encoder takes when the
bitrate does not limit it, examples/performance.rs --codec pyrowave --records --paced --bitrate 1000000 at 1920×1080 and 60 fps, four seconds
each, on the RX 7900 XT:
| Picture | Per frame | Bits per pixel | Mbps at 60 fps |
|---|---|---|---|
| Desktop, SDR 4:2:0 | 624 KB | 2.41 | 300 |
| Desktop, HDR 4:4:4 | 478 KB | 1.84 | 229 |
Moving test picture (--synthetic 16), SDR 4:2:0 |
1,822 KB | 7.03 | 875 |
| Moving test picture, HDR 4:4:4 | 1,344 KB | 5.18 | 645 |
The PyroWave README targets "~200+ mbit/s" on a wired LAN. A 720p60 HDR stream to Nonary's client at 149 Mbps (2.7 bits per pixel) worked, at 60 fps and 0.68-0.78 ms from present to encoded frame.
The initial warning used one bit per pixel per frame, less than half of
what that desktop took: once when the stream starts and when the client lowers
the bitrate (PyroWave has too little bitrate), and on the console's stream
card. That is about 55 Mbps at 720p60, 125 Mbps at 1080p60 and 500 Mbps at
4K60 (pyrowave::minimum_kbps). The representative-picture measurements
below replace this initial threshold. The host does not raise the
bitrate itself: the client's choice may reflect its network.
October 7: PyroWave decoded end to end #
The ignored pyrowave::decode_test::pyrowave_decoded_end_to_end test sends
known pictures through Encoder::new_gpu_options and encode_gpu, codec
3, then decodes with the pinned PyroWave 0.6 SDK, bitstream 186f0393.
The decoder has its own Vulkan device on the same AMD adapter. It writes
three imported D3D11 textures through pyrowave_decoder_decode_gpu_buffer;
a shared fence completes before D3D11 staging readback. SDR8 uses R8 UNORM,
and both ten-bit formats use R16 UNORM, retaining the decoder's precision.
The sender's VideoPacketizer::encode_pyrowave produces the datagrams,
including its normal 20% critical FEC for records, with optional detail FEC
disabled. A separate receive parser orders the data shards, removes the
transport header and final padding, then
feeds either the length-prefixed codec packets or the individual records
to pyrowave_decoder_push_packet. Record padding is skipped. The test
requires complete-frame readiness and rejects early readiness. This covers
complete, unencrypted delivery; it does not exercise network loss recovery,
socket pacing or client display.
Two deterministic charts contain colour bars, smooth gradients, one- and two-pixel text strokes, and independent RGB noise between 25% and 75%. An independent double-precision CPU reference converts the actual uploaded pixels to full-range BT.709 SDR or BT.2020 PQ HDR, including the centred 2x2 chroma average for 4:2:0. SDR10 starts from FP16 scRGB, rather than eight-bit pixels in a ten-bit container. HDR tests both FP16 scRGB with 1000-nit white and packed ten-bit PQ input. PSNR uses peaks of 255 or 1023; maximum errors are in those code values, including fractional codes from R16 readback. HDR PSNR is in PQ code space, not linear light.
Each case alternates graphics/compute, compute/graphics, graphics/compute. It asserts that compute actually ran, and that every decoded sample is identical across both conversions, both framings and all three batches. The test resets only the encoder's previous timestamp before each sample, using its normal first-frame budget of 1/60 second: CPU reference and readback time cannot inflate the plain-packet budget. These are picture quality measurements, not sustained throughput measurements.
The 400, 125 and 30 Mbps cases run at both sizes, along with a 1000 Mbps control. At 1000 Mbps the test requires at least 40 dB on every plane and 35 dB on every chart panel; at 400 Mbps it requires 24 dB on every plane. The control is needed because 400 Mbps still loses fine text and noise at 1080p, especially in 4:4:4. These are regression limits for this chart, not recommended quality targets or assertions that the codec is lossless.
On an AMD machine, use the usual Rust SDK environment, then run from the
workspace root. On this machine the environment script is
%USERPROFILE%\.codex\artifacts\butterpollo-rust-20260930\performance-probe\rust-env.ps1.
BUTTERPOLLO_PYROWAVE_ROOT\share\pyrowave-shared\build-info.txt must name
commit 186f0393b77f7755953b5ecde994bb1cec2e4155.
. "$env:USERPROFILE\.codex\artifacts\butterpollo-rust-20260930\performance-probe\rust-env.ps1"
cargo test -p butterpollo-windows --lib --no-run --target-dir target\qa
if ($LASTEXITCODE) { throw 'Test build failed' }
Copy-Item "$env:BUTTERPOLLO_PYROWAVE_ROOT\bin\libpyrowave-shared-0.dll" target\qa\debug\deps\
cargo test -p butterpollo-windows --lib pyrowave::decode_test::pyrowave_decoded_end_to_end --target-dir target\qa -- --ignored --exact --nocapture --test-threads=1 2>&1 | Tee-Object target\qa\pyrowave-decode.log
if ($LASTEXITCODE) { throw 'PyroWave decode test failed' }
PYROWAVE_RESULT lines contain JSON with per-plane PSNR and maximum error,
frame bytes and datagram counts. panels lists bars, gradient, text and noise
for each of Y/Cb/Cr; a null PSNR with zero maximum error means an exact match
(infinite PSNR). Before each batch, PYROWAVE_BATCH records the installed host
log's last connect/disconnect event, or notes that its log is absent. Check
those lines when comparing a machine shared with streams or other GPU work. The
test only reads that log and does not start a host or change its settings.
On October 7, 2026, the AMD Radeon RX 7900 XT with driver 32.0.31041.1004 passed all 1,152 decodes in 620.90 seconds in the debug test build. All 288 batch checks found the last stream event was a disconnect at 12:57:29 UTC. Other GPU work was not controlled. The maximum compute/graphics difference was zero storage units, including all 16 bits of the ten-bit output planes. Both framings and all three repeats also matched exactly: repeat spread was 0.00 dB PSNR and zero maximum-error difference for every picture.
Each row below gives the lower PSNR and larger maximum error of the two pictures, separately for each plane. Repeated measurements do not change those values. The maximum error uses 8-bit codes for SDR8 and 10-bit codes for SDR10/HDR10; it is not a count of R16 storage units.
| Size | Format | Chroma | Mbps | PSNR Y/Cb/Cr (dB) | Max error Y/Cb/Cr (codes) |
|---|---|---|---|---|---|
| 1920x1080 | SDR8 | 4:2:0 | 1000 | 80.62/81.16/80.35 | 1.0/1.0/1.0 |
| 1920x1080 | SDR8 | 4:2:0 | 400 | 33.34/43.05/42.06 | 64.0/36.0/27.0 |
| 1920x1080 | SDR8 | 4:2:0 | 125 | 22.34/28.37/29.01 | 132.0/93.0/124.0 |
| 1920x1080 | SDR8 | 4:2:0 | 30 | 18.64/23.33/26.13 | 186.0/114.0/129.0 |
| 1920x1080 | SDR8 | 4:4:4 | 1000 | 44.78/41.20/40.87 | 23.0/24.0/27.0 |
| 1920x1080 | SDR8 | 4:4:4 | 400 | 28.83/25.90/27.26 | 96.0/97.0/92.0 |
| 1920x1080 | SDR8 | 4:4:4 | 125 | 21.89/23.28/24.87 | 141.0/103.0/116.0 |
| 1920x1080 | SDR8 | 4:4:4 | 30 | 18.63/21.22/23.40 | 186.0/127.0/135.0 |
| 1920x1080 | SDR10 | 4:2:0 | 1000 | 67.65/68.04/67.97 | 3.1/3.0/3.0 |
| 1920x1080 | SDR10 | 4:2:0 | 400 | 33.32/43.35/42.19 | 258.8/145.5/97.4 |
| 1920x1080 | SDR10 | 4:2:0 | 125 | 22.32/28.42/29.00 | 528.8/374.7/492.3 |
| 1920x1080 | SDR10 | 4:2:0 | 30 | 18.63/23.36/26.14 | 745.0/456.2/517.5 |
| 1920x1080 | SDR10 | 4:4:4 | 1000 | 45.22/41.33/40.84 | 108.6/93.6/108.1 |
| 1920x1080 | SDR10 | 4:4:4 | 400 | 28.88/25.41/27.31 | 382.8/387.1/364.5 |
| 1920x1080 | SDR10 | 4:4:4 | 125 | 21.89/23.32/24.91 | 572.2/412.0/480.2 |
| 1920x1080 | SDR10 | 4:4:4 | 30 | 18.61/21.24/23.42 | 745.0/512.3/541.3 |
| 1920x1080 | HDR10 | 4:2:0 | 1000 | 68.12/67.87/66.92 | 3.1/2.7/2.9 |
| 1920x1080 | HDR10 | 4:2:0 | 400 | 39.49/49.67/49.24 | 155.1/79.7/34.4 |
| 1920x1080 | HDR10 | 4:2:0 | 125 | 28.93/35.42/39.77 | 294.4/144.5/83.2 |
| 1920x1080 | HDR10 | 4:2:0 | 30 | 23.49/31.17/37.98 | 466.2/176.4/101.9 |
| 1920x1080 | HDR10 | 4:4:4 | 1000 | 53.65/47.58/51.44 | 43.0/38.5/29.2 |
| 1920x1080 | HDR10 | 4:4:4 | 400 | 35.29/34.12/38.01 | 176.5/133.7/85.5 |
| 1920x1080 | HDR10 | 4:4:4 | 125 | 27.69/31.15/36.34 | 305.7/152.7/85.1 |
| 1920x1080 | HDR10 | 4:4:4 | 30 | 23.48/29.10/35.32 | 476.7/202.0/103.4 |
| 1280x720 | SDR8 | 4:2:0 | 1000 | 77.53/84.60/80.58 | 1.0/1.0/1.0 |
| 1280x720 | SDR8 | 4:2:0 | 400 | 65.69/79.82/76.16 | 2.0/2.0/1.0 |
| 1280x720 | SDR8 | 4:2:0 | 125 | 27.27/33.94/33.93 | 105.0/50.0/62.0 |
| 1280x720 | SDR8 | 4:2:0 | 30 | 19.92/24.09/26.26 | 186.0/103.0/116.0 |
| 1280x720 | SDR8 | 4:4:4 | 1000 | 77.53/62.48/62.88 | 1.0/2.0/2.0 |
| 1280x720 | SDR8 | 4:4:4 | 400 | 42.55/37.25/36.81 | 24.0/42.0/46.0 |
| 1280x720 | SDR8 | 4:4:4 | 125 | 26.04/23.86/25.89 | 109.0/97.0/105.0 |
| 1280x720 | SDR8 | 4:4:4 | 30 | 19.24/22.09/23.81 | 186.0/134.0/122.0 |
| 1280x720 | SDR10 | 4:2:0 | 1000 | 67.49/68.05/67.76 | 4.0/2.6/2.9 |
| 1280x720 | SDR10 | 4:2:0 | 400 | 65.96/68.01/67.74 | 6.5/7.2/4.8 |
| 1280x720 | SDR10 | 4:2:0 | 125 | 27.24/33.93/34.05 | 422.7/199.3/251.2 |
| 1280x720 | SDR10 | 4:2:0 | 30 | 19.91/24.20/26.28 | 745.0/410.3/470.4 |
| 1280x720 | SDR10 | 4:4:4 | 1000 | 67.49/62.36/62.34 | 4.0/5.5/5.2 |
| 1280x720 | SDR10 | 4:4:4 | 400 | 42.62/37.27/36.83 | 109.1/129.2/184.8 |
| 1280x720 | SDR10 | 4:4:4 | 125 | 26.04/23.89/25.92 | 434.2/387.2/408.1 |
| 1280x720 | SDR10 | 4:4:4 | 30 | 19.19/22.13/23.84 | 745.0/498.0/491.5 |
| 1280x720 | HDR10 | 4:2:0 | 1000 | 67.99/67.83/66.85 | 2.6/2.7/3.4 |
| 1280x720 | HDR10 | 4:2:0 | 400 | 67.99/67.83/66.85 | 2.6/2.7/3.4 |
| 1280x720 | HDR10 | 4:2:0 | 125 | 34.40/41.98/42.13 | 163.5/81.9/76.0 |
| 1280x720 | HDR10 | 4:2:0 | 30 | 25.07/31.56/38.60 | 458.5/191.8/87.4 |
| 1280x720 | HDR10 | 4:4:4 | 1000 | 67.99/61.99/61.41 | 2.6/5.6/6.8 |
| 1280x720 | HDR10 | 4:4:4 | 400 | 50.15/44.48/45.80 | 89.0/64.9/55.6 |
| 1280x720 | HDR10 | 4:4:4 | 125 | 33.18/31.73/37.17 | 288.2/144.2/86.9 |
| 1280x720 | HDR10 | 4:4:4 | 30 | 25.05/29.58/35.52 | 459.5/195.7/91.6 |
At the time of this chart test, minimum_kbps was 124,416 kbps at 1080p60
(one bit per pixel per frame).
The 400, 125 and 30 Mbps budgets are 3.215, 1.005 and 0.241 bits per pixel
per frame. At 720p60 the warning was 55,296 kbps, and the same budgets are
7.234, 2.261 and 0.543 bits per pixel per frame. These are nominal encoder
budgets; framing, FEC and network headers add bytes on the wire.
There is substantial detail loss below the warning, but these pictures do not show a sharp quality cliff at one bit per pixel. In the 1080p SDR8 4:2:0 chart, the worst text-panel luma PSNR is 30.08 dB at 400 Mbps, 18.03 dB at 125 Mbps and 13.75 dB at 30 Mbps. The corresponding whole-plane luma scores are 33.34, 22.34 and 18.64 dB. Quality is already poor at the warning's boundary; the larger drop is between 400 and 125 Mbps.
That threshold was a conservative warning about very low bitrate, not a promise of good quality above it. If it is intended to protect fine SDR text, about 3.2 bits per pixel per frame is a candidate for 4:2:0: 400 Mbps is the lowest tested 1080p rate to keep both text and whole-plane luma above 30 dB. That would be about 178 Mbps at 720p60 by pixel-count scaling, an estimate rather than a measured cutoff. Only the 1000 Mbps control clears 40 dB on all planes throughout this matrix. More rates near a proposed boundary and real desktop/game captures are needed before choosing a replacement that applies across content and chroma formats. The following representative picture sweep replaces that provisional warning.
October 7: PyroWave bitrate from representative pictures #
The ignored pyrowave::decode_test::pyrowave_quality_* tests extend the
decoded end-to-end harness above. All pictures are generated in Rust; no
downloaded images or captures are used:
- Desktop: a light editor, coloured text, navigation, toolbars, a graph, selection changes and one-, two- or four-pixel font strokes at 720p, 1080p and 4K respectively.
- Game: a sky gradient, clouds, a bright sun, stone and brick textures, foliage noise at several spatial scales, a crosshair and a health bar. The second picture translates the textures and foliage and moves clouds.
- Dark: a dim light gradient, low-contrast edges and moving fine noise.
Each scene runs at 1280x720, 1920x1080 and 3840x2160, in SDR8 and HDR10, with 4:2:0 and 4:4:4, at nominal 30, 60 and 120 fps. Requested budgets are 0.25, 0.5, 0.75, 1, 1.5, 2, 2.5, 3, 3.2, 3.5, 4, 4.5 and 5 bits per pixel per frame (bpp). Each combination has two pictures and two repeats. The rate order alternates low/high budgets (0.25/5, 0.5/4.5, and so on), then reverses for the repeat. Every batch records the installed host log's last connect/disconnect event. Other GPU work is not controlled.
The path is the same full encoder, packetizer, independent receive parser, Vulkan decoder and staging readback used above, with compute conversion, record framing, 1392-byte packets, 20% critical FEC and no detail FEC. The previous encoder timestamp is reset for every sample, so CPU work cannot inflate the nominal frame budget. These are quality tests at those budgets, not measurements of sustained frame rate, latency or a live client.
At 4K the normal record/FEC transport limit caps the actual encoder budget
at 3,921,592 bytes per frame (about 3.78 bpp). The requested 4, 4.5 and
5 bpp points therefore test the same cap, not those actual bit allocations.
The pyrowave_quality_2160p_without_fec control repeats 4, 4.5 and 5 bpp
with critical FEC disabled in the test configuration. Its larger transport
cap allows those budgets; the control uses the same scenes, formats,
frame rates, phases and alternating repeats. It does not change the
installed host or the normal stream defaults.
The sweep supplies bitrate directly to the encoder. Normal stream setup
also caps bitrate at 2 Gbps, and the runtime /bitrate endpoint caps it at
500 Mbps; this test does not raise either limit. A high requested rate in
the tables is not evidence that a client can negotiate or sustain it.
HDR uses 200-nit desktop/game white, a sun reaching 1000 nits, and a dark scene below 20 nits. Each HDR scene has FP16 scRGB and packed ten-bit PQ input, compared with the independent full-range BT.2020 PQ reference. SDR uses full-range BT.709. PSNR is reported on each Y/Cb/Cr plane with peaks of 255 or 1023.
The simple SSIM diagnostic averages non-overlapping uniform 8x8 windows
(partial edge windows included), using population variance/covariance and
constants (0.01 L)^2 and (0.03 L)^2. It has no Gaussian weighting,
multiscale processing or perceptual HDR weighting. HDR scores are in PQ code
space, not linear light, and are not directly comparable with SDR. Unit tests
cover identity, opposite constant pictures, ten-bit output scaling and inverted
structure.
The quality criteria are:
- Severe-loss floor: desktop and game luma PSNR at least 25 dB and luma SSIM at least 0.80 in every tested picture.
- Clean-picture recommendation: desktop and game PSNR at least 35 dB on every plane and luma SSIM at least 0.95 in every tested picture.
These are explicit engineering targets for these synthetic scenes, not universal visual thresholds. The earlier chart remains a harder stress test. AMD is the measured platform; NVIDIA users should use Vibepollo.
To reproduce, use the SDK environment and DLL from the preceding section:
. "$env:USERPROFILE\.codex\artifacts\butterpollo-rust-20260930\performance-probe\rust-env.ps1"
cargo test -p butterpollo-windows --lib --release --no-run --target-dir target\qa
if ($LASTEXITCODE) { throw 'Test build failed' }
Copy-Item "$env:BUTTERPOLLO_PYROWAVE_ROOT\bin\libpyrowave-shared-0.dll" target\qa\release\deps\
cargo test -p butterpollo-windows --lib --release pyrowave::decode_test::pyrowave_quality_ --target-dir target\qa -- --ignored --nocapture --test-threads=1 2>&1 | Tee-Object target\qa\pyrowave-quality.log
if ($LASTEXITCODE) { throw 'PyroWave quality sweep failed' }
PYROWAVE_QUALITY JSON lines contain the scene, size, format, chroma, fps,
requested bpp/kbps, critical FEC percentage, actual byte budget, padded frame bytes, RTP datagram
bytes (including parity but excluding UDP/IP headers), and per-plane PSNR,
maximum code error and SSIM. Null PSNR with zero error means an exact match.
The existing chart test is still available separately.
Measured results and warning levels #
On October 7, the AMD Radeon RX 7900 XT, driver 32.0.31041.1004,
decoded all 5,616 comparisons in the complete three-size matrix.
The SDK was bitstream 186f0393, with the three patches listed above.
All 216 batch checks found the last service event was CLIENT DISCONNECTED
at 15:51:34 UTC. Other GPU work was not controlled. Matched repeats
had a maximum spread of 0.00 dB PSNR, 0 SSIM and 0 frame bytes.
At fixed bpp, 30/60/120 fps also produced identical scores and frame sizes.
The complete 1080p data is retained from the interrupted first sweep.
The subsequent 720p and 4K sweeps passed in 56.36 and 303.39 seconds
respectively in the release test build. All 1,551 comparable samples
from the interrupted 4K sweep matched the complete rerun exactly.
The tables use only one complete sweep per size. Raw JSON is in
target/qa/pyrowave-quality.log (1080p),
target/qa/pyrowave-quality-720p.log and
target/qa/pyrowave-quality-2160p.log.
Each cell below is minimum PSNR across Y/Cb/Cr, in dB / minimum luma SSIM, over desktop and game pictures, both phases, all three frame rates and both repeats. The two minima need not come from the same picture. At 4K, the 4–5 bpp requests all reach the same 3.78 bpp budget cap. Decisions use unrounded scores.
1280x720: desktop and game
| Requested bpp | SDR 4:2:0 | SDR 4:4:4 | HDR 4:2:0 | HDR 4:4:4 |
|---|---|---|---|---|
| 0.25 | 15.71 / 0.4324 | 15.71 / 0.4324 | 23.68 / 0.6278 | 23.68 / 0.6278 |
| 0.5 | 16.53 / 0.5101 | 16.53 / 0.5101 | 24.41 / 0.6929 | 24.41 / 0.6929 |
| 0.75 | 17.67 / 0.5681 | 17.67 / 0.5681 | 25.88 / 0.7502 | 25.86 / 0.7498 |
| 1 | 19.66 / 0.6288 | 19.65 / 0.6287 | 27.28 / 0.7992 | 27.26 / 0.7989 |
| 1.5 | 23.49 / 0.7619 | 23.35 / 0.7555 | 31.18 / 0.9000 | 31.12 / 0.8981 |
| 2 | 24.84 / 0.8065 | 24.70 / 0.8049 | 32.50 / 0.9231 | 32.40 / 0.9211 |
| 2.5 | 27.12 / 0.8297 | 27.01 / 0.8249 | 35.86 / 0.9400 | 33.44 / 0.9307 |
| 3 | 30.46 / 0.8550 | 29.98 / 0.8466 | 39.41 / 0.9618 | 38.98 / 0.9572 |
| 3.2 | 32.57 / 0.8842 | 30.15 / 0.8500 | 40.56 / 0.9673 | 39.39 / 0.9616 |
| 3.5 | 34.35 / 0.9086 | 32.59 / 0.8850 | 41.28 / 0.9712 | 40.61 / 0.9677 |
| 4 | 36.09 / 0.9369 | 34.64 / 0.9147 | 45.03 / 0.9855 | 41.99 / 0.9750 |
| 4.5 | 39.91 / 0.9662 | 37.08 / 0.9488 | 47.07 / 0.9915 | 45.03 / 0.9855 |
| 5 | 41.82 / 0.9751 | 39.99 / 0.9672 | 48.56 / 0.9946 | 46.83 / 0.9907 |
1920x1080: desktop and game
| Requested bpp | SDR 4:2:0 | SDR 4:4:4 | HDR 4:2:0 | HDR 4:4:4 |
|---|---|---|---|---|
| 0.25 | 18.48 / 0.6140 | 18.47 / 0.6134 | 26.37 / 0.7745 | 26.37 / 0.7746 |
| 0.5 | 19.78 / 0.7070 | 19.77 / 0.7068 | 27.56 / 0.8325 | 27.56 / 0.8325 |
| 0.75 | 20.05 / 0.7489 | 20.05 / 0.7474 | 27.86 / 0.8511 | 27.86 / 0.8507 |
| 1 | 21.69 / 0.7846 | 21.69 / 0.7825 | 30.56 / 0.8935 | 30.08 / 0.8875 |
| 1.5 | 28.56 / 0.8824 | 27.05 / 0.8668 | 36.40 / 0.9559 | 36.38 / 0.9517 |
| 2 | 30.77 / 0.9086 | 30.75 / 0.9073 | 38.78 / 0.9675 | 38.34 / 0.9628 |
| 2.5 | 32.82 / 0.9281 | 32.13 / 0.9233 | 40.36 / 0.9798 | 39.26 / 0.9678 |
| 3 | 37.33 / 0.9611 | 35.24 / 0.9487 | 44.70 / 0.9887 | 42.58 / 0.9809 |
| 3.2 | 38.92 / 0.9721 | 38.26 / 0.9653 | 46.17 / 0.9912 | 45.23 / 0.9894 |
| 3.5 | 42.39 / 0.9854 | 40.79 / 0.9780 | 49.92 / 0.9961 | 49.05 / 0.9948 |
| 4 | 44.82 / 0.9905 | 41.93 / 0.9889 | 53.68 / 0.9986 | 51.35 / 0.9962 |
| 4.5 | 45.85 / 0.9926 | 43.52 / 0.9906 | 57.79 / 0.9994 | 54.54 / 0.9986 |
| 5 | 49.69 / 0.9966 | 43.53 / 0.9925 | 64.56 / 0.9999 | 54.86 / 0.9993 |
3840x2160: desktop and game
| Requested bpp | SDR 4:2:0 | SDR 4:4:4 | HDR 4:2:0 | HDR 4:4:4 |
|---|---|---|---|---|
| 0.25 | 22.33 / 0.7449 | 22.33 / 0.7442 | 30.03 / 0.8435 | 30.03 / 0.8436 |
| 0.5 | 23.65 / 0.7947 | 23.64 / 0.7932 | 31.48 / 0.8701 | 31.48 / 0.8698 |
| 0.75 | 26.22 / 0.8251 | 26.19 / 0.8061 | 34.95 / 0.8960 | 34.55 / 0.8894 |
| 1 | 32.13 / 0.8673 | 30.78 / 0.8469 | 38.28 / 0.9205 | 37.97 / 0.9136 |
| 1.5 | 35.93 / 0.9332 | 35.16 / 0.9188 | 40.46 / 0.9549 | 40.37 / 0.9539 |
| 2 | 37.57 / 0.9464 | 36.98 / 0.9416 | 42.49 / 0.9683 | 41.95 / 0.9648 |
| 2.5 | 39.42 / 0.9655 | 37.84 / 0.9491 | 45.22 / 0.9834 | 42.50 / 0.9684 |
| 3 | 43.07 / 0.9863 | 39.44 / 0.9656 | 49.11 / 0.9934 | 46.23 / 0.9869 |
| 3.2 | 44.73 / 0.9910 | 40.68 / 0.9747 | 50.88 / 0.9956 | 49.24 / 0.9936 |
| 3.5 | 45.31 / 0.9921 | 41.29 / 0.9849 | 51.96 / 0.9966 | 50.62 / 0.9956 |
| 4 | 45.48 / 0.9925 | 42.02 / 0.9910 | 56.18 / 0.9987 | 51.37 / 0.9962 |
| 4.5 | 45.48 / 0.9925 | 42.02 / 0.9910 | 56.18 / 0.9987 | 51.37 / 0.9962 |
| 5 | 45.48 / 0.9925 | 42.02 / 0.9910 | 56.18 / 0.9987 | 51.37 / 0.9962 |
4K control: full 4–5 bpp budgets without critical FEC
All 432 additional decodes passed in 84.64 seconds. All 72 batch checks
again found the last disconnect at 15:51:34 UTC. Repeat and frame-rate
spreads were zero for PSNR, SSIM and frame bytes. This brings the total
to 6,048 decoded comparisons. Raw JSON is in
target/qa/pyrowave-quality-2160p-without-fec.log.
Without critical FEC, the test can pass 4,147,196, 4,665,596 and 5,183,996 bytes to the encoder at 4, 4.5 and 5 bpp respectively. These values are within four bytes of the requested budgets, below the 5,228,792-byte transport budget cap. The following scores use the same desktop/game minimum PSNR / luma SSIM convention as the main tables. These control results are separate from the default-FEC results used for the warnings.
| Requested bpp | SDR 4:2:0 | SDR 4:4:4 | HDR 4:2:0 | HDR 4:4:4 |
|---|---|---|---|---|
| 4 | 46.65 / 0.9940 | 42.03 / 0.9910 | 58.65 / 0.9992 | 51.37 / 0.9962 |
| 4.5 | 51.90 / 0.9981 | 43.60 / 0.9925 | 64.60 / 0.9999 | 54.70 / 0.9988 |
| 5 | 55.12 / 0.9991 | 44.25 / 0.9925 | 64.60 / 0.9999 | 55.80 / 0.9993 |
The dark-scene control scores (minima over both chroma formats) were:
| Requested bpp | SDR | HDR (PQ code space) |
|---|---|---|
| 4 | 57.02 / 0.9998 | 58.12 / 0.9985 |
| 4.5 | 57.02 / 0.9998 | 63.18 / 0.9996 |
| 5 | 57.02 / 0.9998 | 63.18 / 0.9996 |
Dark scene
These are minimum Y/Cb/Cr PSNR / minimum luma SSIM across all three sizes, both chroma formats, phases, frame rates and repeats. The small signal range makes this scene a poor basis for a bitrate recommendation.
| Requested bpp | SDR | HDR (PQ code space) |
|---|---|---|
| 0.25 | 48.72 / 0.9856 | 44.42 / 0.9628 |
| 0.5 | 49.04 / 0.9866 | 44.78 / 0.9657 |
| 0.75 | 49.27 / 0.9872 | 45.01 / 0.9674 |
| 1 | 49.44 / 0.9877 | 45.17 / 0.9687 |
| 1.5 | 50.37 / 0.9901 | 46.58 / 0.9774 |
| 2 | 51.59 / 0.9926 | 47.84 / 0.9832 |
| 2.5 | 52.52 / 0.9940 | 48.94 / 0.9871 |
| 3 | 53.23 / 0.9949 | 50.09 / 0.9901 |
| 3.2 | 53.68 / 0.9954 | 50.68 / 0.9913 |
| 3.5 | 56.07 / 0.9974 | 52.27 / 0.9939 |
| 4 | 56.70 / 0.9993 | 55.02 / 0.9968 |
| 4.5 | 56.70 / 0.9993 | 55.02 / 0.9968 |
| 5 | 56.70 / 0.9993 | 55.02 / 0.9968 |
The lowest tested budgets meeting each criterion, also passing at every higher tested budget, were:
| Size | Format | Chroma | Floor bpp | Clean bpp |
|---|---|---|---|---|
| 720p | SDR8 | 4:2:0 | 2.5 | 4.5 |
| 720p | SDR8 | 4:4:4 | 2.5 | 5 |
| 720p | HDR10 | 4:2:0 | 1.5 | 3 |
| 720p | HDR10 | 4:4:4 | 1.5 | 3 |
| 1080p | SDR8 | 4:2:0 | 1.5 | 3 |
| 1080p | SDR8 | 4:4:4 | 1.5 | 3.2 |
| 1080p | HDR10 | 4:2:0 | 0.5 | 1.5 |
| 1080p | HDR10 | 4:4:4 | 0.5 | 1.5 |
| 2160p | SDR8 | 4:2:0 | 0.75 | 2.5 |
| 2160p | SDR8 | 4:4:4 | 0.75 | 3 |
| 2160p | HDR10 | 4:2:0 | 0.25 | 1.5 |
| 2160p | HDR10 | 4:4:4 | 0.25 | 1.5 |
Use 2.5 bpp for the floor and 5 bpp for the recommendation below 1080 lines, and 1.5 / 3.2 bpp at 1080 lines and above. Keep the same levels for SDR/HDR and 4:2:0/4:4:4. The 1080p levels are conservative at 4K. This avoids treating the 720p one-pixel desktop text as if it were the two- or four-pixel text in the larger pictures. At 720p, 2 bpp still gives only 24.70 dB luma on the worst desktop; 4.5 bpp passes 35 dB on all planes but misses 0.95 SSIM in SDR 4:4:4. At 1080p, 1 bpp gives 21.69 dB / 0.7825 SSIM on the worst desktop, and 3 bpp still misses 0.95 SSIM in SDR 4:4:4. There is no universal visual cliff.
The following scores at the selected levels retain separate scene results. Each cell is minimum Y/Cb/Cr PSNR / minimum luma SSIM across all tested formats, phases, rates and repeats at that size.
| Size | Level | bpp | Desktop | Game |
|---|---|---|---|---|
| 720p | Floor | 2.5 | 27.01 / 0.8249 | 37.52 / 0.9468 |
| 720p | Recommended | 5 | 39.99 / 0.9672 | 43.57 / 0.9923 |
| 1080p | Floor | 1.5 | 27.05 / 0.8668 | 34.96 / 0.9135 |
| 1080p | Recommended | 3.2 | 38.26 / 0.9658 | 39.39 / 0.9653 |
| 2160p | Floor | 1.5 | 37.30 / 0.9628 | 35.16 / 0.9188 |
| 2160p | Recommended | 3.2 | 48.26 / 0.9952 | 40.68 / 0.9747 |
Rates at 60 fps, calculated from those measured budget levels and rounded up to whole Mbps (the functions round up to kbps):
| Stream | Floor | Recommended |
|---|---|---|
| 720p60 | 139 Mbps | 277 Mbps |
| 1080p60 | 187 Mbps | 399 Mbps |
| 4K60 | 747 Mbps | 1593 Mbps |
Within each height band, rates scale with width, height and frame rate. A desktop with smaller text at 1080p or 4K can need more. The recommendation meets the stated criteria for these scenes; it does not make the earlier stress chart, every game, or HDR rendering on a real display clean.
Packet overhead, padding and recovery data require network headroom. Ranges cover every tested scene and format:
| Stream | Requested Mbps | RTP bytes/frame converted to Mbps |
|---|---|---|
| 720p60 | 276.480 | 227.8–331.8 |
| 1080p60 | 398.132 | 412.3–458.9 |
| 2160p60 | 1592.525 | 1643.6–1884.9 |
These rates exclude UDP/IP and link headers. The 4K60 recommendation
needs more than gigabit Ethernet. The scaled 4K120 recommendation
(3185 Mbps rounded up) exceeds the current 2 Gbps stream-setup limit;
the harness can supply that budget directly, but a normal stream cannot
negotiate it. The /bitrate limit of 500 Mbps can also prevent reaching
the 4K60 target through a runtime change. Use HEVC or AV1 when the client
or network cannot carry the rate. Neither limit is changed here.
Checks passed: cargo fmt --all; Clippy for core, Windows and host with
all targets and warnings denied; 171 core tests; 63 host binary tests
(two existing tests ignored); and the PSNR/SSIM unit test. npm ci and
npm run check passed with 0 errors and 0 warnings across 163 files.
Playwright checked the Overview card at 1440x1080 and 390x844 with mocked
sessions: red below the floor, amber at the floor and below the
recommendation, no warning at the recommendation, no PyroWave warning
for HEVC, and the 720p/4K examples. The cards wrapped without horizontal
overflow; there were no page errors, console warnings/errors or framework
overlays. The temporary web server did not connect to the installed host.
October 7: 4:4:4 from AMF #
Whether the native AMF encoder could offer HDR 4:4:4 HEVC or AV1 was checked on the RX 7900 XT, driver 32.0.31041.1004, AMF runtime 1.5.2.0, with a standalone probe outside Rubylight. It cannot; the hardware encodes 4:2:0 only.
- The encoder caps report
HevcMaxProfile2 (Main10) andAv1MaxProfile1 (Main). Input formats are YUV420P, YV12, BGRA, RGBA, ARGB, NV12 and P010; output formats NV12 and P010. The runtime's own property table listsHevcProfileas {Main, Main10} andAv1Profileas {Main}, and has no chroma-format property. H.264 stops at High. Initwith AYUV, Y410 or Y416 input returnsAMF_INVALID_FORMATfor both codecs. SettingHevcProfileto 3, 4, 5 or 16, orAv1Profileto 2 or higher, returnsAMF_INVALID_ARGand the profile stays Main10 or Main.- RGBA, BGRA and R10G10B10A2 input is accepted, and FFprobe reads the output
as
yuv420poryuv420p10le: the encoder converts RGB to 4:2:0 itself. - The driver's D3D12 video encoding API, queried separately on the same GPU, supports HEVC Main and Main10 only (not Main12, the 4:2:2 profiles or any of the four 4:4:4 profiles) and AV1 Main only (not High or Professional), with NV12 and P010 input.
- The AMF SDK on GitHub, 1.5.3 of September 29, has the same profile enums. Its 4:4:4 note (1.5.0) is about the video converter, not the encoders. AMD's AMF maintainer answered requests for it with "4:4:4 and 4:2:2 codecs are currently not supported by hardware" (AMF issue 483, September 2025) and said RX 9000 encodes HEVC "4:2:0, 8 and 10 bit" (issue 539). The AMF wiki's hardware table says "All codecs are 4:2:0" through VCN 5.0. RDNA 4 could not be tested here.
Packing 4:4:4 into a larger 4:2:0 picture, or into two 4:2:0 streams as RDP's AVC444 does, needs a client that reassembles the planes. Moonlight decodes a single stream at the negotiated size and shows it as is, so a stock client would display the packed layout. The native AMF encoder keeps refusing 4:4:4, which the startup probe already treats as unavailable, so an AMD host never offers it; Moonlight PC then warns "Your host PC doesn't support YUV 4:4:4 streaming" and uses 4:2:0. PyroWave remains the full-chroma path on AMD, including 10-bit HDR 4:4:4.
October 7: where WGC loses a millisecond #
On the RX 7900 XT, an 8×1-pixel window in the display's bottom-right corner
encoded a changing picture ID. windows/examples/capture_phase_probe.rs
matched captured IDs to QPC samples immediately before the renderer's
Present(1) call, recorded host acquisition, and waited for a D3D11 event
query after the host-owned texture copy. Only then did it read the eight
pixels. Thus Present-to-copied includes composition, capture delivery and
the completed host copy, but excludes encoder claim, conversion, encoding,
networking and decoding. It is not a stream or input-to-display measurement.
The main batch used a 1920×1080 virtual display reporting 120 Hz, kept alive
by a test-owned SYSTEM task. The probes themselves ran as the signed-in
user. helper forces the production WGC helper process and its three shared
textures; wgc captures in-process; ddx uses Desktop Duplication. Each
condition ran WGC, helper, DDX, DDX, helper, WGC, nine seconds per run,
excluding the first second and final 250 ms. The loaded runs used
gpu_load SECONDS 1000 0 200. The fixture checked the installed host log for
a connected client before runs; the JSON does not record other agents' GPU
activity. The range below retains that batch's spread.
Arithmetic means of two per-run means, with their range in parentheses:
| Present to completed host copy, ms | DDX | WGC in-process | WGC helper |
|---|---|---|---|
| Idle, 59.3 Hz source | 5.05 (5.03–5.08) | 5.88 (5.62–6.15) | 6.05 (5.95–6.16) |
| Idle, vsync-paced source | 6.38 (6.31–6.46) | 6.78 (6.70–6.86) | 7.08 (7.02–7.13) |
| Heavy GPU load, 59.3 Hz requested | 35.62 (35.36–35.89) | 35.23 (34.92–35.55) | 34.28 (31.77–36.79) |
The second row requested 500 Hz and used vsync. Although the virtual display reported 120 Hz, the recorded Present intervals were about 4.167 ms (240 calls/s), not 8.333 ms. It should not be described as a verified 120 Hz source. The slow idle source measured 16.863 ms between Presents. Idle picture coverage was 100%, except one fast DDX run at 99.57%. Under load it was 86.1–88.7% for DDX, 95.6–96.5% for direct WGC and 89.8–92.6% for the helper; the table measures pictures actually captured.
Subtracting these batch means, the helper was 1.00 ms behind DDX at 59.3 Hz and 0.69 ms behind it in the vsync-paced case. The helper-minus-direct WGC differences were only 0.17 and 0.29 ms. These alternating-run differences estimate the broker hop, rather than measuring the hop on the same frame. Most of the idle gap therefore lies in WGC delivery itself. Loaded per-run means were roughly 32–37 ms; the helper alone varied by 5.02 ms, so there is no established latency saving from bypassing it under this load.
The earlier SYSTEM-host batch corroborated the idle gap: at 59.3 Hz, helper/DDX means were 5.85/4.89 ms, and in the vsync-paced case 7.43/6.38 ms. Its loaded batch ended with a helper first-frame timeout, so it is not a complete loaded comparison. On the physical display, the initial 59.3 Hz batch recorded 6.35 ms for direct WGC, 6.21/6.61 ms for the helper and 4.76/4.70 ms for DDX. The final direct-WGC run captured only 36.1% of IDs; do not pool it into that comparison. Capturing two paths simultaneously also changed their timings, so the side-by-side runs are excluded above.
The intermediate stamp_qpc in these files is the host's legacy capture
stamp: DDX's last presentation/cursor update or WGC's clamped
SystemRelativeTime. It is not an independent DWM-present observation on
WGC. The separate wgc_stamp_probe found 92% of physical-display stamps
in the future at arrival, with mean arrival-minus-stamp about -0.79 ms.
That explains why detect_mean_ms could say about 0.12 ms while the
picture-ID probe measured a real delivery gap. New signed
wgc_stamp_to_host_* fields and valid/future stamp counts expose this
limitation; the legacy age fields and pacing clock remain unchanged.
Metric definitions distinguish
these offsets from actual Present-to-copied measurements.
An unbuilt direct IddCx frame ring is estimated to save about 1 ms at idle if it avoids this WGC delivery gap, and nothing measurable under the tested load. Only about 0.2–0.3 ms is the helper hop. It is not worth building for latency now. This is an estimate, not a measurement of an IddCx ring. The rc.17 end-to-end fixture also puts the component result in context: DDX and WGC picture age were only 0.15 ms apart idle (14.71 versus 14.86 ms), while WGC was about 2 ms better under load (33.42 versus 35.36 ms). Component differences do not predict that entire journey.
Raw artifacts are under
%USERPROFILE%\AppData\Local\Temp\claude\C--src-butterpollo\7e5651cf-4ad6-4e53-8a70-43a4eede758f\scratchpad\:
vphase/u-01 through u-18 supply the main table, vphase/v-* the earlier
SYSTEM batch, and phase/idle59-*, phase/t-* and phase/stampsrc-* the
physical checks. The raw stamp probe's aggregate above was reported by the
original study; those JSON files do not retain its raw stamp samples. Reproduce
with the usage headers in capture_phase_probe.rs, wgc_stamp_probe.rs
and session_command_long.rs. Virtual runs require a test-owned SYSTEM
scheduled task (the bench-rc17 harness), no active stream, and task/display
cleanup afterward. No capture wait or pool-size optimization was made here.
October 7: AMF split-frame encoding #
AMF's HevcMultiHwInstanceEncode and Av1MultiHwInstanceEncode let the
driver split one frame across a GPU's video encode engines (AMD's samples
call it "split frame encode", default on); H.264 has no such property. The
original C++ host asked for it before Init when the caps reported more than
one engine and the driver had it off. The Rust host never wrote it. A real
split would show as roughly half the time per frame at large sizes;
alternating whole frames between engines would show as more frames per
second at the same time per frame.
Method: examples/performance.rs --synthetic 16 --paced --seconds 3 on the
RX 7900 XT, driver and AMF runtime 32.0.31041.1004, with the defaults
(ultra-low latency, speed, vbr_latency, input converted on the D3D12
compute queue) at 20, 40, 80, 80 and 150 Mbps for 1920×1080, 2560×1440,
3840×2160, 5120×1440 and 7680×2160. Each case ran with the property
untouched (a measurement build that skipped the write), forced on and forced
off, twice, in the order untouched, on, off, off, on, untouched. The
settings line confirmed the value after Init. Before every run the host log
was checked for a stream; a first batch that overlapped a client's stream was
discarded. --slices N and --intra-refresh were added to the probe for the
interaction runs. Every run's JSON, settings line and decision line are in
%USERPROFILE%\.codex\artifacts\butterpollo-amf-split-frame-20261007
(probe.jsonl is the discarded batch).
The driver reports two engines for both codecs (HevcNumOfHwInstances=2,
Av1CapNumOfHwInstances=2) and has the property on before Init with nothing
written, in all 630 runs, with every usage tried. On, off and untouched gave
the same time per frame, the same frames per second and the same bytes per
frame (within 0.2%, the difference of a frame in a three-second run) in all
60 combinations of HEVC and AV1, the five sizes, 60, 120 and 240 fps, SDR
and HDR. At 60 fps, SDR, mean of each run in ms:
| Submission to output | Untouched | Forced on | Forced off |
|---|---|---|---|
| HEVC 1920×1080 | 2.21, 2.20 | 2.29, 2.29 | 2.25, 2.30 |
| HEVC 2560×1440 | 3.29, 3.27 | 3.35, 3.27 | 3.31, 3.34 |
| HEVC 3840×2160 | 6.19, 6.10 | 6.13, 6.13 | 6.18, 6.15 |
| HEVC 5120×1440 | 5.59, 5.65 | 5.68, 5.60 | 5.69, 5.64 |
| HEVC 7680×2160 | 11.34, 11.38 | 11.42, 11.44 | 11.38, 11.24 |
| AV1 1920×1080 | 1.79, 2.02 | 1.86, 1.92 | 1.85, 1.94 |
| AV1 2560×1440 | 3.02, 2.99 | 2.98, 2.91 | 2.96, 3.00 |
| AV1 3840×2160 | 7.10¹, 5.08 | 5.04, 5.08 | 5.08, 5.04 |
| AV1 5120×1440 | 4.88, 4.77 | 4.76, failed¹ | 4.74, 4.79 |
| AV1 7680×2160 | 9.34, 9.41 | 9.44, 9.38 | 9.35, 9.47 |
¹ A stall; see below. HDR and the 120 and 240 fps runs agree the same way: no median differs by more than 0.2 ms between the three, in no consistent direction.
The time per frame grows with the picture as one engine's would: 11.3 ms
at 7680×2160 against 6.1 ms at 3840×2160 for HEVC. Where one engine cannot
keep up, a split would have helped most, and nothing changed. Frames per
second without pacing (--seconds 4), two runs each:
| Unpaced | Untouched | Forced on | Forced off |
|---|---|---|---|
| HEVC 1920×1080 | 660, 660 | 659, 660 | 660, 660 |
| HEVC 3840×2160 | 196, 196 | 195, 196 | 196, 196 |
| HEVC 5120×1440 | 216, 216 | 216, 216 | 216, 216 |
| HEVC 7680×2160 | 100, 100 | 100, 100 | 100, 100 |
| AV1 1920×1080 | 835, 835 | 836, 835 | 835, 836 |
| AV1 3840×2160 | 248, 248 | 248, 248 | 248, 249 |
| AV1 5120×1440 | 270, 270 | 270, 270 | 270, 269 |
| AV1 7680×2160 | 126, 126 | 126, 126 | 126, 126 |
HEVC at 7680×2160 and 120 fps settles at 100 fps with 82 ms from submission to output in all three. Frames do overlap in the encoder (196 frames per second at 3840×2160 against 164 from the time per frame), but a second engine would nearly double that. So on this GPU and driver one stream uses one engine, whatever the property says, and the rc.19 note that two frames in the encoder keep both of a Radeon's instances busy was wrong; its comment now says so.
Nothing else made the driver split. At 3840×2160 and 60 fps, mean of two runs each in ms, untouched / on / off:
| Also set | HEVC | AV1 |
|---|---|---|
| 2 slices (AV1: tiles) | 6.30 / 6.33 / 6.34 | 5.22 / 5.23 / 5.23 |
| 4 slices (AV1: tiles) | 6.34 / 6.28 / 6.23 | 5.17 / 5.19 / 5.19 |
| Intra refresh | 6.29 / 6.22 / 6.21 | 5.16 / 5.16 / 5.14 |
| 2 LTR frames | 6.41 / 6.35 / 6.34 | 5.06 / 9.02¹ / 5.01 |
| Quality preset balanced | 6.46 / 6.50 / 6.45 | 5.11 / 5.05 / 5.01 |
| Quality preset quality | 7.47 / 7.46 / 7.48 | 17.48 / 17.43 / 17.44 |
| Pre-analysis | 25.85 / 26.01 / 26.02 | 25.22 / 25.08 / 25.08 |
| CBR | 6.39 / 6.40 / 6.33 | 5.26 / 5.21 / 5.20 |
| Usage transcoding | 25.11 / 24.80 / 25.14 | 12.37 / 12.33 / 12.28 |
| Usage low latency | 23.81 / 25.06 / 25.52 | 12.24 / 12.29 / 12.34 |
| Usage high quality | 20.32 / 20.32 / 20.42 | 19.48 / 19.52 / 19.56 |
| SmartAccess Video on | 6.48 / 6.45 / 6.41 | 5.19 / 5.23 / 5.15 |
D3D11 input (gpu_compute_conversion=false) |
6.45 / 6.42 / 6.55 | 5.35 / 5.23 / 5.24 |
The usages other than ultra-low latency queue frames (p95 of 19-70 ms), so their means vary more. AV1 needs two tiles above 4096 pixels wide and the driver chose them by itself at 5120 and 7680 wide; two or four slices or tiles, SmartAccess Video, the transcoding usage and D3D11 input at 7680×2160 also left the three alike. Bytes per frame matched in every pair.
Six of these 630 runs stalled once: two gave up (AMF GPU queue failed to drain), one stopped submitting for 35 seconds, and three lost up to 0.55 s
of frames or raised the mean by 2-8 ms while the median stayed put. Four
were forced on, two untouched, none forced off. No stream was running and
the system log has no driver reset, but other builds and an installer ran on
the machine during the matrix. The six cases then ran five more times each
for eight seconds in all three variants (90 runs, twelve minutes of
encoding): no stall, the means within 0.05 ms of each other, and the driver
again had the property on before Init. The stalls do not follow the
property.
amd_split_frame now does what the original host did: auto (the default)
asks for split-frame encoding only when the caps report more than one engine
and the driver has it off, enabled asks whenever there is more than one
engine, and disabled turns it off. With one engine, as the RX 9070 XT's HEVC
encoder, nothing is written in any mode, and a driver that rejects the property
keeps the stream. The host logs the setting, the engine count and the driver's
value (AMF split-frame encoding left to the driver or requested), and the
settings line shows the value after Init. On the RX 7900 XT auto writes
nothing.
2026-10-07: send PyroWave FEC blocks sooner #
These measurements use an AMD RX 7900 XT (driver 32.0.31041.1004) and Ryzen 7
5800X3D. The baseline is 523aa283794690a3cd3beb3ca3ed2b172ecd87bb.
The sender now prepares and sends one FEC block at a time. The coarse block
and its parity leave before later blocks are copied into RTP shards and
encrypted; preparing the next block can use part of the previous batch's
pacing interval. Record packing sizes its fit tree to the records present
and stops updating ancestors once their earliest record is unchanged.
DetailFec uses a pre-sized hash map for record lookup; it never depends on
map iteration order. Its stability thresholds, wire budget and FEC plans
are unchanged. An abandoned iterator advances the frame number once a
block has escaped and keeps the consumed sequence numbers and nonces.
Temporary probes timed mapped-bitstream access and SDK packetizing,
record_frame, DetailFec::observe, layout/FEC planning, shard allocation
and copying, parity, encryption, pacing waits and Winsock calls. They also
recorded the first send batch and final send relative to capture claim and
presentation. These logs are removed from production; the existing
stream timings counters remain. "Sent" here means the socket call
returned, not a hardware timestamp on a physical link. The first-batch
measurement includes sending every datagram in that batch.
The encoder probe used windows/examples/performance.rs --codec pyrowave --records --hdr --yuv444 --paced --synthetic 8 --seconds 6, with
--width 1920 --height 1080 --fps 120 --bitrate 400000 and
--width 3840 --height 2160 --fps 60 --bitrate 800000. All eight probes in
the two complete A/B pairs reached the requested cadence. Mean encoder
submission-to-output time, including packetizing and record packing, was
0.969–0.972 ms before / 0.963–0.969 ms after at 1080p, and 2.467–2.565 ms
before / 2.612–2.850 ms after at 4K. These are not GPU-only encode times.
The 12-second encrypted host runs used the isolated release/e2e.py fixtures
and the pinned Nonary transport referenced above, with GPU decode removed from
a temporary receiver. The receiver still decrypts and assembles the stream; its
inherited "decoded" counter counts delivered frames in this mode. The source
was a 5120×1440 SDR desktop with the motion strip, scaled to the requested HDR
4:4:4 output. pacing_max_bitrate_kbps=0 was unchanged: loopback has no
reported physical link speed, so the measured pacing rates were about 442 Mbps
and 880 Mbps, including the existing headroom/traffic-demand rule. The
requested codec rates were 400 and 800 Mbps; negotiation reported 398,988 and
798,988 kbps.
The installed service log ended in CLIENT DISCONNECTED before every
GPU batch. Other worktrees were compiling and running GPU tests. Runs
alternated A/B/A/B; a port collision interrupted an intermediate pair,
which is excluded. The repeat used unused fixture ports 51618–51644.
Complete transport runs are target/pw-pipeline/transport-{before,after}-{1,3};
per-stage summaries are in target/pw-pipeline/transport-summary.json.
These local artifacts are not shipped.
Mean time per frame in the first complete pair, milliseconds (before → after):
| Stage | 1080p120 / 400 Mbps | 4K60 / 800 Mbps |
|---|---|---|
| Mapped access and SDK packetizing | 0.554 → 0.538 | 1.168 → 1.162 |
| Record packing | 0.239 → 0.231 | 0.968 → 0.877 |
| Adaptive detail FEC observation | 0.299 → 0.212 | 0.929 → 0.124 |
| Layout and FEC planning | 0.035 → 0.042 | 0.174 → 0.266 |
| RTP shard allocation/copying | 0.037 → 0.046 | 0.128 → 0.102 |
| Parity computation | 0.001 → 0.002 | 0.007 → 0.007 |
| Headers and encryption | 0.296 → 0.299 | 0.871 → 0.804 |
| Pacing waits | 7.439 → 7.598 | 12.120 → 11.438 |
| Socket sends | 1.264 → 1.455 | 3.393 → 2.744 |
Stage means use each stage's completed frames; frames replaced while waiting for the sender mean these columns are not one additive timeline. The large 4K detail-observation change also reflects more frames arriving within the nominal interval, where the existing controller skips hashing. It is not a measurement of the map replacement alone. In the second pair, 4K record packing was 1.198 → 1.345 ms and socket sends 4.281 → 4.705 ms, showing the background-load variation.
Ranges of the two run means, milliseconds:
| Measurement | 1080p before | 1080p after | 4K before | 4K after |
|---|---|---|---|---|
| Encoder return → first send batch | 5.18–5.23 | 4.35–4.47 | 9.96–10.77 | 2.06–8.84 |
| Encoder return → last send | 13.60–13.61 | 13.38–13.71 | 25.33–26.99 | 17.03–26.05 |
| Present → first send batch | 6.50–8.07 | 5.68–5.80 | 13.02–13.97 | 6.93–14.23 |
| Present → last send | 14.87–16.49 | 14.72–15.05 | 28.39–30.20 | 21.90–31.44 |
Present-to-last-send p95 ranged from 18.77–23.23 ms before to
18.71–19.10 ms after at 1080p, and 36.42–37.96 ms before to
30.91–41.09 ms after at 4K. The stream's own rolling stream timings
reported mean present-to-send of 14.85–19.41 → 14.60–14.85 ms and
28.49–30.56 → 20.48–31.34 ms respectively. This supports earlier first
sends after encoder return, but not a consistent 4K present-latency or
1080p completion improvement under this shared load.
A CPU replay separates preparation from that background GPU/capture load. It used identical synthetic, structurally valid records (not encoded pictures), 419,536 / 1,679,240 raw bytes, and verified both packers produced identical framed bytes. It alternated the old/new packer and controller and the eager/incremental packet APIs 150 times per size after ten warmups, with encrypted 1360-byte packetizer settings and 20% critical FEC. Detail observation used unchanged frames 20 ms apart. This measures CPU work, not socket or presentation latency:
| Replay stage, mean / p95 ms | 1080p before | 1080p after | 4K before | 4K after |
|---|---|---|---|---|
| Record packing | 0.403 / 0.542 | 0.307 / 0.405 | 1.177 / 1.481 | 1.067 / 1.380 |
| Detail observation | 0.275 / 0.364 | 0.200 / 0.268 | 1.164 / 1.418 | 0.614 / 0.822 |
| First block ready, including planning | 0.489 / 0.583 | 0.064 / 0.099 | 1.749 / 2.051 | 0.214 / 0.279 |
The eager API must finish every block before returning the first. Building
and releasing all blocks still took 0.531 → 0.467 ms and 1.828 → 1.791 ms;
most of the first-block saving moves later work out of its way. The replay
and its log remain in target/pw-pipeline/pyrowave_pipeline_probe.rs and
target/pw-pipeline/cpu-replay.log.
Pacing remains the largest cost. The existing sender already uses
WSASendMsg scatter/gather with UDP segmentation and Timer::until_precise;
there is no additional payload concatenation in the socket path. Sending
at FEC boundaries increased mean socket calls from about 7 to 9 at 1080p
and 21 to 22 at 4K. Critical parity cost only microseconds in these runs,
so no parity worker or timer change was added. The SDK still packetizes
into its reusable CPU buffer before record packing; skipping that copy
would require replacing SDK packetization, not simply borrowing its raw
mapped buffer. Encryption remains in place in the final shards.
Compatibility checks preserve the ordering required by Nonary's pinned FEC queue and the critical-packet count and restart markers used by its depacketizer. Twelve golden packet fingerprints were regenerated from the baseline source and match, including framing, critical/detail parity, encryption, unaligned payload fallback, minimum parity and sequence/frame wraps over two consecutive frames. Separate tests cover abandoning a partial frame and the old controller's exact decisions through record reordering, sequence changes, motion, a nominal-cadence reset and a pause.
The final release build generated all 24 native record/container transport
fixtures. tests/pyrowave_transport.py matched parity against the original C++
nanors DLL, recovered 12 deliberately lost coarse shards across the protected
fixtures, and decoded every recovered frame with the independent vendor
decoder. The final encrypted live decoder runs also passed the receiver's
interoperability checks: 1,180/1,180 frames at 1080p and 537/537 at 4K, with
zero partial frames or decoding failures. Earlier shared-GPU decoder runs
logged queue overflows and failures in both versions; the latency comparison
therefore uses the transport-only receiver described above.
The generic e2e evaluator still returns failure because this receiver
does not emit its picture/motion/tone timing fields. These are transport
and PyroWave decode checks, not a strict e2e evaluator pass. Final checks:
cargo fmt --all; clippy for core, host and Windows with all targets and
warnings denied; 158 core tests passed; 46 host tests passed with two
pre-existing ignored tests; and the release host/examples build passed.
No web files changed. The installed service and display configuration
were not changed.
October 7: input on the control thread #
Every client's input passes through one control thread: ENet receives the
datagram, the host decrypts and decodes it, and the injector hands it to
Windows or the virtual gamepad driver. Five things on that thread made
input wait; each is now changed and measured against the code before it
(72b8b0cc).
Ryzen 7 5800X3D (16 threads), Windows 11 26200, release builds of
rust/windows/examples/input_path_probe.rs and
rust/host/examples/enet_input_probe.rs, with the stream's process setup
(HIGH priority class, 1 ms timer resolution, power throttling off). Load is
a child process of normal priority class spinning one thread per logical
CPU at the named thread priority, standing in for a CPU-bound game. Mouse
input is always a zero-distance relative move. The pad sections plug one
neutral VHF pad and remove it, and ran while no client streamed. Network
rows are loopback: they include the host's socket calls and scheduling,
but no NIC, interrupt moderation or wire.
Virtual gamepad calls #
Each pad call is a blocking DeviceIoControl into the VHF driver, which
runs in WUDFHost at normal priority. The raw calls (vhf, 1,000 states per
round at 1 kHz, 500 feedback polls):
| Load | State p50 | State max | Feedback poll max |
|---|---|---|---|
| None | 136 µs | 0.25, 0.28 ms | 0.12 ms |
| Normal | 53-55 µs | 6.8, 21.9 ms | 11.1 ms |
| Highest | 54-56 µs | 279, 229 ms | 0.34 ms |
They ran on the control thread: every controller state, the feedback poll
every 8 ms and the driver's first open (2.9-6.2 ms) and plug. A pad now
has its own thread; the control thread queues its events and collects
feedback. pad-contention sends one controller state and one mouse move
per millisecond through the injector for 6 s, with the 8 ms refresh, and
records when the mouse move is done after its pass began:
| Load | Before p50 / p99 / max | After p50 / p99 / max |
|---|---|---|
| None | 230 µs / 1.37 ms / 6.2 ms | 45 µs / 0.39 ms / 1.8 ms |
| Normal | 107 µs / 2.11 ms / 27.6 ms | 41 µs / 0.12 ms / 2.1 ms |
| Highest | 92 µs / 2.12 ms / 287 ms | 39 µs / 0.13 ms / 2.0 ms |
Queueing a state takes 3-6 µs (max 125 µs); the rest is SendInput itself
(p50 35-39 µs, max 1.8-2.1 ms). A rerun with --trace showed the pad
plugged and no driver call failing on its thread. The driver's own
stalls remain and now delay only that pad's input. Raising WUDFHost's
priority would shorten them but changes another process; it is left as
it is. One earlier run of the new code, before the probe timed controller
and mouse separately, recorded a single 344 ms pass with no load; four
later runs without load did not repeat it, and its cause was not found.
Input thread priority #
The control thread registered with MMCSS and then set its own thread
priority, which cancels the boost. priority reads the thread's effective
priority (13 without either); contention sends loopback datagrams to a
thread waiting as the control loop does, with TIME_CRITICAL spinners on
every CPU, 1,500 each:
| Thread setup | Priority | p50 | p99 | max |
|---|---|---|---|---|
Priority::new (control thread before) |
14 | 3,541 µs | 20,138 µs | 26,062 µs |
Priority::input (control thread now) |
18 | 17 µs | 30 µs | 287 µs |
The run before the change gave 3,535 / 23,274 / 26,983 µs for
Priority::new. The gamepad thread uses Priority::input too. Capture,
encode, audio and pacing threads still use Priority::new; whether they
should keep their boost needs its own measurement beside a game.
ENet acknowledgement #
rusty_enet's service() sends a packet's acknowledgement (one sendto,
22-24 µs here) before it returns the packet. The control socket now holds
what ENet sends until the pass has applied its input, then sends it in
order before host.flush(). enet_input_probe, 3,000 reliable 36-byte
packets on loopback, two alternating runs per mode, median:
Acknowledged inside service() |
Held until after apply | |
|---|---|---|
| Client send to server apply step | 69, 71 µs | 46, 46 µs |
| Server wake to first event | 31, 33 µs | 9, 9 µs |
SendInput per pass #
A SendInput call costs about the same for one input as for eight. The
injector now sends a pass's keyboard and mouse input in one call, in
order; touch, pen, and a key or button whose handling depends on an
unsent press of its kind, split the pass. batch, zero-distance moves,
600 passes per row, alternating:
| Events per pass | One call each, p50 / p99 | One call per pass, p50 / p99 |
|---|---|---|
| 1 | 37 / 157 µs | 36 / 121 µs |
| 2 | 62 / 339 µs | 33 / 198 µs |
| 4 | 98 / 347 µs | 31 / 212 µs |
| 8 | 180 / 577 µs | 38 / 270 µs |
Mouse moves within a pass were already merged, so this helps a move with a click or scroll, key combinations and text, which was one call per character.
One-off calls #
Finding the display for absolute input enumerates every display
(display_rect: p50 1.6 ms, max 2.6 ms; display::monitors once 21 ms).
It ran when the injector was made with the first input, every 500 ms while
the stream's display did not exist, and on every pass after a rename until
the new name was found. Lookups now run on their own thread; absolute
mouse, touch and pen wait for one still running, other input never does.
The injector is made when the session is first seen.
injector |
Before | After |
|---|---|---|
Injector::new_options, existing display, p50 |
3,419 µs | 69 µs |
Injector::new_options, missing display, p50 |
4,069 µs | 68 µs |
set_output + refresh per 1 ms pass, display missing, 3 s, max |
18,333 µs | 65 µs |
| Passes over 200 µs | 5 | 0 |
Synthetic touch and pen devices (2.0-3.0 ms to create) are still made on
the first touch or pen event. While a synthetic touch device exists,
Windows reports touch to every application: SM_MAXIMUMTOUCHES went from
0 to 32 and SM_DIGITIZER from 0 to 193 (integrated multi-touch, ready),
and back after it was destroyed. Making one ahead of time would change
what a game sees in sessions that never touch.
October 7, 2026: AMF rate control and recovery keyframes #
The rc.19 loss report motivated this test: a recovery keyframe can be several ordinary frames of data, making a congested wireless route worse. These are local encoder measurements on an AMD Radeon RX 7900 XT, driver 32.0.31041.1004, AMF runtime 1.5.2.0, Rust 1.98.1 on Windows GNU. RDNA4 was not available.
Method #
rust/windows/examples/performance.rs now reports non-keyframe size mean,
p50, p99 and maximum, startup keyframe bytes, each requested recovery
keyframe's bytes, and maximum recovery size divided by mean non-keyframe
size. AV1 keyframes occupy the same idr_bytes field as HEVC IDRs.
Percentiles use the sorted sample at floor((N-1)*p). Encode time here is
submission through observed completed output, including GPU conversion and
output polling, not just time inside the codec. The probe drains outstanding
output before reporting, so its submission and completion counts can be
compared. No network traffic is generated.
The main matrix uses the release build, 16 deterministic moving synthetic pictures, SDR 8-bit 4:2:0, GPU input, two seconds of paced warmup, and eight seconds of measurement. It requests a recovery keyframe every nominal second. Startup is measured separately, including the first keyframe before warmup. Ordinary frames following a recovery request remain in the non-keyframe distribution: recovering its bit budget is part of rate-control behaviour. Runs normally complete 481 frames at 60 fps or 961 at 120 fps; the last scheduled submission is drained after the eight-second interval.
Each mode runs the settings in order and then in reverse order, keeping
comparisons close and exposing run-to-run spread. Before and after every
run, the installed service log is checked for CLIENT CONNECTED without a
later CLIENT DISCONNECTED; other running performance probes are recorded.
The installed service, driver, display settings and other processes are not
changed. The local JSON results, settings logs and run manifests are in
target/qa/ratecontrol/; the tables below retain the per-run measurements.
From the worktree root, after sourcing the supplied rust-env.ps1:
cargo build --release -p butterpollo-windows --example performance --target-dir target\qa
Copy-Item "$env:BUTTERPOLLO_PYROWAVE_ROOT\bin\libpyrowave-shared-0.dll" target\qa\release\examples\
New-Item -ItemType Directory -Force target\qa\ratecontrol | Out-Null
Set-Content target\qa\ratecontrol\cap2.conf 'amd_max_frame_size=2'
$env:RUST_LOG = 'info'
target\qa\release\examples\performance.exe --encoder amf --synthetic 16 --paced --seconds 8 --width 3840 --height 2160 --fps 60 --bitrate 40000 --codec hevc --idr-interval 60 --config target\qa\ratecontrol\cap2.conf
The defaults are ultra-low-latency usage, speed, latency-constrained VBR,
VBAQ on, pre-analysis off, LTR off, HRD off and no filler or frame skipping.
The Rust host sets frame rate and target bitrate at startup. Before this
change it did not explicitly set peak bitrate or VBV there: on this driver
VBR derives a 1.2× peak and one frame of VBV, whereas CBR derives a 1× peak
and one second of VBV. The existing bitrate-update path attempts to scale the
driver's positive rate-control values; its AV1 frame-cap property name is
now corrected from Av1MaxAUSize to Av1MaxCompressedFrameSize.
The new optional limits are applied after frame rate and target bitrate and
read back before Init. Explicit rejected requests produce an AMF setting error.
LowLatencyInternal and InputQueueSize remain untouched by default.
| Table setting | Configuration added to the defaults |
|---|---|
| default | none |
| cbr | amd_rc=cbr |
| cbr_vbv1 | amd_rc=cbr, amd_vbv_buffer_frames=1 |
| peak1 / peak15 / peak2 | amd_peak_bitrate_ratio=1 / 1.5 / 2 |
| vbv05 / vbv1 / vbv2 | amd_vbv_buffer_frames=0.5 / 1 / 2 |
| cap1 / cap2 / cap4 | amd_max_frame_size=1 / 2 / 4 |
| hrd | amd_enforce_hrd=true |
| cap2hrd | amd_max_frame_size=2, amd_enforce_hrd=true |
| intra | --intra-refresh --idr-interval 0; continuous refresh, no forced recovery keyframes |
| intra_idr | --intra-refresh with the same once-per-second recovery requests |
Frame budgets are bitrate divided by the exact negotiated frame rate. AMF
expects bits for VBV, MaxAUSize, HevcMaxAUSize and
Av1MaxCompressedFrameSize; the tables report bytes. The names and units
match the retained SDK and AMD's HEVC header
and AV1 header.
Screening: 4K60 at 40 Mbps #
The initial screening used a development build with 20 paced warmup frames and eight seconds of measurement. Builds overlapped some of these runs, so the release matrix is the basis for timing comparisons. Its byte-size comparisons are repeatable; each second pass reverses the settings order.
| Mode / Mbps | Setting / run | Non-keyframes | Steady mean / p50 / p99 / max (bytes) | Startup bytes / steady mean | Recovery max bytes / steady mean | Encode mean / p99 (ms) |
|---|---|---|---|---|---|---|
| HEVC 3840×2160@60 / 40 | default / 1 | 473 | 79908 / 79312 / 93172 / 96174 | 245474 / 3.07× | 242986 / 3.04× | 6.117 / 6.817 |
| HEVC 3840×2160@60 / 40 | cbr / 1 | 473 | 79849 / 77566 / 143632 / 158407 | 615045 / 7.70× | 242453 / 3.04× | 5.932 / 6.818 |
| HEVC 3840×2160@60 / 40 | peak1 / 1 | 473 | 79908 / 79312 / 93172 / 96174 | 245474 / 3.07× | 242986 / 3.04× | 6.144 / 6.822 |
| HEVC 3840×2160@60 / 40 | peak15 / 1 | 473 | 79908 / 79312 / 93172 / 96174 | 245474 / 3.07× | 242986 / 3.04× | 6.269 / 6.885 |
| HEVC 3840×2160@60 / 40 | peak2 / 1 | 473 | 79908 / 79312 / 93172 / 96174 | 245474 / 3.07× | 242986 / 3.04× | 6.206 / 6.818 |
| HEVC 3840×2160@60 / 40 | vbv05 / 1 | 473 | 78514 / 78002 / 92474 / 105469 | 213837 / 2.72× | 278811 / 3.55× | 6.233 / 6.842 |
| HEVC 3840×2160@60 / 40 | vbv1 / 1 | 473 | 79908 / 79312 / 93172 / 96174 | 245474 / 3.07× | 242986 / 3.04× | 6.226 / 6.878 |
| HEVC 3840×2160@60 / 40 | vbv2 / 1 | 473 | 80504 / 80229 / 98435 / 105270 | 293486 / 3.65× | 248412 / 3.09× | 6.185 / 6.784 |
| HEVC 3840×2160@60 / 40 | cap1 / 1 | 473 | 79400 / 79008 / 93301 / 94779 | 178313 / 2.25× | 92557 / 1.17× | 6.186 / 6.819 |
| HEVC 3840×2160@60 / 40 | cap2 / 1 | 473 | 79194 / 78595 / 93307 / 93494 | 245474 / 3.10× | 167152 / 2.11× | 6.165 / 6.800 |
| HEVC 3840×2160@60 / 40 | cap4 / 1 | 473 | 79908 / 79312 / 93172 / 96174 | 245474 / 3.07× | 242986 / 3.04× | 6.180 / 6.802 |
| HEVC 3840×2160@60 / 40 | hrd / 1 | 473 | 79908 / 79312 / 93172 / 96174 | 245474 / 3.07× | 242986 / 3.04× | 6.191 / 6.792 |
| HEVC 3840×2160@60 / 40 | cap2hrd / 1 | 473 | 79194 / 78595 / 93307 / 93494 | 245474 / 3.10× | 167152 / 2.11× | 6.185 / 6.858 |
| HEVC 3840×2160@60 / 40 | intra / 1 | 481 | 82031 / 80867 / 93440 / 94347 | 245658 / 2.99× | — | 6.236 / 6.933 |
| HEVC 3840×2160@60 / 40 | intra_idr / 1 | 473 | 79901 / 79312 / 93137 / 96257 | 245658 / 3.07× | 243298 / 3.04× | 6.191 / 6.797 |
| HEVC 3840×2160@60 / 40 | intra_idr / 2 | 473 | 79901 / 79312 / 93137 / 96257 | 245658 / 3.07× | 243298 / 3.04× | 6.170 / 6.758 |
| HEVC 3840×2160@60 / 40 | intra / 2 | 481 | 82031 / 80867 / 93440 / 94347 | 245658 / 2.99× | — | 6.086 / 6.677 |
| HEVC 3840×2160@60 / 40 | cap2hrd / 2 | 473 | 79194 / 78595 / 93307 / 93494 | 245474 / 3.10× | 167152 / 2.11× | 6.122 / 6.712 |
| HEVC 3840×2160@60 / 40 | hrd / 2 | 473 | 79908 / 79312 / 93172 / 96174 | 245474 / 3.07× | 242986 / 3.04× | 5.999 / 6.404 |
| HEVC 3840×2160@60 / 40 | cap4 / 2 | 473 | 79908 / 79312 / 93172 / 96174 | 245474 / 3.07× | 242986 / 3.04× | 6.087 / 6.711 |
| HEVC 3840×2160@60 / 40 | cap2 / 2 | 473 | 79194 / 78595 / 93307 / 93494 | 245474 / 3.10× | 167152 / 2.11× | 6.105 / 6.758 |
| HEVC 3840×2160@60 / 40 | cap1 / 2 | 473 | 79400 / 79008 / 93301 / 94779 | 178313 / 2.25× | 92557 / 1.17× | 6.144 / 6.772 |
| HEVC 3840×2160@60 / 40 | vbv2 / 2 | 473 | 80504 / 80229 / 98435 / 105270 | 293486 / 3.65× | 248412 / 3.09× | 6.233 / 6.826 |
| HEVC 3840×2160@60 / 40 | vbv1 / 2 | 473 | 79908 / 79312 / 93172 / 96174 | 245474 / 3.07× | 242986 / 3.04× | 6.205 / 6.819 |
| HEVC 3840×2160@60 / 40 | vbv05 / 2 | 473 | 78514 / 78002 / 92474 / 105469 | 213837 / 2.72× | 278811 / 3.55× | 6.242 / 6.842 |
| HEVC 3840×2160@60 / 40 | peak2 / 2 | 473 | 79908 / 79312 / 93172 / 96174 | 245474 / 3.07× | 242986 / 3.04× | 6.291 / 6.887 |
| HEVC 3840×2160@60 / 40 | peak15 / 2 | 473 | 79908 / 79312 / 93172 / 96174 | 245474 / 3.07× | 242986 / 3.04× | 6.155 / 6.796 |
| HEVC 3840×2160@60 / 40 | peak1 / 2 | 473 | 79908 / 79312 / 93172 / 96174 | 245474 / 3.07× | 242986 / 3.04× | 6.077 / 6.873 |
| HEVC 3840×2160@60 / 40 | cbr / 2 | 473 | 79849 / 77566 / 143632 / 158407 | 615045 / 7.70× | 242453 / 3.04× | 5.988 / 6.704 |
| HEVC 3840×2160@60 / 40 | default / 2 | 473 | 79908 / 79312 / 93172 / 96174 | 245474 / 3.07× | 242986 / 3.04× | 6.009 / 6.587 |
| AV1 3840×2160@60 / 40 | default / 1 | 473 | 79001 / 79208 / 103293 / 113084 | 185400 / 2.35× | 153531 / 1.94× | 4.967 / 5.663 |
| AV1 3840×2160@60 / 40 | cbr / 1 | 473 | 82409 / 81892 / 128763 / 141099 | 307290 / 3.73× | 162898 / 1.98× | 5.042 / 5.701 |
| AV1 3840×2160@60 / 40 | peak1 / 1 | 473 | 79001 / 79208 / 103293 / 113084 | 185400 / 2.35× | 153531 / 1.94× | 4.919 / 5.576 |
| AV1 3840×2160@60 / 40 | peak15 / 1 | 473 | 79001 / 79208 / 103293 / 113084 | 185400 / 2.35× | 153531 / 1.94× | 5.033 / 5.770 |
| AV1 3840×2160@60 / 40 | peak2 / 1 | 473 | 79001 / 79208 / 103293 / 113084 | 185400 / 2.35× | 153531 / 1.94× | 4.885 / 5.449 |
| AV1 3840×2160@60 / 40 | vbv05 / 1 | 473 | 78369 / 79091 / 93732 / 99924 | 158745 / 2.03× | 155231 / 1.98× | 4.908 / 5.713 |
| AV1 3840×2160@60 / 40 | vbv1 / 1 | 473 | 79001 / 79208 / 103293 / 113084 | 185400 / 2.35× | 153531 / 1.94× | 4.958 / 5.477 |
| AV1 3840×2160@60 / 40 | vbv2 / 1 | 473 | 79098 / 79399 / 105131 / 115271 | 225386 / 2.85× | 160404 / 2.03× | 4.988 / 5.808 |
| AV1 3840×2160@60 / 40 | cap1 / 1 | 473 | 78197 / 79029 / 93730 / 94345 | 132770 / 1.70× | 81211 / 1.04× | 5.040 / 5.714 |
| AV1 3840×2160@60 / 40 | cap2 / 1 | 473 | 78997 / 79251 / 103113 / 111306 | 185400 / 2.35× | 152792 / 1.93× | 4.982 / 5.605 |
| AV1 3840×2160@60 / 40 | cap4 / 1 | 473 | 79001 / 79208 / 103293 / 113084 | 185400 / 2.35× | 153531 / 1.94× | 5.058 / 5.787 |
| AV1 3840×2160@60 / 40 | hrd / 1 | 473 | 79001 / 79208 / 103293 / 113084 | 185400 / 2.35× | 153531 / 1.94× | 4.953 / 5.754 |
| AV1 3840×2160@60 / 40 | cap2hrd / 1 | 473 | 78997 / 79251 / 103113 / 111306 | 185400 / 2.35× | 152792 / 1.93× | 5.044 / 5.802 |
| AV1 3840×2160@60 / 40 | intra / 1 | 481 | 80105 / 79238 / 93845 / 94157 | 185400 / 2.31× | — | 4.904 / 5.434 |
| AV1 3840×2160@60 / 40 | intra_idr / 1 | 473 | 78811 / 79184 / 105086 / 112245 | 185400 / 2.35× | 153888 / 1.95× | 5.017 / 5.789 |
| AV1 3840×2160@60 / 40 | intra_idr / 2 | 473 | 78811 / 79184 / 105086 / 112245 | 185400 / 2.35× | 153888 / 1.95× | 4.964 / 5.496 |
| AV1 3840×2160@60 / 40 | intra / 2 | 481 | 80105 / 79238 / 93845 / 94157 | 185400 / 2.31× | — | 4.948 / 5.575 |
| AV1 3840×2160@60 / 40 | cap2hrd / 2 | 473 | 78997 / 79251 / 103113 / 111306 | 185400 / 2.35× | 152792 / 1.93× | 5.082 / 5.799 |
| AV1 3840×2160@60 / 40 | hrd / 2 | 473 | 79001 / 79208 / 103293 / 113084 | 185400 / 2.35× | 153531 / 1.94× | 5.092 / 5.856 |
| AV1 3840×2160@60 / 40 | cap4 / 2 | 473 | 79001 / 79208 / 103293 / 113084 | 185400 / 2.35× | 153531 / 1.94× | 5.113 / 5.856 |
| AV1 3840×2160@60 / 40 | cap2 / 2 | 473 | 78997 / 79251 / 103113 / 111306 | 185400 / 2.35× | 152792 / 1.93× | 4.972 / 5.630 |
| AV1 3840×2160@60 / 40 | cap1 / 2 | 473 | 78197 / 79029 / 93730 / 94345 | 132770 / 1.70× | 81211 / 1.04× | 4.950 / 5.761 |
| AV1 3840×2160@60 / 40 | vbv2 / 2 | 473 | 79098 / 79399 / 105131 / 115271 | 225386 / 2.85× | 160404 / 2.03× | 4.983 / 5.564 |
| AV1 3840×2160@60 / 40 | vbv1 / 2 | 473 | 79001 / 79208 / 103293 / 113084 | 185400 / 2.35× | 153531 / 1.94× | 5.024 / 5.867 |
| AV1 3840×2160@60 / 40 | vbv05 / 2 | 473 | 78369 / 79091 / 93732 / 99924 | 158745 / 2.03× | 155231 / 1.98× | 5.104 / 5.804 |
| AV1 3840×2160@60 / 40 | peak2 / 2 | 473 | 79001 / 79208 / 103293 / 113084 | 185400 / 2.35× | 153531 / 1.94× | 5.011 / 5.861 |
| AV1 3840×2160@60 / 40 | peak15 / 2 | 473 | 79001 / 79208 / 103293 / 113084 | 185400 / 2.35× | 153531 / 1.94× | 4.976 / 5.598 |
| AV1 3840×2160@60 / 40 | peak1 / 2 | 473 | 79001 / 79208 / 103293 / 113084 | 185400 / 2.35× | 153531 / 1.94× | 5.203 / 5.928 |
| AV1 3840×2160@60 / 40 | cbr / 2 | 473 | 82409 / 81892 / 128763 / 141099 | 307290 / 3.73× | 162898 / 1.98× | 5.055 / 5.755 |
| AV1 3840×2160@60 / 40 | default / 2 | 473 | 79001 / 79208 / 103293 / 113084 | 185400 / 2.35× | 153531 / 1.94× | 5.087 / 5.899 |
Release matrix #
Rows marked † overlap a live service stream (15:50:54–15:51:34 UTC). They are retained as raw observations, excluded from latency comparisons, and repeated below. The AV1 4K40 default run completed 455 frames and the HEVC 4K80 cap1 run 448; all other matrix runs completed the usual 481 or 961. Every submitted frame was returned.
| Mode / Mbps | Setting / run | Non-keyframes | Steady mean / p50 / p99 / max (bytes) | Startup bytes / steady mean | Recovery max bytes / steady mean | Encode mean / p99 (ms) |
|---|---|---|---|---|---|---|
| HEVC 1920×1080@60 / 20 | default / 1 | 473 | 40681 / 41542 / 48500 / 49616 | 90690 / 2.23× | 96594 / 2.37× | 2.284 / 2.779 |
| HEVC 1920×1080@60 / 20 | cbr / 1 | 473 | 41054 / 41130 / 52972 / 55554 | 154192 / 3.76× | 80037 / 1.95× | 2.161 / 2.747 |
| HEVC 1920×1080@60 / 20 | cbr_vbv1 / 1 | 473 | 41038 / 41302 / 49118 / 50360 | 154192 / 3.76× | 86468 / 2.11× | 2.149 / 2.728 |
| HEVC 1920×1080@60 / 20 | cap1 / 1 | 473 | 40361 / 40935 / 48345 / 48722 | 77843 / 1.93× | 47530 / 1.18× | 2.250 / 2.888 |
| HEVC 1920×1080@60 / 20 | cap2 / 1 | 473 | 40669 / 41544 / 48338 / 50587 | 90690 / 2.23× | 92231 / 2.27× | 2.217 / 2.854 |
| HEVC 1920×1080@60 / 20 | intra / 1 | 481 | 41309 / 42372 / 48517 / 48668 | 90746 / 2.20× | — | 2.199 / 2.787 |
| HEVC 1920×1080@60 / 20 | intra / 2 | 481 | 41309 / 42372 / 48517 / 48668 | 90746 / 2.20× | — | 2.254 / 3.214 |
| HEVC 1920×1080@60 / 20 | cap2 / 2 | 473 | 40669 / 41544 / 48338 / 50587 | 90690 / 2.23× | 92231 / 2.27× | 2.278 / 2.901 |
| HEVC 1920×1080@60 / 20 | cap1 / 2 | 473 | 40361 / 40935 / 48345 / 48722 | 77843 / 1.93× | 47530 / 1.18× | 2.448 / 3.190 |
| HEVC 1920×1080@60 / 20 | cbr_vbv1 / 2 | 473 | 41038 / 41302 / 49118 / 50360 | 154192 / 3.76× | 86468 / 2.11× | 2.423 / 2.980 |
| HEVC 1920×1080@60 / 20 | cbr / 2 | 473 | 41054 / 41130 / 52972 / 55554 | 154192 / 3.76× | 80037 / 1.95× | 2.410 / 2.852 |
| HEVC 1920×1080@60 / 20 | default / 2 | 473 | 40681 / 41542 / 48500 / 49616 | 90690 / 2.23× | 96594 / 2.37× | 2.391 / 3.216 |
| AV1 1920×1080@60 / 20 | default / 1 | 473 | 41146 / 41475 / 47605 / 56855 | 66499 / 1.62× | 76640 / 1.86× | 1.959 / 2.145 |
| AV1 1920×1080@60 / 20 | cbr / 1 | 473 | 41384 / 40699 / 55727 / 59922 | 79012 / 1.91× | 63611 / 1.54× | 1.851 / 2.036 |
| AV1 1920×1080@60 / 20 | cbr_vbv1 / 1 | 473 | 41141 / 41258 / 49984 / 54971 | 79012 / 1.92× | 64778 / 1.57× | 1.886 / 2.241 |
| AV1 1920×1080@60 / 20 | cap1 / 1 | 473 | 40883 / 41081 / 47339 / 54164 | 56286 / 1.38× | 43347 / 1.06× | 1.822 / 1.996 |
| AV1 1920×1080@60 / 20 | cap2 / 1 | 473 | 41146 / 41475 / 47605 / 56855 | 66499 / 1.62× | 76640 / 1.86× | 1.782 / 1.905 |
| AV1 1920×1080@60 / 20 | intra / 1 | 481 | 41235 / 41608 / 46994 / 47452 | 66499 / 1.61× | — | 1.827 / 2.072 |
| AV1 1920×1080@60 / 20 | intra / 2 | 481 | 41235 / 41608 / 46994 / 47452 | 66499 / 1.61× | — | 1.845 / 2.024 |
| AV1 1920×1080@60 / 20 | cap2 / 2 | 473 | 41146 / 41475 / 47605 / 56855 | 66499 / 1.62× | 76640 / 1.86× | 1.858 / 2.062 |
| AV1 1920×1080@60 / 20 | cap1 / 2 | 473 | 40883 / 41081 / 47339 / 54164 | 56286 / 1.38× | 43347 / 1.06× | 1.745 / 1.954 |
| AV1 1920×1080@60 / 20 | cbr_vbv1 / 2 | 473 | 41141 / 41258 / 49984 / 54971 | 79012 / 1.92× | 64778 / 1.57× | 1.818 / 1.957 |
| AV1 1920×1080@60 / 20 | cbr / 2 | 473 | 41384 / 40699 / 55727 / 59922 | 79012 / 1.91× | 63611 / 1.54× | 1.818 / 2.117 |
| AV1 1920×1080@60 / 20 | default / 2 | 473 | 41146 / 41475 / 47605 / 56855 | 66499 / 1.62× | 76640 / 1.86× | 1.804 / 1.960 |
| HEVC 1920×1080@60 / 40 | default / 1 | 473 | 81591 / 82190 / 95755 / 98074 | 109896 / 1.35× | 165972 / 2.03× | 2.238 / 2.825 |
| HEVC 1920×1080@60 / 40 | cbr / 1 | 473 | 82498 / 81777 / 96972 / 102106 | 154192 / 1.87× | 136004 / 1.65× | 2.283 / 2.888 |
| HEVC 1920×1080@60 / 40 | cbr_vbv1 / 1 | 473 | 81987 / 82608 / 92698 / 93796 | 154192 / 1.88× | 140645 / 1.72× | 2.207 / 2.734 |
| HEVC 1920×1080@60 / 40 | cap1 / 1 | 473 | 81706 / 82426 / 94881 / 95460 | 109896 / 1.35× | 96997 / 1.19× | 2.178 / 2.827 |
| HEVC 1920×1080@60 / 40 | cap2 / 1 | 473 | 81683 / 82237 / 94522 / 97190 | 109896 / 1.35× | 153716 / 1.88× | 2.287 / 2.796 |
| HEVC 1920×1080@60 / 40 | intra / 1 | 481 | 82018 / 82519 / 94952 / 96880 | 109929 / 1.34× | — | 2.228 / 2.791 |
| HEVC 1920×1080@60 / 40 | intra / 2 | 481 | 82018 / 82519 / 94952 / 96880 | 109929 / 1.34× | — | 2.341 / 2.848 |
| HEVC 1920×1080@60 / 40 | cap2 / 2 | 473 | 81683 / 82237 / 94522 / 97190 | 109896 / 1.35× | 153716 / 1.88× | 2.338 / 2.877 |
| HEVC 1920×1080@60 / 40 | cap1 / 2 | 473 | 81706 / 82426 / 94881 / 95460 | 109896 / 1.35× | 96997 / 1.19× | 2.290 / 2.865 |
| HEVC 1920×1080@60 / 40 | cbr_vbv1 / 2 | 473 | 81987 / 82608 / 92698 / 93796 | 154192 / 1.88× | 140645 / 1.72× | 2.357 / 3.008 |
| HEVC 1920×1080@60 / 40 | cbr / 2 | 473 | 82498 / 81777 / 96972 / 102106 | 154192 / 1.87× | 136004 / 1.65× | 2.396 / 3.227 |
| HEVC 1920×1080@60 / 40 | default / 2 | 473 | 81591 / 82190 / 95755 / 98074 | 109896 / 1.35× | 165972 / 2.03× | 2.353 / 3.152 |
| AV1 1920×1080@60 / 40 | default / 1 | 473 | 82772 / 83111 / 100624 / 105872 | 71907 / 0.87× | 120405 / 1.45× | 1.972 / 2.256 |
| AV1 1920×1080@60 / 40 | cbr / 1 | 473 | 83206 / 84552 / 97402 / 97986 | 79012 / 0.95× | 100260 / 1.20× | 1.873 / 2.127 |
| AV1 1920×1080@60 / 40 | cbr_vbv1 / 1 | 473 | 82008 / 84113 / 94902 / 95264 | 79012 / 0.96× | 97148 / 1.18× | 1.843 / 2.080 |
| AV1 1920×1080@60 / 40 | cap1 / 1 | 473 | 82055 / 82708 / 100503 / 101143 | 71907 / 0.88× | 77800 / 0.95× | 1.814 / 2.001 |
| AV1 1920×1080@60 / 40 | cap2 / 1 | 473 | 82772 / 83111 / 100624 / 105872 | 71907 / 0.87× | 120405 / 1.45× | 1.877 / 2.057 |
| AV1 1920×1080@60 / 40 | intra / 1 | 481 | 82726 / 83067 / 100639 / 101241 | 71907 / 0.87× | — | 1.881 / 2.091 |
| AV1 1920×1080@60 / 40 | intra / 2 | 481 | 82726 / 83067 / 100639 / 101241 | 71907 / 0.87× | — | 1.901 / 2.075 |
| AV1 1920×1080@60 / 40 | cap2 / 2 | 473 | 82772 / 83111 / 100624 / 105872 | 71907 / 0.87× | 120405 / 1.45× | 1.894 / 2.107 |
| AV1 1920×1080@60 / 40 | cap1 / 2 | 473 | 82055 / 82708 / 100503 / 101143 | 71907 / 0.88× | 77800 / 0.95× | 1.874 / 2.035 |
| AV1 1920×1080@60 / 40 | cbr_vbv1 / 2 | 473 | 82008 / 84113 / 94902 / 95264 | 79012 / 0.96× | 97148 / 1.18× | 1.861 / 2.102 |
| AV1 1920×1080@60 / 40 | cbr / 2 | 473 | 83206 / 84552 / 97402 / 97986 | 79012 / 0.95× | 100260 / 1.20× | 1.856 / 1.990 |
| AV1 1920×1080@60 / 40 | default / 2 | 473 | 82772 / 83111 / 100624 / 105872 | 71907 / 0.87× | 120405 / 1.45× | 1.877 / 2.060 |
| HEVC 1920×1080@60 / 80 | default / 1 | 473 | 163579 / 162262 / 191592 / 192365 | 127237 / 0.78× | 309871 / 1.89× | 2.344 / 2.741 |
| HEVC 1920×1080@60 / 80 | cbr / 1 | 473 | 165849 / 165127 / 176931 / 190737 | 154192 / 0.93× | 260312 / 1.57× | 2.397 / 2.823 |
| HEVC 1920×1080@60 / 80 | cbr_vbv1 / 1 | 473 | 163872 / 167352 / 184602 / 185931 | 154192 / 0.94× | 286142 / 1.75× | 2.337 / 2.727 |
| HEVC 1920×1080@60 / 80 | cap1 / 1 | 473 | 163736 / 162409 / 191766 / 192129 | 127237 / 0.78× | 187840 / 1.15× | 2.334 / 2.776 |
| HEVC 1920×1080@60 / 80 | cap2 / 1 | 473 | 163279 / 161757 / 191459 / 192365 | 127237 / 0.78× | 309675 / 1.90× | 2.366 / 2.749 |
| HEVC 1920×1080@60 / 80 | intra / 1 | 481 | 164616 / 163113 / 191800 / 192106 | 127343 / 0.77× | — | 2.307 / 2.713 |
| HEVC 1920×1080@60 / 80 | intra / 2 | 481 | 164616 / 163113 / 191800 / 192106 | 127343 / 0.77× | — | 2.287 / 2.672 |
| HEVC 1920×1080@60 / 80 | cap2 / 2 | 473 | 163279 / 161757 / 191459 / 192365 | 127237 / 0.78× | 309675 / 1.90× | 2.286 / 2.700 |
| HEVC 1920×1080@60 / 80 | cap1 / 2 | 473 | 163736 / 162409 / 191766 / 192129 | 127237 / 0.78× | 187840 / 1.15× | 2.264 / 2.678 |
| HEVC 1920×1080@60 / 80 | cbr_vbv1 / 2 | 473 | 163872 / 167352 / 184602 / 185931 | 154192 / 0.94× | 286142 / 1.75× | 2.285 / 2.718 |
| HEVC 1920×1080@60 / 80 | cbr / 2 | 473 | 165849 / 165127 / 176931 / 190737 | 154192 / 0.93× | 260312 / 1.57× | 2.319 / 2.692 |
| HEVC 1920×1080@60 / 80 | default / 2 | 473 | 163579 / 162262 / 191592 / 192365 | 127237 / 0.78× | 309871 / 1.89× | 2.298 / 2.698 |
| AV1 1920×1080@60 / 80 | default / 1 | 473 | 163248 / 162500 / 204673 / 208384 | 74987 / 0.46× | 246750 / 1.51× | 1.962 / 2.204 |
| AV1 1920×1080@60 / 80 | cbr / 1 | 473 | 165879 / 166058 / 200869 / 230081 | 79012 / 0.48× | 238012 / 1.43× | 1.977 / 2.335 |
| AV1 1920×1080@60 / 80 | cbr_vbv1 / 1 | 473 | 161297 / 166396 / 203637 / 204852 | 79012 / 0.49× | 199973 / 1.24× | 1.907 / 1.977 |
| AV1 1920×1080@60 / 80 | cap1 / 1 | 473 | 161583 / 159934 / 205139 / 205607 | 74987 / 0.46× | 137621 / 0.85× | 1.975 / 2.139 |
| AV1 1920×1080@60 / 80 | cap2 / 1 | 473 | 163522 / 163182 / 205090 / 208100 | 74987 / 0.46× | 246750 / 1.51× | 1.916 / 2.031 |
| AV1 1920×1080@60 / 80 | intra / 1 | 481 | 164629 / 163207 / 205290 / 206221 | 74987 / 0.46× | — | 2.000 / 2.343 |
| AV1 1920×1080@60 / 80 | intra / 2 | 481 | 164629 / 163207 / 205290 / 206221 | 74987 / 0.46× | — | 1.930 / 2.058 |
| AV1 1920×1080@60 / 80 | cap2 / 2 | 473 | 163522 / 163182 / 205090 / 208100 | 74987 / 0.46× | 246750 / 1.51× | 1.921 / 2.116 |
| AV1 1920×1080@60 / 80 | cap1 / 2 | 473 | 161583 / 159934 / 205139 / 205607 | 74987 / 0.46× | 137621 / 0.85× | 1.953 / 2.260 |
| AV1 1920×1080@60 / 80 | cbr_vbv1 / 2 | 473 | 161297 / 166396 / 203637 / 204852 | 79012 / 0.49× | 199973 / 1.24× | 1.984 / 2.169 |
| AV1 1920×1080@60 / 80 | cbr / 2 | 473 | 165879 / 166058 / 200869 / 230081 | 79012 / 0.48× | 238012 / 1.43× | 1.910 / 2.076 |
| AV1 1920×1080@60 / 80 | default / 2 | 473 | 163248 / 162500 / 204673 / 208384 | 74987 / 0.46× | 246750 / 1.51× | 1.926 / 2.154 |
| HEVC 2560×1440@120 / 20 | default / 1 | 953 | 20500 / 20082 / 24354 / 24406 | 65915 / 3.22× | 63289 / 3.09× | 3.157 / 3.706 |
| HEVC 2560×1440@120 / 20 | cbr / 1 | 953 | 20846 / 20221 / 26649 / 30504 | 271818 / 13.04× | 51971 / 2.49× | 3.111 / 3.616 |
| HEVC 2560×1440@120 / 20 | cbr_vbv1 / 1 | 953 | 20563 / 20171 / 25463 / 25558 | 154202 / 7.50× | 58660 / 2.85× | 3.118 / 3.668 |
| HEVC 2560×1440@120 / 20 | cap1 / 1 | 953 | 20538 / 20155 / 24346 / 24426 | 45481 / 2.21× | 20680 / 1.01× | 3.177 / 3.703 |
| HEVC 2560×1440@120 / 20 | cap2 / 1 | 953 | 20506 / 20059 / 24370 / 24406 | 65915 / 3.21× | 41048 / 2.00× | 3.111 / 3.636 |
| HEVC 2560×1440@120 / 20 | intra / 1 | 961 | 20665 / 20139 / 24343 / 24402 | 65854 / 3.19× | — | 3.095 / 3.611 |
| HEVC 2560×1440@120 / 20 | intra / 2 | 961 | 20665 / 20139 / 24343 / 24402 | 65854 / 3.19× | — | 3.119 / 3.634 |
| HEVC 2560×1440@120 / 20 | cap2 / 2 | 953 | 20506 / 20059 / 24370 / 24406 | 65915 / 3.21× | 41048 / 2.00× | 3.163 / 3.726 |
| HEVC 2560×1440@120 / 20 | cap1 / 2 | 953 | 20538 / 20155 / 24346 / 24426 | 45481 / 2.21× | 20680 / 1.01× | 3.133 / 3.667 |
| HEVC 2560×1440@120 / 20 | cbr_vbv1 / 2 | 953 | 20563 / 20171 / 25463 / 25558 | 154202 / 7.50× | 58660 / 2.85× | 3.089 / 3.577 |
| HEVC 2560×1440@120 / 20 | cbr / 2 | 953 | 20846 / 20221 / 26649 / 30504 | 271818 / 13.04× | 51971 / 2.49× | 3.136 / 3.676 |
| HEVC 2560×1440@120 / 20 | default / 2 | 953 | 20500 / 20082 / 24354 / 24406 | 65915 / 3.22× | 63289 / 3.09× | 3.158 / 3.696 |
| AV1 2560×1440@120 / 20 | default / 1 | 953 | 20544 / 20317 / 24969 / 25066 | 53028 / 2.58× | 47847 / 2.33× | 2.558 / 3.074 |
| AV1 2560×1440@120 / 20 | cbr / 1 | 953 | 20593 / 19360 / 25502 / 46398 | 134832 / 6.55× | 43867 / 2.13× | 2.547 / 3.026 |
| AV1 2560×1440@120 / 20 | cbr_vbv1 / 1 | 953 | 20823 / 20412 / 27197 / 27797 | 86642 / 4.16× | 49660 / 2.38× | 2.546 / 3.006 |
| AV1 2560×1440@120 / 20 | cap1 / 1 | 953 | 20571 / 20223 / 24976 / 25106 | 34657 / 1.68× | 23931 / 1.16× | 2.605 / 3.143 |
| AV1 2560×1440@120 / 20 | cap2 / 1 | 953 | 20553 / 20252 / 24944 / 25196 | 53028 / 2.58× | 40503 / 1.97× | 2.602 / 3.145 |
| AV1 2560×1440@120 / 20 | intra / 1 | 961 | 20649 / 20271 / 24989 / 25088 | 53028 / 2.57× | — | 2.583 / 3.138 |
| AV1 2560×1440@120 / 20 | intra / 2 | 961 | 20649 / 20271 / 24989 / 25088 | 53028 / 2.57× | — | 2.694 / 3.163 |
| AV1 2560×1440@120 / 20 | cap2 / 2 | 953 | 20553 / 20252 / 24944 / 25196 | 53028 / 2.58× | 40503 / 1.97× | 2.574 / 3.136 |
| AV1 2560×1440@120 / 20 | cap1 / 2 | 953 | 20571 / 20223 / 24976 / 25106 | 34657 / 1.68× | 23931 / 1.16× | 2.558 / 3.089 |
| AV1 2560×1440@120 / 20 | cbr_vbv1 / 2 | 953 | 20823 / 20412 / 27197 / 27797 | 86642 / 4.16× | 49660 / 2.38× | 2.737 / 3.238 |
| AV1 2560×1440@120 / 20 | cbr / 2 | 953 | 20593 / 19360 / 25502 / 46398 | 134832 / 6.55× | 43867 / 2.13× | 2.573 / 3.107 |
| AV1 2560×1440@120 / 20 | default / 2 | 953 | 20544 / 20317 / 24969 / 25066 | 53028 / 2.58× | 47847 / 2.33× | 2.573 / 3.130 |
| HEVC 2560×1440@120 / 40 | default / 1 | 953 | 40275 / 41108 / 46697 / 54280 | 120344 / 2.99× | 137967 / 3.43× | 3.170 / 3.685 |
| HEVC 2560×1440@120 / 40 | cbr / 1 | 953 | 40896 / 40339 / 51911 / 82915 | 273504 / 6.69× | 102765 / 2.51× | 3.193 / 3.693 |
| HEVC 2560×1440@120 / 40 | cbr_vbv1 / 1 | 953 | 40861 / 41148 / 48109 / 58489 | 241930 / 5.92× | 121840 / 2.98× | 3.101 / 3.624 |
| HEVC 2560×1440@120 / 40 | cap1 / 1 | 953 | 40056 / 40840 / 46687 / 47410 | 87706 / 2.19× | 44501 / 1.11× | 3.113 / 3.655 |
| HEVC 2560×1440@120 / 40 | cap2 / 1 | 953 | 40109 / 40920 / 46697 / 47624 | 120344 / 3.00× | 81830 / 2.04× | 3.145 / 3.688 |
| HEVC 2560×1440@120 / 40 | intra / 1 | 961 | 40713 / 41292 / 46793 / 47701 | 120492 / 2.96× | — | 3.155 / 3.702 |
| HEVC 2560×1440@120 / 40 | intra / 2 | 961 | 40713 / 41292 / 46793 / 47701 | 120492 / 2.96× | — | 3.168 / 3.706 |
| HEVC 2560×1440@120 / 40 | cap2 / 2 | 953 | 40109 / 40920 / 46697 / 47624 | 120344 / 3.00× | 81830 / 2.04× | 3.179 / 3.734 |
| HEVC 2560×1440@120 / 40 | cap1 / 2 | 953 | 40056 / 40840 / 46687 / 47410 | 87706 / 2.19× | 44501 / 1.11× | 3.229 / 3.761 |
| HEVC 2560×1440@120 / 40 | cbr_vbv1 / 2 | 953 | 40861 / 41148 / 48109 / 58489 | 241930 / 5.92× | 121840 / 2.98× | 3.289 / 3.869 |
| HEVC 2560×1440@120 / 40 | cbr / 2 | 953 | 40896 / 40339 / 51911 / 82915 | 273504 / 6.69× | 102765 / 2.51× | 3.256 / 3.774 |
| HEVC 2560×1440@120 / 40 | default / 2 | 953 | 40275 / 41108 / 46697 / 54280 | 120344 / 2.99× | 137967 / 3.43× | 3.153 / 3.730 |
| AV1 2560×1440@120 / 40 | default / 1 | 953 | 39980 / 40456 / 45874 / 54962 | 91912 / 2.30× | 76289 / 1.91× | 2.752 / 3.606 |
| AV1 2560×1440@120 / 40 | cbr / 1 | 953 | 41005 / 41562 / 52080 / 73551 | 137697 / 3.36× | 76697 / 1.87× | 2.628 / 3.197 |
| AV1 2560×1440@120 / 40 | cbr_vbv1 / 1 | 953 | 41309 / 39539 / 48656 / 64844 | 126261 / 3.06× | 76626 / 1.85× | 2.690 / 3.236 |
| AV1 2560×1440@120 / 40 | cap1 / 1 | 953 | 39773 / 39881 / 44503 / 47864 | 64934 / 1.63× | 38589 / 0.97× | 2.658 / 3.191 |
| AV1 2560×1440@120 / 40 | cap2 / 1 | 953 | 40002 / 40268 / 45692 / 55494 | 91912 / 2.30× | 75412 / 1.89× | 2.573 / 3.137 |
| AV1 2560×1440@120 / 40 | intra / 1 | 961 | 40053 / 40482 / 45359 / 45964 | 91912 / 2.29× | — | 2.546 / 2.758 |
| AV1 2560×1440@120 / 40 | intra / 2 | 961 | 40053 / 40482 / 45359 / 45964 | 91912 / 2.29× | — | 2.561 / 3.109 |
| AV1 2560×1440@120 / 40 | cap2 / 2 | 953 | 40002 / 40268 / 45692 / 55494 | 91912 / 2.30× | 75412 / 1.89× | 2.606 / 3.142 |
| AV1 2560×1440@120 / 40 | cap1 / 2 | 953 | 39773 / 39881 / 44503 / 47864 | 64934 / 1.63× | 38589 / 0.97× | 2.665 / 3.191 |
| AV1 2560×1440@120 / 40 | cbr_vbv1 / 2 | 953 | 41309 / 39539 / 48656 / 64844 | 126261 / 3.06× | 76626 / 1.85× | 2.784 / 4.661 |
| AV1 2560×1440@120 / 40 | cbr / 2 | 953 | 41005 / 41562 / 52080 / 73551 | 137697 / 3.36× | 76697 / 1.87× | 2.565 / 3.120 |
| AV1 2560×1440@120 / 40 | default / 2 | 953 | 39980 / 40456 / 45874 / 54962 | 91912 / 2.30× | 76289 / 1.91× | 2.563 / 3.113 |
| HEVC 2560×1440@120 / 80 | default / 1 | 953 | 79432 / 79648 / 89451 / 91833 | 164372 / 2.07× | 172584 / 2.17× | 3.316 / 3.755 |
| HEVC 2560×1440@120 / 80 | cbr / 1 | 953 | 82951 / 80834 / 97724 / 114942 | 273504 / 3.30× | 139427 / 1.68× | 3.296 / 3.749 |
| HEVC 2560×1440@120 / 80 | cbr_vbv1 / 1 | 953 | 82445 / 85425 / 92433 / 96763 | 273504 / 3.32× | 179230 / 2.17× | 3.407 / 4.052 |
| HEVC 2560×1440@120 / 80 | cap1 / 1 | 953 | 79325 / 79384 / 89523 / 90888 | 149801 / 1.89× | 86920 / 1.10× | 3.318 / 3.757 |
| HEVC 2560×1440@120 / 80 | cap2 / 1 | 953 | 79396 / 79613 / 89539 / 91863 | 164372 / 2.07× | 154532 / 1.95× | 3.320 / 3.764 |
| HEVC 2560×1440@120 / 80 | intra / 1 | 961 | 79826 / 80282 / 89434 / 89686 | 164421 / 2.06× | — | 3.283 / 3.734 |
| HEVC 2560×1440@120 / 80 | intra / 2 | 961 | 79826 / 80282 / 89434 / 89686 | 164421 / 2.06× | — | 3.303 / 3.757 |
| HEVC 2560×1440@120 / 80 | cap2 / 2 | 953 | 79396 / 79613 / 89539 / 91863 | 164372 / 2.07× | 154532 / 1.95× | 3.350 / 3.783 |
| HEVC 2560×1440@120 / 80 | cap1 / 2 | 953 | 79325 / 79384 / 89523 / 90888 | 149801 / 1.89× | 86920 / 1.10× | 3.376 / 3.793 |
| HEVC 2560×1440@120 / 80 | cbr_vbv1 / 2 | 953 | 82445 / 85425 / 92433 / 96763 | 273504 / 3.32× | 179230 / 2.17× | 3.347 / 3.800 |
| HEVC 2560×1440@120 / 80 | cbr / 2 | 953 | 82951 / 80834 / 97724 / 114942 | 273504 / 3.30× | 139427 / 1.68× | 3.309 / 3.754 |
| HEVC 2560×1440@120 / 80 | default / 2 | 953 | 79432 / 79648 / 89451 / 91833 | 164372 / 2.07× | 172584 / 2.17× | 3.277 / 3.738 |
| AV1 2560×1440@120 / 80 | default / 1 | 953 | 79251 / 79181 / 90271 / 96057 | 115988 / 1.46× | 129240 / 1.63× | 2.617 / 3.144 |
| AV1 2560×1440@120 / 80 | cbr / 1 | 953 | 82938 / 81195 / 95358 / 121335 | 137697 / 1.66× | 112415 / 1.36× | 2.605 / 3.150 |
| AV1 2560×1440@120 / 80 | cbr_vbv1 / 1 | 953 | 82452 / 78778 / 93965 / 113246 | 137697 / 1.67× | 119465 / 1.45× | 2.745 / 3.226 |
| AV1 2560×1440@120 / 80 | cap1 / 1 | 953 | 79122 / 79041 / 88929 / 94888 | 105266 / 1.33× | 79608 / 1.01× | 2.734 / 3.259 |
| AV1 2560×1440@120 / 80 | cap2 / 1 | 953 | 79251 / 79181 / 90271 / 96057 | 115988 / 1.46× | 129240 / 1.63× | 2.759 / 3.275 |
| AV1 2560×1440@120 / 80 | intra / 1 | 961 | 79638 / 80068 / 90338 / 93959 | 115988 / 1.46× | — | 2.712 / 3.209 |
| AV1 2560×1440@120 / 80 | intra / 2 | 961 | 79638 / 80068 / 90338 / 93959 | 115988 / 1.46× | — | 2.655 / 3.188 |
| AV1 2560×1440@120 / 80 | cap2 / 2 | 953 | 79251 / 79181 / 90271 / 96057 | 115988 / 1.46× | 129240 / 1.63× | 2.643 / 3.188 |
| AV1 2560×1440@120 / 80 | cap1 / 2 | 953 | 79122 / 79041 / 88929 / 94888 | 105266 / 1.33× | 79608 / 1.01× | 2.630 / 3.181 |
| AV1 2560×1440@120 / 80 | cbr_vbv1 / 2 | 953 | 82452 / 78778 / 93965 / 113246 | 137697 / 1.67× | 119465 / 1.45× | 2.673 / 3.184 |
| AV1 2560×1440@120 / 80 | cbr / 2 | 953 | 82938 / 81195 / 95358 / 121335 | 137697 / 1.66× | 112415 / 1.36× | 2.578 / 3.134 |
| AV1 2560×1440@120 / 80 | default / 2 | 953 | 79251 / 79181 / 90271 / 96057 | 115988 / 1.46× | 129240 / 1.63× | 2.621 / 3.171 |
| HEVC 3840×2160@60 / 20 | default / 1 | 473 | 41318 / 39558 / 49226 / 50142 | 133527 / 3.23× | 113784 / 2.75× | 5.959 / 6.354 |
| HEVC 3840×2160@60 / 20 | cbr / 1 | 473 | 40300 / 37894 / 50655 / 53448 | 563110 / 13.97× | 102447 / 2.54× | 5.940 / 6.458 |
| HEVC 3840×2160@60 / 20 | cbr_vbv1 / 1 | 473 | 41254 / 37898 / 51685 / 53500 | 321254 / 7.79× | 102447 / 2.48× | 5.992 / 6.512 |
| HEVC 3840×2160@60 / 20 | cap1 / 1 | 473 | 41572 / 39799 / 50024 / 50212 | 90517 / 2.18× | 44496 / 1.07× | 5.971 / 6.369 |
| HEVC 3840×2160@60 / 20 | cap2 / 1 | 473 | 41256 / 39771 / 49265 / 50142 | 133527 / 3.24× | 84020 / 2.04× | 5.989 / 6.353 |
| HEVC 3840×2160@60 / 20 | intra / 1 | 481 | 41830 / 40919 / 49278 / 49351 | 133416 / 3.19× | — | 5.824 / 6.282 |
| HEVC 3840×2160@60 / 20 | intra / 2 | 481 | 41830 / 40919 / 49278 / 49351 | 133416 / 3.19× | — | 5.878 / 6.425 |
| HEVC 3840×2160@60 / 20 | cap2 / 2 | 473 | 41256 / 39771 / 49265 / 50142 | 133527 / 3.24× | 84020 / 2.04× | 6.034 / 6.443 |
| HEVC 3840×2160@60 / 20 | cap1 / 2 | 473 | 41572 / 39799 / 50024 / 50212 | 90517 / 2.18× | 44496 / 1.07× | 5.996 / 6.637 |
| HEVC 3840×2160@60 / 20 | cbr_vbv1 / 2 | 473 | 41254 / 37898 / 51685 / 53500 | 321254 / 7.79× | 102447 / 2.48× | 5.985 / 6.531 |
| HEVC 3840×2160@60 / 20 | cbr / 2 | 473 | 40300 / 37894 / 50655 / 53448 | 563110 / 13.97× | 102447 / 2.54× | 5.977 / 6.528 |
| HEVC 3840×2160@60 / 20 | default / 2 | 473 | 41318 / 39558 / 49226 / 50142 | 133527 / 3.23× | 113784 / 2.75× | 5.985 / 6.676 |
| AV1 3840×2160@60 / 20 | default / 1 | 473 | 41001 / 41103 / 48304 / 48958 | 106459 / 2.60× | 90172 / 2.20× | 4.834 / 5.327 |
| AV1 3840×2160@60 / 20 | cbr / 1 | 473 | 40984 / 38304 / 63241 / 74613 | 285815 / 6.97× | 91166 / 2.22× | 4.939 / 5.500 |
| AV1 3840×2160@60 / 20 | cbr_vbv1 / 1 | 473 | 41184 / 41061 / 53037 / 53149 | 181936 / 4.42× | 102945 / 2.50× | 4.918 / 5.380 |
| AV1 3840×2160@60 / 20 | cap1 / 1 | 473 | 41263 / 41379 / 48755 / 50545 | 68709 / 1.67× | 45928 / 1.11× | 4.923 / 5.389 |
| AV1 3840×2160@60 / 20 | cap2 / 1 | 473 | 41063 / 41246 / 48492 / 49312 | 106459 / 2.59× | 82943 / 2.02× | 4.938 / 5.547 |
| AV1 3840×2160@60 / 20 | intra / 1 | 481 | 41528 / 41283 / 49533 / 50787 | 106459 / 2.56× | — | 5.041 / 5.778 |
| AV1 3840×2160@60 / 20 | intra / 2 | 481 | 41528 / 41283 / 49533 / 50787 | 106459 / 2.56× | — | 4.994 / 5.718 |
| AV1 3840×2160@60 / 20 | cap2 / 2 | 473 | 41063 / 41246 / 48492 / 49312 | 106459 / 2.59× | 82943 / 2.02× | 5.050 / 5.779 |
| AV1 3840×2160@60 / 20 | cap1 / 2 | 473 | 41263 / 41379 / 48755 / 50545 | 68709 / 1.67× | 45928 / 1.11× | 5.037 / 5.524 |
| AV1 3840×2160@60 / 20 | cbr_vbv1 / 2 | 473 | 41184 / 41061 / 53037 / 53149 | 181936 / 4.42× | 102945 / 2.50× | 5.033 / 5.671 |
| AV1 3840×2160@60 / 20 | cbr / 2 | 473 | 40984 / 38304 / 63241 / 74613 | 285815 / 6.97× | 91166 / 2.22× | 5.051 / 5.725 |
| AV1 3840×2160@60 / 20 | default / 2 | 473 | 41001 / 41103 / 48304 / 48958 | 106459 / 2.60× | 90172 / 2.20× | 5.042 / 5.773 |
| HEVC 3840×2160@60 / 40 | default / 1 | 473 | 79820 / 79234 / 93258 / 96964 | 245474 / 3.08× | 265995 / 3.33× | 6.216 / 6.771 |
| HEVC 3840×2160@60 / 40 | cbr / 1 | 473 | 80935 / 78090 / 143341 / 158445 | 615045 / 7.60× | 245278 / 3.03× | 6.210 / 6.789 |
| HEVC 3840×2160@60 / 40 | cbr_vbv1 / 1 | 473 | 79922 / 78940 / 100353 / 112325 | 512986 / 6.42× | 253462 / 3.17× | 6.050 / 6.721 |
| HEVC 3840×2160@60 / 40 | cap1 / 1 | 473 | 79397 / 78767 / 93307 / 93482 | 178313 / 2.25× | 89626 / 1.13× | 6.103 / 6.752 |
| HEVC 3840×2160@60 / 40 | cap2 / 1 | 473 | 79425 / 78987 / 93313 / 93857 | 245474 / 3.09× | 167074 / 2.10× | 6.083 / 6.716 |
| HEVC 3840×2160@60 / 40 | intra / 1 | 481 | 82048 / 80867 / 93426 / 93797 | 245658 / 2.99× | — | 6.071 / 6.548 |
| HEVC 3840×2160@60 / 40 | intra / 2 | 481 | 82048 / 80867 / 93426 / 93797 | 245658 / 2.99× | — | 6.070 / 6.488 |
| HEVC 3840×2160@60 / 40 | cap2 / 2 | 473 | 79425 / 78987 / 93313 / 93857 | 245474 / 3.09× | 167074 / 2.10× | 6.034 / 6.395 |
| HEVC 3840×2160@60 / 40 | cap1 / 2 | 473 | 79397 / 78767 / 93307 / 93482 | 178313 / 2.25× | 89626 / 1.13× | 6.005 / 6.386 |
| HEVC 3840×2160@60 / 40 | cbr_vbv1 / 2 | 473 | 79922 / 78940 / 100353 / 112325 | 512986 / 6.42× | 253462 / 3.17× | 5.991 / 6.359 |
| HEVC 3840×2160@60 / 40 | cbr / 2 | 473 | 80935 / 78090 / 143341 / 158445 | 615045 / 7.60× | 245278 / 3.03× | 5.954 / 6.396 |
| HEVC 3840×2160@60 / 40 | default / 2 | 473 | 79820 / 79234 / 93258 / 96964 | 245474 / 3.08× | 265995 / 3.33× | 6.007 / 6.376 |
| AV1 3840×2160@60 / 40 | default / 1 | 473 | 79254 / 79331 / 105540 / 112372 | 185400 / 2.34× | 154097 / 1.94× | 5.079 / 5.774 |
| AV1 3840×2160@60 / 40 | cbr / 1 | 473 | 82148 / 81758 / 128763 / 141099 | 307290 / 3.74× | 162112 / 1.97× | 4.945 / 5.659 |
| AV1 3840×2160@60 / 40 | cbr_vbv1 / 1 | 473 | 82217 / 79758 / 124565 / 127992 | 266944 / 3.25× | 164820 / 2.00× | 4.923 / 5.737 |
| AV1 3840×2160@60 / 40 | cap1 / 1 | 473 | 78366 / 78989 / 93744 / 94929 | 132770 / 1.69× | 82356 / 1.05× | 4.982 / 5.708 |
| AV1 3840×2160@60 / 40 | cap2 / 1 | 473 | 79150 / 79293 / 105441 / 111427 | 185400 / 2.34× | 152792 / 1.93× | 4.865 / 5.665 |
| AV1 3840×2160@60 / 40 | intra / 1 | 481 | 80191 / 79271 / 93847 / 94157 | 185400 / 2.31× | — | 4.997 / 5.805 |
| AV1 3840×2160@60 / 40 | intra / 2 | 481 | 80191 / 79271 / 93847 / 94157 | 185400 / 2.31× | — | 4.929 / 5.724 |
| AV1 3840×2160@60 / 40 | cap2 / 2 | 473 | 79150 / 79293 / 105441 / 111427 | 185400 / 2.34× | 152792 / 1.93× | 5.007 / 5.660 |
| AV1 3840×2160@60 / 40 | cap1 / 2 | 473 | 78366 / 78989 / 93744 / 94929 | 132770 / 1.69× | 82356 / 1.05× | 4.914 / 5.314 |
| AV1 3840×2160@60 / 40 | cbr_vbv1 / 2 | 473 | 82217 / 79758 / 124565 / 127992 | 266944 / 3.25× | 164820 / 2.00× | 5.001 / 5.591 |
| AV1 3840×2160@60 / 40 | cbr / 2 | 473 | 82148 / 81758 / 128763 / 141099 | 307290 / 3.74× | 162112 / 1.97× | 5.024 / 5.798 |
| AV1 3840×2160@60 / 40 | default / 2 † stream/probe overlap | 448 | 79360 / 79284 / 105540 / 112372 | 185400 / 2.34× | 154097 / 1.94× | 6.423 / 12.266 |
| HEVC 3840×2160@60 / 80 | default / 1 † stream/probe overlap | 473 | 156383 / 155179 / 180262 / 182161 | 358474 / 2.29× | 324108 / 2.07× | 6.136 / 6.645 |
| HEVC 3840×2160@60 / 80 | cbr / 1 † stream/probe overlap | 473 | 164289 / 162660 / 215923 / 216938 | 615045 / 3.74× | 315279 / 1.92× | 6.114 / 6.806 |
| HEVC 3840×2160@60 / 80 | cbr_vbv1 / 1 † stream/probe overlap | 473 | 163989 / 160788 / 195259 / 199848 | 615045 / 3.75× | 340352 / 2.08× | 6.157 / 6.766 |
| HEVC 3840×2160@60 / 80 | cap1 / 1 † stream/probe overlap | 441 | 154612 / 152135 / 181661 / 182206 | 308507 / 2.00× | 170031 / 1.10× | 8.273 / 64.236 |
| HEVC 3840×2160@60 / 80 | cap2 / 1 | 473 | 156500 / 154927 / 180484 / 182115 | 358474 / 2.29× | 315456 / 2.02× | 6.156 / 6.750 |
| HEVC 3840×2160@60 / 80 | intra / 1 | 481 | 158262 / 161579 / 181698 / 185403 | 358503 / 2.27× | — | 6.136 / 6.757 |
| HEVC 3840×2160@60 / 80 | intra / 2 | 481 | 158262 / 161579 / 181698 / 185403 | 358503 / 2.27× | — | 6.127 / 6.731 |
| HEVC 3840×2160@60 / 80 | cap2 / 2 | 473 | 156500 / 154927 / 180484 / 182115 | 358474 / 2.29× | 315456 / 2.02× | 6.008 / 6.704 |
| HEVC 3840×2160@60 / 80 | cap1 / 2 | 473 | 154374 / 151574 / 181661 / 182206 | 308507 / 2.00× | 170031 / 1.10× | 6.108 / 6.749 |
| HEVC 3840×2160@60 / 80 | cbr_vbv1 / 2 | 473 | 163989 / 160788 / 195259 / 199848 | 615045 / 3.75× | 340352 / 2.08× | 6.240 / 6.836 |
| HEVC 3840×2160@60 / 80 | cbr / 2 | 473 | 164289 / 162660 / 215923 / 216938 | 615045 / 3.74× | 315279 / 1.92× | 6.242 / 6.853 |
| HEVC 3840×2160@60 / 80 | default / 2 | 473 | 156383 / 155179 / 180262 / 182161 | 358474 / 2.29× | 324108 / 2.07× | 6.126 / 6.779 |
| AV1 3840×2160@60 / 80 | default / 1 | 473 | 154544 / 155726 / 173910 / 187397 | 253332 / 1.64× | 255986 / 1.66× | 4.804 / 5.318 |
| AV1 3840×2160@60 / 80 | cbr / 1 | 473 | 165737 / 162718 / 228771 / 251926 | 307290 / 1.85× | 250269 / 1.51× | 4.996 / 5.728 |
| AV1 3840×2160@60 / 80 | cbr_vbv1 / 1 | 473 | 164476 / 161764 / 208183 / 226477 | 307290 / 1.87× | 250464 / 1.52× | 4.935 / 5.770 |
| AV1 3840×2160@60 / 80 | cap1 / 1 | 473 | 153742 / 154901 / 174143 / 176661 | 215311 / 1.40× | 157031 / 1.02× | 4.900 / 5.750 |
| AV1 3840×2160@60 / 80 | cap2 / 1 | 473 | 154544 / 155726 / 173910 / 187397 | 253332 / 1.64× | 255986 / 1.66× | 4.924 / 5.730 |
| AV1 3840×2160@60 / 80 | intra / 1 | 481 | 155089 / 156143 / 172484 / 175581 | 253332 / 1.63× | — | 4.975 / 5.759 |
| AV1 3840×2160@60 / 80 | intra / 2 | 481 | 155089 / 156143 / 172484 / 175581 | 253332 / 1.63× | — | 4.984 / 5.482 |
| AV1 3840×2160@60 / 80 | cap2 / 2 | 473 | 154544 / 155726 / 173910 / 187397 | 253332 / 1.64× | 255986 / 1.66× | 5.006 / 5.894 |
| AV1 3840×2160@60 / 80 | cap1 / 2 | 473 | 153742 / 154901 / 174143 / 176661 | 215311 / 1.40× | 157031 / 1.02× | 4.952 / 5.736 |
| AV1 3840×2160@60 / 80 | cbr_vbv1 / 2 | 473 | 164476 / 161764 / 208183 / 226477 | 307290 / 1.87× | 250464 / 1.52× | 4.949 / 5.451 |
| AV1 3840×2160@60 / 80 | cbr / 2 | 473 | 165737 / 162718 / 228771 / 251926 | 307290 / 1.85× | 250269 / 1.51× | 4.961 / 5.539 |
| AV1 3840×2160@60 / 80 | default / 2 | 473 | 154544 / 155726 / 173910 / 187397 | 253332 / 1.64× | 255986 / 1.66× | 4.865 / 5.358 |
Frequent recovery requests at 4K60 and 63 Mbps #
The same release method, with --bitrate 63000 --idr-interval 6, requests
ten recovery keyframes per second, approximating frequent loss reports
without injecting network loss. cbr_cap1 and cbr_cap2 add the respective
cap to amd_rc=cbr and amd_vbv_buffer_frames=1.
| Mode / Mbps | Setting / run | Non-keyframes | Steady mean / p50 / p99 / max (bytes) | Startup bytes / steady mean | Recovery max bytes / steady mean | Encode mean / p99 (ms) |
|---|---|---|---|---|---|---|
| HEVC 3840×2160@60 / 63 | default / 1 | 401 | 109013 / 106784 / 139821 / 142313 | 334673 / 3.07× | 294072 / 2.70× | 5.924 / 6.351 |
| HEVC 3840×2160@60 / 63 | cbr_vbv1 / 1 | 401 | 114254 / 108384 / 165169 / 165169 | 615045 / 5.38× | 301985 / 2.64× | 6.083 / 6.736 |
| HEVC 3840×2160@60 / 63 | cap1 / 1 | 401 | 101273 / 100411 / 119624 / 140142 | 254477 / 2.51× | 136605 / 1.35× | 5.971 / 6.585 |
| HEVC 3840×2160@60 / 63 | cap2 / 1 | 401 | 108302 / 104644 / 136414 / 140626 | 334673 / 3.09× | 254958 / 2.35× | 5.997 / 6.669 |
| HEVC 3840×2160@60 / 63 | cbr_cap1 / 1 | 401 | 107310 / 101679 / 145249 / 162267 | 448837 / 4.18× | 186982 / 1.74× | 5.971 / 6.419 |
| HEVC 3840×2160@60 / 63 | cbr_cap2 / 1 | 401 | 113592 / 106497 / 159120 / 165644 | 615045 / 5.41× | 271885 / 2.39× | 6.106 / 6.834 |
| HEVC 3840×2160@60 / 63 | cbr_cap2 / 2 | 401 | 113592 / 106497 / 159120 / 165644 | 615045 / 5.41× | 271885 / 2.39× | 6.048 / 6.608 |
| HEVC 3840×2160@60 / 63 | cbr_cap1 / 2 | 401 | 107310 / 101679 / 145249 / 162267 | 448837 / 4.18× | 186982 / 1.74× | 5.890 / 6.519 |
| HEVC 3840×2160@60 / 63 | cap2 / 2 | 401 | 108302 / 104644 / 136414 / 140626 | 334673 / 3.09× | 254958 / 2.35× | 5.956 / 6.738 |
| HEVC 3840×2160@60 / 63 | cap1 / 2 | 401 | 101273 / 100411 / 119624 / 140142 | 254477 / 2.51× | 136605 / 1.35× | 6.078 / 6.822 |
| HEVC 3840×2160@60 / 63 | cbr_vbv1 / 2 | 401 | 114254 / 108384 / 165169 / 165169 | 615045 / 5.38× | 301985 / 2.64× | 6.014 / 6.678 |
| HEVC 3840×2160@60 / 63 | default / 2 | 401 | 109013 / 106784 / 139821 / 142313 | 334673 / 3.07× | 294072 / 2.70× | 5.910 / 6.486 |
| AV1 3840×2160@60 / 63 | default / 1 | 401 | 116619 / 115511 / 148503 / 148606 | 243277 / 2.09× | 213511 / 1.83× | 4.763 / 5.303 |
| AV1 3840×2160@60 / 63 | cbr_vbv1 / 1 | 401 | 123402 / 123240 / 162894 / 162894 | 307290 / 2.49× | 203733 / 1.65× | 4.810 / 5.257 |
| AV1 3840×2160@60 / 63 | cap1 / 1 | 401 | 110072 / 109551 / 135051 / 140585 | 180535 / 1.64× | 131815 / 1.20× | 4.790 / 5.234 |
| AV1 3840×2160@60 / 63 | cap2 / 1 | 401 | 116619 / 115511 / 148503 / 148606 | 243277 / 2.09× | 213511 / 1.83× | 4.864 / 5.291 |
| AV1 3840×2160@60 / 63 | cbr_cap1 / 1 | 401 | 118700 / 117256 / 155871 / 155871 | 244193 / 2.06× | 161413 / 1.36× | 5.190 / 5.813 |
| AV1 3840×2160@60 / 63 | cbr_cap2 / 1 | 401 | 123402 / 123240 / 162894 / 162894 | 307290 / 2.49× | 203733 / 1.65× | 4.947 / 6.385 |
| AV1 3840×2160@60 / 63 | cbr_cap2 / 2 | 401 | 123402 / 123240 / 162894 / 162894 | 307290 / 2.49× | 203733 / 1.65× | 4.935 / 5.336 |
| AV1 3840×2160@60 / 63 | cbr_cap1 / 2 | 401 | 118700 / 117256 / 155871 / 155871 | 244193 / 2.06× | 161413 / 1.36× | 4.917 / 5.376 |
| AV1 3840×2160@60 / 63 | cap2 / 2 | 401 | 116619 / 115511 / 148503 / 148606 | 243277 / 2.09× | 213511 / 1.83× | 4.944 / 5.702 |
| AV1 3840×2160@60 / 63 | cap1 / 2 | 401 | 110072 / 109551 / 135051 / 140585 | 180535 / 1.64× | 131815 / 1.20× | 4.904 / 5.562 |
| AV1 3840×2160@60 / 63 | cbr_vbv1 / 2 | 401 | 123402 / 123240 / 162894 / 162894 | 307290 / 2.49× | 203733 / 1.65× | 4.989 / 5.449 |
| AV1 3840×2160@60 / 63 | default / 2 | 401 | 116619 / 115511 / 148503 / 148606 | 243277 / 2.09× | 213511 / 1.83× | 4.987 / 5.696 |
Peak and VBV recheck after two seconds of warmup #
The development screening's short warmup could confound VBV comparisons. These release runs repeat the peak and buffer options at 4K60/40 Mbps with the same two-second warmup as the main matrix. They also check HRD, the four-frame cap, and intra refresh with explicit recovery requests.
| Mode / Mbps | Setting / run | Non-keyframes | Steady mean / p50 / p99 / max (bytes) | Startup bytes / steady mean | Recovery max bytes / steady mean | Encode mean / p99 (ms) |
|---|---|---|---|---|---|---|
| HEVC 3840×2160@60 / 40 | default / 1 | 473 | 79820 / 79234 / 93258 / 96964 | 245474 / 3.08× | 265995 / 3.33× | 5.957 / 6.621 |
| HEVC 3840×2160@60 / 40 | peak1 / 1 | 473 | 79820 / 79234 / 93258 / 96964 | 245474 / 3.08× | 265995 / 3.33× | 6.056 / 6.575 |
| HEVC 3840×2160@60 / 40 | peak15 / 1 | 473 | 79820 / 79234 / 93258 / 96964 | 245474 / 3.08× | 265995 / 3.33× | 6.014 / 6.453 |
| HEVC 3840×2160@60 / 40 | peak2 / 1 | 473 | 79820 / 79234 / 93258 / 96964 | 245474 / 3.08× | 265995 / 3.33× | 6.027 / 6.463 |
| HEVC 3840×2160@60 / 40 | vbv05 / 1 | 473 | 78485 / 78020 / 93492 / 117456 | 213837 / 2.72× | 296535 / 3.78× | 6.011 / 6.385 |
| HEVC 3840×2160@60 / 40 | vbv1 / 1 | 473 | 79820 / 79234 / 93258 / 96964 | 245474 / 3.08× | 265995 / 3.33× | 6.025 / 6.396 |
| HEVC 3840×2160@60 / 40 | vbv2 / 1 | 473 | 80653 / 80187 / 98329 / 105790 | 293486 / 3.64× | 242837 / 3.01× | 6.050 / 6.615 |
| HEVC 3840×2160@60 / 40 | cap4 / 1 | 473 | 79820 / 79234 / 93258 / 96964 | 245474 / 3.08× | 265995 / 3.33× | 6.073 / 6.458 |
| HEVC 3840×2160@60 / 40 | hrd / 1 | 473 | 79820 / 79234 / 93258 / 96964 | 245474 / 3.08× | 265995 / 3.33× | 6.124 / 6.632 |
| HEVC 3840×2160@60 / 40 | cap2hrd / 1 | 473 | 79425 / 78987 / 93313 / 93857 | 245474 / 3.09× | 167074 / 2.10× | 6.121 / 6.679 |
| HEVC 3840×2160@60 / 40 | intra_idr / 1 | 473 | 79829 / 79143 / 92769 / 96778 | 245658 / 3.08× | 266805 / 3.34× | 6.104 / 6.496 |
| HEVC 3840×2160@60 / 40 | intra_idr / 2 | 473 | 79829 / 79143 / 92769 / 96778 | 245658 / 3.08× | 266805 / 3.34× | 6.042 / 6.773 |
| HEVC 3840×2160@60 / 40 | cap2hrd / 2 | 473 | 79425 / 78987 / 93313 / 93857 | 245474 / 3.09× | 167074 / 2.10× | 6.092 / 6.544 |
| HEVC 3840×2160@60 / 40 | hrd / 2 | 473 | 79820 / 79234 / 93258 / 96964 | 245474 / 3.08× | 265995 / 3.33× | 6.032 / 6.679 |
| HEVC 3840×2160@60 / 40 | cap4 / 2 | 473 | 79820 / 79234 / 93258 / 96964 | 245474 / 3.08× | 265995 / 3.33× | 6.059 / 6.771 |
| HEVC 3840×2160@60 / 40 | vbv2 / 2 | 473 | 80653 / 80187 / 98329 / 105790 | 293486 / 3.64× | 242837 / 3.01× | 6.158 / 6.732 |
| HEVC 3840×2160@60 / 40 | vbv1 / 2 | 473 | 79820 / 79234 / 93258 / 96964 | 245474 / 3.08× | 265995 / 3.33× | 6.130 / 6.660 |
| HEVC 3840×2160@60 / 40 | vbv05 / 2 | 473 | 78485 / 78020 / 93492 / 117456 | 213837 / 2.72× | 296535 / 3.78× | 6.129 / 6.718 |
| HEVC 3840×2160@60 / 40 | peak2 / 2 | 473 | 79820 / 79234 / 93258 / 96964 | 245474 / 3.08× | 265995 / 3.33× | 6.052 / 6.624 |
| HEVC 3840×2160@60 / 40 | peak15 / 2 | 473 | 79820 / 79234 / 93258 / 96964 | 245474 / 3.08× | 265995 / 3.33× | 6.054 / 6.537 |
| HEVC 3840×2160@60 / 40 | peak1 / 2 | 473 | 79820 / 79234 / 93258 / 96964 | 245474 / 3.08× | 265995 / 3.33× | 6.202 / 6.730 |
| HEVC 3840×2160@60 / 40 | default / 2 | 473 | 79820 / 79234 / 93258 / 96964 | 245474 / 3.08× | 265995 / 3.33× | 6.202 / 6.748 |
| AV1 3840×2160@60 / 40 | default / 1 | 473 | 79254 / 79331 / 105540 / 112372 | 185400 / 2.34× | 154097 / 1.94× | 4.871 / 5.325 |
| AV1 3840×2160@60 / 40 | peak1 / 1 | 473 | 79254 / 79331 / 105540 / 112372 | 185400 / 2.34× | 154097 / 1.94× | 5.021 / 5.767 |
| AV1 3840×2160@60 / 40 | peak15 / 1 | 473 | 79254 / 79331 / 105540 / 112372 | 185400 / 2.34× | 154097 / 1.94× | 4.960 / 5.752 |
| AV1 3840×2160@60 / 40 | peak2 / 1 | 473 | 79254 / 79331 / 105540 / 112372 | 185400 / 2.34× | 154097 / 1.94× | 4.773 / 5.304 |
| AV1 3840×2160@60 / 40 | vbv05 / 1 | 473 | 78508 / 79142 / 93829 / 102300 | 158745 / 2.02× | 163561 / 2.08× | 4.937 / 5.512 |
| AV1 3840×2160@60 / 40 | vbv1 / 1 | 473 | 79254 / 79331 / 105540 / 112372 | 185400 / 2.34× | 154097 / 1.94× | 4.959 / 5.559 |
| AV1 3840×2160@60 / 40 | vbv2 / 1 | 473 | 79425 / 79431 / 105676 / 114494 | 225386 / 2.84× | 153760 / 1.94× | 4.901 / 5.604 |
| AV1 3840×2160@60 / 40 | cap4 / 1 | 473 | 79254 / 79331 / 105540 / 112372 | 185400 / 2.34× | 154097 / 1.94× | 4.899 / 5.736 |
| AV1 3840×2160@60 / 40 | hrd / 1 | 473 | 79254 / 79331 / 105540 / 112372 | 185400 / 2.34× | 154097 / 1.94× | 4.901 / 5.292 |
| AV1 3840×2160@60 / 40 | cap2hrd / 1 | 473 | 79150 / 79293 / 105441 / 111427 | 185400 / 2.34× | 152792 / 1.93× | 4.854 / 5.718 |
| AV1 3840×2160@60 / 40 | intra_idr / 1 | 473 | 78990 / 79133 / 105404 / 112083 | 185400 / 2.35× | 153531 / 1.94× | 5.000 / 5.732 |
| AV1 3840×2160@60 / 40 | intra_idr / 2 | 473 | 78990 / 79133 / 105404 / 112083 | 185400 / 2.35× | 153531 / 1.94× | 4.997 / 5.762 |
| AV1 3840×2160@60 / 40 | cap2hrd / 2 | 473 | 79150 / 79293 / 105441 / 111427 | 185400 / 2.34× | 152792 / 1.93× | 4.922 / 5.750 |
| AV1 3840×2160@60 / 40 | hrd / 2 | 473 | 79254 / 79331 / 105540 / 112372 | 185400 / 2.34× | 154097 / 1.94× | 4.814 / 5.266 |
| AV1 3840×2160@60 / 40 | cap4 / 2 | 473 | 79254 / 79331 / 105540 / 112372 | 185400 / 2.34× | 154097 / 1.94× | 4.876 / 5.363 |
| AV1 3840×2160@60 / 40 | vbv2 / 2 | 473 | 79425 / 79431 / 105676 / 114494 | 225386 / 2.84× | 153760 / 1.94× | 4.808 / 5.337 |
| AV1 3840×2160@60 / 40 | vbv1 / 2 | 473 | 79254 / 79331 / 105540 / 112372 | 185400 / 2.34× | 154097 / 1.94× | 4.967 / 5.374 |
| AV1 3840×2160@60 / 40 | vbv05 / 2 | 473 | 78508 / 79142 / 93829 / 102300 | 158745 / 2.02× | 163561 / 2.08× | 4.786 / 5.316 |
| AV1 3840×2160@60 / 40 | peak2 / 2 | 473 | 79254 / 79331 / 105540 / 112372 | 185400 / 2.34× | 154097 / 1.94× | 4.951 / 5.498 |
| AV1 3840×2160@60 / 40 | peak15 / 2 | 473 | 79254 / 79331 / 105540 / 112372 | 185400 / 2.34× | 154097 / 1.94× | 4.942 / 5.661 |
| AV1 3840×2160@60 / 40 | peak1 / 2 | 473 | 79254 / 79331 / 105540 / 112372 | 185400 / 2.34× | 154097 / 1.94× | 4.954 / 5.670 |
| AV1 3840×2160@60 / 40 | default / 2 | 473 | 79254 / 79331 / 105540 / 112372 | 185400 / 2.34× | 154097 / 1.94× | 4.929 / 5.692 |
Repeats of modes affected by the live stream #
Both affected mode groups were repeated in full, again in forward/reverse order. All 24 runs below had no active stream or other observed performance probe, completed 481 of 481 submissions, and reached the requested cadence. Use these clean comparisons for AV1 4K60/40 Mbps and HEVC 4K60/80 Mbps; the marked original rows remain above for transparency.
| Mode / Mbps | Setting / run | Non-keyframes | Steady mean / p50 / p99 / max (bytes) | Startup bytes / steady mean | Recovery max bytes / steady mean | Encode mean / p99 (ms) |
|---|---|---|---|---|---|---|
| AV1 3840×2160@60 / 40 | default / 1 | 473 | 79254 / 79331 / 105540 / 112372 | 185400 / 2.34× | 154097 / 1.94× | 4.904 / 5.454 |
| AV1 3840×2160@60 / 40 | cbr / 1 | 473 | 82148 / 81758 / 128763 / 141099 | 307290 / 3.74× | 162112 / 1.97× | 5.026 / 5.794 |
| AV1 3840×2160@60 / 40 | cbr_vbv1 / 1 | 473 | 82217 / 79758 / 124565 / 127992 | 266944 / 3.25× | 164820 / 2.00× | 5.027 / 5.771 |
| AV1 3840×2160@60 / 40 | cap1 / 1 | 473 | 78366 / 78989 / 93744 / 94929 | 132770 / 1.69× | 82356 / 1.05× | 4.950 / 5.321 |
| AV1 3840×2160@60 / 40 | cap2 / 1 | 473 | 79150 / 79293 / 105441 / 111427 | 185400 / 2.34× | 152792 / 1.93× | 4.969 / 5.695 |
| AV1 3840×2160@60 / 40 | intra / 1 | 481 | 80191 / 79271 / 93847 / 94157 | 185400 / 2.31× | — | 4.904 / 5.353 |
| AV1 3840×2160@60 / 40 | intra / 2 | 481 | 80191 / 79271 / 93847 / 94157 | 185400 / 2.31× | — | 4.969 / 5.414 |
| AV1 3840×2160@60 / 40 | cap2 / 2 | 473 | 79150 / 79293 / 105441 / 111427 | 185400 / 2.34× | 152792 / 1.93× | 4.943 / 5.427 |
| AV1 3840×2160@60 / 40 | cap1 / 2 | 473 | 78366 / 78989 / 93744 / 94929 | 132770 / 1.69× | 82356 / 1.05× | 4.938 / 5.423 |
| AV1 3840×2160@60 / 40 | cbr_vbv1 / 2 | 473 | 82217 / 79758 / 124565 / 127992 | 266944 / 3.25× | 164820 / 2.00× | 4.897 / 5.289 |
| AV1 3840×2160@60 / 40 | cbr / 2 | 473 | 82148 / 81758 / 128763 / 141099 | 307290 / 3.74× | 162112 / 1.97× | 4.883 / 5.367 |
| AV1 3840×2160@60 / 40 | default / 2 | 473 | 79254 / 79331 / 105540 / 112372 | 185400 / 2.34× | 154097 / 1.94× | 4.893 / 5.370 |
| HEVC 3840×2160@60 / 80 | default / 1 | 473 | 156383 / 155179 / 180262 / 182161 | 358474 / 2.29× | 324108 / 2.07× | 6.141 / 6.724 |
| HEVC 3840×2160@60 / 80 | cbr / 1 | 473 | 164289 / 162660 / 215923 / 216938 | 615045 / 3.74× | 315279 / 1.92× | 6.127 / 6.700 |
| HEVC 3840×2160@60 / 80 | cbr_vbv1 / 1 | 473 | 163989 / 160788 / 195259 / 199848 | 615045 / 3.75× | 340352 / 2.08× | 6.173 / 6.726 |
| HEVC 3840×2160@60 / 80 | cap1 / 1 | 473 | 154374 / 151574 / 181661 / 182206 | 308507 / 2.00× | 170031 / 1.10× | 6.066 / 6.718 |
| HEVC 3840×2160@60 / 80 | cap2 / 1 | 473 | 156500 / 154927 / 180484 / 182115 | 358474 / 2.29× | 315456 / 2.02× | 6.081 / 6.560 |
| HEVC 3840×2160@60 / 80 | intra / 1 | 481 | 158262 / 161579 / 181698 / 185403 | 358503 / 2.27× | — | 6.138 / 6.697 |
| HEVC 3840×2160@60 / 80 | intra / 2 | 481 | 158262 / 161579 / 181698 / 185403 | 358503 / 2.27× | — | 6.096 / 6.789 |
| HEVC 3840×2160@60 / 80 | cap2 / 2 | 473 | 156500 / 154927 / 180484 / 182115 | 358474 / 2.29× | 315456 / 2.02× | 6.146 / 6.776 |
| HEVC 3840×2160@60 / 80 | cap1 / 2 | 473 | 154374 / 151574 / 181661 / 182206 | 308507 / 2.00× | 170031 / 1.10× | 6.132 / 6.657 |
| HEVC 3840×2160@60 / 80 | cbr_vbv1 / 2 | 473 | 163989 / 160788 / 195259 / 199848 | 615045 / 3.75× | 340352 / 2.08× | 6.151 / 6.801 |
| HEVC 3840×2160@60 / 80 | cbr / 2 | 473 | 164289 / 162660 / 215923 / 216938 | 615045 / 3.74× | 315279 / 1.92× | 6.159 / 6.800 |
| HEVC 3840×2160@60 / 80 | default / 2 | 473 | 156383 / 155179 / 180262 / 182161 | 358474 / 2.29× | 324108 / 2.07× | 6.135 / 6.739 |
Decision and limits #
Keep vbr_latency, ultra-low-latency usage, speed, VBAQ on and HRD off as
the defaults. Peak bitrate, VBV and the maximum frame size remain driver
defaults (0 in the three new settings). No input-queue, internal-latency,
lookahead, filler or frame-skipping setting is added to implement a cap.
The measurements do not justify a universal change that both removes
bursts and preserves encode time and picture quality across these modes.
At 4K60/40 Mbps HEVC its non-keyframe p99 is 143,341 bytes against VBR's
93,258; explicitly restoring a one-frame VBV reduces that to 100,353. At
1080p60/80 Mbps CBR improves that comparison. At 4K60/20 Mbps AV1, CBR with a
one-frame VBV instead increases recovery maximum by 14.2%. Imported
amd_rc=cbr settings are not overwritten.
Explicit 1×, 1.5× and 2× peak requests read back correctly but produce identical frame sizes in the tested latency-constrained VBR mode. One frame of explicit VBV reproduces the default. Half a frame increases HEVC's 4K40 recovery maximum from 265,995 to 296,535 bytes; two frames reduce it to 242,837 but raise non-keyframe p99 from 93,258 to 98,329. HRD adds no size reduction in these tests, including when paired with a two-frame cap.
The frame cap is useful as an opt-in trade-off, not a hard bound.
amd_max_frame_size=1 reduces 4K40 HEVC recovery maximum from 265,995 to
89,626 bytes (66.3%). Non-keyframe p99 barely changes, 93,258 to 93,307.
The two mean encode times are 6.216/6.007 ms without the cap and
6.103/6.005 ms with it. A two-frame cap gives 167,074 bytes (37.2% smaller)
and 6.083/6.034 ms. On AV1 a two-frame cap often does nothing because its
keyframes already fit that budget; the one-frame cap has a larger effect.
With ten recovery requests per second at 4K60/63 Mbps, HEVC VBR's maximum falls from 294,072 to 136,605 bytes with a one-frame cap, but measured encoded payload also falls from 64.12 to 51.01 Mbps. Mean encode time is 5.924/5.910 ms uncapped and 5.971/6.078 ms capped: a small 0.108 ms increase in the two-run mean. CBR plus a one-frame VBV and one-frame cap still reaches 186,982 bytes for a recovery frame and 448,837 at startup.
Even outside the frequent-recovery test, the cap can be exceeded. The 4K40 HEVC
one-frame request is 83,333 bytes, but its startup frame is 178,313 bytes. Some
ordinary frames exceed it too. All new properties were accepted on this RX 7900
XT; that is not RDNA4 validation. The previous RDNA4 freeze reports concern
LowLatencyInternal and InputQueueSize; this change forces neither.
Intra refresh stays client-negotiated. With no forced keyframes it avoids the periodic spikes, but explicitly requesting recovery still produces a full keyframe: 266,805 bytes for HEVC with refresh versus 265,995 without at 4K40. The HEVC policy refreshes seven of 2,040 CTBs per slot at 4K. The AMF HEVC API defines that property in 64×64 CTBs per slot. Replacing a decoder-reset keyframe request with that gradual process is not justified by this test.
For an affected AMD user who can compare picture quality, the new setting allows testing a one- or two-frame cap with the existing VBR default. It does not promise that an arbitrary Wi-Fi route can carry the stream. NVIDIA users should use Vibepollo.
Wire-time estimate, not a network measurement: at 2×40 Mbps pacing and 20%
FEC, ignoring packet headers and link overhead, the measured HEVC 4K40 maximum
occupies about 31.9 ms of packet transmission uncapped, 10.8 ms with a
one-frame cap and 20.0 ms with a two-frame cap (bytes × 8 × 1.2 / 80,000,000). That explains the potential to reduce burst pressure. The paced
submission-to-output results check the encoder's contribution to latency;
capture, networking, decoding and presentation remain outside this probe.
Verification: 368 recorded runs completed (60 development screening runs
and 308 release runs). Every submitted frame returned, with one latency
sample per completed frame. Five original matrix runs overlapped a live
stream; they are marked above and both affected mode groups were repeated.
Two of those overlapping runs missed the requested cadence. Every clean
run reached 481 submissions at 60 fps or 961 at 120 fps. No other
performance process was observed at the pre-run checks.
The release performance example built successfully. Final checks passed:
cargo fmt --all; Clippy for butterpollo-core and butterpollo-windows,
all targets with -D warnings; 166 core tests; and 61 host binary tests.
The host's two existing ignored tests remain ignored (all-interface
listener/firewall interaction and official installer download). npm ci
and npm run check passed with 0 errors and 0 warnings across 163 files.
Policy tests cover driver-default preservation, codec property names,
bit units at fractional frame rates, and invalid settings. All Cargo
commands used the supplied environment and --target-dir target\qa
where applicable.
October 7: PyroWave release picture checks #
On the AMD Radeon RX 7900 XT (driver 32.0.31041.1004), the release receiver now
decodes PyroWave with SDK bitstream 186f0393 and uses the same pixel barcode,
motion, cadence and Opus tone checks as the other codecs.
build-pyrowave-client.ps1 builds moonlight_client.c against the existing
pinned Nonary transport. That transport negotiates records and decrypts video
and audio; the receiver rejects codec fallback, a different bitstream, missing
encryption, lost buffers, invalid records and incomplete SDK frames. Every
delivered picture must decode. The former separate SDK receiver is folded into
the common fixture, so its decoded pixels now supply the release measurements.
The shared barcode reader now accounts for the encoder's letterbox and measures QPC picture age when the explicit host address is loopback. The PyroWave gate requires age samples for at least 95% of steady pictures and records their mean, p95, p99 and maximum. Age ends after SDK decode and CPU readback, before any client display. Existing limits remain: 97–103% of the requested cadence, at least 90% fresh pictures, 95% barcode coverage, no decode failures, bounded delivery gaps, and a continuous audible test tone.
release.ps1 adds required 1920×1080 at 60 fps runs for SDR 4:2:0 and HDR
4:4:4, each requesting 400 Mbps for 12 seconds with a three-second warmup.
finalize.py requires both results. They use the existing retry once and
fail-release path. Only the isolated PyroWave profile enables PyroWave and
requires LAN encryption; no installed configuration is changed. The first
connection checks rejected the default profile because it disabled video
encryption, despite the client's request to encrypt everything.
The live checks used the installed rc.20 executable as a portable host:
source f06e72c73c1960665d031d27f8dad02202bca56e, SHA-256
07b0487e1d9d7312da58fe3430c6d53d0e25e949632284896a94332cc99bc074.
Its own profile and ports were under target/pyrowave-e2e; the running
service was not stopped, restarted, reinstalled or reconfigured. Before
each run, the installed log's last stream event was CLIENT DISCONNECTED
at 15:51:34 UTC. The source remained a 5120×1440 SDR desktop, with the
fixture's 120 Hz bottom strip and virtual-speaker tone. The host letterboxed
it into 1920×1080. HDR therefore checks conversion and HDR/4:4:4 negotiation,
not native HDR capture. The SDK CPU output is eight-bit even for HDR;
full-precision HDR quality remains covered by the separate GPU harness above.
SDR and HDR alternated twice. All four runs passed, with 100% barcode and age coverage, zero repeated steady pictures, zero partial pictures, zero decode failures, and continuous tone:
| Measurement | SDR runs | HDR 4:4:4 runs |
|---|---|---|
| Pictures decoded / delivered | 707/707, 700/700 | 711/711, 704/704 |
| Fresh pictures/s after warmup | 59.994, 60.054 | 60.462, 60.292 |
| Picture age mean, ms | 21.332, 26.025 | 22.787, 24.186 |
| Picture age p95, ms | 22.192, 29.541 | 26.653, 28.707 |
| Picture age maximum, ms | 29.648, 39.370 | 28.330, 32.674 |
| Arrival interval p99, ms | 17.977, 18.005 | 18.650, 18.871 |
| Arrival interval maximum, ms | 19.067, 18.534 | 19.522, 23.357 |
These are two-run spreads on a shared machine, not isolated latency
comparisons. Artifacts are target/pyrowave-e2e/batch{2,3}/e2e-pyrowave*;
batch1 retains the rejected unencrypted connections. Reproduce each case
after loading the Rust environment and building the receiver and the two
probes beside it:
python rust/release/e2e.py --package 'C:\Program Files\ButterpolloRust' `
--work "$PWD/target/pyrowave-e2e/repeat" `
--client "$PWD/target/pyrowave-e2e/fixtures/moonlight-pyrowave-client.exe" `
--codec pyrowave --mode 1920x1080x60 --seconds 12 --bitrate 400000
# Repeat with --codec pyrowave-hdr-444. Use the release Python environment.
Two additional 12-second H.264/HEVC runs at 2560×720 decoded all 874/864 delivered pictures, with zero decode errors and 100% barcode coverage, but failed the unchanged audio-continuity gate: minimum tone RMS fell to 0.000013 and 0.000003 respectively. Aggregate live command time, including the early rejected connections and host startup/cleanup, was about 171 seconds. AV1 and HEVC VRR were not rerun within that budget.
Checks passed: 12 release-gate unit tests, both C receiver builds (warnings
denied, except the shared fixture's unused callback parameters and compact
indentation), Python compilation, PowerShell parsing, cargo fmt --all, clippy
for core/Windows/host with all targets and warnings denied, 167 core tests, and
63 host tests with two existing ignored tests. The host and both probe examples
also built. The ordinary receiver rejects pyrowave before connecting instead
of silently requesting H.264.
October 7: Release audio continuity under CPU load #
On the AMD Radeon RX 7900 XT (driver 32.0.31041.1004) and Ryzen 7
5800X3D (8 cores, 16 logical processors), CPU load reproduced the release
audio failure at the test-tone renderer: it ran out of queued samples.
Giving that fixture the existing media-worker
scheduling guard removed the observed underruns; the audio acceptance
thresholds are unchanged.
These runs used the installed rc.20 butterpollo.exe, SHA-256
07b0487e1d9d7312da58fe3430c6d53d0e25e949632284896a94332cc99bc074.
e2e.py always launches a separate host, so this tested that executable
with the release script's isolated profile and ports, not the running
service's configuration. The service was not stopped, restarted, paired,
reinstalled or reconfigured. Before every run its last stream event was
CLIENT DISCONNECTED at 15:51:34 UTC; the script also checked its idle
server state. The original receiver and probes came from
C:\src\butterpollo-release\review-fixes\qa\fixtures.
Each stream requested H.264 or HEVC at 2560×720/60, 20,000 kbps and 30 seconds, as the standard release cases do on this 5120×1440 SDR desktop. The receiver's repeated sleeps lengthened some CPU-loaded runs beyond 30 seconds; cadence uses its measured steady interval. No-added-load runs were not an idle-machine baseline: other worktrees were compiling. Their sampled whole-run CPU means ranged from 41.1% to 82.5% (the first pair did not have utilization sampling).
CPU load used 16 busy processes at default priority. The first two CPU cases started the workers before host initialization, with a 100-second worker limit; the H.264 case also overlapped this worktree's first probe build. The subsequent old/new/new/old comparisons started the workers after the host capability probe and kept them running through teardown. Those matched CPU samples were 99.9–100%. Each codec had two old and two fixed renderer runs, with the order reversed for the second pair.
GPU-only cases alternated with no-added-load cases and ran gpu_load 45 1000 0 200. The load probe completed at 162.2–167.9 FPS across the four runs, with
frame-time p95 of 6.883–8.431 ms. The combined cases started the same shader
workload alongside the CPU workers after host initialization and stopped only
these owned processes afterwards. CPU contention also starved the GPU load
process: one combined-case sample showed only 10% 3D-engine utilization for
that process.
Counts below are pass / fail. Audio and the complete e2e gate are
separate; every completed stream decoded every delivered picture with
zero decode errors. "Original" uses the supplied fixtures; "fixed" changes
only audio_probe.exe, keeping the same receiver, motion probe and host.
| Fixture | Added load | H.264 audio | HEVC audio | H.264 complete e2e | HEVC complete e2e |
|---|---|---|---|---|---|
| Original | None | 3 / 0 | 3 / 0 | 3 / 0 | 3 / 0 |
| Fixed | None | 1 / 0 | 1 / 0 | 1 / 0 | 1 / 0 |
| Original | CPU | 0 / 3 | 0 / 3 | 0 / 3 | 0 / 3 |
| Fixed | CPU | 2 / 0 | 2 / 0 | 1 / 1 | 2 / 0 |
| Original | GPU | 1 / 0 | 1 / 0 | 0 / 1 | 0 / 1 |
| Fixed | GPU | 1 / 0 | 1 / 0 | 0 / 1 | 0 / 1 |
| Original | CPU + GPU probe | 1 / 0 | 0 / 1 | 1 / 0 | 0 / 1 |
| Fixed | CPU + GPU probe | 1 / 0 | 1 / 0 | 0 / 1 | 0 / 1 |
In the four matched CPU comparisons, original minimum tone RMS was 0.000001–0.000007; fixed minimum RMS was 0.008942 in every run. All ten fixed-renderer runs had continuous audio and zero source underruns. Five still failed video cadence or fresh-picture requirements under load; those failures remain failures. The GPU-only steady picture rates ranged from 62.777 to 66.831 FPS and fresh-picture rates from 42.074 to 50.048 FPS, outside the existing release limits despite successful decoding.
A separate instrumented H.264 CPU run recorded the renderer's padding
and every received audio packet. Excluding the initial empty buffer,
the 4,800-frame / 100 ms render buffer ran dry 18 times. Nine groups of
quiet decoded blocks occurred 3.72–34.67 seconds into decoded audio;
each group's start was within 18.3 ms of an empty-buffer refill. The
29 affected packets were ordinary encoded packets, not concealment:
all 8,011 decoded packets contained 240 frames (5 ms), and receiver
statistics recorded zero failed FEC recovery, recovered packets,
out-of-sequence packets or PLC calls. There were no host
audio lost on the host before sending or UDP-send warnings. This
places the reproduced interruption at the tone source, before host
capture, rather than in the send path, loopback transport or decoder.
Both instrumented no-added-load runs passed. A diagnostic HEVC CPU
attempt timed out at host initialization before streaming and is not
counted as an audio result in the table.
The low-energy blocks were real interruptions, not borderline threshold or startup failures: the receiver already skips two seconds of decoded audio, and its 2.5 ms windows still require at least half the strongest window's RMS. A private negative-test renderer deliberately slept for 200 ms eight seconds after starting. The unchanged gate rejected it, with minimum RMS 0.000004 and one source underrun, while all 1,780 video pictures decoded. No startup exclusion or dropout tolerance was widened.
The probe now uses Priority::new() as the host's media workers do and logs
AUDIO_RENDER_UNDERRUN when its queue empties after initial filling. e2e.py
includes the source log in evaluation. Results record the source underrun count
(unknown for old probes), and an interrupted tone with source underruns retains
its failure and adds that evidence. The release script prints the failure
reasons before its existing single retry, preserves the first attempt, and
still stops on a second failure.
Loopback is explicitly recognized by peer.ip().to_canonical().is_loopback()
and keeps the 800 Mbps pacing ceiling; these tests did not exercise the
then-current unknown-route 2× default. The host audio, send-path and pacing
sources are unchanged between rc.20 source f06e72c7 and this worktree's
starting 7908fb4a. No host changes were made here. These measurements do not
implicate QoS, control ACK holding or the optional AMF limits in the reproduced
failure.
Raw results, CPU samples, commands and temporary diagnostic sources are
in this worktree's target/audio-e2e. baseline-*, compare-* and
fixed-* contain the table's runs; trace-* and negative-dropout
contain the diagnostic and deliberate-failure cases. batch.py and
compare.py record the installed log event before each case and clean
up only their own load processes. measurements.json collects the
receiver results. The new probe is built by the existing release script;
manual runs can copy the receiver and motion probe into a private
fixture directory and replace only its audio probe with the executable
built by cargo build --release -p butterpollo-windows --example audio_probe --target-dir target\qa after loading rust-env.ps1.
Validation passed: cargo fmt --all; Windows clippy with all targets and
warnings denied; 169 core tests; 63 host tests with two existing ignores; and
14 release-gate unit tests. The release audio/GPU probes built. A PowerShell
syntax check and a stubbed execution of the actual retry loop verified that the
first failure is retained and a second failure stops the release. The
deliberate source-pause e2e failed as expected.
October 7: 116 FPS VRR capture, WGC and DDX #
This checks the report of an RX 9070 XT alternating between about 116 and
60–95 FPS in five-second stream windows. The available machine is an AMD
Radeon RX 7900 XT, driver 32.0.31041.1004, Windows 26200.9550, with the
Sunshine virtual display driver 1.6.3.0. It is not a reproduction on the
reporter's GPU or game.
Both WGC and DDX delivered every distinct picture in capture-only runs whose VRR source held 116 or 120 FPS, including 4K HDR. The 1080p stream comparisons also sustained about 116 FPS with front-edge and async RTSS pacing. Simultaneous-work runs and the local receiver had substantial slowdowns, described separately below.
The release host was built from dac86ada in this worktree. The stream
tests use an isolated SYSTEM host on localhost port 48923, a private
configuration, per-client virtual displays, HEVC HDR, compute conversion
and the normal AMF defaults. The initial runs use an exclusive layout;
the later RTSS and receive-only runs use an extended layout so another
task's physical-display soak can continue. WGC uses the signed-in user's
helper. The source is motion_probe, a borderless full-display D3D11
window with a frame number and QPC timestamp in its pixels. Its successful
Present(1) calls are recorded independently of capture. A receiver built from
tests/moonlight_client.c uses D3D11VA and reads those numbers after decode.
Receiver work therefore shares this GPU; this is not a remote-client or
physical VRR scanout test.
The capture, frame-generation policy, display-session, display and limiter
sources match rc.20 (f06e72c7); the intervening stream.rs changes do not
alter capture or VRR policy. This is not a comparison between two host
versions with different capture defaults.
The stream fixture sets clientVrrRequested=1; RTSP carries that launch
request into vrr_low_latency, and the host log confirms vrr=true.
With the default frame_limiter_auto_virtual_framegen=legacy, the policy
requests 1000 Hz when VRR is requested and twice the stream rate otherwise
(232 or 240 Hz here). Turning virtual display refresh off bypasses that
automatic 1000 Hz policy. A manual display refresh or device display-mode
override also needs to be checked when diagnosing a different machine.
kept_timings=true in the activation message means the other displays
kept their timings while Windows switched on the new target. It is not a
measurement of the new display's refresh.
RTSS SyncLimiter=1 is front-edge sync, 0 is async and 2 is back-edge
sync. The installed RTSS help at
Help/Properties/General/SYNC_LIMITER describes front-edge sync as pacing
the start of the presentation call against the system clock. Scanline sync
is a separate setting; front-edge sync does not itself request half rate.
The virtual-display policy normally selects front-edge sync on AMD even
when the configured RTSS mode is async. To test async, enable Use this
mode on virtual displays (rtss_allow_virtual_display_override=true)
as well as choosing RTSS limit mode → Async. Game-provided frame
generation has its own override policy in core/src/framegen.rs.
RTSS was copied to the private artifact directory, with global hooking
disabled and only the uniquely named vrr_rtss_source.exe opted in. The
source runs as the signed-in user without its own rate cap; its module
list confirms the private RTSSHooks64.dll is loaded. An initial run with
only global RTSS settings presented 239.30 FPS despite the host verifying
Limit=116. A later run with the private application profile set to 116
gave 115.99 source FPS and all 2,320 measured pictures through WGC in
20 seconds. This is why the host's verified global limit is insufficient
evidence of the game's effective limit: check its application profile and
actual presentation rate too. Subsequent limiter comparisons set both
the global lease and that private application profile to the same rate
and mode. No installed RTSS profile is edited.
An additional attempt to force 60 FPS through that private application's profile did not establish a 60 FPS source: it presented 140.93 FPS in the WGC run and 136.06 FPS in the DDX run. The hook was absent from the WGC module snapshot and present in the DDX snapshot. Both failed the motion gate under simultaneous work. These are failed limiter controls, not evidence that an application profile caused the reported 60 FPS problem.
The rc.20 source already sets WGC's MinUpdateInterval to zero, uses a
two-frame pool, and disables slot-aligned publication for VRR streams.
The legacy diagnostic wgc_high_rate_capture=true requests 1 ms instead.
Those settings should not be described as a new fix for this report.
Capture-only measurements use capture_phase_probe: ten seconds per
run, excluding the first second and last 250 ms, in WGC-helper / DDX /
DDX / WGC-helper order. The eight-pixel source changes a sequence number
at the requested rate; capture reads it only after the full desktop copy
finishes. This counts distinct captured pictures, without an encoder or
receiver. The temporary vrr_hdr_probe variant uses the production
display guard to enable HDR on its own virtual display and restores that
lease afterwards. Its capture textures were float16 (DXGI format 10),
versus BGRA8 (87) in the original probe. Its source and preparation script
remain with the raw artifacts, not in the shipped examples.
In the main capture-only matrix, 38 of 48 runs held their requested rate within 0.1 FPS. Those runs captured all 39,202 presented pictures. The table combines WGC and DDX counts; each rate normally has two runs per backend. Runs whose source slowed are included in the denominator, and described below, rather than being counted as successful fixed-rate tests.
| Desktop | Format | Measured refresh | Requested source FPS | Runs holding target / total | Pictures captured / presented in those runs |
|---|---|---|---|---|---|
| 1080p | SDR | 1000 Hz | 116 and 120 | 8 / 8 | 8260 / 8260 |
| 4K | SDR | 1000 Hz | 116 and 120 | 8 / 8 | 8261 / 8261 |
| 1080p | SDR | 232 Hz | 116 | 1 / 4 | 1015 / 1015 |
| 1080p | SDR | 240 Hz | 120 | 0 / 4 | no fixed-rate result |
| 4K | SDR | 232 Hz | 116 | 4 / 4 | 4061 / 4061 |
| 4K | SDR | 240 Hz | 120 | 4 / 4 | 4200 / 4200 |
| 1080p | HDR | 1000 Hz | 116 and 120 | 7 / 8 | 7245 / 7245 |
| 4K | HDR | 1000 Hz | 116 and 120 | 6 / 8 | 6160 / 6160 |
Repeating the incomplete controls in the same alternating order gave four successful runs each at 1080p SDR / 232 Hz / 116 FPS, 1080p SDR / 240 Hz / 120 FPS, and 4K HDR / 1000 Hz / 120 FPS. All 12 held the target and captured all 12,460 presented pictures. Across the main matrix and these repeats, the 50 runs that held their rate captured 51,662 of 51,662 pictures; the ten earlier source-slowdown runs remain recorded.
The initial 1080p HEVC HDR streams below use the source's own cap and no host limiter. Each row is one run. Source and distinct decoded pictures are counted over the same 20-second source window; host FPS is the fixture's steady send counter and can include repeated pictures.
| Requested FPS | VRR / measured Hz | Capture | Source FPS | Host FPS | Distinct decoded FPS |
|---|---|---|---|---|---|
| 116 | on / 1000 | WGC helper | 116.00 | 116.83 | 115.25 |
| 116 | on / 1000 | DDX | 116.00 | 116.88 | 114.75 |
| 116 | off / 232 | WGC helper | 115.42 | 116.71 | 113.00 |
| 116 | off / 232 | DDX | 115.33 | 116.74 | 111.25 |
| 120 | on / 1000 | WGC helper | 119.40 | 120.76 | 118.40 |
| 120 | on / 1000 | DDX | 120.00 | 120.90 | 118.80 |
| 120 | off / 240 | WGC helper | 120.00 | 120.81 | 118.40 |
| 120 | off / 240 | DDX | 118.80 | 117.61 | 112.55 |
All eight passed the fixture's interoperability and motion checks. Distinct-picture coverage was 94.74–99.35%, so these are not claims of lossless end-to-end delivery. Neither backend fell to a sustained half rate. In the 116 FPS VRR pair, four steady five-second windows were 116.80–117.02 FPS with WGC and 116.79–116.95 FPS with DDX; send-interval p95 was 9.08–9.13 ms and 9.09–9.17 ms respectively. With RTSS pacing the uncapped 1080p source at 116 FPS and VRR on, both modes delivered every picture in the common 20-second window:
| RTSS mode | Capture | Source FPS | Distinct decoded FPS | Source pictures received |
|---|---|---|---|---|
| Front-edge | WGC helper | 116.00 | 116.00 | 2320 / 2320 |
| Front-edge | DDX | 115.65 | 115.65 | 2313 / 2313 |
| Async | DDX | 115.56 | 115.60 | 2312 / 2312 |
| Async | WGC helper | 115.85 | 115.85 | 2317 / 2317 |
The small difference between an interval-derived source FPS and pictures divided by 20 seconds is rounding and the window boundary. These runs do not support replacing front-edge sync with async as a general fix.
At 4K with VRR and front-edge sync, WGC had 116.00 source FPS and 116.05 assembled frames per second; DDX had 114.40 and 114.50. These use the receive-only client described below and the common 20-second window. The 4K async DDX run slowed to 92.84 source FPS and received 95.25 frames per second; the async WGC case hit the outer launcher timeout. The 4K async comparison is therefore inconclusive.
Several simultaneous-work runs failed to maintain the requested source rate. For example, the last two 4K HDR / 120 FPS runs presented 76.03 and 59.54 FPS, while DDX captured 72.04 and WGC captured 51.98 respectively. The first 1080p HDR / 116 run presented 80.83 and captured 77.74 with WGC. The 1080p SDR / 240 Hz batch fell as low as 38.92 source FPS and 29.68 captured FPS. These are retained observations, not clean comparisons of the backends. The soak suite and other builds were active during this part of the study; their individual contributions were not isolated. A small capture age or a lower game resolution alone would not distinguish this situation from source-side pacing or scheduling problems.
The first full 4K receiver comparison was also unsuitable for measuring capture losses. Its hardware-decoder callback includes GPU-to-CPU readback and took 33.70 ms on average in the WGC run and 14.32 ms in the DDX run. Received rates were 28.35 and 67.98 FPS, while the host's steady send counters were 106.71 and 116.75. The WGC case failed the motion gate and had repeated keyframe requests; the DDX case passed interoperability but did not sustain the requested rate. Neither is a valid end-to-end 4K/116 performance pass. Subsequent 4K stream runs use a private receiver that counts assembled frames without decoding; those measurements must not be labelled decoded or fresh-picture FPS.
The original fixture also waited for a fixed number of 100 ms sleeps.
Under simultaneous work, a nominal 32-second receiver run lasted 54.36
seconds, outliving the 36-second source. Its 69.86 host FPS average is
misleading: the common 20-second window had 109.05 source FPS and 108.50
assembled frames per second. The later private receiver uses a wall-clock
deadline. Two early cases exceeded the SYSTEM launcher's 120-second
deadline, and another receiver timed out; their partial logs remain in
the artifacts. Later launches use the existing session_command_long
example with a 300-second outer deadline.
The final 4K matrix reduced motion_probe to a 128-pixel-high window to
reduce its drawing work. The desktop, capture and encoded output remain
3840×2160 HDR. This uses the source's own cap, no host limiter, the
wall-clock receiver and the same 20-second source/receive window:
| Requested FPS | VRR / measured Hz | Capture | Source FPS | Assembled frames per second |
|---|---|---|---|---|
| 116 | on / 1000 | WGC helper | 96.34 | 98.65 |
| 116 | on / 1000 | DDX | 95.53 | 98.20 |
| 116 | off / 232 | WGC helper | 104.91 | 104.70 |
| 116 | off / 232 | DDX | 105.20 | 104.70 |
| 120 | on / 1000 | WGC helper | 119.24 | 119.25 |
| 120 | on / 1000 | DDX | 120.00 | 120.00 |
| 120 | off / 240 | WGC helper | 119.95 | 119.60 |
| 120 | off / 240 | DDX | 100.27 | 100.55 |
All eight completed the receive-only interoperability check. The source still failed to hold its cap in several rows, so this is not a clean ranking of the capture backends. Assembled-frame counts can include repeats and other desktop updates; they cannot establish fresh-picture coverage. The capture-only sequence-number measurements above answer that narrower question. A quiet-machine 4K encode/decode comparison and the original RX 9070 XT/game reproduction remain outstanding.
No capture or limiter policy change is justified by these measurements. In particular, they do not support automatically preferring DDX for VRR, changing the WGC frame pool or dirty-region handling, or changing the default RTSS sync mode.
For that report, try Capture method → Desktop Duplication as a single comparison, with the same game scene, client settings and verified source rate. Check the in-game FPS overlay both before streaming and during the slow stream windows. If the game itself is near 60, inspect its VSync and frame-cap settings, the selected virtual display's actual refresh, and its RTSS application profile. A useful limiter control is Limiter → None under Frame limiter, with RTSS disabled for that application and an in-game 116 FPS cap. Alternatively, test Async with the virtual-display override described above. Async is a diagnostic comparison, not a demonstrated fix.
Raw commands, isolated profiles, source timestamps, received picture IDs,
host five-second windows and connection checks are in
target/vrr-capture; measurements.json and phase-measurements.json
collect the counts. The SYSTEM launcher uses session_command.exe from
the supplied bench-rc17 harness and, for later batches, the existing
session_command_long example built in this worktree. It does not change
the installed service's files, settings or lifetime. Stream cases check the installed log
and public session counts before starting and watch them during the run;
capture-only batches record the latest connection event and process list.
The installed service stayed idle, with its last connection event the
15:51:34 UTC disconnect. The other RX 9070 investigation overlapped the
initial capture pilot; the soak task and other builds overlapped later
batches. Process snapshots are retained beside the measurements.
Test-owned processes exited and their scheduled tasks were removed after
the runs. The installed RTSS global-profile SHA-256 is unchanged.
Validation passed: cargo fmt --all; clippy for butterpollo-core,
butterpollo-windows and butterpollo, with all targets and warnings
denied; 171 core tests; and 63 host tests, with two existing ignores.
The release host and capture probes built successfully. This section is
the only tracked change; the experimental harness and receiver remain
under target/vrr-capture.
October 12: single-pass D3D11 4:2:0 conversion #
The D3D11 fallback converts NV12/P010 and PyroWave 4:2:0 with one compute
dispatch on the existing immediate context, reusing the existing shaders.
Unsupported shader or unordered-access-view creation retains the pixel-pass
fallback. The separate D3D12 compute queue and PyroWave 4:4:4 MRT draw remain
unchanged. This applies to the fallback, including
gpu_compute_conversion=false; the default AMD D3D12 path is unchanged.
Release-build D3D11 timestamps on the RX 7900 XT compared the previous pixel
passes with the single dispatch in ABBA order: two runs per path, 32 warmups
and 128 samples per run. Outputs were 1920×1080, 2560×1440 and 3840×2160,
using a 3840×2160 BGRA8 SDR source or FP16 HDR source. The loaded comparison
used gpu_load 20 1000 60 200. These GPU conversion times exclude capture,
encoding, transport, decoding and scanout.
| 3840×2160 output | Idle previous / single pass, mean ms | Loaded previous / single pass, mean ms |
|---|---|---|
| NV12 SDR | 0.2348 / 0.1230 | 0.3159 / 0.2018 |
| P010 HDR | 0.2300 / 0.1450 | 0.3125 / 0.2157 |
| PyroWave SDR 4:2:0 | 0.3060 / 0.1032 | 0.4667 / 0.1427 |
| PyroWave HDR 4:2:0 | 0.3065 / 0.1237 | 0.4771 / 0.1619 |
PyroWave mean GPU time fell 32–68% idle and 33–69% under this load across the three resolutions. NV12/P010 improvements were strongest at 1440p and 4K. Loaded 1080p video results were less decisive: NV12 mean changed from 0.3055 to 0.2990 ms, while p95 changed from 0.2446 to 0.2482 ms; P010 p95 changed from 0.2686 to 0.2764 ms. The 4K case avoids resampling the 4K source, so its times do not establish scaling cost from resolution alone. CPU call times were mixed. These measurements cover only this Radeon GPU.
All nine native GPU correctness tests passed. Exact comparisons covered 480 NV12/P010 cases and 480 PyroWave planar cases, including every luma/chroma byte, SDR eight/ten-bit, FP16 SDR/HDR, matrix/range settings, scaling, letterboxing, dimensions that do not divide evenly into thread groups, and monochrome/clipped/alpha cursors. The D3D11 debug layer accepted both video plane UAV bindings without warnings or errors. Existing D3D12/pixel comparisons, 4:4:4 output, HDR colour and codec-held texture tests also passed.
Isolated 1080p60 streams with gpu_compute_conversion=false passed the AMF
HEVC SDR/HDR and PyroWave SDR 4:2:0 gates: respectively 686/686, 715/715 and
676/676 pictures decoded, with continuous audio and full motion coverage.
WGC captured a 5120×1440 SDR desktop, so the HDR streams check conversion and
negotiation rather than native HDR capture. The first PyroWave HDR run
overlapped unit tests and failed the delivery-gap limit with nine long
intervals, despite decoding all 716 pictures. With other jobs stopped,
20-second repeats passed for both the previous build (1176/1176 decoded,
60.012 fresh fps, three long intervals) and the candidate (1199/1199,
59.982 fresh fps, four long intervals), with no decode errors or audio
underruns. The failed first run remains recorded. These checks establish
interop and correctness; no CPU savings or end-to-end latency improvement
is claimed.
After removing the pacing experiments, the final GPU-only 2.2.8 build passed
four quiet 20-second loopback runs at 1080p60/40 Mbps with the same fallback
configuration. HEVC SDR/HDR and PyroWave SDR/HDR decoded respectively
1198/1198, 1198/1198, 1181/1181 and 1199/1199 pictures, at 59.994–60.002
steady fps. Audio was continuous, with no decode errors; long intervals
were respectively zero, zero, zero and one. Evidence is under
work/gpu-only-e2e. The pacing follow-up
records the unsuccessful combined-candidate Wi-Fi comparison and a separate
completed GPU-only laptop check. Four ABBA runs against 2.2.7 used official
Moonlight 6.2.0 on a Radeon 780M over WLAN, at 1080p60 HEVC SDR with the
same fallback configuration. Each ran for approximately 165 seconds, with
120-second requested host measurement windows. All four reported 59.99
rendered fps, 0.00% network drops and 0.03% jitter drops; the host windows
had no sender drops, IDR requests or reference invalidations. The repeated
baseline matched candidate send p95. This short check found no observed
regression at that workload; rounded client statistics and absent FEC reports
do not establish exact packet loss, and no end-to-end latency improvement
is claimed. The final GPU-only build contains no pacing changes.
Reproduce with the ignored release tests in gpu_color::tests:
gpu_420_single_pass_timing, gpu_420_single_pass_matches_pixel_passes and
gpu_420_video_plane_uavs_are_disjoint. The timing test retains the old pixel
passes as its reference and writes JSON to BUTTERPOLLO_TEST_GPU_REPORT.
Run timing separately from builds and other workloads, except the explicit
synthetic GPU load. Local evidence: gpu-420-timing-idle.json,
gpu-420-timing-loaded.json, gpu-regressions.log, and the candidate-e2e,
baseline-e2e-quiet and candidate-e2e-quiet stream results.
October 12: Rubylight 2.2.9 released-package refresh #
The physical-SDR local fixtures, real Wi-Fi receiver matrix, native HDR headline/pixel measurements, stock Moonlight checks and retained AMF settings sweeps have completed. Their individual outcomes and limitations remain separate; completion does not mean every case passed. The portable repeat-frame matrix passed all 81 cases on October 12, 2026, from 06:15 to 06:28 CEST. CPU, packet, input, wait and native GPU component probes also completed, alongside 16/16 PyroWave compute/load cases, 16/16 synthetic-motion cases, 32/32 AMF quality cases and 4/4 independent UDP pacing checks. The retained AMF property sweeps completed 1,030 cases: 1,018 passed and 12 failed, with every original failure retained.
The current stream record retains every outcome: 36/36 short release streams, 12/12 controls, 8/8 compute checks, 4/4 default-pacing streams and 12/12 static-IDR checks passed. The supplemental stage passed 10/12 checks; both 4K120 HEVC streams and both 240 FPS saturation attempts failed. The audio/load/80 ms outage batch passed 6/11 full-stream checks, with continuous audio in all 11. All four 90-second controls passed; all four deliberate 20 ms outage streams failed the normal gap gate. Both 120-second audio/video streams passed. The corrected short soak completed 67 cases: 64 passed, three failed GPU-load freshness; all 52 churn cycles, eight codec switches, capture-helper recovery and host working-set, handle and thread growth checks passed. The GPU-memory follow-up reproduced the rising process counter as a reporting artifact: all 30 HEVC reconnects passed, and adapter-wide dedicated usage returned to baseline after idle. No sustained VRAM growth was observed in that focused test. The soak record retains the original counter readings separately from adapter consumption. The initial soak attempt could not start its direct receivers because of a missing DLL search path and is retained as invalid fixture evidence, not a set of product failures.
The real Radeon 780M Wi-Fi matrix passed 20/20 ordinary receiver checks: 12 HDR-output conversion runs and eight SDR control/recovery runs, including four injected-outage cases. 19/20 calibrated timing records passed exact source validation; AV1 HDR 1080p60 run 2 remains unavailable because three decoded barcode timestamps did not match the source ledger. The hardware receiver's arrival boundary is frame assembly/enqueue, before decoder queueing, decoding and display. Its 1.177 ms clock-fit diagnostic is not a confidence interval or proven error bound. These are physical-SDR source measurements, not native HDR.
The native HDR streams and pixels passed 28/28 and 4/4 checks respectively. The SDR capture-only sweep attempted 32 runs: 20 matched the requested mode; 12 have unavailable requested-mode timings because Windows selected different refresh rates. The historical private HDR capture/RTSS fixture sources were not retained. Current results do not establish matched deltas against the earlier native-HDR, Wi-Fi or historical competitor evidence.
Native virtual-HDR results (October 12) #
All 28/28 native headline streams passed the ordinary receiver gates and exact decoded source ID/QPC validation. The virtual capture source is native HDR at 120 Hz, with DDX and WGC verified separately. The active physical desktop remains 5120x1440 SDR at 240 Hz; the historical October 8 runs used a 2560x1440 / 120 Hz physical desktop. The current host/driver and all dated competitor results retain their separate provenance; no percentage improvement or matched before/after conclusion follows from this table.
These use the published host and a pinned independent D3D11VA receiver on
the same RX 7900 XT. Picture age runs from the source QPC timestamp through
decoded-barcode readback, excluding presentation and display scanout. The
separate hardware arrival_ms field estimates frame assembly/enqueue,
not decode start; adding callback time to it omits decoder-queue waiting.
This local picture-age endpoint also differs from the Wi-Fi
render-to-received endpoint. Each stream requests 35 seconds with five
seconds excluded for warmup. The loaded fixture is gpu_load 45 1000 60 200;
all 12 load processes completed naturally at 60.000 FPS / 2,701 frames.
Every cell below is the arithmetic mean of two per-run statistics, including the p95 and p99 columns: these are not pooled percentiles. All age and host columns are milliseconds. The per-repeat record retains variation, decoder times, source checks and executable hashes.
| Capture | Codec / output / requested FPS / Mbps / load | Picture age mean / p95 / p99 | Fresh FPS | Host mean |
|---|---|---|---|---|
| DDX | HEVC HDR / 1968x2184 / 120 / 80 / idle | 9.649 / 10.868 / 12.165 | 119.993 | 3.296 |
| DDX | AV1 HDR / 1968x2184 / 120 / 80 / idle | 10.502 / 11.256 / 11.610 | 119.998 | 3.032 |
| DDX | HEVC HDR / 2560x1440 / 120 / 50 / idle | 9.385 / 9.892 / 10.194 | 119.999 | 2.970 |
| DDX | HEVC HDR / 1920x1080 / 60 / 20 / idle | 10.092 / 10.557 / 10.763 | 60.000 | 2.139 |
| DDX | HEVC HDR / 1968x2184 / 120 / 80 / load | 14.339 / 16.026 / 18.151 | 119.990 | 3.347 |
| DDX | AV1 HDR / 1968x2184 / 120 / 80 / load | 13.645 / 17.995 / 18.951 | 119.984 | 5.249 |
| DDX | HEVC HDR / 1920x1080 / 60 / 20 / load | 10.638 / 18.265 / 18.580 | 60.002 | 2.237 |
| WGC | HEVC HDR / 1968x2184 / 120 / 80 / idle | 12.238 / 13.734 / 15.910 | 119.983 | 3.262 |
| WGC | AV1 HDR / 1968x2184 / 120 / 80 / idle | 10.572 / 11.775 / 13.520 | 120.008 | 3.037 |
| WGC | HEVC HDR / 2560x1440 / 120 / 50 / idle | 11.291 / 11.996 / 12.203 | 120.001 | 2.972 |
| WGC | HEVC HDR / 1920x1080 / 60 / 20 / idle | 9.359 / 9.927 / 10.228 | 60.001 | 2.273 |
| WGC | HEVC HDR / 1968x2184 / 120 / 80 / load | 16.747 / 19.389 / 19.750 | 119.945 | 3.383 |
| WGC | AV1 HDR / 1968x2184 / 120 / 80 / load | 17.320 / 19.478 / 21.933 | 119.868 | 3.043 |
| WGC | HEVC HDR / 1920x1080 / 60 / 20 / load | 11.938 / 12.517 / 15.487 | 59.999 | 2.371 |
The separate native HDR pixel roundtrip
passed 4/4 WGC software-decoded HEVC/AV1 checks at 720p60 / 40 Mbps.
This confirms the existing pixel gates, not physical-panel calibration.
The SDR VRR capture-only record
retains 20 requested-mode-valid runs out of 32 attempted. All 20 captured
every retained presented picture. The other 12 requested-mode timings are
unavailable: 1080p240 selected 120 Hz, 4K232 selected 928 Hz, and 4K240
selected 960 Hz. They were not retried or counted as requested-mode passes.
All eight capture groups restored the recorded active-monitor state.
Full raw paths and validation hashes are indexed in work\native-summary.json.
Package, source and scope #
- Published release: 2.2.9, source commit
d0bfcb5e764095250b8f0e22fd5a0cb3656d5ea6. - Release ZIP:
rubylight-2.2.9-windows-x64.zip, SHA-2562a9b76a0380ec60e189de040656366302dfce3ee2d84d895720eb84a36b81289. - Packaged
butterpollo-performance.exe, SHA-256078470dac3e1ccfbb33cd83e97d4b74d4735ea054cb891571f4a3a84f1ee4ced. - Host: Ryzen 7 5800X3D, 8 cores / 16 threads; Radeon RX 7900 XT, AMD driver 32.0.32015.2008; Windows 11 Pro build 26200.
- Capture source for every portable case: 5120×1440 SDR at 240 Hz, the physical Odyssey G93SC, through WGC. Display mode and HDR state were unchanged.
The probe captures one desktop image per process and repeatedly encodes that image. The desktop was not frozen across cases, so this is not a matched content comparison with earlier measurements. HDR output below is converted from the SDR source; it is not native HDR capture. The 4K rows are resized output from that source, not native 4K capture.
Each row contains three separate runs: unpaced GPU input, unpaced CPU compatibility input, and GPU input paced at 120 fps. CPU mode reads the captured image back once before measurement; it does not measure repeated GPU readback or CPU colour conversion in isolation. AMF remains the video encoder for H.264, HEVC and AV1. PyroWave uses its own encoder and record framing, with 4:2:0 or 4:4:4 as labelled.
These 81 runs executed serially without added synthetic GPU load. Each requested eight measurement seconds; recorded durations were 8.000–8.036 seconds after warmup. There is one run per path and setting, not a mean across repeats. Capacity is completed encoded frames divided by measured time. The paced column is submission-to-observed-output mean / p95 in milliseconds; those runs completed 120.09–120.11 fps. These values exclude capture age, transport, client decoding and display latency. Completed encodes alone do not establish independent decoding, exact coded dimensions or fresh-picture throughput.
1920×1080 output #
Requested bitrate: 20,000 kbps for H.264 / HEVC / AV1 and 400,000 kbps for PyroWave. Every path requests 120 fps; the two capacity paths are unpaced.
| Codec / output | GPU capacity, fps | CPU path capacity, fps | GPU paced at 120 fps, mean / p95 ms |
|---|---|---|---|
| H.264 SDR | 667.00 | 144.95 | 2.224 / 2.399 |
| HEVC SDR | 671.09 | 146.29 | 2.315 / 2.612 |
| HEVC HDR output | 670.63 | 101.14 | 2.252 / 2.598 |
| AV1 SDR | 849.88 | 147.29 | 1.755 / 1.872 |
| AV1 HDR output | 848.43 | 101.17 | 1.900 / 2.028 |
| PyroWave SDR 4:2:0 | 2423.90 | 99.41 | 0.666 / 0.781 |
| PyroWave SDR 4:4:4 | 2270.61 | 114.92 | 0.827 / 0.986 |
| PyroWave HDR output 4:2:0 | 2462.08 | 125.33 | 0.959 / 1.077 |
| PyroWave HDR output 4:4:4 | 2211.53 | 132.70 | 0.985 / 1.025 |
2560×1440 output #
Requested bitrate: 50,000 kbps for H.264 / HEVC / AV1 and 600,000 kbps for PyroWave. This is not the older 40,000 kbps HEVC fixture.
| Codec / output | GPU capacity, fps | CPU path capacity, fps | GPU paced at 120 fps, mean / p95 ms |
|---|---|---|---|
| H.264 SDR | 419.06 | 133.73 | 3.174 / 3.597 |
| HEVC SDR | 415.95 | 134.13 | 3.354 / 3.641 |
| HEVC HDR output | 415.75 | 60.31 | 3.425 / 3.709 |
| AV1 SDR | 521.62 | 133.17 | 2.688 / 3.069 |
| AV1 HDR output | 520.86 | 60.91 | 2.709 / 3.083 |
| PyroWave SDR 4:2:0 | 2066.73 | 134.83 | 1.209 / 1.355 |
| PyroWave SDR 4:4:4 | 1722.53 | 136.84 | 1.012 / 1.210 |
| PyroWave HDR output 4:2:0 | 2054.49 | 138.41 | 1.006 / 1.106 |
| PyroWave HDR output 4:4:4 | 1668.18 | 138.62 | 1.027 / 1.224 |
3840×2160 output #
Requested bitrate: 80,000 kbps for H.264 / HEVC / AV1 and 800,000 kbps for PyroWave.
| Codec / output | GPU capacity, fps | CPU path capacity, fps | GPU paced at 120 fps, mean / p95 ms |
|---|---|---|---|
| H.264 SDR | 199.48 | 71.76 | 5.954 / 6.166 |
| HEVC SDR | 200.48 | 69.51 | 5.876 / 6.167 |
| HEVC HDR output | 200.82 | 27.63 | 5.788 / 6.147 |
| AV1 SDR | 250.00 | 71.83 | 5.106 / 5.364 |
| AV1 HDR output | 249.25 | 26.80 | 4.851 / 5.166 |
| PyroWave SDR 4:2:0 | 1660.24 | 136.91 | 1.106 / 1.264 |
| PyroWave SDR 4:4:4 | 1114.62 | 136.55 | 1.368 / 1.566 |
| PyroWave HDR output 4:2:0 | 1603.32 | 138.60 | 1.096 / 1.230 |
| PyroWave HDR output 4:4:4 | 1028.88 | 137.21 | 1.402 / 1.521 |
PyroWave compute conversion with and without GPU load #
All 16/16 repeat-frame cases passed, from 07:16 to 07:24 CEST on October 12. They use the same package and physical 5120×1440 SDR / 240 Hz WGC source, converted to 1920×1080 HDR 4:4:4 output at 120 fps and 400,000 kbps, with PyroWave record framing. Each process captures its own repeated image; desktop content was not frozen across cases. These are SDR-source conversion results, not native-HDR capture or a matched comparison with the older fixture. Capture age, transport, decoding and display latency are excluded.
gpu_compute_conversion is explicitly false or true. Repeat 1 tests false
then true within each load condition; repeat 2 reverses that order. Each
setting has a separate unpaced capacity run and a run paced at 120 fps, with
eight measurement seconds after 20 or 240 warmup frames respectively.
Entries below preserve r1 / r2. Latency is submission to observed encoder
output in milliseconds. The paced runs completed 120.105–120.113 fps.
| Compute | Load | Capacity, fps | Capacity mean, ms | Capacity p95, ms | Paced mean, ms | Paced p95, ms |
|---|---|---|---|---|---|---|
| false | Idle | 2149.409 / 2148.261 | 0.464 / 0.464 | 0.610 / 0.610 | 1.103 / 0.971 | 1.256 / 1.216 |
| true | Idle | 2227.784 / 2227.324 | 0.447 / 0.447 | 0.552 / 0.549 | 0.971 / 0.782 | 1.026 / 0.888 |
| false | GPU load | 1333.729 / 1326.874 | 0.748 / 0.752 | 0.875 / 0.912 | 0.959 / 0.953 | 1.292 / 1.087 |
| true | GPU load | 2107.694 / 2128.274 | 0.473 / 0.468 | 0.569 / 0.559 | 0.734 / 0.743 | 0.869 / 0.847 |
Every loaded case used the finite gpu_load.exe 45 1000 60 200 fixture,
started two seconds before the probe. It remained alive through measurement
and exited naturally before the next case. All eight load summaries recorded
2,701 frames at 60.0 fps over the complete load run; this is the load
fixture's throughput, not GPU utilization or streaming throughput. Its frame
timings are retained separately from the encoder timings:
| Compute / probe | Load frame p50, r1 / r2 (ms) | Load frame p95, r1 / r2 (ms) |
|---|---|---|
| false / capacity | 9.869 / 9.963 | 10.010 / 9.997 |
| false / paced | 9.895 / 9.778 | 10.096 / 9.982 |
| true / capacity | 9.971 / 10.193 | 10.631 / 10.940 |
| true / paced | 9.888 / 9.896 | 10.001 / 10.006 |
Raw evidence is retained in
C:\Users\ramaz\Documents\Codex\2026-10-12\wait\work\measurements\current-portable-load-20261012T051656834135Z.
Each case retains the full-precision encoder JSON in stdout.log, its
compute.conf, source.json, exact arguments/status in run.json, and load
stdout/stderr where applicable. Stage metadata and the 16-entry summary
record completion. The task-local reproduction is
work\run_current_matrix.py --stages portable-load; its cases request
--codec pyrowave --hdr --records --yuv444, with --paced only for paced runs.
Synthetic-motion AMF compute conversion under GPU load #
All 16/16 cases passed from 07:47 to 07:50 CEST on October 12, using the
same published 2.2.9 performance executable and host above. This fixture cycles
through 16 generated BGRA8 pictures with moving detail at the requested
output dimensions, converted to HDR10. It does not capture the physical
desktop or native HDR input, and excludes transport, independent decoding and
display latency. The JSON's generic repeat-frame encoder throughput label
does not distinguish this synthetic mode; capture_backend=synthetic and the
saved --synthetic 16 arguments establish the actual source.
HEVC uses 1920×1080 at 60 fps / 20,000 kbps; AV1 uses 1968×2184 at
120 fps / 80,000 kbps. Both use AMF ultralowlatency, speed and
vbr_latency, paced for six measured seconds after 120 or 240 warmup frames.
The eight codec/load/compute combinations run once, then in reverse order for
repeat 2. AMF settings in every case confirm D3D11/host input with
gpu_compute_conversion=false and D3D12 input with it enabled. No
priority or direct-input variant is requested.
Entries preserve r1 / r2, in milliseconds. encode_call is the probe's
host-side encode-call duration, separate from submission to observed output;
neither is an isolated GPU hardware timer. Each HEVC run completed 361 frames
at 60.151–60.165 fps, and each AV1 run completed 721 frames at
120.134–120.147 fps. Measured durations were 6.000–6.002 seconds.
| Codec | Load | Compute | Encode-call mean | Submit-to-output mean | Submit-to-output p95 | Submit-to-output max |
|---|---|---|---|---|---|---|
| HEVC | Idle | false | 0.842 / 0.946 | 2.557 / 2.347 | 3.130 / 2.598 | 3.643 / 2.951 |
| HEVC | Idle | true | 1.081 / 1.103 | 2.078 / 2.034 | 2.571 / 2.154 | 2.622 / 2.666 |
| HEVC | GPU load | false | 0.960 / 0.870 | 7.570 / 8.728 | 12.647 / 15.587 | 14.111 / 21.365 |
| HEVC | GPU load | true | 1.089 / 1.099 | 2.047 / 2.104 | 2.201 / 2.332 | 2.888 / 5.605 |
| AV1 | Idle | false | 0.633 / 0.770 | 3.128 / 3.113 | 3.500 / 3.411 | 3.605 / 3.695 |
| AV1 | Idle | true | 1.094 / 1.112 | 2.932 / 2.868 | 3.125 / 3.121 | 3.592 / 3.828 |
| AV1 | GPU load | false | 0.903 / 0.962 | 7.621 / 7.840 | 14.612 / 14.915 | 22.941 / 21.994 |
| AV1 | GPU load | true | 1.194 / 1.134 | 2.928 / 2.914 | 3.137 / 3.138 | 4.644 / 6.871 |
The finite load is gpu_load.exe 12 1000 0 1: 12 seconds, 1,000 iterations,
uncapped, one draw, started 1.5 seconds before the encoder process. These
are the archived October 4 runner's defaults. Its original invocation
overrides were not retained, and the original target/release/examples
load binary is absent; this run reuses the archived bench-rc21 executable.
Consequently these are not matched old/new measurements. They also use a
different load from the capped 200-draw PyroWave fixture above.
All eight load processes remained alive through encoder completion and then exited naturally with status zero. Their actual throughput and frame timings over the complete load run follow; these are not encoder throughput or GPU utilization. Entries again preserve r1 / r2.
| Codec / compute | Load frames | Load fps | Load frame p50, ms | Load frame p95, ms |
|---|---|---|---|---|
| HEVC / false | 2353 / 2359 | 196.0 / 196.5 | 5.032 / 5.015 | 5.423 / 5.417 |
| HEVC / true | 2338 / 2368 | 194.8 / 197.3 | 5.080 / 5.023 | 5.407 / 5.336 |
| AV1 / false | 2326 / 2320 | 193.8 / 193.3 | 5.064 / 5.072 | 5.664 / 5.683 |
| AV1 / true | 2332 / 2331 | 194.3 / 194.2 | 5.106 / 5.107 | 5.441 / 5.436 |
Raw evidence is retained in
C:\Users\ramaz\Documents\Codex\2026-10-12\wait\work\measurements\synthetic-compute-20261012T054709781835Z:
per-case run.json, result.json, encoder.conf, encoder stdout/stderr,
and load JSON/logs, plus stage metadata, hardware and the 16-entry summary.
The task-local reproduction is work\run_synthetic_compute.py; its SHA-256 is
5d0245ff34b981de4a27966942c170f54c04e5bac2de6ca86d90d6631dcfc8c5.
For example, the HEVC command is
butterpollo-performance.exe --synthetic 16 --width 1920 --height 1080 --fps 60 --codec hevc --hdr --encoder amf --paced --seconds 6 --bitrate 20000 --config <case>\encoder.conf.
The retained configurations and run records specify the compute toggle and
load command for each repeat.
Isolated colour conversion and GPU correctness #
These probes ran serially on the same host, using release-built test binaries from the exact 2.2.9 source commit above. They exercise synthetic images and individual functions, not the captured desktop or a complete HDR stream.
The CPU probe calls color::hdr_rgba_scaled on the historical synthetic
1968×2184 RGBA FP16 input, with one warmup call and three measurement seconds
per output. It excludes capture, encoding, transport and client work. This is
separate from the portable CPU-input encoder rows above.
| Output | Calls | Mean, ms | p95, ms | Maximum, ms |
|---|---|---|---|---|
| 1920×1080 | 376 | 7.984 | 8.895 | 9.278 |
| 2560×1440 | 223 | 13.489 | 14.891 | 15.976 |
| 3840×2160 | 103 | 29.310 | 32.452 | 33.719 |
The independent gpu_conversion_timing fixture converts a synthetic
1968×2184 FP16 image to P010. It records 64 GPU timestamp pairs after 16
warmup conversions. GPU timestamps and CPU call times measure different
parts of the operation and are not additive.
| Output | GPU mean / p95, ms | CPU call mean / p95, ms |
|---|---|---|
| 1968×2184 | 0.138 / 0.153 | 0.087 / 0.209 |
| 3840×2160 | 0.224 / 0.382 | 0.092 / 0.279 |
The D3D11 4:2:0 fallback fixture compares the retained pixel-pass reference
with the current single-pass converter. Each case uses an ABBA order,
128 GPU timestamp samples per run, and synthetic 3840×2160 BGRA8 or FP16
input. Each table entry is the arithmetic mean of the two run means for that
path, in milliseconds; it is not a percentile or a full-stream latency.
The loaded pass used the established gpu_load.exe 60 1000 60 200 fixture,
which remained alive through the measurements. Video means NV12/P010 encoder
surfaces; planar means PyroWave planes.
| Output / format | Idle pixel passes | Idle single pass | Loaded pixel passes | Loaded single pass |
|---|---|---|---|---|
| 1920×1080 SDR video | 0.246 | 0.198 | 0.340 | 0.294 |
| 1920×1080 SDR planar | 0.252 | 0.172 | 0.379 | 0.259 |
| 1920×1080 HDR video | 0.227 | 0.211 | 0.356 | 0.337 |
| 1920×1080 HDR planar | 0.243 | 0.191 | 0.364 | 0.284 |
| 2560×1440 SDR video | 0.312 | 0.155 | 0.470 | 0.239 |
| 2560×1440 SDR planar | 0.394 | 0.133 | 0.574 | 0.169 |
| 2560×1440 HDR video | 0.322 | 0.235 | 0.480 | 0.363 |
| 2560×1440 HDR planar | 0.388 | 0.223 | 0.577 | 0.322 |
| 3840×2160 SDR video | 0.255 | 0.129 | 0.352 | 0.190 |
| 3840×2160 SDR planar | 0.315 | 0.110 | 0.482 | 0.145 |
| 3840×2160 HDR video | 0.240 | 0.155 | 0.375 | 0.246 |
| 3840×2160 HDR planar | 0.309 | 0.133 | 0.460 | 0.205 |
All nine selected native correctness tests passed: compute/video parity, compute/PyroWave-plane parity, single-pass/pixel-pass parity, disjoint video plane UAVs, 4:4:4 chroma and ten-bit codes, SDR/HDR pointer composition, absolute HDR luminance/gamut/linear resizing, retained-frame pool ownership, and SDR ten-bit matrix/full-range handling. These native GPU tests validate their synthetic fixtures; they do not establish native-HDR capture or end-to-end HDR pixel correctness.
FEC and video packet preparation #
The packaged protocol probe reports the following current Rubylight FEC medians over seven timing rounds. All four parity outputs matched the existing reference byte for byte. No reference timing is refreshed here.
| Data / parity shards | Bytes per shard | Rubylight median, µs |
|---|---|---|
| 32 / 7 | 1416 | 5.053 |
| 96 / 20 | 1416 | 43.271 |
| 192 / 39 | 1416 | 167.246 |
| 4 / 2 | 144 | 0.122 |
The packaged video-packet probe measures CPU packetization, 20% FEC, optional AES-GCM and packet release, excluding capture, encoding and UDP sending. Values are medians of seven 250 ms rounds. Fresh and reusable are two buffer-allocation paths in the current build, not release comparisons.
| Data shards / payload bytes | Plaintext fresh, µs | Plaintext reusable, µs | AES-GCM fresh, µs | AES-GCM reusable, µs |
|---|---|---|---|---|
| 32 / 44,024 | 8.147 | 5.621 | 15.439 | 12.821 |
| 192 / 264,184 | 158.085 | 145.109 | 199.857 | 189.920 |
| 576 / 792,568 | 476.564 | 433.632 | 605.496 | 565.106 |
UDP pacing on Windows loopback #
All 4/4 current-pacer component cases passed on October 12, from 07:41
to 07:42 CEST. The release-built udp_pacing_probe uses the same 2.2.9 source
commit, in paced mode, with an independent Python UDP receiver on
127.0.0.1. Each case sent exactly 600 synthetic frames, 46,042 data
packets and 64,458,800 UDP payload bytes. Data packets are 1,400 bytes,
including the fixture header; the separate end marker is excluded from
those counts. The fixture requests 60 frames/s with a nominal 50 Mbps frame
budget and eight-times-sized keyframes at frames 0 and 300. The pacing
budgets are 100 or 800 Mbps, with a 0 or 3 ms scheduler stall after the second
packet batch of each keyframe.
Sender times cover each frame's batch-send loop, including pacing waits and the imposed stall. Values are mean / p99 / maximum, in milliseconds.
| Pacing budget, Mbps | Stall, ms | Sender frame span, ms | Batch-send system calls |
|---|---|---|---|
| 100 | 0 | 7.832 / 8.792 / 69.599 | 3062 |
| 100 | 3 | 7.837 / 8.798 / 72.264 | 3062 |
| 800 | 0 | 0.452 / 0.634 / 8.115 | 1222 |
| 800 | 3 | 0.449 / 0.508 / 10.842 | 1222 |
The receiver timestamps userspace recvfrom completion with
time.perf_counter_ns. Frame span runs from the first to last received
packet; completion gap runs between completed frames in frame-ID order.
Both distributions include the two large keyframes. The receive buffer was
4,194,304 bytes in all cases. Values again show mean / p99 / maximum.
| Pacing budget, Mbps | Stall, ms | Receiver frame span, ms | Completion gap, ms | Gaps over 25 / 50 ms |
|---|---|---|---|---|
| 100 | 0 | 7.773 / 8.005 / 69.320 | 16.563 / 16.725 / 78.628 | 1 / 1 |
| 100 | 3 | 7.804 / 8.025 / 72.128 | 16.558 / 16.712 / 81.396 | 1 / 1 |
| 800 | 0 | 0.498 / 0.669 / 7.971 | 16.653 / 16.837 / 24.290 | 0 / 0 |
| 800 | 3 | 0.501 / 0.537 / 11.009 | 16.648 / 16.774 / 27.027 | 1 / 0 |
Every case received all 46,042 unique packets and all 600 frames intact,
with zero missing, duplicate, corrupt or malformed data packets, partial
frames or sender-reported drops. Passing here means exact delivery and
integrity, not an absence of timing gaps. kernel_overflow is unavailable
(null), not a measured zero. Python scheduling and receive processing
affect these userspace spans and gaps; they are not kernel arrival timestamps
or one-way latency. This Windows loopback component excludes capture,
codecs, FEC, the physical NIC, LAN/Wi-Fi and client display. It does not
replace real-client or historical pacing measurements.
Raw evidence is retained in
C:\Users\ramaz\Documents\Codex\2026-10-12\wait\work\measurements\udp-pacing-loopback-20261012T054151299614Z.
Each case contains sender.json, receiver.json with every frame's timing,
run.json and process logs. manifest.json records exact commands and hashes
for the source-built sender and adapted Windows receiver. The sender SHA-256
is bc7f7decfd1718c0fec92c5d3e2355ab470ef383d5ba4856ae72436b911ac565.
The retained task-local runner is work\run_udp_pacing.py, using
udp_pacing_probe.exe <loopback-peer> paced <100-or-800> <0-or-3>.
Wait and input components #
wait_performance recorded 40 samples per method and requested duration.
Entries are observed wait mean / p95 in milliseconds, not additional
stream latency or overshoot alone.
| Requested wait, µs | Condition variable | Thread sleep | Capture event + timer | Input timer |
|---|---|---|---|---|
| 250 | 15.413 / 16.172 | 0.517 / 0.541 | 0.512 / 0.525 | 0.514 / 0.544 |
| 500 | 15.492 / 16.103 | 1.050 / 1.213 | 1.038 / 1.407 | 0.540 / 0.969 |
| 1000 | 15.232 / 16.299 | 1.524 / 1.571 | 1.519 / 1.558 | 1.082 / 1.517 |
| 16667 | 30.775 / 31.955 | 16.965 / 17.229 | 16.957 / 17.178 | 16.930 / 17.139 |
Input probes used HIGH process priority, 1 ms timer resolution and disabled
power throttling. They inject zero relative mouse movement or neutral
controller states; these are host component measurements, not input-to-display
latency. Control decrypt-and-decode averaged 0.3 µs for both protocol variants
over 20,000 datagrams each, with p99 of 0.4 µs for v2=true and 0.5 µs for
v2=false.
Idle SendInput batching produced the following per-input times, with zero
failed calls:
| Inputs per call | Calls | Mean per input, µs | p99 per input, µs | Maximum per input, µs |
|---|---|---|---|---|
| 1 | 1500 | 30.6 | 89.8 | 156.8 |
| 2 | 750 | 16.0 | 56.5 | 74.6 |
| 4 | 375 | 8.3 | 43.9 | 51.9 |
| 8 | 200 | 6.2 | 37.5 | 41.3 |
Under TIME_CRITICAL spinners on every CPU, the receiver-priority fixture recorded 1,500 loopback input samples for each current scheduling variant:
| Receiver variant | Mean, µs | p99, µs | Maximum, µs |
|---|---|---|---|
| Poll1 PriorityNew | 7501.6 | 24855.7 | 32111.7 |
| Poll1 PriorityInput | 17.4 | 30.0 | 211.0 |
| Poll1 MmcssOnly | 25.9 | 33.7 | 214.0 |
The separate queued-controller fixture measures completion of a zero mouse move immediately after a neutral controller state through the injector. Its normal/highest worker loads are distinct from the TIME_CRITICAL fixture.
| Worker load | Samples | Mean, µs | p99, µs | Maximum, µs |
|---|---|---|---|---|
| None | 6000 | 49.6 | 204.8 | 652.9 |
| Normal | 5949 | 50.1 | 130.3 | 2128.3 |
| Highest | 5887 | 57.8 | 167.4 | 2261.3 |
Direct synchronous VHF state calls remain a different scope, with two rounds of 1,000 calls each per worker load:
| Worker load / round | Mean, µs | p99, µs | Maximum, µs |
|---|---|---|---|
| None / 1 | 103.1 | 145.9 | 211.0 |
| None / 2 | 100.0 | 129.4 | 217.6 |
| Normal / 1 | 81.7 | 170.4 | 4680.8 |
| Normal / 2 | 100.6 | 2035.8 | 10442.7 |
| Highest / 1 | 559.5 | 2072.6 | 218535.4 |
| Highest / 2 | 306.5 | 2069.7 | 212881.8 |
Direct driver calls still showed a 218.535 ms tail, while the queued controller/mouse fixture above had a 2.261 ms maximum. Passing the probe does not establish a bound on driver-call latency. VHF open/first neutral state/drop took 3281.6 / 954.9 / 407.4 µs; the neutral pad was removed at completion.
Two ENet loopback repeats held acknowledgments until after apply and used 3,000 samples each. Both recorded zero socket sends inside the service drain.
| Repeat | Client send+flush → apply, mean / p99 µs | Server wake → drain end, mean / p99 µs |
|---|---|---|
| 1 | 34.7 / 51.4 | 8.8 / 17.0 |
| 2 | 34.8 / 51.5 | 9.1 / 17.0 |
Two additional immediate-ACK reference runs completed on October 12 at 09:37 UTC, with the same exact-source executable and original return-code / 30-second timeout gates. Each sent 3,000 reliable 36-byte loopback packets; the server uses HIGH priority and a 1 ms timer. These ran later than the deferred-ACK pair above and are not an alternating matched comparison. No input-latency threshold was added to the gate.
| Immediate-ACK repeat | Send+flush → apply, mean / p99 / max µs | Wake → drain end, mean / p99 / max µs | Socket send time in drain, mean / p99 µs |
|---|---|---|---|
| 1 | 48.9 / 72.5 / 99.7 | 22.9 / 36.4 / 57.9 | 16.2 / 24.2 |
| 2 | 49.7 / 73.2 / 243.8 | 23.3 / 36.9 / 201.0 | 16.5 / 24.7 |
Both immediate runs averaged 1.0 socket send inside the drain, with a
maximum of two. Those are counts, despite the probe's generic us suffix
on count-labelled lines. Raw evidence is in
work\measurements\enet-immediate-20261012T093736Z; the executable SHA-256 is
c9195204c667cec0b2e0c0a0fb40be2f2824c2ba55b71f03fb8ba1989571ec3f.
The two original deferred runs and their measurements remain unchanged.
PyroWave decoded pictures and quality budgets #
Five serial source-built tests, using the published package's
libpyrowave-shared-1.dll and bitstream 186f0393, completed all 7,200
expected decoded comparisons. The log audit found no missing, duplicate or
unexpected sample combinations. These tests encode synthetic pictures,
packetize them, reconstruct complete delivery in an independent parser, then
decode through Vulkan and read back the planes. They exclude live network
loss, socket pacing, sustained frame rate and client display latency.
| Test | Decoded comparisons / expected | Result | Test duration, s |
|---|---|---|---|
| Chart, 720p and 1080p | 1,152 / 1,152 | Pass | 73.48 |
| Quality, 720p, default critical FEC | 1,872 / 1,872 | Pass | 126.58 |
| Quality, 1080p, default critical FEC | 1,872 / 1,872 | Pass | 158.11 |
| Quality, 2160p, default critical FEC | 1,872 / 1,872 | Pass | 323.60 |
| Quality, 2160p, critical FEC disabled | 432 / 432 | Pass | 100.95 |
The chart covers SDR8, SDR10 and HDR10, both chroma formats, and requested 30/125/400/1000 Mbps budgets. Its passing assertions require identical decoded samples across graphics/compute conversion, plain/record framing and three alternating batches. The separate quality sweeps use compute conversion, record framing, 1392-byte packets, 20% critical FEC and no detail FEC, except for the explicitly labelled no-FEC control. Each encoder interval is reset before the picture, so nominal 30/60/120 fps defines its byte budget rather than measuring sustained throughput. Quality scores and frame sizes matched exactly across both repeats and all three nominal frame rates.
Current warning/recommended budgets, in bits per pixel per frame (bpp), remain 2.5/5 bpp below 1080 lines and 1.5/3.2 bpp at 1080 lines and above. Every row below met the warning-floor criterion and the recommended-quality criterion in 24/24 desktop/game comparisons at each level: two scenes, two pictures, two repeats and three frame rates. The floor requires luma PSNR ≥25 dB and luma SSIM ≥0.80; the recommendation requires every plane's PSNR ≥35 dB and luma SSIM ≥0.95.
| Size | Output | Floor / recommended bpp | Recommended minimum plane PSNR / luma SSIM | Derived RTP Mbps at 60 fps |
|---|---|---|---|---|
| 720p | SDR8 4:2:0 | 2.5 / 5 | 41.82 / 0.9751 | 236.5–295.3 |
| 720p | SDR8 4:4:4 | 2.5 / 5 | 39.99 / 0.9672 | 227.8–286.6 |
| 720p | HDR10 4:2:0 | 2.5 / 5 | 48.56 / 0.9946 | 289.3–331.8 |
| 720p | HDR10 4:4:4 | 2.5 / 5 | 46.83 / 0.9907 | 285.9–303.5 |
| 1080p | SDR8 4:2:0 | 1.5 / 3.2 | 38.92 / 0.9721 | 412.3–424.4 |
| 1080p | SDR8 4:4:4 | 1.5 / 3.2 | 38.26 / 0.9653 | 412.9–441.3 |
| 1080p | HDR10 4:2:0 | 1.5 / 3.2 | 46.17 / 0.9912 | 412.3–458.9 |
| 1080p | HDR10 4:4:4 | 1.5 / 3.2 | 45.23 / 0.9894 | 412.9–458.9 |
| 2160p | SDR8 4:2:0 | 1.5 / 3.2 | 44.73 / 0.9910 | 1645.0–1737.6 |
| 2160p | SDR8 4:4:4 | 1.5 / 3.2 | 40.68 / 0.9747 | 1649.7–1710.6 |
| 2160p | HDR10 4:2:0 | 1.5 / 3.2 | 50.88 / 0.9956 | 1649.7–1884.9 |
| 2160p | HDR10 4:4:4 | 1.5 / 3.2 | 49.24 / 0.9936 | 1643.6–1884.9 |
PSNR and SSIM minima can come from different pictures. PSNR uses peaks of 255/1023; HDR is measured in PQ code space. SSIM uses uniform 8×8 windows. These synthetic-scene targets do not establish visually perfect output. The RTP ranges cover 12 samples per row at the recommended budget, including the dark scene: three scenes, two pictures and two repeats at nominal 60 fps. They are measured datagram bytes converted to bits and scaled by 60 fps, including FEC but excluding UDP/IP/link headers; they are not measured network throughput. Ranges are observed minima/maxima, not confidence intervals.
At 4K, the default-FEC encoder budget remained capped at 3,921,592 bytes for all requested 4/4.5/5 bpp points. The separate no-FEC control allowed 4,147,196 / 4,665,596 / 5,183,996 bytes respectively, with 144/144 decoded comparisons at each point for each FEC setting. These requested high budgets do not establish that a live client can negotiate or sustain those rates.
Raw evidence is under
C:\Users\ramaz\Documents\Codex\2026-10-12\wait\work\measurements\remaining-probes-20261012T043354Z,
in pyrowave-decoded, pyrowave-quality-720p, pyrowave-quality-1080p,
pyrowave-quality-2160p and pyrowave-quality-2160p_without_fec. Each has
stdout.log, stderr.log and run.json; the stage manifest pins the test
binary and published DLL hashes. The task-local
C:\Users\ramaz\Documents\Codex\2026-10-12\wait\work\pyrowave-summary.json
retains the coverage audit, unrounded scores, byte ranges and repeat checks,
produced by work\summarize_pyrowave.py without changing the evidence.
Retained AMF property sweeps (October 12) #
The published 2.2.9 performance executable completed 1,030 serial cases
between 10:21 and 13:04 UTC: 30/30 low-latency-property cases,
280/280 rate-control/IDR cases and 708/720 split-frame cases passed.
These ran on the Ryzen 7 5800X3D / RX 7900 XT, AMD driver
32.0.32015.2008, Windows build 26200. All 12 failures remain recorded;
there were no interrupted attempts or measurement retries.
Each process cycles 16 deterministic synthetic BGRA8 pictures through GPU conversion and AMF. HDR rows convert those SDR pictures to HDR output. This excludes capture, network, independent decoding and display latency; it does not measure perceptual quality. Paced cases warm up for two seconds; unpaced cases warm up for 20 frames. The original gate checks process return code, requested fixture identity, measured duration, and equality of submitted frames, completed frames and latency samples. Pass does not mean that a requested cadence or latency threshold was met, or that every optional property took effect. Installed-stream and competing-workload guards ran around the cases; the installed host was also checked during each case.
Low-latency properties #
All runs use 2560x1440 SDR / 120 FPS / 20 Mbps, eight measurement seconds.
auto is paired with amd_lowlatency_mode=enabled for H.264/HEVC or
amd_av1_latency_mode=lowest for AV1. The ultralowlatency usage has three
repeats per arm; the other usages have one. Values below are auto / forced,
with latency in milliseconds. For the three-repeat rows, each number is the
arithmetic mean of the per-run statistic, not a pooled percentile.
| Usage | Codec | Runs per arm | Submission-to-output mean, auto / forced | p99, auto / forced | Driver property readback, auto / forced |
|---|---|---|---|---|---|
| ultralowlatency | H.264 | 3 | 3.107 / 3.064 | 3.608 / 3.585 | 1 / 1 |
| ultralowlatency | HEVC | 3 | 3.229 / 3.213 | 3.658 / 3.624 | 1 / 1 |
| ultralowlatency | AV1 | 3 | 2.634 / 2.659 | 3.105 / 3.115 | 3 / 3 |
| lowlatency | HEVC | 1 | 3.539 / 3.228 | 3.941 / 3.636 | 0 / 1 |
| lowlatency | AV1 | 1 | 3.649 / 2.641 | 4.130 / 3.082 | 0 / 3 |
| lowlatency_high_quality | HEVC | 1 | 3.567 / 3.567 | 3.966 / 3.986 | 1 / 1 |
| lowlatency_high_quality | AV1 | 1 | 3.476 / 3.493 | 3.909 / 3.906 | 3 / 3 |
| transcoding | HEVC | 1 | 3.543 / 3.240 | 3.957 / 3.687 | 0 / 1 |
| transcoding | AV1 | 1 | 4.005 / 2.640 | 4.437 / 3.098 | 0 / 3 |
The readback is LowLatencyInternal for H.264/HEVC and
Av1EncodingLatencyMode for AV1. Current ultralowlatency defaults already
read enabled/lowest; measured times are similar, not numerically identical.
The single lowlatency/transcoding pairs show lower means when forcing the
property, but do not establish a quality tradeoff, confidence interval or
reason to change the default usage. The old table did not retain a duration;
this rerun uses the current probe's eight-second default and does not claim
an exact historical timing comparison.
Rate control and recovery-frame sizes #
The 280 runs preserve 140 unique codec/fixture/configuration conditions, two repeats each. Main rows use HEVC/AV1 SDR at 1080p60, 1440p120 and 4K60, 20/40/80 Mbps, with default, CBR, CBR+VBV1, cap1, cap2 and intra-refresh settings. Frequent-IDR rows use 4K60 / 63 Mbps and an IDR every six frames. The peak/VBV recheck uses 4K60 / 40 Mbps. These request eight measurement seconds after two seconds of warmup. Duplicate default rows are represented once; historical contamination replacements and private development screening are not additional current conditions.
In the following table, ranges are minimum–maximum per-run observations over each setting's actual retained conditions, not pooled distributions or matched comparisons between differently populated rows. Latencies are milliseconds. Actual/requested bitrate is encoded bytes × 8 / measured seconds / requested bitrate. IDR ratio is the largest measured IDR divided by the same run's mean non-IDR frame size; startup IDRs are separate.
| Setting | Runs | Mean submission-to-output range | p99 range | Actual/requested bitrate range | Largest-IDR / mean non-IDR range |
|---|---|---|---|---|---|
| default | 40 | 1.779–6.196 | 1.865–6.703 | 0.936–1.018 | 1.45–3.43 |
| cbr | 36 | 1.777–6.209 | 1.871–6.719 | 0.986–1.011 | 1.20–3.03 |
| cbr_vbv1 | 40 | 1.780–6.155 | 1.870–6.696 | 0.973–1.072 | 1.18–3.17 |
| cap1 | 40 | 1.755–6.176 | 1.813–6.683 | 0.810–1.001 | 0.85–1.35 |
| cap2 | 40 | 1.757–6.193 | 1.802–6.729 | 0.936–1.007 | 1.45–2.35 |
| intra | 36 | 1.756–6.187 | 1.822–6.709 | 0.932–1.006 | no measured IDR |
| cbr_cap1 | 4 | 4.967–6.009 | 5.392–6.637 | 0.869–0.921 | 1.36–1.74 |
| cbr_cap2 | 4 | 5.007–6.059 | 5.422–6.675 | 1.028–1.042 | 1.65–2.39 |
| peak1 | 4 | 4.906–6.000 | 5.348–6.422 | 0.966–0.985 | 1.94–3.33 |
| peak15 | 4 | 4.925–5.993 | 5.346–6.404 | 0.966–0.985 | 1.94–3.33 |
| peak2 | 4 | 4.927–5.979 | 5.329–6.365 | 0.966–0.985 | 1.94–3.33 |
| vbv05 | 4 | 4.943–5.964 | 5.282–6.613 | 0.958–0.965 | 2.08–3.78 |
| vbv1 | 4 | 4.943–6.007 | 5.372–6.504 | 0.966–0.985 | 1.94–3.33 |
| vbv2 | 4 | 4.956–5.992 | 5.370–6.413 | 0.969–0.999 | 1.94–3.01 |
| cap4 | 4 | 4.919–5.995 | 5.346–6.856 | 0.966–0.985 | 1.94–3.33 |
| hrd | 4 | 4.895–6.000 | 5.344–6.596 | 0.966–0.985 | 1.94–3.33 |
| cap2hrd | 4 | 4.948–5.993 | 5.316–6.623 | 0.964–0.969 | 1.93–2.10 |
| intra_idr | 4 | 4.931–5.957 | 5.308–6.374 | 0.963–0.986 | 1.94–3.34 |
Names map to the saved configurations: cbr sets amd_rc=cbr; vbv05,
vbv1, vbv2 set amd_vbv_buffer_frames to 0.5, 1, 2; cap1, cap2,
cap4 set amd_max_frame_size to 1, 2, 4; peak1, peak15, peak2
set amd_peak_bitrate_ratio to 1, 1.5, 2. hrd sets amd_enforce_hrd=true.
cbr_cap1/2 combine CBR, VBV1 and that cap; cap2hrd combines cap2 and HRD.
intra uses --intra-refresh without periodic IDRs; intra_idr keeps the
one-second IDR interval. The saved per-condition index preserves the exact
codec, dimensions, rate, bitrate, IDR interval and requested/applied fields.
The logs retain actual driver readbacks rather than inferring them from the configuration. For example, 4K60 / 40 Mbps peak1/1.5/2 reads peak bitrates of 40/60/80 Mbps; cap1/2 reads 666,667 / 1,333,333 bits, not bytes. Observed bitrate and IDR ratios remain measurements, not hard bounds or proof of visual quality. No recovery decoder or packet-loss test runs in this component fixture.
Split-frame settings #
All 240 fixture/repeat slots ran current release auto, enabled and
disabled, for 720 cases. Main: 60 fixtures × two repeats × three modes;
interaction: 37 × two × three; unpaced throughput: eight × two × three;
stability: six × five × three. Main/interaction request three measurement
seconds, throughput four, stability eight. The retained fixtures include
HEVC/AV1, SDR and converted-HDR output, five widths from 1920 to 7680,
60/120/240 FPS requests, and 20/40/80/150 Mbps targets, plus their saved
slices, intra-refresh, LTR, quality, preanalysis, usage, Smart Access Video
and rate-control interactions. Current auto replaces the old private
untouched-binary arm; it is not that historical binary.
The table gives min–max per-run latency observations over each subgroup's completed cases, not averages across different fixtures. Failures remain in the denominators and have no accepted latency values.
| Subgroup | Mode | Passed / attempted | Mean submission-to-output range, ms | p99 range, ms |
|---|---|---|---|---|
| main | auto | 120 / 120 | 1.702–87.228 | 1.768–89.815 |
| main | enabled | 120 / 120 | 1.702–87.131 | 1.752–89.794 |
| main | disabled | 120 / 120 | 1.711–87.133 | 1.761–89.799 |
| interaction | auto | 70 / 74 | 4.833–43.527 | 5.184–72.844 |
| interaction | enabled | 70 / 74 | 4.835–43.575 | 5.194–72.919 |
| interaction | disabled | 70 / 74 | 4.804–43.585 | 5.192–73.229 |
| throughput | auto | 16 / 16 | 9.804–87.901 | 10.513–89.735 |
| throughput | enabled | 16 / 16 | 9.800–87.837 | 10.485–89.724 |
| throughput | disabled | 16 / 16 | 9.795–87.937 | 10.498–89.725 |
| stability | auto | 30 / 30 | 3.117–6.145 | 3.592–6.692 |
| stability | enabled | 30 / 30 | 3.098–6.166 | 3.601–6.688 |
| stability | disabled | 30 / 30 | 3.089–6.149 | 3.600–6.695 |
Unpaced capacity below is the arithmetic mean of two runs for each identical fixture. All eight throughput fixtures are SDR; the FPS values are encoded capacity, not independently decoded or fresh captured pictures.
| Codec / SDR output / Mbps | Auto FPS | Enabled FPS | Disabled FPS |
|---|---|---|---|
| HEVC / 1920x1080 / 20 | 669.11 | 669.44 | 668.89 |
| HEVC / 3840x2160 / 80 | 197.32 | 197.37 | 197.36 |
| HEVC / 5120x1440 / 80 | 217.93 | 217.92 | 217.97 |
| HEVC / 7680x2160 / 150 | 102.05 | 102.07 | 102.06 |
| AV1 / 1920x1080 / 20 | 842.40 | 842.81 | 842.81 |
| AV1 / 3840x2160 / 80 | 250.45 | 250.51 | 250.48 |
| AV1 / 5120x1440 / 80 | 273.81 | 273.73 | 273.76 |
| AV1 / 7680x2160 / 150 | 127.04 | 126.88 | 126.96 |
All 720 split-frame readbacks were present: auto and enabled read
MultiHwInstanceEncode=1, while disabled read 0, with the codec-specific
prefix. A matching property value does not prove how much work each hardware
engine performed. Throughput was similar across these arms, not identical.
66 passed paced cases completed below 97% of requested FPS; this is a
descriptive observation, not a retroactively added gate. For example, HEVC
7680x2160 SDR at a requested 240 FPS completed 102.532–102.733 FPS across
its six main-matrix runs. The high submission-to-output tails above include
such capacity-limited fixtures and are not display latency.
All 12 failures are the retained amd_preanalysis=true interaction at
3840x2160 SDR / 60 FPS / 80 Mbps, for both codecs, all three split-frame
settings and both repeats:
| Codec / interaction row | Nonzero probe exits | Original sample-accounting failures | Accepted timing results |
|---|---|---|---|
| HEVC / 7 | 6 | 0 | 0 |
| AV1 / 19 | 4 | 2 | 0 |
The ten nonzero exits report encoder did not complete measured frames.
AV1 auto run 1 and disabled run 1 return zero with 181 submitted and
completed frames but 180 latency samples, failing the unchanged equality
gate. Their raw JSON is retained as failed-case diagnostics and excluded
from accepted timing aggregates. The results do not isolate a cause to
split-frame mode: every mode failed this preanalysis condition. No run was
retried, fixed or silently dropped.
Across the full property sweep, the logs retain 78 AV1 1080p geometry
warnings and six HEVC LTR-setting warnings. The former warn about AMD
padding and client cropping; the latter state that amd_ltr_frames applies
only to AV1 and the HEVC request is ignored. There were no logged
AMF setting unavailable warnings. Completion and primary-property
readback therefore remain distinct from support for every requested
interaction, independent decoding or visual quality.
Property evidence and validation #
Raw results are in
work\measurements\amf-properties-20261012T102110Z, produced by
work\run_amf_properties.py (SHA-256
b11eabc9a2ee9d76dbb858a3582353b31fa60e1ccb6dc8359603aa8922fddf66).
The executable is the published package probe identified above. Each case
retains its exact configuration, arguments, stdout/stderr, status and hashes;
planned.json and manifest.json index all 1,030 cases. The saved CSVs and
dated rate-control tables are pinned in the manifest, with current source
and package identities.
work\amf-properties-summary.json, generated by
work\summarize_amf_properties.py, verifies all 1,030 run records and
4,110 raw-file hashes against the original evidence and rechecks the
original gates. It indexes every case, requested/applied field, warning and
failure, plus 491 distinct fixture/configuration aggregates with
per-run ranges. Only identical repeats are averaged; no percentiles are
pooled, and no failed result is promoted to a pass. The same summary
retains the two immediate-ACK ENet runs and their original gates/provenance.
Historical and competitor measurements remain unchanged.
Reproduction and retained evidence #
From the extracted 2.2.9 package, the following commands reproduce the 1440p HEVC HDR-output triple with the recorded requested settings:
.\butterpollo-performance.exe --encoder amf --capture wgc --codec hevc --width 2560 --height 1440 --fps 120 --bitrate 50000 --seconds 8 --hdr
.\butterpollo-performance.exe --encoder amf --capture wgc --codec hevc --width 2560 --height 1440 --fps 120 --bitrate 50000 --seconds 8 --hdr --paced
.\butterpollo-performance.exe --encoder amf --capture wgc --codec hevc --width 2560 --height 1440 --fps 120 --bitrate 50000 --seconds 8 --hdr --cpu
Use the dimensions and bitrates above for the other rows, --codec h264,
--codec hevc, --codec av1 or --codec pyrowave, and omit --hdr for SDR.
PyroWave additionally uses --records, plus --yuv444 for its 4:4:4 rows.
Keep the source, display state and lack of competing workloads consistent;
the recorded source was SDR even when --hdr was requested.
Local raw evidence is retained in
C:\Users\ramaz\Documents\Codex\2026-10-12\wait\work\measurements\current-portable-20261012T041533202030Z.
Each <width>x<height>-<codec>-gpu-capacity, -gpu-paced or -cpu-capacity
directory contains stdout.log with the full measurement JSON, stderr.log,
run.json with arguments, status and timestamps, and source.json with the
actual display state. Stage metadata.json, hardware.json and summary.json
retain executable hashes, the driver/OS inventory and all 81 completion records.
The task-local runner is work\run_current_matrix.py --stages portable.
The component results above are retained separately in
C:\Users\ramaz\Documents\Codex\2026-10-12\wait\work\measurements\remaining-probes-20261012T043354Z.
Its manifest.json records source revision, package/test-binary hashes and
completion status; each named case has run.json with the exact executable,
arguments and timestamps, plus stdout/stderr. The FEC values above come only
from fec\rubylight-only.json; GPU timing comes from each case's gpu.json.
The CPU fixture source and its input specification are retained under
work\cpu-color-probe. Input diagnostic detail not tabulated above remains
in the complete logs.
Reproduce the source-built components with the existing wait_performance,
input_path_probe and enet_input_probe release examples from the recorded
commit. Input modes were the default invocation, vhf --load=none,normal,highest --trace,
pad-contention --load=none,normal,highest --trace, and
contention --samples=1500; ENet used 3000 defer twice, followed later by
two separate 3000 immediate-ACK reference runs. The isolated CPU
fixture had no arguments. Native GPU cases used their gpu_color::tests
names with --exact --ignored --nocapture --test-threads=1 and wrote timing
reports through BUTTERPOLLO_TEST_GPU_REPORT. Run these serially and keep
the explicitly loaded GPU pass separate from idle measurements. These
source-built tests supplement the published-package probes; they are not
additional downloaded release executables.
Hardware covered #
Measurements ran with AMD AMF on an RX 7900 XT host, with real-client Wi-Fi runs to a Radeon 780M laptop recorded for 2.2.9 on October 12 and rc.24 on October 8. Native NVENC calls the installed NVIDIA driver directly and supports reviewed API versions 11.0–13.0, reference frame invalidation, D3D11 4:2:0/8-bit 4:4:4 and GPU-only CUDA interop for ten-bit 4:4:4. QSV has native D3D11 imports, and TrueHDR has a shared-device GPU path. PyroWave uses shared D3D11/Vulkan planar GPU inputs and reads back only the encoded bitstream. Unsupported native formats and software encoding use CPU compatibility paths. The GPU texture pools and native encoder queues are bounded to eight retained frames. Features lists everything the host does.