From c08d61c355d2d17f9abb72bccb14cc24db21b720 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Wed, 4 Nov 2026 04:28:15 +0000 Subject: [PATCH] AV1: decode tiles serially when parallel tile metadata copies would be huge --- crates/av1/src/decoder.rs | 18 +++++++++++++++++- crates/av1/src/state.rs | 7 +++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/crates/av1/src/decoder.rs b/crates/av1/src/decoder.rs index d463493a1..66e87a548 100644 --- a/crates/av1/src/decoder.rs +++ b/crates/av1/src/decoder.rs @@ -777,6 +777,14 @@ fn decode_frame(job: &FrameJob, pool: &Pool, stats: &mut DecodeStats) -> Result< Ok((rf, pic)) } +/// Bytes all parallel tile states may hold of whole-frame CDEF and restoration metadata; past it +/// the tiles decode one after another on the frame's own metadata. +const TILE_METADATA_BUDGET: usize = 256 << 20; + +fn tile_metadata_fits(tiles: usize, per_tile: usize) -> bool { + tiles.checked_mul(per_tile).is_some_and(|n| n <= TILE_METADATA_BUDGET) +} + /// Decode the tiles of a frame (tile number, data), in parallel when several; returns the final /// CDFs of tile context_update_tile_id. fn decode_tiles(pool: &Pool, fs: &mut FrameState, cdfs: &Cdfs, tiles: &[(usize, Vec)]) -> Result>> { @@ -790,7 +798,7 @@ fn decode_tiles(pool: &Pool, fs: &mut FrameState, cdfs: &Cdfs, tiles: &[(usize, }; // Intra block copy reads the current frame through the tile's own buffer: keep whole-frame // buffers (tiles one after another) for it. - if tiles.len() > 1 && pool.is_parallel() && !fs.fh.allow_intrabc { + if tiles.len() > 1 && pool.is_parallel() && !fs.fh.allow_intrabc && tile_metadata_fits(tiles.len(), fs.tile_metadata_bytes()) { let mut states: Vec = tiles .iter() .map(|(num, _)| { @@ -1075,6 +1083,14 @@ mod guard_tests { #[cfg(test)] mod budget_tests { use super::*; + + #[test] + fn tile_metadata_copies_are_budgeted() { + assert!(tile_metadata_fits(64, 1 << 20)); + assert!(!tile_metadata_fits(4096, 1 << 20)); + assert!(!tile_metadata_fits(usize::MAX, 2)); + } + use deckcraft_bitstream::budget::DECODE_MEMORY_BUDGET; fn color(mono: bool) -> crate::header::ColorConfig { diff --git a/crates/av1/src/state.rs b/crates/av1/src/state.rs index 5a89c9b4d..5e6443477 100644 --- a/crates/av1/src/state.rs +++ b/crates/av1/src/state.rs @@ -253,6 +253,13 @@ impl FrameState { self.lr = t.lr; } + /// Bytes of CDEF and loop-restoration metadata each parallel tile state copies whole. + pub fn tile_metadata_bytes(&self) -> usize { + let lr = &self.lr; + // 1 (type) + 6 (Wiener) + 1 (SGR set) + 2 (SGR weights) bytes per unit. + self.cdef.len() + lr.lr_type.iter().map(|v| v.len() * 10).sum::() + } + /// Private buffers for the tile covering mode info rows [r0, r1) x columns [c0, c1); tiles /// on the right / bottom frame edge also cover the allocation margins. pub fn tile_state(&self, r0: usize, r1: usize, c0: usize, c1: usize) -> TileState {