collective

Browse / github.com/storytold/filmcraft / patch

hevc: dequantise while decoding residuals, table-driven sig_coeff contexts

hevc: dequantise while decoding residuals, table-driven sig_coeff contexts Found while auditing `crates/hevc` at upstream/main bfc23840 (Ryzen 7800X3D, single thread, `perf stat -e instructions:u,cycles:u` and flat `perf record` profiles; libx265 fixtures: 1080p long-GOP B with/without WPP, 4 slices, Main10, intra-only, and a ~100 Mbit/s 2160p Main10 stream, each with an ffmpeg md5). Today single-thread: 1080p long-GOP ~108 fps, Main10 86 fps, intra 44 fps, 2160p Main10 high bitrate 6.2 fps. Gains marked "estimated" were not measured. **Residual: two-pass dequant and a full-block clear** (`slicedec.rs`): `:1088` `coeffs[..n*n].fill(0)` every TB (4 KB at 32x32); `:12

Status
open
Score
0.00
Adopters
0 other operators
Author
vangel76 (imported from pull request #3188, open)
Base
bfc23840b603e69c782302c6ed04d4a9f6ec26a8
Size
+222 −111 in 3 files
Review
pending
Shared
2026-10-28
Id
6fa9e402e320a8d30fe48543

How it went

No other operator has reported using it yet.

Diff

raw · crates/hevc/src/mvpred.rs crates/hevc/src/slicedec.rs crates/hevc/src/tables.rs

diff --git a/crates/hevc/src/mvpred.rs b/crates/hevc/src/mvpred.rs--- a/crates/hevc/src/mvpred.rs+++ b/crates/hevc/src/mvpred.rs@@ -143,38 +143,35 @@ impl<'a, S: crate::picture::Sample> SliceDecoder<'a, S> {             col.ref_idx[1] = 0;             col_avail = true;         }-        let mut cands: Vec<MvField> = list[..n].to_vec();         if col_avail {-            cands.push(col);+            list[n] = col;+            n += 1;         }-        if merge_idx < cands.len() {-            return self.finish_merge(cands[merge_idx], w0, h0);+        if merge_idx < n {+            return self.finish_merge(list[merge_idx], w0, h0);         }         // combined bi-predictive candidates (8.5.3.2.4)-        let num_orig = cands.len();+        let num_orig = n;         if self.sh.is_b() && num_orig > 1 && num_orig < max {             let mut comb = 0;-            while comb < num_orig * (num_orig - 1) && cands.len() < max {+            while comb < num_orig * (num_orig - 1) && n < max {                 let (i0, i1) = COMB[comb];-                let (c0, c1) = (cands[i0], cands[i1]);+                let (c0, c1) = (list[i0], list[i1]);                 if c0.pred(0) && c1.pred(1) && (self.ref_pic(0, c0.ref_idx[0]).poc != self.ref_pic(1, c1.ref_idx[1]).poc || c0.mv[0] != c1.mv[1]) {-                    cands.push(MvField { mv: [c0.mv[0], c1.mv[1]], ref_idx: [c0.ref_idx[0], c1.ref_idx[1]] });+                    list[n] = MvField { mv: [c0.mv[0], c1.mv[1]], ref_idx: [c0.ref_idx[0], c1.ref_idx[1]] };+                    n += 1;                 }                 comb += 1;             }         }-        if merge_idx < cands.len() {-            return self.finish_merge(cands[merge_idx], w0, h0);+        if merge_idx < n {+            return self.finish_merge(list[merge_idx], w0, h0);         }-        // zero candidates (8.5.3.2.5)+        // zero candidates (8.5.3.2.5): candidate merge_idx is the (merge_idx - n)-th one         let num_ref = if self.sh.is_b() { self.sh.num_ref_idx[0].min(self.sh.num_ref_idx[1]) } else { self.sh.num_ref_idx[0] } as usize;-        let mut zero_idx = 0;-        while cands.len() <= merge_idx {-            let r = if zero_idx < num_ref { zero_idx as i8 } else { 0 };-            cands.push(MvField { mv: [[0; 2]; 2], ref_idx: [r, if self.sh.is_b() { r } else { -1 }] });-            zero_idx += 1;-        }-        self.finish_merge(cands[merge_idx], w0, h0)+        let zero_idx = merge_idx - n;+        let r = if zero_idx < num_ref { zero_idx as i8 } else { 0 };+        self.finish_merge(MvField { mv: [[0; 2]; 2], ref_idx: [r, if self.sh.is_b() { r } else { -1 }] }, w0, h0)     }      fn finish_merge(&self, mut f: MvField, w: i32, h: i32) -> MvField {diff --git a/crates/hevc/src/slicedec.rs b/crates/hevc/src/slicedec.rs--- a/crates/hevc/src/slicedec.rs+++ b/crates/hevc/src/slicedec.rs@@ -11,7 +11,7 @@ use crate::params::{Layout, Pps, Sps}; use crate::picture::{FrameRef, RefPic, Sample}; use crate::slice::{SliceHeader, SliceType}; use crate::spec_tables::*;-use crate::tables::{CTX_IDX_MAP, LEVEL_SCALE, SCAN_4, qpc_420, scan_order};+use crate::tables::{LEVEL_SCALE, SCAN_4, SIG_CTX, qpc_420, scan_order, scan_pos}; use crate::transform; use std::sync::Arc; @@ -260,6 +260,8 @@ pub struct SliceDecoder<'a, S> {     pub cu_part: PartMode,     // scratch     pub coeffs: Vec<i32>,+    /// Scaled non-zero coefficients of the current transform block: (index in `coeffs`, value).+    pub nz: Vec<(u16, i32)>,     pub pred0: Vec<i16>,     pub pred1: Vec<i16>,     pub mc: crate::inter::McScratch,@@ -381,6 +383,7 @@ impl<'a, S: Sample> SliceDecoder<'a, S> {             intra_split: false,             cu_part: PartMode::P2Nx2N,             coeffs: vec![0; 32 * 32],+            nz: vec![(0, 0); 32 * 32],             pred0: vec![0; 64 * 64],             pred1: vec![0; 64 * 64],             mc: Default::default(),@@ -1083,9 +1086,23 @@ impl<'a, S: Sample> SliceDecoder<'a, S> {         let sb_scan = scan_order(log2sb, scan_idx);         let pos_scan = &SCAN_4[scan_idx];         let sb_w = 1usize << log2sb;-        let last_sb = sb_scan.iter().position(|&(x, y)| x as u32 == lx >> 2 && y as u32 == ly >> 2).unwrap_or(0);-        let last_pos = pos_scan.iter().position(|&(x, y)| x as u32 == lx & 3 && y as u32 == ly & 3).unwrap_or(0);-        self.coeffs[..n * n].fill(0);+        let last_sb = scan_pos(log2sb, scan_idx, lx >> 2, ly >> 2);+        let last_pos = scan_pos(2, scan_idx, lx & 3, ly & 3);+        // scaling (8.6.3), applied as each coefficient is decoded+        let bd = if c == 0 { self.pic.bd_y } else { self.pic.bd_c };+        let bd_shift = bd + log2 - 5;+        let round = 1i64 << (bd_shift - 1);+        let scale = (LEVEL_SCALE[(qp % 6) as usize] as i64) << (qp / 6);+        let matrix = match &self.pic.scaling {+            Some(s) if !bypass && !(ts && n > 4) => {+                let size_id = (log2 - 2) as usize;+                let matrix_id = if self.cu_intra { c } else { 3 + c };+                Some((s.clone(), size_id * 6 + matrix_id))+            }+            _ => None,+        };+        let m: Option<&[u8]> = matrix.as_ref().map(|(s, i)| &s[*i][..]);+        let mut nnz = 0;         let mut csbf = [0u8; 64];         let mut prev_g1: Option<u32> = None;         let sdh = self.pps.sign_data_hiding && !bypass;@@ -1114,57 +1131,30 @@ impl<'a, S: Sample> SliceDecoder<'a, S> {                 15             };             if csbf[ys * 8 + xs] != 0 {-                let prev_csbf = right + (below << 1);+                let prev_csbf = (right + (below << 1)) as usize;+                let ctx_tab = if log2 == 2 {+                    SIG_CTX[scan_idx][4]+                } else {+                    let off = if c == 0 {+                        (if xs + ys > 0 { 3 } else { 0 }) + if log2 == 3 { if scan_idx == 0 { 9 } else { 15 } } else { 21 }+                    } else if log2 == 3 {+                        9+                    } else {+                        12+                    };+                    let mut t = SIG_CTX[scan_idx][prev_csbf];+                    for v in t.iter_mut() {+                        *v += off;+                    }+                    if i == 0 {+                        t[0] = 0;+                    }+                    t+                };                 let mut np = start;                 while np >= 0 {-                    let (xp, yp) = (pos_scan[np as usize].0 as usize, pos_scan[np as usize].1 as usize);-                    let (xc, yc) = (xs * 4 + xp, ys * 4 + yp);                     if np > 0 || !infer_dc {-                        let sig_ctx = if log2 == 2 {-                            CTX_IDX_MAP[(yc << 2) + xc] as usize-                        } else if xc + yc == 0 {-                            0-                        } else {-                            let mut s = match prev_csbf {-                                0 => {-                                    if xp + yp == 0 {-                                        2-                                    } else if xp + yp < 3 {-                                        1-                                    } else {-                                        0-                                    }-                                }-                                1 => {-                                    if yp == 0 {-                                        2-                                    } else if yp == 1 {-                                        1-                                    } else {-                                        0-                                    }-                                }-                                2 => {-                                    if xp == 0 {-                                        2-                                    } else if xp == 1 {-                                        1-                                    } else {-                                        0-                                    }-                                }-                                _ => 2,-                            };-                            if c == 0 {-                                if xs + ys > 0 {-                                    s += 3;-                                }-                                s += if log2 == 3 { if scan_idx == 0 { 9 } else { 15 } } else { 21 };-                            } else {-                                s += if log2 == 3 { 9 } else { 12 };-                            }-                            s-                        };+                        let sig_ctx = ctx_tab[np as usize] as usize;                         if self.c.decode_decision(SIG_COEFF + c_off_sig + sig_ctx) == 1 {                             sig[nsig] = np as u8;                             nsig += 1;@@ -1249,12 +1239,17 @@ impl<'a, S: Sample> SliceDecoder<'a, S> {                         v = -v;                     }                 }-                self.coeffs[yc * n + xc] = v;+                if !bypass {+                    let f = m.map_or(16, |m| m[yc * n + xc] as i64);+                    v = ((v as i64 * f * scale + round) >> bd_shift).clamp(-32768, 32767) as i32;+                }+                self.nz[nnz] = ((yc * n + xc) as u16, v);+                nnz += 1;                 max_x = max_x.max(xc);                 max_y = max_y.max(yc);             }         }-        self.reconstruct(x0, y0, log2, c, qp, ts, max_x, max_y)+        self.reconstruct(x0, y0, log2, c, ts, nnz, max_x, max_y)     }      fn coeff_abs_level_remaining(&mut self, rice: u32) -> Result<i32> {@@ -1272,54 +1267,50 @@ impl<'a, S: Sample> SliceDecoder<'a, S> {         }     } -    /// Scaling, transform and addition to the prediction.-    fn reconstruct(&mut self, x0: i32, y0: i32, log2: u32, c: usize, qp: i32, ts: bool, max_x: usize, max_y: usize) -> Result<()> {+    /// Transform of the scaled coefficients `nz[..nnz]` and addition to the prediction.+    fn reconstruct(&mut self, x0: i32, y0: i32, log2: u32, c: usize, ts: bool, nnz: usize, max_x: usize, max_y: usize) -> Result<()> {         let n = 1usize << log2;         let bd = if c == 0 { self.pic.bd_y } else { self.pic.bd_c };+        let bypass = self.cu_transquant_bypass;+        let dst = self.cu_intra && c == 0 && n == 4;+        let (stride, max) = (if c == 0 { self.pic.width } else { self.pic.cwidth }, (1i32 << bd) - 1);+        let off = y0 as usize * stride + x0 as usize;+        let plane = &mut self.pic.planes[c];+        let bd_shift = 20 - bd;+        if !bypass && !ts && max_x == 0 && max_y == 0 && !dst {+            let v = transform::inverse_dc(self.nz[0].1, bd_shift);+            match n {+                4 => add_dc::<S, 4>(plane, off, stride, v, max),+                8 => add_dc::<S, 8>(plane, off, stride, v, max),+                16 => add_dc::<S, 16>(plane, off, stride, v, max),+                _ => add_dc::<S, 32>(plane, off, stride, v, max),+            }+            return Ok(());+        }+        // the inverse transform reads only the bounding box of the coefficients         let coeffs = &mut self.coeffs[..n * n];-        let mut dc_only = false;-        if !self.cu_transquant_bypass {-            // scaling (8.6.3)-            let bd_shift = bd + log2 - 5;-            let round = 1i64 << (bd_shift - 1);-            let scale = (LEVEL_SCALE[(qp % 6) as usize] as i64) << (qp / 6);-            let m: Option<&[u8]> = match &self.pic.scaling {-                Some(s) if !(ts && n > 4) => {-                    let size_id = (log2 - 2) as usize;-                    let matrix_id = if self.cu_intra { c } else { 3 + c };-                    Some(&s[size_id * 6 + matrix_id])-                }-                _ => None,-            };-            for y in 0..=max_y {-                for x in 0..=max_x {-                    let v = coeffs[y * n + x];-                    if v != 0 {-                        let f = m.map_or(16, |m| m[y * n + x] as i64);-                        coeffs[y * n + x] = ((v as i64 * f * scale + round) >> bd_shift).clamp(-32768, 32767) as i32;-                    }-                }+        if bypass || ts {+            coeffs.fill(0);+        } else {+            for row in coeffs.chunks_exact_mut(n).take(max_y + 1) {+                row[..=max_x].fill(0);             }-            let bd_shift = 20 - bd;+        }+        for &(p, v) in &self.nz[..nnz] {+            coeffs[p as usize] = v;+        }+        if !bypass {             if ts {                 transform::transform_skip(coeffs, n, bd_shift);-            } else if max_x == 0 && max_y == 0 && !(self.cu_intra && c == 0 && n == 4) {-                let v = transform::inverse_dc(coeffs[0], bd_shift);-                coeffs.fill(v);-                dc_only = true;             } else {-                let dst = self.cu_intra && c == 0 && n == 4;                 transform::inverse_transform(coeffs, n, dst, max_x, max_y, bd_shift);             }         }-        let _ = dc_only;-        let (stride, max) = (if c == 0 { self.pic.width } else { self.pic.cwidth }, (1i32 << bd) - 1);-        let plane = &mut self.pic.planes[c];-        for y in 0..n {-            let row = &mut plane[(y0 as usize + y) * stride + x0 as usize..][..n];-            for (x, s) in row.iter_mut().enumerate() {-                *s = S::from_i32((s.to_i32() + coeffs[y * n + x]).clamp(0, max));-            }+        match n {+            4 => add_residual::<S, 4>(plane, off, stride, coeffs, max),+            8 => add_residual::<S, 8>(plane, off, stride, coeffs, max),+            16 => add_residual::<S, 16>(plane, off, stride, coeffs, max),+            _ => add_residual::<S, 32>(plane, off, stride, coeffs, max),         }         Ok(())     }@@ -1513,6 +1504,25 @@ impl<'a, S: Sample> SliceDecoder<'a, S> {     } } +#[inline(always)]+fn add_residual<S: Sample, const N: usize>(plane: &mut [S], off: usize, stride: usize, res: &[i32], max: i32) {+    for (y, r) in res.as_chunks::<N>().0.iter().take(N).enumerate() {+        let row = &mut plane[off + y * stride..][..N];+        for (s, &d) in row.iter_mut().zip(r) {+            *s = S::from_i32((s.to_i32() + d).max(0).min(max));+        }+    }+}++#[inline(always)]+fn add_dc<S: Sample, const N: usize>(plane: &mut [S], off: usize, stride: usize, v: i32, max: i32) {+    for y in 0..N {+        for s in &mut plane[off + y * stride..][..N] {+            *s = S::from_i32((s.to_i32() + v).max(0).min(max));+        }+    }+}+ /// Whether the explicit weights of the lists `f` predicts from are the default ones for component /// `c` (weight 2^denom, offset 0). fn default_weights(pwt: &crate::slice::PredWeightTable, f: &MvField, c: usize) -> bool {@@ -1524,3 +1534,30 @@ fn default_weights(pwt: &crate::slice::PredWeightTable, f: &MvField, c: usize) -         })     }) }++#[cfg(test)]+mod tests {+    use crate::transform;++    /// `reconstruct` clears only the coefficient bounding box, so the inverse transform must not+    /// read the stale values left outside it by the previous block.+    #[test]+    fn inverse_transform_reads_only_the_coefficient_box() {+        for &(n, dst) in &[(4usize, true), (4, false), (8, false), (16, false), (32, false)] {+            for (max_x, max_y) in [(0, 1), (1, 0), (2, 3), (n / 2, 1), (n - 1, 0), (n - 1, n - 1)] {+                let (mut clean, mut stale) = (vec![0i32; n * n], vec![0i32; n * n]);+                for (i, (c, s)) in clean.iter_mut().zip(stale.iter_mut()).enumerate() {+                    let (x, y) = (i % n, i / n);+                    *s = 12345 - i as i32;+                    if x <= max_x && y <= max_y {+                        *c = (i as i32 * 37) % 201 - 100;+                        *s = *c;+                    }+                }+                transform::inverse_transform(&mut clean, n, dst, max_x, max_y, 12);+                transform::inverse_transform(&mut stale, n, dst, max_x, max_y, 12);+                assert_eq!(clean, stale, "n {n} dst {dst} box ({max_x}, {max_y})");+            }+        }+    }+}diff --git a/crates/hevc/src/tables.rs b/crates/hevc/src/tables.rs--- a/crates/hevc/src/tables.rs+++ b/crates/hevc/src/tables.rs@@ -51,6 +51,72 @@ pub fn scan_order(log2: u32, scan_idx: usize) -> &'static [(u8, u8)] {     } } +/// Inverse of a scan of an `N x N` block: scan position at `[y * N + x]`.+const fn inverse<const L: usize>(scan: [(u8, u8); L], n: usize) -> [u8; L] {+    let mut out = [0u8; L];+    let mut i = 0;+    while i < L {+        out[scan[i].1 as usize * n + scan[i].0 as usize] = i as u8;+        i += 1;+    }+    out+}++static INV_SCAN_1: [[u8; 1]; 3] = [[0], [0], [0]];+static INV_SCAN_2: [[u8; 4]; 3] = [inverse(diag::<4>(2), 2), inverse(hv::<4>(2, false), 2), inverse(hv::<4>(2, true), 2)];+static INV_SCAN_4: [[u8; 16]; 3] = [inverse(diag::<16>(4), 4), inverse(hv::<16>(4, false), 4), inverse(hv::<16>(4, true), 4)];+static INV_SCAN_8: [[u8; 64]; 3] = [inverse(diag::<64>(8), 8), inverse(hv::<64>(8, false), 8), inverse(hv::<64>(8, true), 8)];++/// Position of (x, y) in ScanOrder[log2BlockSize][scanIdx] (log2BlockSize 0..=3).+#[inline]+pub fn scan_pos(log2: u32, scan_idx: usize, x: u32, y: u32) -> usize {+    let i = ((y << log2) + x) as usize;+    (match log2 {+        0 => INV_SCAN_1[scan_idx][i],+        1 => INV_SCAN_2[scan_idx][i],+        2 => INV_SCAN_4[scan_idx][i],+        _ => INV_SCAN_8[scan_idx][i],+    }) as usize+}++/// sig_coeff_flag ctxInc (9.3.4.2.5) by scan position n inside a 4x4 sub-block, `[scanIdx][k][n]`:+/// k = prevCsbf (0..=3) gives sigCtx of blocks larger than 4x4 before the position and size+/// offsets; k = 4 gives ctxIdxMap of 4x4 blocks.+pub static SIG_CTX: [[[u8; 16]; 5]; 3] = [sig_ctx(diag::<16>(4)), sig_ctx(hv::<16>(4, false)), sig_ctx(hv::<16>(4, true))];++const fn sig_ctx(scan: [(u8, u8); 16]) -> [[u8; 16]; 5] {+    let mut out = [[0u8; 16]; 5];+    let mut n = 0;+    while n < 16 {+        let (x, y) = (scan[n].0, scan[n].1);+        out[0][n] = if x + y == 0 {+            2+        } else if x + y < 3 {+            1+        } else {+            0+        };+        out[1][n] = if y == 0 {+            2+        } else if y == 1 {+            1+        } else {+            0+        };+        out[2][n] = if x == 0 {+            2+        } else if x == 1 {+            1+        } else {+            0+        };+        out[3][n] = 2;+        out[4][n] = CTX_IDX_MAP[(y as usize) * 4 + x as usize];+        n += 1;+    }+    out+}+ /// Up-right diagonal scan for log2 size 2 or 3. pub fn scan_diag(log2: u32) -> &'static [(u8, u8)] {     scan_order(log2, 0)@@ -69,7 +135,7 @@ pub fn qpc_420(qpi: i32) -> i32 { }  /// Table 9-50 ctxIdxMap for 4x4 sig_coeff_flag contexts.-pub static CTX_IDX_MAP: [u8; 16] = [0, 1, 4, 5, 2, 3, 4, 5, 6, 6, 8, 8, 7, 7, 8, 8];+pub const CTX_IDX_MAP: [u8; 16] = [0, 1, 4, 5, 2, 3, 4, 5, 6, 6, 8, 8, 7, 7, 8, 8];  /// levelScale (8-309). pub static LEVEL_SCALE: [i32; 6] = [40, 45, 51, 57, 64, 72];@@ -91,6 +157,17 @@ mod tests {         assert_eq!(SCAN_4[1][1], (1, 0));     } +    #[test]+    fn scan_pos_inverts_scan_order() {+        for log2 in 0..4 {+            for scan_idx in 0..3 {+                for (i, &(x, y)) in scan_order(log2, scan_idx).iter().enumerate() {+                    assert_eq!(scan_pos(log2, scan_idx, x as u32, y as u32), i);+                }+            }+        }+    }+     #[test]     fn dct_matrix_properties() {         // rows are nearly orthogonal with norm ~ 64^2 * 32