From bde833aad3aeb473e2a2f99bcad9b231aaede9bc Mon Sep 17 00:00:00 2001 From: Gunwoo Kim Date: Mon, 9 Nov 2026 04:01:20 +0900 Subject: [PATCH 1/2] docs(hevc): plan optional prediction comparison Signed-off-by: Gunwoo Kim --- docs/design/hevc-inter.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/design/hevc-inter.md b/docs/design/hevc-inter.md index 83f159dbe..ef14e063f 100644 --- a/docs/design/hevc-inter.md +++ b/docs/design/hevc-inter.md @@ -42,6 +42,8 @@ Store resolved motion at the required minimum prediction granularity. Each retai ## Scalar numerical work and filtering +An unweighted comparison prediction is needed only when a component has effective nondefault weights, to count samples changed by explicit weighting. Guard its scratch rectangle and combination call with that per-component condition. Keep the actual destination prediction, validation, weighting, stores and all nondefault comparison statistics unchanged. Default or explicitly default weights do not populate unused comparison scratch; a later component cannot make an earlier component's comparison necessary. + Add allocation-free safe kernels for reference-edge extension, luma quarter-sample eight-tap interpolation, chroma eighth-sample four-tap interpolation, unweighted uni/bi prediction and explicit weighted prediction at both depths. Preserve the normative internal precision, offsets, signed shifts, rounding and final clipping; do not clip an intermediate predictor as if it were a final sample. Checked source/destination geometry and bounded scratch prevent partial writes on rejected public inputs. Keep syntax and candidate derivation out of the kernel crate. The bitstream owner supplies resolved motion, reference views and effective weights. Cover every phase, integer/one-axis/two-axis paths, rectangular partitions, independent strides, picture edges, negative motion, extreme legal samples and weights with independently equation-derived expectations. From 4b3011993621e03f7b59d108ae8e9c7ae01d3bde Mon Sep 17 00:00:00 2001 From: Gunwoo Kim Date: Mon, 9 Nov 2026 04:08:36 +0900 Subject: [PATCH 2/2] perf(hevc): skip unused comparison predictions Signed-off-by: Gunwoo Kim --- .../coreav-hevc/src/reconstruction/inter.rs | 110 +++++++++++++++++- 1 file changed, 105 insertions(+), 5 deletions(-) diff --git a/crates/coreav-hevc/src/reconstruction/inter.rs b/crates/coreav-hevc/src/reconstruction/inter.rs index 535eafe89..49ce81abd 100644 --- a/crates/coreav-hevc/src/reconstruction/inter.rs +++ b/crates/coreav-hevc/src/reconstruction/inter.rs @@ -1021,11 +1021,14 @@ pub(super) fn predict( width, height, )?; - let output = kernels::RectMut::new(&mut frame.scratch.inter_output, width, width, height)?; - match motion { - Motion::L0(_) => kernels::predict_uni(frame.depth, a, output)?, - Motion::L1(_) => kernels::predict_uni(frame.depth, b, output)?, - Motion::Bi(_) => kernels::predict_bi(frame.depth, a, b, output)?, + if nondefault { + let output = + kernels::RectMut::new(&mut frame.scratch.inter_output, width, width, height)?; + match motion { + Motion::L0(_) => kernels::predict_uni(frame.depth, a, output)?, + Motion::L1(_) => kernels::predict_uni(frame.depth, b, output)?, + Motion::Bi(_) => kernels::predict_bi(frame.depth, a, b, output)?, + } } let offset = y * plane_width + x; let plane = frame @@ -1796,6 +1799,103 @@ mod tests { assert_eq!(morton(3, 3, 4, 2), morton(0, 0, 4, 2)); } + #[test] + fn comparison_prediction_is_only_populated_for_nondefault_components() -> Result<()> { + for bits in [8, 10] { + let fixture = Fixture::new(bits, 1)?; + let dpb = fixture.dpb()?; + for direction in 0..3 { + for weighting in [None, Some(false), Some(true)] { + let changed_chroma = weighting == Some(true); + let mut inter = controls([1, 1]); + if let Some(nondefault) = weighting { + let mut weights = crate::slice::Weights { + denominators: [2; 2], + lists: [[[crate::slice::Weight { + weight: 4, + offset: 0, + }; 3]; 15]; 2], + }; + if nondefault { + for list in &mut weights.lists { + list[0][1].weight = 8; + } + } + inter.weights = Some(weights); + } + let input = fixture.input(0, inter); + let mut frame = super::super::Frame::new_full( + &input, + 2, + dpb.current_references(), + 0, + 1_000_000, + None, + )?; + for cell in &mut frame.cells { + cell.slice = 1; + } + frame.scratch.inter_output.fill(u16::MAX); + let pu = Pu { + x: 8, + y: 8, + width: 8, + height: 8, + }; + let mut expected_planes = frame.planes.clone(); + for (component, plane) in expected_planes.iter_mut().enumerate() { + let divisor = if component == 0 { 1 } else { 2 }; + let sample = (42 + component as u16) + * if changed_chroma && component == 1 { + 2 + } else { + 1 + }; + for y in pu.y / divisor..(pu.y + pu.height) / divisor { + for x in pu.x / divisor..(pu.x + pu.width) / divisor { + plane[y * (64 / divisor) + x] = sample; + } + } + } + let mut walker = syntax::Walker { + frame: &mut frame, + input: &input, + location: fixture.layout.at_raster(0)?, + slice: 1, + dpb: Some(&dpb), + }; + let lists = state(&walker)?.2; + let motion = match direction { + 0 => Motion::L0(lists.get(0, 0, ZERO)?), + 1 => Motion::L1(lists.get(1, 0, ZERO)?), + _ => Motion::Bi([lists.get(0, 0, ZERO)?, lists.get(1, 0, ZERO)?]), + }; + let mut expected_stats = walker.frame.inter_stats; + expected_stats.prediction_units = 1; + expected_stats.b_units = 1; + expected_stats.bi_units = u64::from(direction == 2); + expected_stats.weighted_units = u64::from(changed_chroma); + expected_stats.weighted_changed_samples = + if changed_chroma { [0, 16, 0] } else { [0; 3] }; + predict(&mut walker, pu, motion, false, false)?; + assert_eq!(walker.frame.planes, expected_planes); + assert_eq!(walker.frame.inter_stats, expected_stats); + for (index, &sample) in walker.frame.scratch.inter_output.iter().enumerate() { + assert_eq!( + sample, + if changed_chroma && index < 16 { + 43 + } else { + u16::MAX + } + ); + } + } + } + } + Ok(()) + } + #[test] fn prediction_uses_duplicate_active_index_weights_and_marks_motion_without_reconstruction() -> Result<()> {