docs(encode): close out the tile-aware AV1 sub-frame reader — measured, not worth it

#95 disarmed sub-frame readback for AV1, which means AV1 forgoes the latency
win HEVC gets from shipping slice 1 while slice 2 encodes. The follow-up was to
teach the reader AV1's units: cut on OBU boundaries rather than byte counts and
arm from the driver's reported unit count. Measured on .21 (RTX 5070 Ti,
av1_nvenc) before writing any of it, and the measurement closes it rather than
scoping it.

Reading the frame headers av1_nvenc actually emits at 4K:

  width_in_sbs_minus_1[0]      = 59        one tile column, the full 3840
  height_in_sbs_minus_1[0..1]  = 16, 16    two tile rows
  tile_start_and_end_present_flag = 0      BOTH TILES IN ONE TILE GROUP OBU

That last flag is the finding. "Cut on OBU boundaries" presumes the tiles are
separate OBUs and they are not — there is no boundary between them to cut on.
Shipping tile 1 early would need the HOST to re-author AV1 syntax per chunk,
synthesising a fresh Tile Group OBU header with tile_start_and_end_present_flag
= 1 and its own tg_start/tg_end. That is bitstream surgery on the encode path,
not the reader change it was assumed to be.

And the prize would be small even then, because split encode already spent it.
The two tile rows go to two split-encode engines that run CONCURRENTLY, so they
complete at nearly the same moment — the win is bounded by the skew between
engines, not by half a frame. Whole-frame encode measures 3.3-3.6 ms at 4K60
against a 16.7 ms p50 end-to-end, so even the sequential-tiles fantasy caps near
1.7 ms and the real number is a fraction of it. HEVC's win is bigger for a
structural reason that does not transfer: forced split and sub-frame are
mutually unsupported, so HEVC's slices genuinely are produced one after another.

1080p settles it further: tile_cols_log2 = tile_rows_log2 = 0, a single tile, so
there is nothing to pipeline at the commonest streaming resolution at all.

Recorded next to the disarm with the reopen condition named — NVENC emitting one
OBU per tile, or setting tile_start_and_end_present_flag = 1 — so this is closed
on evidence rather than left as an open maybe.

Documentation only — no behaviour change.
This commit is contained in:
2026-08-07 19:26:32 +02:00
parent 6b4be28d24
commit c64cdc4ef7
+30
View File
@@ -137,6 +137,36 @@ pub(super) fn subframe_env_forced() -> bool {
/// drop-in alternative — `poll_chunk` cuts at `bitstreamSizeInBytes` on the reasoning that
/// "slices are contiguous Annex-B", which AV1's OBUs are not.
///
/// # A tile-aware chunk reader was considered and is CLOSED, not deferred
///
/// The obvious follow-up is to teach the reader AV1's units — cut on OBU boundaries instead
/// of byte counts and arm from the driver's reported unit count — so AV1 gets the sub-frame
/// latency win HEVC gets (ship tile 1 while tile 2 encodes). Measured on `.21` (RTX 5070 Ti,
/// `av1_nvenc`, 2026-08-07) before writing any of it, and the measurement closes it:
///
/// * **4K carries two tiles, and they share ONE Tile Group OBU.** The frame header reads
/// `width_in_sbs_minus_1[0] = 59` (one tile column, the full 3840) and
/// `height_in_sbs_minus_1[] = {16, 16}` (two tile rows) — but
/// `tile_start_and_end_present_flag = 0`, which puts both tiles in a single Tile Group
/// OBU. There is no OBU boundary between them to cut on. Shipping tile 1 early would mean
/// the HOST re-authoring AV1 syntax per chunk — synthesising a fresh Tile Group OBU header
/// with `tile_start_and_end_present_flag = 1` and its own `tg_start`/`tg_end` — which is
/// bitstream surgery on the encode path, not a reader change.
/// * **1080p carries one tile** (`tile_cols_log2 = tile_rows_log2 = 0`), so there is nothing
/// to pipeline at the commonest streaming resolution regardless.
/// * **The prize is small even at 4K, because split encode already spent it.** The two tile
/// rows go to two split-encode engines that run CONCURRENTLY, so they finish at nearly the
/// same moment: the win is bounded by the skew between engines, not by half the frame.
/// Whole-frame encode measures 3.33.6 ms at 4K60 against a 16.7 ms p50 end-to-end, so
/// even the sequential-tiles fantasy caps out near 1.7 ms and the real figure is a
/// fraction of that. HEVC's sub-frame win is larger for a structural reason that does not
/// transfer: forced split and sub-frame are mutually unsupported (below), so HEVC's slices
/// really are produced one after another.
///
/// Reopen only if NVENC starts emitting one OBU per tile, or sets
/// `tile_start_and_end_present_flag = 1` — at that point the cut points exist and the reader
/// change becomes the small piece it was assumed to be.
///
/// Returns the `(split_mode, subframe)` to ACTUALLY configure. The caller must store BOTH back
/// (the chunked-poll latch and `CeilingKey` key on them) — a silent in-params drop would leave
/// `poll_chunk` busy-polling its full budget every AU (`numSlices` stays 0 without