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
@@ -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 {@@ -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})");+ }+ }+ }+}@@ -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