mirror of
https://github.com/storytold/filmcraft.git
synced 2026-10-24 10:10:05 -06:00
Merge pull request #1067 from vangel76/perf-hevc-u8
#1039: 8-bit HEVC pictures as u8, in-place motion compensation (−14…23 % per frame), big-picture analysis
This commit is contained in:
@@ -64,8 +64,10 @@ POC / RPS / DPB bookkeeping; the CTU layer of each picture runs as a job (rayon
|
||||
reconstructs into private planes, deblocks CTB row r once row r+1 is decoded, applies SAO to row r
|
||||
once row r+1 is deblocked, and publishes final rows (samples + 16x16 compressed motion for TMVP)
|
||||
into the shared `Frame` (`OnceLock` per CTB row). Motion compensation of later pictures blocks per
|
||||
row on exactly the reference rows it reads. Samples are stored as `u16` internally for all bit
|
||||
depths.
|
||||
row on exactly the reference rows it reads. Samples are stored as `u8` for 8-bit pictures (half
|
||||
the memory traffic in prediction reads, in-loop filters and output) and as `u16` above 8 bits: the
|
||||
slice decoder and the filters are generic over the sample type (`picture::Sample`), monomorphised
|
||||
once per type.
|
||||
|
||||
Draft mode (`Decoder::set_draft(true)`; FilmCraft enables it with Settings ▸ Playback ▸ Draft
|
||||
decoding while the Program monitor plays at 1/2 or 1/4): sub-layer non-reference pictures
|
||||
|
||||
+64
-28
@@ -8,10 +8,10 @@
|
||||
use crate::dpb::{Dpb, Output, OutputMeta, PocState, RefPicSet, build_ref_lists};
|
||||
use crate::error::{Error, Result, ensure, invalid};
|
||||
use crate::params::{Layout, Pps, Sps, Vps};
|
||||
use crate::picture::{Frame, FrameRef};
|
||||
use crate::picture::{Frame, FrameRef, Sample, stores_u8};
|
||||
use crate::slice::{NalHeader, SliceHeader, nal_type};
|
||||
use crate::slicedec::{F_INTRA, F_SKIP, PicState, SliceDecoder, SliceJob};
|
||||
use crate::{ColorInfo, Picture, Plane};
|
||||
use crate::slicedec::{Blk, F_INTRA, F_SKIP, PicState, SliceDecoder, SliceJob};
|
||||
use crate::{ColorInfo, Picture};
|
||||
use filmcraft_bitstream::{annexb_nals, length_prefixed_nals, unescape_rbsp};
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::{Arc, Mutex};
|
||||
@@ -507,7 +507,7 @@ impl Decoder {
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode all slices of one picture and publish its rows.
|
||||
/// Decode all slices of one picture and publish its rows, in the picture's sample type.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn run_job(
|
||||
frame: FrameRef,
|
||||
@@ -519,7 +519,25 @@ fn run_job(
|
||||
draft: bool,
|
||||
shared: &Shared,
|
||||
) {
|
||||
let mut pic = PicState::new(frame.clone(), sps, pps, layout, scaling);
|
||||
if stores_u8(frame.bit_depth, frame.bit_depth_c) {
|
||||
run_job_as::<u8>(frame, sps, pps, layout, scaling, slices, draft, shared)
|
||||
} else {
|
||||
run_job_as::<u16>(frame, sps, pps, layout, scaling, slices, draft, shared)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn run_job_as<S: Sample>(
|
||||
frame: FrameRef,
|
||||
sps: Arc<Sps>,
|
||||
pps: Arc<Pps>,
|
||||
layout: Arc<Layout>,
|
||||
scaling: Option<Arc<Vec<Vec<u8>>>>,
|
||||
slices: Vec<SliceJob>,
|
||||
draft: bool,
|
||||
shared: &Shared,
|
||||
) {
|
||||
let mut pic = PicState::<S>::new(frame.clone(), sps, pps, layout, scaling);
|
||||
pic.draft = draft;
|
||||
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
let mut first_err = None;
|
||||
@@ -535,33 +553,34 @@ fn run_job(
|
||||
Ok(e) => e,
|
||||
Err(_) => {
|
||||
// make sure readers never block on this frame
|
||||
let gray = Frame::gray(0, frame.poc, &frame);
|
||||
for r in 0..frame.num_rows() {
|
||||
if !frame.is_published(r) {
|
||||
let row = gray.row(r);
|
||||
frame.publish(r, crate::picture::FrameRow { y: row.y.clone(), cb: row.cb.clone(), cr: row.cr.clone(), col: row.col.clone() });
|
||||
}
|
||||
}
|
||||
frame.publish_gray();
|
||||
Some(Error::Invalid("internal error while decoding a picture".into()))
|
||||
}
|
||||
};
|
||||
if let Ok(mut s) = shared.stats.lock() {
|
||||
s.pictures += 1;
|
||||
for b in &pic.blk {
|
||||
if b.flags & F_INTRA != 0 {
|
||||
s.intra_4x4_blocks += 1;
|
||||
} else if b.flags & F_SKIP != 0 {
|
||||
s.skip_4x4_blocks += 1;
|
||||
} else if b.flags != 0 {
|
||||
s.inter_4x4_blocks += 1;
|
||||
}
|
||||
}
|
||||
let [intra, skip, inter] = block_counts(&pic.blk);
|
||||
s.intra_4x4_blocks += intra;
|
||||
s.skip_4x4_blocks += skip;
|
||||
s.inter_4x4_blocks += inter;
|
||||
}
|
||||
if let (Some(e), Ok(mut slot)) = (err, shared.error.lock()) {
|
||||
slot.get_or_insert(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// Intra, skipped and other inter 4x4 blocks of a picture, branch-free (one pass over every
|
||||
/// block): intra blocks have F_INTRA, skipped ones F_SKIP without it, inter ones any other flags.
|
||||
fn block_counts(blk: &[Blk]) -> [u64; 3] {
|
||||
let (mut intra, mut skip, mut coded) = (0u64, 0u64, 0u64);
|
||||
for b in blk {
|
||||
intra += (b.flags & F_INTRA != 0) as u64;
|
||||
skip += (b.flags & (F_INTRA | F_SKIP) == F_SKIP) as u64;
|
||||
coded += (b.flags != 0) as u64;
|
||||
}
|
||||
[intra, skip, coded.saturating_sub(intra + skip)]
|
||||
}
|
||||
|
||||
fn output_meta(sps: &Sps, pts: i64, key: bool, draft: bool) -> OutputMeta {
|
||||
let vui = sps.vui.clone().unwrap_or_default();
|
||||
OutputMeta {
|
||||
@@ -583,13 +602,7 @@ fn make_picture(o: &Output<FrameRef>) -> Picture {
|
||||
let (cx, cy, cw, ch) = (cx as usize, cy as usize, cw as usize, ch as usize);
|
||||
let (ccw, cch) = (cw.div_ceil(2), ch.div_ceil(2));
|
||||
let bd = o.meta.bit_depth;
|
||||
let (y, u, v) = if bd <= 8 {
|
||||
let (y, u, v) = o.frame.copy_cropped_u8((cx, cy, cw, ch));
|
||||
(Plane::U8(y), Plane::U8(u), Plane::U8(v))
|
||||
} else {
|
||||
let (y, u, v) = o.frame.copy_cropped((cx, cy, cw, ch));
|
||||
(Plane::U16(y), Plane::U16(u), Plane::U16(v))
|
||||
};
|
||||
let (y, u, v) = o.frame.cropped_planes((cx, cy, cw, ch), bd <= 8);
|
||||
Picture {
|
||||
width: cw as u32,
|
||||
height: ch as u32,
|
||||
@@ -614,3 +627,26 @@ fn make_picture(o: &Output<FrameRef>) -> Picture {
|
||||
draft: o.meta.draft,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// The branch-free block counts classify every flag combination as the if / else-if chain did.
|
||||
#[test]
|
||||
fn block_counts_match_the_classification_chain() {
|
||||
let blk: Vec<Blk> = (0..=255u8).flat_map(|f| [Blk { flags: f, ..Blk::default() }; 3]).collect();
|
||||
let mut want = [0u64; 3];
|
||||
for b in &blk {
|
||||
if b.flags & F_INTRA != 0 {
|
||||
want[0] += 1;
|
||||
} else if b.flags & F_SKIP != 0 {
|
||||
want[1] += 1;
|
||||
} else if b.flags != 0 {
|
||||
want[2] += 1;
|
||||
}
|
||||
}
|
||||
assert_eq!(block_counts(&blk), want);
|
||||
assert_eq!(block_counts(&[]), [0; 3]);
|
||||
}
|
||||
}
|
||||
|
||||
+45
-45
@@ -4,13 +4,13 @@
|
||||
//! Row r is deblocked once rows 0..=r+1 are decoded (row r+1 still needs the unfiltered bottom samples
|
||||
//! of row r for intra prediction); SAO of row r runs once row r+1 is deblocked; then row r is final.
|
||||
|
||||
use crate::picture::{ColMv, FrameRow};
|
||||
use crate::picture::{ColMv, FrameRow, Sample};
|
||||
use crate::slicedec::{E_PU_H, E_PU_V, E_TU_H, E_TU_V, F_CBF, F_CODED, F_INTRA, F_NOFILTER, MvField, PicState};
|
||||
use crate::spec_tables::{BETA_TABLE, TC_TABLE};
|
||||
use crate::tables::qpc_420;
|
||||
|
||||
/// Called after each decoded CTB of CTB row `ry`.
|
||||
pub fn ctb_decoded(pic: &mut PicState, ry: usize) {
|
||||
pub fn ctb_decoded<S: Sample>(pic: &mut PicState<S>, ry: usize) {
|
||||
pic.row_done[ry] += 1;
|
||||
while pic.rows_complete < pic.hctb && pic.row_done[pic.rows_complete] as usize == pic.wctb {
|
||||
pic.rows_complete += 1;
|
||||
@@ -19,7 +19,7 @@ pub fn ctb_decoded(pic: &mut PicState, ry: usize) {
|
||||
}
|
||||
|
||||
/// Run the filters / publication as far as the decoded rows allow (`all`: end of picture).
|
||||
pub fn advance(pic: &mut PicState, all: bool) {
|
||||
pub fn advance<S: Sample>(pic: &mut PicState<S>, all: bool) {
|
||||
let h = pic.hctb;
|
||||
loop {
|
||||
let r = pic.rows_deblocked;
|
||||
@@ -39,7 +39,7 @@ pub fn advance(pic: &mut PicState, all: bool) {
|
||||
}
|
||||
|
||||
/// End of picture: filter and publish everything.
|
||||
pub fn finish(pic: &mut PicState) {
|
||||
pub fn finish<S: Sample>(pic: &mut PicState<S>) {
|
||||
pic.rows_complete = pic.hctb;
|
||||
advance(pic, true);
|
||||
}
|
||||
@@ -53,7 +53,7 @@ fn tc_beta(q: i32, bs: i32, tc_off: i32, beta_off: i32, bd: u32) -> (i32, i32) {
|
||||
|
||||
/// Boundary strength (8.7.2.4) between 4x4 blocks p and q, in slices `ps` and `qs` (indices into
|
||||
/// `pic.slices`, from [`edge_params`]).
|
||||
fn boundary_strength(pic: &PicState, pi: usize, qi: usize, tu_edge: bool, ps: u32, qs: u32) -> i32 {
|
||||
fn boundary_strength<S: Sample>(pic: &PicState<S>, pi: usize, qi: usize, tu_edge: bool, ps: u32, qs: u32) -> i32 {
|
||||
let (p, q) = (&pic.blk[pi], &pic.blk[qi]);
|
||||
if (p.flags | q.flags) & F_INTRA != 0 {
|
||||
return 2;
|
||||
@@ -70,14 +70,14 @@ fn boundary_strength(pic: &PicState, pi: usize, qi: usize, tu_edge: bool, ps: u3
|
||||
}
|
||||
|
||||
/// The picture referenced by list `l` of motion `f` in slice `s`.
|
||||
fn ref_id(pic: &PicState, s: u32, f: &MvField, l: usize) -> u32 {
|
||||
fn ref_id<S: Sample>(pic: &PicState<S>, s: u32, f: &MvField, l: usize) -> u32 {
|
||||
if s == u32::MAX {
|
||||
return u32::MAX;
|
||||
}
|
||||
pic.slices.get(s as usize).and_then(|sl| sl.ref_ids[l].get(f.ref_idx[l] as usize)).copied().unwrap_or(u32::MAX - 1)
|
||||
}
|
||||
|
||||
fn motion_bs(pic: &PicState, ps: u32, p: &MvField, qs: u32, q: &MvField) -> i32 {
|
||||
fn motion_bs<S: Sample>(pic: &PicState<S>, ps: u32, p: &MvField, qs: u32, q: &MvField) -> i32 {
|
||||
let np = p.pred(0) as u32 + p.pred(1) as u32;
|
||||
let nq = q.pred(0) as u32 + q.pred(1) as u32;
|
||||
if np != nq {
|
||||
@@ -106,7 +106,7 @@ fn motion_bs(pic: &PicState, ps: u32, p: &MvField, qs: u32, q: &MvField) -> i32
|
||||
|
||||
/// Whether the edge between blocks p and q (q = current block, owning the edge) is filtered, and the
|
||||
/// slice-level parameters (tc offset, beta offset) of q's slice, then the slices of p and q.
|
||||
fn edge_params(pic: &PicState, px: usize, py: usize, qx: usize, qy: usize) -> Option<(i32, i32, u32, u32)> {
|
||||
fn edge_params<S: Sample>(pic: &PicState<S>, px: usize, py: usize, qx: usize, qy: usize) -> Option<(i32, i32, u32, u32)> {
|
||||
let s = pic.log2_ctb;
|
||||
let pc = (py >> s) * pic.wctb + (px >> s);
|
||||
let qc = (qy >> s) * pic.wctb + (qx >> s);
|
||||
@@ -131,7 +131,7 @@ fn edge_params(pic: &PicState, px: usize, py: usize, qx: usize, qy: usize) -> Op
|
||||
}
|
||||
|
||||
/// Deblock CTB row `r`: vertical edges of the row, then horizontal edges whose q side is in the row.
|
||||
fn deblock_row(pic: &mut PicState, r: usize) {
|
||||
fn deblock_row<S: Sample>(pic: &mut PicState<S>, r: usize) {
|
||||
if !pic.deblocking_enabled_anywhere || pic.draft {
|
||||
return;
|
||||
}
|
||||
@@ -236,7 +236,7 @@ fn deblock_row(pic: &mut PicState, r: usize) {
|
||||
/// the 4 lines at a time for horizontal ones (`along` = 1): no per-sample index arithmetic or
|
||||
/// bounds checks in the filter itself.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn filter_luma(s: &mut [u16], o: usize, step: usize, along: usize, tc: i32, beta: i32, nfp: bool, nfq: bool, bd: u32) {
|
||||
fn filter_luma<S: Sample>(s: &mut [S], o: usize, step: usize, along: usize, tc: i32, beta: i32, nfp: bool, nfq: bool, bd: u32) {
|
||||
// l[k][i]: line k, i = 0..4 -> p3, p2, p1, p0 and i = 4..8 -> q0, q1, q2, q3
|
||||
let mut l = [[0i32; 8]; 4];
|
||||
let Some(start) = o.checked_sub(4 * step) else { return };
|
||||
@@ -244,14 +244,14 @@ fn filter_luma(s: &mut [u16], o: usize, step: usize, along: usize, tc: i32, beta
|
||||
for (k, line) in l.iter_mut().enumerate() {
|
||||
let Some(src) = s.get(start + k * along..).and_then(|v| v.first_chunk::<8>()) else { return };
|
||||
for (d, &v) in line.iter_mut().zip(src) {
|
||||
*d = v as i32;
|
||||
*d = v.to_i32();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for i in 0..8 {
|
||||
let Some(src) = s.get(start + i * step..).and_then(|v| v.first_chunk::<4>()) else { return };
|
||||
for (k, &v) in src.iter().enumerate() {
|
||||
l[k][i] = v as i32;
|
||||
l[k][i] = v.to_i32();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -262,14 +262,14 @@ fn filter_luma(s: &mut [u16], o: usize, step: usize, along: usize, tc: i32, beta
|
||||
for (k, line) in l.iter().enumerate() {
|
||||
let Some(dst) = s.get_mut(start + k * along..).and_then(|v| v.first_chunk_mut::<8>()) else { return };
|
||||
for (d, &v) in dst.iter_mut().zip(line) {
|
||||
*d = v as u16;
|
||||
*d = S::from_i32(v);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for i in 1..7 {
|
||||
let Some(dst) = s.get_mut(start + i * step..).and_then(|v| v.first_chunk_mut::<4>()) else { return };
|
||||
for (k, d) in dst.iter_mut().enumerate() {
|
||||
*d = l[k][i] as u16;
|
||||
*d = S::from_i32(l[k][i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -340,26 +340,26 @@ fn filter_luma_lines(l: &mut [[i32; 8]; 4], tc: i32, beta: i32, nfp: bool, nfq:
|
||||
|
||||
/// Filter one 4-sample chroma edge segment.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn filter_chroma(s: &mut [u16], o: usize, step: usize, along: usize, tc: i32, nfp: bool, nfq: bool, bd: u32) {
|
||||
fn filter_chroma<S: Sample>(s: &mut [S], o: usize, step: usize, along: usize, tc: i32, nfp: bool, nfq: bool, bd: u32) {
|
||||
let max = (1i32 << bd) - 1;
|
||||
for k in 0..4 {
|
||||
let base = o + k * along;
|
||||
let (p0, p1) = (s[base - step] as i32, s[base - 2 * step] as i32);
|
||||
let (q0, q1) = (s[base] as i32, s[base + step] as i32);
|
||||
let (p0, p1) = (s[base - step].to_i32(), s[base - 2 * step].to_i32());
|
||||
let (q0, q1) = (s[base].to_i32(), s[base + step].to_i32());
|
||||
let delta = ((((q0 - p0) << 2) + p1 - q1 + 4) >> 3).clamp(-tc, tc);
|
||||
if !nfp {
|
||||
s[base - step] = (p0 + delta).clamp(0, max) as u16;
|
||||
s[base - step] = S::from_i32((p0 + delta).clamp(0, max));
|
||||
}
|
||||
if !nfq {
|
||||
s[base] = (q0 - delta).clamp(0, max) as u16;
|
||||
s[base] = S::from_i32((q0 - delta).clamp(0, max));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// SAO of CTB row `r` from the deblocked planes into new row buffers (luma, Cb, Cr).
|
||||
fn sao_row(pic: &PicState, r: usize) -> [Vec<u16>; 3] {
|
||||
fn sao_row<S: Sample>(pic: &PicState<S>, r: usize) -> [Vec<S>; 3] {
|
||||
let ctb = 1usize << pic.log2_ctb;
|
||||
let mut out: [Vec<u16>; 3] = [Vec::new(), Vec::new(), Vec::new()];
|
||||
let mut out: [Vec<S>; 3] = [Vec::new(), Vec::new(), Vec::new()];
|
||||
for c in 0..3 {
|
||||
let (w, h, cs) = if c == 0 { (pic.width, pic.height, ctb) } else { (pic.cwidth, pic.cheight, ctb / 2) };
|
||||
let y0 = r * cs;
|
||||
@@ -388,8 +388,8 @@ fn sao_row(pic: &PicState, r: usize) -> [Vec<u16>; 3] {
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn sao_ctb(
|
||||
pic: &PicState,
|
||||
fn sao_ctb<S: Sample>(
|
||||
pic: &PicState<S>,
|
||||
c: usize,
|
||||
rs: usize,
|
||||
x0: usize,
|
||||
@@ -399,14 +399,14 @@ fn sao_ctb(
|
||||
w: usize,
|
||||
h: usize,
|
||||
p: &crate::slicedec::SaoParams,
|
||||
dst: &mut [u16],
|
||||
dst: &mut [S],
|
||||
) {
|
||||
let bd = if c == 0 { pic.bd_y } else { pic.bd_c };
|
||||
let max = (1i32 << bd) - 1;
|
||||
let sc = if c == 0 { 0 } else { 1 };
|
||||
let offs = p.offsets[c].map(|v| v as i32);
|
||||
// samples of CUs that must not be modified
|
||||
let nofilter = |pic: &PicState, x: usize, y: usize| pic.blk[((y << sc) >> 2) * pic.w4 + ((x << sc) >> 2)].flags & F_NOFILTER != 0;
|
||||
let nofilter = |pic: &PicState<S>, x: usize, y: usize| pic.blk[((y << sc) >> 2) * pic.w4 + ((x << sc) >> 2)].flags & F_NOFILTER != 0;
|
||||
let any_nofilter = {
|
||||
let mut a = false;
|
||||
for by in ((y0 << sc) >> 2)..(((y1 << sc) + 3) >> 2).min(pic.h4) {
|
||||
@@ -432,8 +432,8 @@ fn sao_ctb(
|
||||
if nofilter(pic, x, y) {
|
||||
continue;
|
||||
}
|
||||
let v = pic.planes[c][y * w + x] as i32;
|
||||
dst[(y - y0) * w + x] = (v + table[(v >> shift) as usize]).clamp(0, max) as u16;
|
||||
let v = pic.planes[c][y * w + x].to_i32();
|
||||
dst[(y - y0) * w + x] = S::from_i32((v + table[(v >> shift) as usize]).clamp(0, max));
|
||||
}
|
||||
}
|
||||
return;
|
||||
@@ -450,7 +450,7 @@ fn sao_ctb(
|
||||
let cur_slice = pic.ctb_slice[rs];
|
||||
let lf_cur = pic.slices[cur_slice as usize].lf_across;
|
||||
let cur_ts = pic.layout.rs_to_ts[rs];
|
||||
let cross_ok = |pic: &PicState, xn: usize, yn: usize| -> bool {
|
||||
let cross_ok = |pic: &PicState<S>, xn: usize, yn: usize| -> bool {
|
||||
let nrs = (yn >> s) * pic.wctb + (xn >> s);
|
||||
if nrs == rs {
|
||||
return true;
|
||||
@@ -478,12 +478,12 @@ fn sao_ctb(
|
||||
// Offset per (2 + sum of neighbour signs); edgeIdx 0 adds nothing.
|
||||
let by_sum = edge_map.map(|e| if e == 0 { 0 } else { offs[e - 1] });
|
||||
// Any sample: neighbours outside the picture or in a CTB that may not be used leave it as is.
|
||||
let general = |x: usize, y: usize| -> Option<u16> {
|
||||
let general = |x: usize, y: usize| -> Option<S> {
|
||||
if any_nofilter && nofilter(pic, x, y) {
|
||||
return None;
|
||||
}
|
||||
let mut sum = 0i32;
|
||||
let v = pic.planes[c][y * w + x] as i32;
|
||||
let v = pic.planes[c][y * w + x].to_i32();
|
||||
for k in 0..2 {
|
||||
let xn = x as i32 + hp[k];
|
||||
let yn = y as i32 + vp[k];
|
||||
@@ -494,9 +494,9 @@ fn sao_ctb(
|
||||
if (xn < x0 || xn >= x1 || yn < y0 || yn >= y1) && !cross_ok(pic, xn, yn) {
|
||||
return None;
|
||||
}
|
||||
sum += (v - pic.planes[c][yn * w + xn] as i32).signum();
|
||||
sum += (v - pic.planes[c][yn * w + xn].to_i32()).signum();
|
||||
}
|
||||
Some((v + by_sum[(2 + sum) as usize]).clamp(0, max) as u16)
|
||||
Some(S::from_i32((v + by_sum[(2 + sum) as usize]).clamp(0, max)))
|
||||
};
|
||||
// Samples whose two neighbours lie inside this CTB need none of those checks: a tight loop
|
||||
// (same arithmetic) for them, the general path for the CTB border.
|
||||
@@ -535,14 +535,14 @@ const SAO_LANES: usize = 16;
|
||||
/// the sum of the two neighbour signs selects the offset (no table lookup), so the loop
|
||||
/// vectorises; bit-exact with the per-sample form.
|
||||
#[inline(always)]
|
||||
fn sao_edge_run(cur: &[u16], na: &[u16], nb: &[u16], out: &mut [u16], by_sum: &[i32; 5], max: i32) {
|
||||
fn sao_edge_run<S: Sample>(cur: &[S], na: &[S], nb: &[S], out: &mut [S], by_sum: &[i32; 5], max: i32) {
|
||||
let o = by_sum.map(|v| v as i16);
|
||||
let max = max as i16;
|
||||
let lane = |v: i16, a: i16, b: i16| -> u16 {
|
||||
let lane = |v: i16, a: i16, b: i16| -> S {
|
||||
let s = ((v > a) as i16 - (v < a) as i16) + ((v > b) as i16 - (v < b) as i16);
|
||||
let m = |k: i16| -((s == k) as i16);
|
||||
let off = (o[0] & m(-2)) | (o[1] & m(-1)) | (o[3] & m(1)) | (o[4] & m(2));
|
||||
(v + off).max(0).min(max) as u16
|
||||
S::from_i32((v + off).max(0).min(max) as i32)
|
||||
};
|
||||
let n = cur.len();
|
||||
let full = n - n % SAO_LANES;
|
||||
@@ -551,36 +551,36 @@ fn sao_edge_run(cur: &[u16], na: &[u16], nb: &[u16], out: &mut [u16], by_sum: &[
|
||||
return;
|
||||
};
|
||||
for l in 0..SAO_LANES {
|
||||
d[l] = lane(c[l] as i16, a[l] as i16, b[l] as i16);
|
||||
d[l] = lane(c[l].to_i16(), a[l].to_i16(), b[l].to_i16());
|
||||
}
|
||||
}
|
||||
for i in full..n {
|
||||
out[i] = lane(cur[i] as i16, na[i] as i16, nb[i] as i16);
|
||||
out[i] = lane(cur[i].to_i16(), na[i].to_i16(), nb[i].to_i16());
|
||||
}
|
||||
}
|
||||
|
||||
/// `SAO_LANES` samples from `i`, if all present.
|
||||
#[inline(always)]
|
||||
fn lanes(s: &[u16], i: usize) -> Option<&[u16; SAO_LANES]> {
|
||||
fn lanes<S>(s: &[S], i: usize) -> Option<&[S; SAO_LANES]> {
|
||||
s.get(i..)?.first_chunk()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn lanes_mut(s: &mut [u16], i: usize) -> Option<&mut [u16; SAO_LANES]> {
|
||||
fn lanes_mut<S>(s: &mut [S], i: usize) -> Option<&mut [S; SAO_LANES]> {
|
||||
s.get_mut(i..)?.first_chunk_mut()
|
||||
}
|
||||
|
||||
/// Band offset of a run of samples: the band index relative to `band_pos` selects one of the
|
||||
/// four offsets per lane.
|
||||
#[inline(always)]
|
||||
fn sao_band_run(src: &[u16], out: &mut [u16], shift: u32, (band_pos, offs): (i32, [i32; 4]), max: i32) {
|
||||
fn sao_band_run<S: Sample>(src: &[S], out: &mut [S], shift: u32, (band_pos, offs): (i32, [i32; 4]), max: i32) {
|
||||
let o = offs.map(|v| v as i16);
|
||||
let (bp, max) = (band_pos as i16, max as i16);
|
||||
let lane = |v: i16| -> u16 {
|
||||
let lane = |v: i16| -> S {
|
||||
let k = ((v >> shift) - bp) & 31;
|
||||
let m = |j: i16| -((k == j) as i16);
|
||||
let off = (o[0] & m(0)) | (o[1] & m(1)) | (o[2] & m(2)) | (o[3] & m(3));
|
||||
(v + off).max(0).min(max) as u16
|
||||
S::from_i32((v + off).max(0).min(max) as i32)
|
||||
};
|
||||
let n = src.len();
|
||||
let full = n - n % SAO_LANES;
|
||||
@@ -589,16 +589,16 @@ fn sao_band_run(src: &[u16], out: &mut [u16], shift: u32, (band_pos, offs): (i32
|
||||
return;
|
||||
};
|
||||
for l in 0..SAO_LANES {
|
||||
d[l] = lane(c[l] as i16);
|
||||
d[l] = lane(c[l].to_i16());
|
||||
}
|
||||
}
|
||||
for i in full..n {
|
||||
out[i] = lane(src[i] as i16);
|
||||
out[i] = lane(src[i].to_i16());
|
||||
}
|
||||
}
|
||||
|
||||
/// Publish CTB row `r` (final samples + compressed motion) into the frame.
|
||||
fn publish_row(pic: &mut PicState, r: usize) {
|
||||
fn publish_row<S: Sample>(pic: &mut PicState<S>, r: usize) {
|
||||
let ctb = 1usize << pic.log2_ctb;
|
||||
let (y0, y1) = (r * ctb, ((r + 1) * ctb).min(pic.height));
|
||||
let (c0, c1) = (y0 / 2, y1.div_ceil(2));
|
||||
|
||||
+286
-88
@@ -1,6 +1,6 @@
|
||||
//! Fractional sample interpolation (8.5.3.3.3) and weighted sample prediction (8.5.3.3.4).
|
||||
|
||||
use crate::picture::Frame;
|
||||
use crate::picture::{Frame, Sample};
|
||||
use crate::spec_tables::{CHROMA_FILTER, LUMA_FILTER};
|
||||
|
||||
/// Max prediction block size + filter margin.
|
||||
@@ -18,18 +18,49 @@ impl Default for McScratch {
|
||||
}
|
||||
}
|
||||
|
||||
/// `out[j] = (sum_k f[k] * src[j + k * step]) >> shift` for `W` outputs: taps outer, outputs
|
||||
/// inner, so each tap is one vector multiply-add across the row (exact integer sums).
|
||||
/// A sample the interpolation filters read: reference samples (`u8` / `u16`) or 16-bit window
|
||||
/// and first-pass values.
|
||||
trait Px: Copy {
|
||||
fn w32(self) -> i32;
|
||||
fn w16(self) -> i16;
|
||||
}
|
||||
|
||||
impl Px for i16 {
|
||||
#[inline(always)]
|
||||
fn w32(self) -> i32 {
|
||||
self as i32
|
||||
}
|
||||
#[inline(always)]
|
||||
fn w16(self) -> i16 {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Sample> Px for S {
|
||||
#[inline(always)]
|
||||
fn w32(self) -> i32 {
|
||||
self.to_i32()
|
||||
}
|
||||
#[inline(always)]
|
||||
fn w16(self) -> i16 {
|
||||
self.to_i16()
|
||||
}
|
||||
}
|
||||
|
||||
/// `out[j] = (sum_k f[k] * tap(k)[j]) >> shift` for `W` outputs: taps outer, outputs inner, so
|
||||
/// each tap is one vector multiply-add across the row (exact integer sums). `tap(k)` starts at
|
||||
/// the samples under filter tap k: the row shifted by k (horizontal) or the k-th row (vertical);
|
||||
/// a function rather than an array of slices so nothing goes through the stack.
|
||||
#[inline(always)]
|
||||
fn filt_w<const T: usize, const W: usize>(src: &[i16], step: usize, f: &[i8; T], shift: u32, out: &mut [i16]) {
|
||||
fn filt_w<'a, P: Px + 'a, const T: usize, const W: usize>(tap: impl Fn(usize) -> Option<&'a [P]>, f: &[i8; T], shift: u32, out: &mut [i16]) {
|
||||
let mut acc = [0i32; W];
|
||||
for k in 0..T {
|
||||
let c = f[k] as i32;
|
||||
let Some(p) = src.get(k * step..).and_then(|s| s.first_chunk::<W>()) else {
|
||||
let Some(p) = tap(k).and_then(|s| s.first_chunk::<W>()) else {
|
||||
return;
|
||||
};
|
||||
for j in 0..W {
|
||||
acc[j] += c * p[j] as i32;
|
||||
acc[j] += c * p[j].w32();
|
||||
}
|
||||
}
|
||||
let Some(o) = out.first_chunk_mut::<W>() else {
|
||||
@@ -45,15 +76,15 @@ fn filt_w<const T: usize, const W: usize>(src: &[i16], step: usize, f: &[i8; T],
|
||||
/// luma or chroma filter, 8.5.3.3.3.1 / 8.5.3.3.3.2), so it fits in an `i16`, and wrapping sums
|
||||
/// are exact whenever the final value fits. Twice the lanes of [`filt_w`] per vector.
|
||||
#[inline(always)]
|
||||
fn filt16_w<const T: usize, const W: usize>(src: &[i16], step: usize, f: &[i8; T], out: &mut [i16]) {
|
||||
fn filt16_w<'a, P: Px + 'a, const T: usize, const W: usize>(tap: impl Fn(usize) -> Option<&'a [P]>, f: &[i8; T], out: &mut [i16]) {
|
||||
let mut acc = [0i16; W];
|
||||
for k in 0..T {
|
||||
let c = f[k] as i16;
|
||||
let Some(p) = src.get(k * step..).and_then(|s| s.first_chunk::<W>()) else {
|
||||
let Some(p) = tap(k).and_then(|s| s.first_chunk::<W>()) else {
|
||||
return;
|
||||
};
|
||||
for j in 0..W {
|
||||
acc[j] = acc[j].wrapping_add(c.wrapping_mul(p[j]));
|
||||
acc[j] = acc[j].wrapping_add(c.wrapping_mul(p[j].w16()));
|
||||
}
|
||||
}
|
||||
if let Some(o) = out.first_chunk_mut::<W>() {
|
||||
@@ -64,45 +95,59 @@ fn filt16_w<const T: usize, const W: usize>(src: &[i16], step: usize, f: &[i8; T
|
||||
/// [`filt_w`] for a run of `w` outputs (prediction block widths are fixed sizes); in 16-bit lanes
|
||||
/// ([`filt16_w`]) when `narrow` (a first pass over 8-bit samples).
|
||||
#[inline(always)]
|
||||
fn filt<const T: usize>(src: &[i16], step: usize, f: &[i8; T], shift: u32, out: &mut [i16], w: usize, narrow: bool) {
|
||||
fn filt<'a, P: Px + 'a, const T: usize>(tap: impl Fn(usize) -> Option<&'a [P]> + Copy, f: &[i8; T], shift: u32, out: &mut [i16], w: usize, narrow: bool) {
|
||||
if narrow {
|
||||
match w {
|
||||
4 => return filt16_w::<T, 4>(src, step, f, out),
|
||||
8 => return filt16_w::<T, 8>(src, step, f, out),
|
||||
12 => return filt16_w::<T, 12>(src, step, f, out),
|
||||
16 => return filt16_w::<T, 16>(src, step, f, out),
|
||||
24 => return filt16_w::<T, 24>(src, step, f, out),
|
||||
32 => return filt16_w::<T, 32>(src, step, f, out),
|
||||
48 => return filt16_w::<T, 48>(src, step, f, out),
|
||||
64 => return filt16_w::<T, 64>(src, step, f, out),
|
||||
4 => return filt16_w::<P, T, 4>(tap, f, out),
|
||||
8 => return filt16_w::<P, T, 8>(tap, f, out),
|
||||
12 => return filt16_w::<P, T, 12>(tap, f, out),
|
||||
16 => return filt16_w::<P, T, 16>(tap, f, out),
|
||||
24 => return filt16_w::<P, T, 24>(tap, f, out),
|
||||
32 => return filt16_w::<P, T, 32>(tap, f, out),
|
||||
48 => return filt16_w::<P, T, 48>(tap, f, out),
|
||||
64 => return filt16_w::<P, T, 64>(tap, f, out),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
match w {
|
||||
2 => filt_w::<T, 2>(src, step, f, shift, out),
|
||||
4 => filt_w::<T, 4>(src, step, f, shift, out),
|
||||
6 => filt_w::<T, 6>(src, step, f, shift, out),
|
||||
8 => filt_w::<T, 8>(src, step, f, shift, out),
|
||||
12 => filt_w::<T, 12>(src, step, f, shift, out),
|
||||
16 => filt_w::<T, 16>(src, step, f, shift, out),
|
||||
24 => filt_w::<T, 24>(src, step, f, shift, out),
|
||||
32 => filt_w::<T, 32>(src, step, f, shift, out),
|
||||
48 => filt_w::<T, 48>(src, step, f, shift, out),
|
||||
64 => filt_w::<T, 64>(src, step, f, shift, out),
|
||||
2 => filt_w::<P, T, 2>(tap, f, shift, out),
|
||||
4 => filt_w::<P, T, 4>(tap, f, shift, out),
|
||||
6 => filt_w::<P, T, 6>(tap, f, shift, out),
|
||||
8 => filt_w::<P, T, 8>(tap, f, shift, out),
|
||||
12 => filt_w::<P, T, 12>(tap, f, shift, out),
|
||||
16 => filt_w::<P, T, 16>(tap, f, shift, out),
|
||||
24 => filt_w::<P, T, 24>(tap, f, shift, out),
|
||||
32 => filt_w::<P, T, 32>(tap, f, shift, out),
|
||||
48 => filt_w::<P, T, 48>(tap, f, shift, out),
|
||||
64 => filt_w::<P, T, 64>(tap, f, shift, out),
|
||||
_ => {
|
||||
for (j, o) in out[..w].iter_mut().enumerate() {
|
||||
let s: i32 = (0..T).map(|k| f[k] as i32 * src[j + k * step] as i32).sum();
|
||||
for (j, o) in out.iter_mut().take(w).enumerate() {
|
||||
let s: i32 = (0..T).map(|k| f[k] as i32 * tap(k).and_then(|s| s.get(j)).map_or(0, |p| p.w32())).sum();
|
||||
*o = (s >> shift) as i16;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Separable sub-sample interpolation of a w x h block from `win` (stride WIN, the block's
|
||||
/// top-left sample at (m, m) for a T-tap filter with m = T / 2 - 1).
|
||||
/// The taps of a horizontal filter along `row`: the row shifted by k samples.
|
||||
#[inline(always)]
|
||||
fn h_taps<'a, P>(row: &'a [P]) -> impl Fn(usize) -> Option<&'a [P]> + Copy {
|
||||
move |k| row.get(k..)
|
||||
}
|
||||
|
||||
/// The taps of a vertical filter: row `r + k` from sample `x` on.
|
||||
#[inline(always)]
|
||||
fn v_taps<'a, P>(rows: &'a [&'a [P]], r: usize, x: usize) -> impl Fn(usize) -> Option<&'a [P]> + Copy {
|
||||
move |k| rows.get(r + k).and_then(|row| row.get(x..))
|
||||
}
|
||||
|
||||
/// Separable sub-sample interpolation of a w x h block from the window `rows` (h + T - 1 rows of
|
||||
/// at least w + T - 1 samples; the block's top-left sample at (m, m) for a T-tap filter with
|
||||
/// m = T / 2 - 1). The window rows are the reference picture's own lines when the window lies
|
||||
/// inside it horizontally, or lines of an edge-replicated copy.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn interpolate<const T: usize>(
|
||||
win: &[i16],
|
||||
fn interpolate<P: Px, const T: usize>(
|
||||
rows: &[&[P]],
|
||||
tmp: &mut [i16],
|
||||
fx: usize,
|
||||
fy: usize,
|
||||
@@ -117,81 +162,122 @@ fn interpolate<const T: usize>(
|
||||
let m = T / 2 - 1;
|
||||
// 8-bit samples: first passes in 16-bit lanes (see filt16_w)
|
||||
let narrow = shift1 == 0 && shift3 == 6;
|
||||
let out_rows = out.chunks_mut(w).take(h);
|
||||
if fx == 0 && fy == 0 {
|
||||
for r in 0..h {
|
||||
for (o, &s) in out[r * w..r * w + w].iter_mut().zip(&win[(r + m) * WIN + m..]) {
|
||||
*o = s << shift3;
|
||||
for (r, o) in out_rows.enumerate() {
|
||||
let Some(src) = rows.get(r + m).and_then(|row| row.get(m..)) else { return };
|
||||
for (o, &s) in o.iter_mut().zip(src) {
|
||||
*o = s.w16() << shift3;
|
||||
}
|
||||
}
|
||||
} else if fy == 0 {
|
||||
for r in 0..h {
|
||||
filt(&win[(r + m) * WIN..], 1, hf, shift1, &mut out[r * w..], w, narrow);
|
||||
for (r, o) in out_rows.enumerate() {
|
||||
let Some(row) = rows.get(r + m) else { return };
|
||||
filt(h_taps(row), hf, shift1, o, w, narrow);
|
||||
}
|
||||
} else if fx == 0 {
|
||||
for r in 0..h {
|
||||
filt(&win[r * WIN + m..], WIN, vf, shift1, &mut out[r * w..], w, narrow);
|
||||
for (r, o) in out_rows.enumerate() {
|
||||
filt(v_taps(rows, r, m), vf, shift1, o, w, narrow);
|
||||
}
|
||||
} else {
|
||||
for r in 0..h + T - 1 {
|
||||
filt(&win[r * WIN..], 1, hf, shift1, &mut tmp[r * WIN..], w, narrow);
|
||||
for (row, t) in rows.iter().take(h + T - 1).zip(tmp.chunks_mut(WIN)) {
|
||||
filt(h_taps(row), hf, shift1, t, w, narrow);
|
||||
}
|
||||
for r in 0..h {
|
||||
filt(&tmp[r * WIN..], WIN, vf, 6, &mut out[r * w..], w, false);
|
||||
let mut trows: [&[i16]; WIN] = [&[]; WIN];
|
||||
for (t, row) in trows.iter_mut().zip(tmp.chunks(WIN)).take(h + T - 1) {
|
||||
*t = row;
|
||||
}
|
||||
for (r, o) in out_rows.enumerate() {
|
||||
filt(v_taps(&trows, r, 0), vf, 6, o, w, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Prediction samples of a w x h block of plane `c` whose filter window (`T`-tap filters) starts
|
||||
/// at (x0, y0): read straight from the reference lines when the window is inside the picture
|
||||
/// horizontally, else from an edge-replicated copy.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn predict_block<S: Sample, const T: usize>(
|
||||
f: &Frame,
|
||||
c: usize,
|
||||
(x0, y0): (i32, i32),
|
||||
(fx, fy): (usize, usize),
|
||||
filters: (&[i8; T], &[i8; T]),
|
||||
(w, h): (usize, usize),
|
||||
(shift1, shift3): (u32, u32),
|
||||
out: &mut [i16],
|
||||
sc: &mut McScratch,
|
||||
) {
|
||||
let (ww, wh) = (w + T - 1, h + T - 1);
|
||||
let McScratch { win, tmp } = sc;
|
||||
let mut rows: [&[S]; WIN] = [&[]; WIN];
|
||||
if let Some(rows) = rows.get_mut(..wh)
|
||||
&& f.window_rows(c, x0, y0, ww, rows)
|
||||
{
|
||||
interpolate::<S, T>(rows, tmp, fx, fy, filters.0, filters.1, w, h, shift1, shift3, out);
|
||||
return;
|
||||
}
|
||||
if c == 0 {
|
||||
f.luma_window::<S>(x0, y0, ww, wh, win, WIN);
|
||||
} else {
|
||||
f.chroma_window::<S>(c - 1, x0, y0, ww, wh, win, WIN);
|
||||
}
|
||||
let mut wrows: [&[i16]; WIN] = [&[]; WIN];
|
||||
for (r, row) in wrows.iter_mut().zip(win.chunks(WIN)).take(wh) {
|
||||
*r = row;
|
||||
}
|
||||
interpolate::<i16, T>(&wrows[..wh.min(WIN)], tmp, fx, fy, filters.0, filters.1, w, h, shift1, shift3, out);
|
||||
}
|
||||
|
||||
/// Luma prediction samples (14-bit intermediate precision) of a w x h block at (x, y) displaced by the
|
||||
/// quarter-sample motion vector `mv`, into `out` (stride w).
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn mc_luma(f: &Frame, x: i32, y: i32, mv: [i16; 2], w: usize, h: usize, out: &mut [i16], sc: &mut McScratch) {
|
||||
pub fn mc_luma<S: Sample>(f: &Frame, x: i32, y: i32, mv: [i16; 2], w: usize, h: usize, out: &mut [i16], sc: &mut McScratch) {
|
||||
let bd = f.bit_depth;
|
||||
let (fx, fy) = ((mv[0] & 3) as usize, (mv[1] & 3) as usize);
|
||||
let xi = x + (mv[0] as i32 >> 2);
|
||||
let yi = y + (mv[1] as i32 >> 2);
|
||||
let shift1 = bd.min(12) - 8;
|
||||
let shift3 = 14 - bd;
|
||||
f.luma_window(xi - 3, yi - 3, w + 7, h + 7, &mut sc.win, WIN);
|
||||
interpolate::<8>(&sc.win, &mut sc.tmp, fx, fy, &LUMA_FILTER[fx], &LUMA_FILTER[fy], w, h, shift1, shift3, out);
|
||||
predict_block::<S, 8>(f, 0, (xi - 3, yi - 3), (fx, fy), (&LUMA_FILTER[fx], &LUMA_FILTER[fy]), (w, h), (shift1, shift3), out, sc);
|
||||
}
|
||||
|
||||
/// Chroma prediction samples of a w x h chroma block at chroma position (x, y) with a 1/8-sample
|
||||
/// vector (4:2:0).
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn mc_chroma(f: &Frame, c: usize, x: i32, y: i32, mv: [i16; 2], w: usize, h: usize, out: &mut [i16], sc: &mut McScratch) {
|
||||
pub fn mc_chroma<S: Sample>(f: &Frame, c: usize, x: i32, y: i32, mv: [i16; 2], w: usize, h: usize, out: &mut [i16], sc: &mut McScratch) {
|
||||
let bd = f.bit_depth_c;
|
||||
let (fx, fy) = ((mv[0] & 7) as usize, (mv[1] & 7) as usize);
|
||||
let xi = x + (mv[0] as i32 >> 3);
|
||||
let yi = y + (mv[1] as i32 >> 3);
|
||||
let shift1 = bd.min(12) - 8;
|
||||
let shift3 = 14 - bd;
|
||||
f.chroma_window(c, xi - 1, yi - 1, w + 3, h + 3, &mut sc.win, WIN);
|
||||
interpolate::<4>(&sc.win, &mut sc.tmp, fx, fy, &CHROMA_FILTER[fx], &CHROMA_FILTER[fy], w, h, shift1, shift3, out);
|
||||
predict_block::<S, 4>(f, c + 1, (xi - 1, yi - 1), (fx, fy), (&CHROMA_FILTER[fx], &CHROMA_FILTER[fy]), (w, h), (shift1, shift3), out, sc);
|
||||
}
|
||||
|
||||
/// Calls `$f::<W>(args)` with `W` the prediction block width `$w` as a constant (block widths are
|
||||
/// Calls `$f::<_, W>(args)` with `W` the prediction block width `$w` as a constant (block widths are
|
||||
/// fixed sizes; a constant width lets the row loops vectorise even for 4- and 8-sample rows), or
|
||||
/// `$f::<0>` for any other width.
|
||||
macro_rules! by_width {
|
||||
($w:expr, $f:ident($($a:expr),*)) => {
|
||||
match $w {
|
||||
2 => $f::<2>($($a),*),
|
||||
4 => $f::<4>($($a),*),
|
||||
6 => $f::<6>($($a),*),
|
||||
8 => $f::<8>($($a),*),
|
||||
12 => $f::<12>($($a),*),
|
||||
16 => $f::<16>($($a),*),
|
||||
24 => $f::<24>($($a),*),
|
||||
32 => $f::<32>($($a),*),
|
||||
48 => $f::<48>($($a),*),
|
||||
64 => $f::<64>($($a),*),
|
||||
_ => $f::<0>($($a),*),
|
||||
2 => $f::<_, 2>($($a),*),
|
||||
4 => $f::<_, 4>($($a),*),
|
||||
6 => $f::<_, 6>($($a),*),
|
||||
8 => $f::<_, 8>($($a),*),
|
||||
12 => $f::<_, 12>($($a),*),
|
||||
16 => $f::<_, 16>($($a),*),
|
||||
24 => $f::<_, 24>($($a),*),
|
||||
32 => $f::<_, 32>($($a),*),
|
||||
48 => $f::<_, 48>($($a),*),
|
||||
64 => $f::<_, 64>($($a),*),
|
||||
_ => $f::<_, 0>($($a),*),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Default weighted prediction, one list (8-262).
|
||||
pub fn put_uni(p: &[i16], w: usize, h: usize, bd: u32, dst: &mut [u16], ds: usize) {
|
||||
pub fn put_uni<S: Sample>(p: &[i16], w: usize, h: usize, bd: u32, dst: &mut [S], ds: usize) {
|
||||
by_width!(w, put_uni_w(p, w, h, bd, dst, ds))
|
||||
}
|
||||
|
||||
@@ -202,7 +288,7 @@ pub fn put_uni(p: &[i16], w: usize, h: usize, bd: u32, dst: &mut [u16], ds: usiz
|
||||
/// in an `i16` and the sums are exact. SSE2 clips 16-bit lanes in one instruction each, 32-bit lanes
|
||||
/// in four.
|
||||
#[inline(always)]
|
||||
fn put_uni_w<const W: usize>(p: &[i16], w: usize, h: usize, bd: u32, dst: &mut [u16], ds: usize) {
|
||||
fn put_uni_w<S: Sample, const W: usize>(p: &[i16], w: usize, h: usize, bd: u32, dst: &mut [S], ds: usize) {
|
||||
let w = if W > 0 { W } else { w };
|
||||
let shift = 14 - bd;
|
||||
let off: i16 = if shift > 0 { 1 << (shift - 1) } else { 0 };
|
||||
@@ -210,13 +296,13 @@ fn put_uni_w<const W: usize>(p: &[i16], w: usize, h: usize, bd: u32, dst: &mut [
|
||||
// Rows as slices and min / max clipping (`clamp` asserts its bounds) so the loop vectorises.
|
||||
for (d, s) in dst.chunks_mut(ds).zip(p[..w * h].chunks(w)) {
|
||||
for (d, &s) in d[..w].iter_mut().zip(s) {
|
||||
*d = (s.wrapping_add(off) >> shift).max(0).min(max) as u16;
|
||||
*d = S::from_i32((s.wrapping_add(off) >> shift).max(0).min(max) as i32);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Default weighted prediction, bi-prediction (8-263).
|
||||
pub fn put_bi(p0: &[i16], p1: &[i16], w: usize, h: usize, bd: u32, dst: &mut [u16], ds: usize) {
|
||||
pub fn put_bi<S: Sample>(p0: &[i16], p1: &[i16], w: usize, h: usize, bd: u32, dst: &mut [S], ds: usize) {
|
||||
by_width!(w, put_bi_w(p0, p1, w, h, bd, dst, ds))
|
||||
}
|
||||
|
||||
@@ -226,7 +312,7 @@ pub fn put_bi(p0: &[i16], p1: &[i16], w: usize, h: usize, bd: u32, dst: &mut [u1
|
||||
/// `((a >> 1) + (b >> 1) + (a & b & 1) + off / 2) >> (shift - 1)` (the two dropped low bits only
|
||||
/// carry when both are set), and `(a >> 1) + (b >> 1)` fits in an `i16` (see [`put_uni_w`]).
|
||||
#[inline(always)]
|
||||
fn put_bi_w<const W: usize>(p0: &[i16], p1: &[i16], w: usize, h: usize, bd: u32, dst: &mut [u16], ds: usize) {
|
||||
fn put_bi_w<S: Sample, const W: usize>(p0: &[i16], p1: &[i16], w: usize, h: usize, bd: u32, dst: &mut [S], ds: usize) {
|
||||
let w = if W > 0 { W } else { w };
|
||||
let shift = 15 - bd; // >= 3 (bd <= 12)
|
||||
let half_off: i16 = 1 << (shift - 2);
|
||||
@@ -234,31 +320,44 @@ fn put_bi_w<const W: usize>(p0: &[i16], p1: &[i16], w: usize, h: usize, bd: u32,
|
||||
for ((d, a), b) in dst.chunks_mut(ds).zip(p0[..w * h].chunks(w)).zip(p1[..w * h].chunks(w)) {
|
||||
for ((d, &a), &b) in d[..w].iter_mut().zip(a).zip(b) {
|
||||
let v = (a >> 1).wrapping_add(b >> 1).wrapping_add(a & b & 1).wrapping_add(half_off) >> (shift - 1);
|
||||
*d = v.max(0).min(max) as u16;
|
||||
*d = S::from_i32(v.max(0).min(max) as i32);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Explicit weighted prediction, one list (8-265). `o` is the offset already scaled to the bit depth.
|
||||
pub fn put_weighted_uni(p: &[i16], w: usize, h: usize, bd: u32, log2wd: u32, wt: i32, o: i32, dst: &mut [u16], ds: usize) {
|
||||
pub fn put_weighted_uni<S: Sample>(p: &[i16], w: usize, h: usize, bd: u32, log2wd: u32, wt: i32, o: i32, dst: &mut [S], ds: usize) {
|
||||
let max = (1i32 << bd) - 1;
|
||||
for r in 0..h {
|
||||
for c in 0..w {
|
||||
let v = p[r * w + c] as i32 * wt;
|
||||
let v = if log2wd >= 1 { ((v + (1 << (log2wd - 1))) >> log2wd) + o } else { v + o };
|
||||
dst[r * ds + c] = v.max(0).min(max) as u16;
|
||||
dst[r * ds + c] = S::from_i32(v.max(0).min(max));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Explicit weighted bi-prediction (8-266).
|
||||
pub fn put_weighted_bi(p0: &[i16], p1: &[i16], w: usize, h: usize, bd: u32, log2wd: u32, w0: i32, w1: i32, o0: i32, o1: i32, dst: &mut [u16], ds: usize) {
|
||||
pub fn put_weighted_bi<S: Sample>(
|
||||
p0: &[i16],
|
||||
p1: &[i16],
|
||||
w: usize,
|
||||
h: usize,
|
||||
bd: u32,
|
||||
log2wd: u32,
|
||||
w0: i32,
|
||||
w1: i32,
|
||||
o0: i32,
|
||||
o1: i32,
|
||||
dst: &mut [S],
|
||||
ds: usize,
|
||||
) {
|
||||
let max = (1i32 << bd) - 1;
|
||||
for r in 0..h {
|
||||
for c in 0..w {
|
||||
let i = r * w + c;
|
||||
let v = (p0[i] as i32 * w0 + p1[i] as i32 * w1 + ((o0 + o1 + 1) << log2wd)) >> (log2wd + 1);
|
||||
dst[r * ds + c] = v.max(0).min(max) as u16;
|
||||
dst[r * ds + c] = S::from_i32(v.max(0).min(max));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -319,6 +418,50 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// Explicit weighted prediction gives exactly 8-265 / 8-266 for every block width, bit depth,
|
||||
/// denominator and the extreme weights and offsets, and with the default weights (2^denom,
|
||||
/// offset 0) exactly the default weighted prediction (8-262 / 8-263) that replaces it then.
|
||||
#[test]
|
||||
fn explicit_weighted_prediction_matches_the_formulas() {
|
||||
let mut rng = Rng(0x7f4a_7c15_9e37_79b9);
|
||||
for bd in [8u32, 10, 12] {
|
||||
let max = (1i32 << bd) - 1;
|
||||
for &w in &WIDTHS {
|
||||
for _ in 0..40 {
|
||||
let h = 2;
|
||||
let p0: Vec<i16> = (0..w * h).map(|_| rng.range(-10_240, 26_624) as i16).collect();
|
||||
let p1: Vec<i16> = (0..w * h).map(|_| rng.range(-10_240, 26_624) as i16).collect();
|
||||
let log2wd = rng.range(0, 7) as u32 + 14 - bd;
|
||||
let wt = |rng: &mut Rng| [-128, 255, rng.range(-128, 255)][rng.range(0, 2) as usize];
|
||||
let (w0, w1) = (wt(&mut rng), wt(&mut rng));
|
||||
let off = |rng: &mut Rng| [-128, 127, rng.range(-128, 127)][rng.range(0, 2) as usize] << (bd - 8);
|
||||
let (o0, o1) = (off(&mut rng), off(&mut rng));
|
||||
let ds = w + 3;
|
||||
let (mut uni, mut bi) = (vec![0u16; ds * h], vec![0u16; ds * h]);
|
||||
put_weighted_uni(&p0, w, h, bd, log2wd, w0, o0, &mut uni, ds);
|
||||
put_weighted_bi(&p0, &p1, w, h, bd, log2wd, w0, w1, o0, o1, &mut bi, ds);
|
||||
let denom = log2wd - (14 - bd);
|
||||
let (mut duni, mut dbi) = (vec![0u16; ds * h], vec![0u16; ds * h]);
|
||||
put_weighted_uni(&p0, w, h, bd, log2wd, 1 << denom, 0, &mut duni, ds);
|
||||
put_weighted_bi(&p0, &p1, w, h, bd, log2wd, 1 << denom, 1 << denom, 0, 0, &mut dbi, ds);
|
||||
let (mut uni0, mut bi0) = (vec![0u16; ds * h], vec![0u16; ds * h]);
|
||||
put_uni(&p0, w, h, bd, &mut uni0, ds);
|
||||
put_bi(&p0, &p1, w, h, bd, &mut bi0, ds);
|
||||
assert_eq!((duni, dbi), (uni0, bi0), "default weights bd {bd} w {w} denom {denom}");
|
||||
for r in 0..h {
|
||||
for c in 0..w {
|
||||
let (a, b) = (p0[r * w + c] as i32, p1[r * w + c] as i32);
|
||||
let v = if log2wd >= 1 { ((a * w0 + (1 << (log2wd - 1))) >> log2wd) + o0 } else { a * w0 + o0 };
|
||||
assert_eq!(uni[r * ds + c] as i32, v.clamp(0, max), "uni bd {bd} w {w}");
|
||||
let v = (a * w0 + b * w1 + ((o0 + o1 + 1) << log2wd)) >> (log2wd + 1);
|
||||
assert_eq!(bi[r * ds + c] as i32, v.clamp(0, max), "bi bd {bd} w {w}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// First passes over 8-bit samples in 16-bit lanes equal the 32-bit sums, including the
|
||||
/// extreme sample patterns of every filter.
|
||||
#[test]
|
||||
@@ -326,8 +469,9 @@ mod tests {
|
||||
let mut rng = Rng(0x2545_f491_4f6c_dd1d);
|
||||
fn check<const T: usize>(f: &[i8; T], src: &[i16], step: usize, w: usize) {
|
||||
let (mut a, mut b) = ([0i16; 64], [0i16; 64]);
|
||||
filt(src, step, f, 0, &mut a, w, true);
|
||||
filt(src, step, f, 0, &mut b, w, false);
|
||||
let taps = |k: usize| src.get(k * step..);
|
||||
filt(taps, f, 0, &mut a, w, true);
|
||||
filt(taps, f, 0, &mut b, w, false);
|
||||
assert_eq!(a[..w], b[..w], "filter {f:?} step {step} w {w}");
|
||||
}
|
||||
for &w in &WIDTHS {
|
||||
@@ -369,36 +513,90 @@ mod tests {
|
||||
Frame::from_planes(1, 0, &like, &y, &c, &c2)
|
||||
}
|
||||
|
||||
/// Interpolation from the reference lines in place gives the same prediction samples as from
|
||||
/// an edge-replicated copy of the window, for every fractional position, block size and bit
|
||||
/// depth, inside the picture and next to its edges (where in-place reads are used for windows
|
||||
/// that cross only the top or bottom edge).
|
||||
#[test]
|
||||
fn in_place_window_matches_the_copied_window() {
|
||||
fn check<S: Sample>(rng: &mut Rng, bd: u32) {
|
||||
let mut sc = McScratch::default();
|
||||
let f = frame(rng, 96, 64, bd);
|
||||
let (mut in_place, mut copied) = (0, 0);
|
||||
for _ in 0..2000 {
|
||||
let c = rng.range(0, 2) as usize;
|
||||
let (w, h) = ([4usize, 8, 12, 16, 24, 32][rng.range(0, 5) as usize], [4usize, 8, 16, 32][rng.range(0, 3) as usize]);
|
||||
let (w, h) = if c == 0 { (w, h) } else { (w / 2, h / 2) };
|
||||
let (x, y) = (rng.range(-12, 100), rng.range(-12, 70));
|
||||
let mv = [rng.range(-64, 64) as i16, rng.range(-64, 64) as i16];
|
||||
let mut a = vec![0i16; w * h];
|
||||
let mut b = vec![0i16; w * h];
|
||||
let (t, frac, unit) = if c == 0 { (8usize, 3, 2) } else { (4, 7, 3) };
|
||||
let (fx, fy) = ((mv[0] & frac) as usize, (mv[1] & frac) as usize);
|
||||
let (x0, y0) = (x + (mv[0] as i32 >> unit) - (t as i32 / 2 - 1), y + (mv[1] as i32 >> unit) - (t as i32 / 2 - 1));
|
||||
let (shift1, shift3) = (bd - 8, 14 - bd);
|
||||
let mut rows: Vec<&[S]> = vec![&[]; h + t - 1];
|
||||
if f.window_rows(c, x0, y0, w + t - 1, &mut rows) {
|
||||
in_place += 1;
|
||||
} else {
|
||||
copied += 1;
|
||||
}
|
||||
// reference: the copied window
|
||||
let mut win = vec![0i16; WIN * WIN];
|
||||
let mut tmp = vec![0i16; WIN * WIN];
|
||||
if c == 0 {
|
||||
f.luma_window::<S>(x0, y0, w + t - 1, h + t - 1, &mut win, WIN);
|
||||
} else {
|
||||
f.chroma_window::<S>(c - 1, x0, y0, w + t - 1, h + t - 1, &mut win, WIN);
|
||||
}
|
||||
let wrows: Vec<&[i16]> = win.chunks(WIN).take(h + t - 1).collect();
|
||||
if c == 0 {
|
||||
mc_luma::<S>(&f, x, y, mv, w, h, &mut a, &mut sc);
|
||||
interpolate::<i16, 8>(&wrows, &mut tmp, fx, fy, &LUMA_FILTER[fx], &LUMA_FILTER[fy], w, h, shift1, shift3, &mut b);
|
||||
} else {
|
||||
mc_chroma::<S>(&f, c - 1, x, y, mv, w, h, &mut a, &mut sc);
|
||||
interpolate::<i16, 4>(&wrows, &mut tmp, fx, fy, &CHROMA_FILTER[fx], &CHROMA_FILTER[fy], w, h, shift1, shift3, &mut b);
|
||||
}
|
||||
assert_eq!(a, b, "plane {c} bd {bd} at ({x}, {y}) mv {mv:?} {w}x{h}");
|
||||
}
|
||||
assert!(in_place > 500 && copied > 100, "{in_place} in place, {copied} copied");
|
||||
}
|
||||
let mut rng = Rng(0x5851_f42d_4c95_7f2d);
|
||||
check::<u8>(&mut rng, 8);
|
||||
check::<u16>(&mut rng, 10);
|
||||
}
|
||||
|
||||
/// The full-sample uni-prediction shortcut (`Frame::copy_block`) gives what interpolation and
|
||||
/// 8-262 give, inside the picture and across every edge.
|
||||
/// 8-262 give, inside the picture and across every edge (8-bit pictures stored as `u8`, 10-bit
|
||||
/// ones as `u16`).
|
||||
#[test]
|
||||
fn full_sample_copy_matches_interpolation() {
|
||||
let mut rng = Rng(0x0123_4567_89ab_cdef);
|
||||
let mut sc = McScratch::default();
|
||||
for bd in [8u32, 10] {
|
||||
let f = frame(&mut rng, 48, 40, bd);
|
||||
fn check<S: Sample>(rng: &mut Rng, bd: u32) {
|
||||
let mut sc = McScratch::default();
|
||||
let f = frame(rng, 48, 40, bd);
|
||||
for _ in 0..300 {
|
||||
let (w, h) = ([4usize, 8, 12, 16, 24, 32][rng.range(0, 5) as usize], [4usize, 8, 16][rng.range(0, 2) as usize]);
|
||||
let (x, y) = (rng.range(-40, 80), rng.range(-40, 70));
|
||||
let mut pred = vec![0i16; w * h];
|
||||
let (mut a, mut b) = (vec![0u16; w * h], vec![0u16; w * h]);
|
||||
inter_luma(&f, x, y, w, h, &mut pred, &mut sc);
|
||||
let (mut a, mut b) = (vec![S::default(); w * h], vec![S::default(); w * h]);
|
||||
mc_luma::<S>(&f, 0, 0, [(x * 4) as i16, (y * 4) as i16], w, h, &mut pred, &mut sc);
|
||||
put_uni(&pred, w, h, bd, &mut a, w);
|
||||
f.copy_block(0, x, y, w, h, &mut b, w);
|
||||
assert_eq!(a, b, "luma bd {bd} at ({x}, {y}) {w}x{h}");
|
||||
// (the window really was read: not all samples equal the default)
|
||||
assert!(a.iter().any(|&v| v != S::default()) || w * h < 8);
|
||||
for c in 1..3 {
|
||||
let (cw, ch) = (w / 2, h / 2);
|
||||
mc_chroma(&f, c - 1, 0, 0, [(x * 8) as i16, (y * 8) as i16], cw, ch, &mut pred, &mut sc);
|
||||
let (mut a, mut b) = (vec![0u16; cw * ch], vec![0u16; cw * ch]);
|
||||
mc_chroma::<S>(&f, c - 1, 0, 0, [(x * 8) as i16, (y * 8) as i16], cw, ch, &mut pred, &mut sc);
|
||||
let (mut a, mut b) = (vec![S::default(); cw * ch], vec![S::default(); cw * ch]);
|
||||
put_uni(&pred, cw, ch, bd, &mut a, cw);
|
||||
f.copy_block(c, x, y, cw, ch, &mut b, cw);
|
||||
assert_eq!(a, b, "chroma {c} bd {bd} at ({x}, {y}) {cw}x{ch}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn inter_luma(f: &Frame, x: i32, y: i32, w: usize, h: usize, out: &mut [i16], sc: &mut McScratch) {
|
||||
mc_luma(f, 0, 0, [(x * 4) as i16, (y * 4) as i16], w, h, out, sc);
|
||||
let mut rng = Rng(0x0123_4567_89ab_cdef);
|
||||
check::<u8>(&mut rng, 8);
|
||||
check::<u16>(&mut rng, 10);
|
||||
}
|
||||
}
|
||||
|
||||
+12
-10
@@ -1,6 +1,7 @@
|
||||
//! Intra sample prediction (8.4.4.2): reference substitution, filtering (incl. strong intra
|
||||
//! smoothing), planar, DC and the 33 angular modes.
|
||||
|
||||
use crate::picture::Sample;
|
||||
use crate::spec_tables::{INTRA_PRED_ANGLE, INV_ANGLE};
|
||||
|
||||
/// Reference samples of an nTbS block: `left[0]` = `top[0]` = p[-1][-1], `left[1 + y]` = p[-1][y],
|
||||
@@ -100,7 +101,7 @@ pub fn filter(r: &mut Refs, mode: u32, n: usize, strong_enabled: bool, is_luma:
|
||||
|
||||
/// Predict an n x n block with `mode` into `out` (stride `os`). `edge_filters`: DC / horizontal /
|
||||
/// vertical boundary smoothing (luma blocks smaller than 32x32).
|
||||
pub fn predict(r: &Refs, mode: u32, n: usize, edge_filters: bool, bit_depth: u32, out: &mut [u16], os: usize) {
|
||||
pub fn predict<S: Sample>(r: &Refs, mode: u32, n: usize, edge_filters: bool, bit_depth: u32, out: &mut [S], os: usize) {
|
||||
let max = (1i32 << bit_depth) - 1;
|
||||
match mode {
|
||||
0 => {
|
||||
@@ -113,7 +114,7 @@ pub fn predict(r: &Refs, mode: u32, n: usize, edge_filters: bool, bit_depth: u32
|
||||
for x in 0..n {
|
||||
let t = r.top[1 + x] as i32;
|
||||
let v = ((n - 1 - x) as i32 * l + (x as i32 + 1) * tr + (n - 1 - y) as i32 * t + (y as i32 + 1) * bl + n as i32) >> (log2 + 1);
|
||||
out[y * os + x] = v as u16;
|
||||
out[y * os + x] = S::from_i32(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,17 +122,18 @@ pub fn predict(r: &Refs, mode: u32, n: usize, edge_filters: bool, bit_depth: u32
|
||||
let log2 = n.trailing_zeros();
|
||||
let sum: u32 = r.top[1..=n].iter().map(|&v| v as u32).sum::<u32>() + r.left[1..=n].iter().map(|&v| v as u32).sum::<u32>();
|
||||
let dc = ((sum + n as u32) >> (log2 + 1)) as u16;
|
||||
let dcs = S::from_u16(dc);
|
||||
for y in 0..n {
|
||||
out[y * os..y * os + n].fill(dc);
|
||||
out[y * os..y * os + n].fill(dcs);
|
||||
}
|
||||
if edge_filters && n < 32 {
|
||||
let d = dc as u32;
|
||||
out[0] = ((r.left[1] as u32 + 2 * d + r.top[1] as u32 + 2) >> 2) as u16;
|
||||
out[0] = S::from_i32(((r.left[1] as u32 + 2 * d + r.top[1] as u32 + 2) >> 2) as i32);
|
||||
for x in 1..n {
|
||||
out[x] = ((r.top[1 + x] as u32 + 3 * d + 2) >> 2) as u16;
|
||||
out[x] = S::from_i32(((r.top[1 + x] as u32 + 3 * d + 2) >> 2) as i32);
|
||||
}
|
||||
for y in 1..n {
|
||||
out[y * os] = ((r.left[1 + y] as u32 + 3 * d + 2) >> 2) as u16;
|
||||
out[y * os] = S::from_i32(((r.left[1 + y] as u32 + 3 * d + 2) >> 2) as i32);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,9 +169,9 @@ pub fn predict(r: &Refs, mode: u32, n: usize, edge_filters: bool, bit_depth: u32
|
||||
let k = (base + i as i32 + idx + 1) as usize;
|
||||
let v = if fact != 0 { ((32 - fact) * refa[k] + fact * refa[k + 1] + 16) >> 5 } else { refa[k] };
|
||||
if horizontal {
|
||||
out[i * os + j] = v as u16;
|
||||
out[i * os + j] = S::from_i32(v);
|
||||
} else {
|
||||
out[j * os + i] = v as u16;
|
||||
out[j * os + i] = S::from_i32(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -177,12 +179,12 @@ pub fn predict(r: &Refs, mode: u32, n: usize, edge_filters: bool, bit_depth: u32
|
||||
if mode == 26 {
|
||||
for y in 0..n {
|
||||
let v = r.top[1] as i32 + ((r.left[1 + y] as i32 - r.left[0] as i32) >> 1);
|
||||
out[y * os] = v.clamp(0, max) as u16;
|
||||
out[y * os] = S::from_i32(v.clamp(0, max));
|
||||
}
|
||||
} else if mode == 10 {
|
||||
for x in 0..n {
|
||||
let v = r.left[1] as i32 + ((r.top[1 + x] as i32 - r.top[0] as i32) >> 1);
|
||||
out[x] = v.clamp(0, max) as u16;
|
||||
out[x] = S::from_i32(v.clamp(0, max));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ fn same_motion(a: &MvField, b: &MvField) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
impl<'a> SliceDecoder<'a> {
|
||||
impl<'a, S: crate::picture::Sample> SliceDecoder<'a, S> {
|
||||
/// Prediction block availability (6.4.2).
|
||||
fn pb_avail(&self, xc: i32, yc: i32, ncb: i32, xp: i32, yp: i32, w: i32, h: i32, part_idx: usize, xn: i32, yn: i32) -> bool {
|
||||
let same_cb = xc <= xn && yc <= yn && xc + ncb > xn && yc + ncb > yn;
|
||||
|
||||
+411
-120
@@ -2,6 +2,7 @@
|
||||
//! deblocking and SAO) together with the compressed motion field used for temporal motion vector
|
||||
//! prediction.
|
||||
|
||||
use crate::Plane;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
|
||||
/// Motion data of one 16x16 block of a decoded picture (8.5.3.2.9 uses the motion of the top-left
|
||||
@@ -15,16 +16,109 @@ pub struct ColMv {
|
||||
pub flags: u8,
|
||||
}
|
||||
|
||||
/// Storage type of decoded samples: `u8` for 8-bit pictures (half the memory and cache traffic
|
||||
/// of 16-bit storage in prediction reads, filters and output), `u16` for higher bit depths.
|
||||
/// Values are always within `0..1 << bit_depth`.
|
||||
pub trait Sample: Copy + Default + PartialEq + std::fmt::Debug + Send + Sync + 'static {
|
||||
fn to_i32(self) -> i32;
|
||||
fn to_i16(self) -> i16;
|
||||
fn to_u16(self) -> u16;
|
||||
/// `v` (a sample value, already clipped to the bit depth) in the storage type.
|
||||
fn from_i32(v: i32) -> Self;
|
||||
/// `v` (a sample value, within the bit depth) in the storage type.
|
||||
fn from_u16(v: u16) -> Self;
|
||||
/// The rows of `f` if they are stored with this type.
|
||||
fn rows(f: &Frame) -> Option<&[OnceLock<FrameRow<Self>>]>;
|
||||
fn wrap_rows(rows: Box<[OnceLock<FrameRow<Self>>]>) -> Rows;
|
||||
}
|
||||
|
||||
impl Sample for u8 {
|
||||
#[inline(always)]
|
||||
fn to_i32(self) -> i32 {
|
||||
self as i32
|
||||
}
|
||||
#[inline(always)]
|
||||
fn to_i16(self) -> i16 {
|
||||
self as i16
|
||||
}
|
||||
#[inline(always)]
|
||||
fn to_u16(self) -> u16 {
|
||||
self as u16
|
||||
}
|
||||
#[inline(always)]
|
||||
fn from_i32(v: i32) -> Self {
|
||||
v as u8
|
||||
}
|
||||
#[inline(always)]
|
||||
fn from_u16(v: u16) -> Self {
|
||||
v as u8
|
||||
}
|
||||
#[inline(always)]
|
||||
fn rows(f: &Frame) -> Option<&[OnceLock<FrameRow<u8>>]> {
|
||||
match &f.rows {
|
||||
Rows::U8(r) => Some(r),
|
||||
Rows::U16(_) => None,
|
||||
}
|
||||
}
|
||||
fn wrap_rows(rows: Box<[OnceLock<FrameRow<u8>>]>) -> Rows {
|
||||
Rows::U8(rows)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sample for u16 {
|
||||
#[inline(always)]
|
||||
fn to_i32(self) -> i32 {
|
||||
self as i32
|
||||
}
|
||||
#[inline(always)]
|
||||
fn to_i16(self) -> i16 {
|
||||
self as i16
|
||||
}
|
||||
#[inline(always)]
|
||||
fn to_u16(self) -> u16 {
|
||||
self
|
||||
}
|
||||
#[inline(always)]
|
||||
fn from_i32(v: i32) -> Self {
|
||||
v as u16
|
||||
}
|
||||
#[inline(always)]
|
||||
fn from_u16(v: u16) -> Self {
|
||||
v
|
||||
}
|
||||
#[inline(always)]
|
||||
fn rows(f: &Frame) -> Option<&[OnceLock<FrameRow<u16>>]> {
|
||||
match &f.rows {
|
||||
Rows::U16(r) => Some(r),
|
||||
Rows::U8(_) => None,
|
||||
}
|
||||
}
|
||||
fn wrap_rows(rows: Box<[OnceLock<FrameRow<u16>>]>) -> Rows {
|
||||
Rows::U16(rows)
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether pictures with these bit depths are stored as `u8` (both 8-bit), else as `u16`.
|
||||
pub fn stores_u8(bit_depth: u32, bit_depth_c: u32) -> bool {
|
||||
bit_depth <= 8 && bit_depth_c <= 8
|
||||
}
|
||||
|
||||
/// One published CTB row.
|
||||
pub struct FrameRow {
|
||||
pub struct FrameRow<S> {
|
||||
/// Luma lines of the row (stride = picture width).
|
||||
pub y: Box<[u16]>,
|
||||
pub cb: Box<[u16]>,
|
||||
pub cr: Box<[u16]>,
|
||||
pub y: Box<[S]>,
|
||||
pub cb: Box<[S]>,
|
||||
pub cr: Box<[S]>,
|
||||
/// Motion per 16x16 block: (width16 * rows16) entries, raster within the row.
|
||||
pub col: Box<[ColMv]>,
|
||||
}
|
||||
|
||||
/// The CTB rows of a frame, in its storage type.
|
||||
pub enum Rows {
|
||||
U8(Box<[OnceLock<FrameRow<u8>>]>),
|
||||
U16(Box<[OnceLock<FrameRow<u16>>]>),
|
||||
}
|
||||
|
||||
/// A decoded (or in-progress) picture as stored in the DPB and referenced by later pictures.
|
||||
pub struct Frame {
|
||||
pub id: u32,
|
||||
@@ -36,69 +130,87 @@ pub struct Frame {
|
||||
pub log2_ctb: u32,
|
||||
pub bit_depth: u32,
|
||||
pub bit_depth_c: u32,
|
||||
rows: Box<[OnceLock<FrameRow>]>,
|
||||
rows: Rows,
|
||||
}
|
||||
|
||||
/// The value of a published row (waiting for it with the `threads` feature).
|
||||
#[inline(always)]
|
||||
fn wait<T>(cell: &OnceLock<T>) -> Option<&T> {
|
||||
#[cfg(feature = "threads")]
|
||||
{
|
||||
Some(cell.wait())
|
||||
}
|
||||
#[cfg(not(feature = "threads"))]
|
||||
{
|
||||
cell.get()
|
||||
}
|
||||
}
|
||||
|
||||
impl Frame {
|
||||
pub fn new(id: u32, poc: i32, width: usize, height: usize, log2_ctb: u32, bit_depth: u32, bit_depth_c: u32) -> Self {
|
||||
let n = height.div_ceil(1 << log2_ctb);
|
||||
Frame {
|
||||
id,
|
||||
poc,
|
||||
width,
|
||||
height,
|
||||
cwidth: width / 2,
|
||||
cheight: height / 2,
|
||||
log2_ctb,
|
||||
bit_depth,
|
||||
bit_depth_c,
|
||||
rows: (0..n).map(|_| OnceLock::new()).collect(),
|
||||
fn cells<S: Sample>(n: usize) -> Rows {
|
||||
S::wrap_rows((0..n).map(|_| OnceLock::new()).collect())
|
||||
}
|
||||
let rows = if stores_u8(bit_depth, bit_depth_c) { cells::<u8>(n) } else { cells::<u16>(n) };
|
||||
Frame { id, poc, width, height, cwidth: width / 2, cheight: height / 2, log2_ctb, bit_depth, bit_depth_c, rows }
|
||||
}
|
||||
|
||||
pub fn num_rows(&self) -> usize {
|
||||
self.rows.len()
|
||||
match &self.rows {
|
||||
Rows::U8(r) => r.len(),
|
||||
Rows::U16(r) => r.len(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Number of luma lines in CTB row `r`.
|
||||
pub fn row_lines(&self, r: usize) -> usize {
|
||||
let ctb = 1usize << self.log2_ctb;
|
||||
ctb.min(self.height - r * ctb)
|
||||
ctb.min(self.height.saturating_sub(r * ctb))
|
||||
}
|
||||
|
||||
pub fn width16(&self) -> usize {
|
||||
self.width.div_ceil(16)
|
||||
}
|
||||
|
||||
/// Publish CTB row `r` (ignored if already published).
|
||||
pub fn publish(&self, r: usize, row: FrameRow) {
|
||||
let _ = self.rows[r].set(row);
|
||||
/// Publish CTB row `r` (ignored if already published, or not of the frame's sample type).
|
||||
pub fn publish<S: Sample>(&self, r: usize, row: FrameRow<S>) {
|
||||
if let Some(cell) = S::rows(self).and_then(|rows| rows.get(r)) {
|
||||
let _ = cell.set(row);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_published(&self, r: usize) -> bool {
|
||||
self.rows[r].get().is_some()
|
||||
match &self.rows {
|
||||
Rows::U8(rows) => rows.get(r).is_some_and(|c| c.get().is_some()),
|
||||
Rows::U16(rows) => rows.get(r).is_some_and(|c| c.get().is_some()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_complete(&self) -> bool {
|
||||
self.rows.last().is_none_or(|r| r.get().is_some())
|
||||
let n = self.num_rows();
|
||||
n == 0 || self.is_published(n - 1)
|
||||
}
|
||||
|
||||
/// CTB row `r`, waiting until it has been published.
|
||||
/// CTB row `r` in sample type `S`, waiting until it has been published (None: no such row,
|
||||
/// or the frame stores another sample type).
|
||||
#[inline]
|
||||
pub fn row(&self, r: usize) -> &FrameRow {
|
||||
#[cfg(feature = "threads")]
|
||||
{
|
||||
self.rows[r].wait()
|
||||
}
|
||||
#[cfg(not(feature = "threads"))]
|
||||
{
|
||||
self.rows[r].get().expect("reference row decoded before use")
|
||||
pub fn row<S: Sample>(&self, r: usize) -> Option<&FrameRow<S>> {
|
||||
wait(S::rows(self)?.get(r)?)
|
||||
}
|
||||
|
||||
/// The motion of CTB row `r`, waiting until it has been published.
|
||||
#[inline]
|
||||
fn col_row(&self, r: usize) -> Option<&[ColMv]> {
|
||||
match &self.rows {
|
||||
Rows::U8(rows) => wait(rows.get(r)?).map(|row| &*row.col),
|
||||
Rows::U16(rows) => wait(rows.get(r)?).map(|row| &*row.col),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wait_complete(&self) {
|
||||
for r in 0..self.rows.len() {
|
||||
self.row(r);
|
||||
for r in 0..self.num_rows() {
|
||||
let _ = self.col_row(r);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,47 +218,78 @@ impl Frame {
|
||||
#[inline]
|
||||
pub fn col_mv(&self, x: usize, y: usize) -> ColMv {
|
||||
let r = y >> self.log2_ctb;
|
||||
let row = self.row(r);
|
||||
let y16 = (y & ((1 << self.log2_ctb) - 1)) >> 4;
|
||||
row.col[y16 * self.width16() + (x >> 4)]
|
||||
self.col_row(r).and_then(|col| col.get(y16 * self.width16() + (x >> 4))).copied().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Copy a w x h luma window at (x0, y0) with edge replication into `out` (stride `os`).
|
||||
pub fn luma_window(&self, x0: i32, y0: i32, w: usize, h: usize, out: &mut [i16], os: usize) {
|
||||
pub fn luma_window<S: Sample>(&self, x0: i32, y0: i32, w: usize, h: usize, out: &mut [i16], os: usize) {
|
||||
let (width, height) = (self.width as i32, self.height as i32);
|
||||
let sh = self.log2_ctb;
|
||||
let mask = (1i32 << sh) - 1;
|
||||
for r in 0..h {
|
||||
let yy = (y0 + r as i32).clamp(0, height - 1);
|
||||
let row = self.row((yy >> sh) as usize);
|
||||
let Some(row) = self.row::<S>((yy >> sh) as usize) else { return };
|
||||
let off = ((yy & mask) * width) as usize;
|
||||
copy_line(&row.y[off..off + width as usize], x0, &mut out[r * os..r * os + w]);
|
||||
}
|
||||
}
|
||||
|
||||
/// Copy a w x h window of chroma component `c` (0 = Cb, 1 = Cr) with edge replication.
|
||||
pub fn chroma_window(&self, c: usize, x0: i32, y0: i32, w: usize, h: usize, out: &mut [i16], os: usize) {
|
||||
pub fn chroma_window<S: Sample>(&self, c: usize, x0: i32, y0: i32, w: usize, h: usize, out: &mut [i16], os: usize) {
|
||||
let (cw, ch) = (self.cwidth as i32, self.cheight as i32);
|
||||
let sh = self.log2_ctb - 1;
|
||||
let mask = (1i32 << sh) - 1;
|
||||
for r in 0..h {
|
||||
let yy = (y0 + r as i32).clamp(0, ch - 1);
|
||||
let row = self.row((yy >> sh) as usize);
|
||||
let Some(row) = self.row::<S>((yy >> sh) as usize) else { return };
|
||||
let plane = if c == 0 { &row.cb } else { &row.cr };
|
||||
let off = ((yy & mask) * cw) as usize;
|
||||
copy_line(&plane[off..off + cw as usize], x0, &mut out[r * os..r * os + w]);
|
||||
}
|
||||
}
|
||||
|
||||
/// The w-sample runs at x0 of the h lines from y0 of plane `c` (0 = luma, 1 = Cb, 2 = Cr) into
|
||||
/// `rows` (h = `rows.len()`), lines outside the picture replicated from its top / bottom edge
|
||||
/// (8-228, 8-229): the reference window of a prediction block without copying it. False (and
|
||||
/// `rows` partly filled) when the runs are not inside the picture horizontally.
|
||||
pub fn window_rows<'f, S: Sample>(&'f self, c: usize, x0: i32, y0: i32, w: usize, rows: &mut [&'f [S]]) -> bool {
|
||||
let (pw, ph, sh) = if c == 0 { (self.width, self.height, self.log2_ctb) } else { (self.cwidth, self.cheight, self.log2_ctb - 1) };
|
||||
let Some(x) = usize::try_from(x0).ok().filter(|&x| x.saturating_add(w) <= pw) else { return false };
|
||||
let mask = (1usize << sh) - 1;
|
||||
let max_y = ph as i32 - 1;
|
||||
let mut cur: Option<(usize, &'f [S])> = None;
|
||||
for (r, slot) in rows.iter_mut().enumerate() {
|
||||
let yy = y0.saturating_add(r as i32).min(max_y).max(0) as usize;
|
||||
let plane = match cur {
|
||||
Some((i, p)) if i == yy >> sh => p,
|
||||
_ => {
|
||||
let Some(row) = self.row::<S>(yy >> sh) else { return false };
|
||||
let p: &'f [S] = match c {
|
||||
0 => &row.y,
|
||||
1 => &row.cb,
|
||||
_ => &row.cr,
|
||||
};
|
||||
cur = Some((yy >> sh, p));
|
||||
p
|
||||
}
|
||||
};
|
||||
let off = (yy & mask) * pw + x;
|
||||
let Some(run) = plane.get(off..off + w) else { return false };
|
||||
*slot = run;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Copy the w x h block at (x0, y0) of plane `c` (0 = luma, 1 = Cb, 2 = Cr) into `dst`
|
||||
/// (stride `ds`), samples outside the picture replicated from its edges (8-228, 8-229).
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn copy_block(&self, c: usize, x0: i32, y0: i32, w: usize, h: usize, dst: &mut [u16], ds: usize) {
|
||||
pub fn copy_block<S: Sample>(&self, c: usize, x0: i32, y0: i32, w: usize, h: usize, dst: &mut [S], ds: usize) {
|
||||
let (pw, ph, sh) = if c == 0 { (self.width, self.height, self.log2_ctb) } else { (self.cwidth, self.cheight, self.log2_ctb - 1) };
|
||||
let inside = usize::try_from(x0).ok().filter(|&x| x.saturating_add(w) <= pw);
|
||||
for (r, d) in dst.chunks_mut(ds).take(h).enumerate() {
|
||||
let yy = y0.saturating_add(r as i32).min(ph as i32 - 1).max(0) as usize;
|
||||
let row = self.row(yy >> sh);
|
||||
let Some(row) = self.row::<S>(yy >> sh) else { return };
|
||||
let plane = match c {
|
||||
0 => &row.y,
|
||||
1 => &row.cb,
|
||||
@@ -160,98 +303,147 @@ impl Frame {
|
||||
None => {
|
||||
let max = (pw as i32 - 1).max(0);
|
||||
for (i, d) in d.iter_mut().enumerate() {
|
||||
*d = line.get(x0.saturating_add(i as i32).clamp(0, max) as usize).copied().unwrap_or(0);
|
||||
*d = line.get(x0.saturating_add(i as i32).clamp(0, max) as usize).copied().unwrap_or_default();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Crop and copy the frame into planar buffers (waits for completion).
|
||||
pub fn copy_cropped(&self, crop: (usize, usize, usize, usize)) -> (Vec<u16>, Vec<u16>, Vec<u16>) {
|
||||
let mut out = (Vec::new(), Vec::new(), Vec::new());
|
||||
self.copy_cropped_with(crop, &mut out, |o, s| o.extend_from_slice(s));
|
||||
out
|
||||
/// Crop the frame into planar buffers (waits for completion): 8-bit planes when `eight_bit`,
|
||||
/// else 16-bit ones. Rows stored in the output type are copied as they are.
|
||||
pub fn cropped_planes(&self, crop: (usize, usize, usize, usize), eight_bit: bool) -> (Plane, Plane, Plane) {
|
||||
fn planes<S: Sample, T>(f: &Frame, crop: (usize, usize, usize, usize), conv: impl Fn(S) -> T, wrap: impl Fn(Vec<T>) -> Plane) -> (Plane, Plane, Plane) {
|
||||
let (y, u, v) = f.copy_cropped::<S, T>(crop, conv);
|
||||
(wrap(y), wrap(u), wrap(v))
|
||||
}
|
||||
match (&self.rows, eight_bit) {
|
||||
(Rows::U8(_), true) => planes::<u8, u8>(self, crop, |v| v, Plane::U8),
|
||||
(Rows::U8(_), false) => planes::<u8, u16>(self, crop, u16::from, Plane::U16),
|
||||
// (8-bit luma with deeper chroma: the 8-bit output keeps the low bits, as before)
|
||||
(Rows::U16(_), true) => planes::<u16, u8>(self, crop, |v| v as u8, Plane::U8),
|
||||
(Rows::U16(_), false) => planes::<u16, u16>(self, crop, |v| v, Plane::U16),
|
||||
}
|
||||
}
|
||||
|
||||
/// [`copy_cropped`](Self::copy_cropped) into 8-bit planes (samples of an 8-bit picture),
|
||||
/// without an intermediate 16-bit copy.
|
||||
pub fn copy_cropped_u8(&self, crop: (usize, usize, usize, usize)) -> (Vec<u8>, Vec<u8>, Vec<u8>) {
|
||||
let mut out = (Vec::new(), Vec::new(), Vec::new());
|
||||
self.copy_cropped_with(crop, &mut out, |o, s| o.extend(s.iter().map(|&v| v as u8)));
|
||||
out
|
||||
}
|
||||
|
||||
fn copy_cropped_with<T>(&self, crop: (usize, usize, usize, usize), out: &mut (Vec<T>, Vec<T>, Vec<T>), put: impl Fn(&mut Vec<T>, &[u16])) {
|
||||
/// The cropped planes of the frame converted sample by sample with `conv` (a plain copy for
|
||||
/// the identity).
|
||||
fn copy_cropped<S: Sample, T>(&self, crop: (usize, usize, usize, usize), conv: impl Fn(S) -> T) -> (Vec<T>, Vec<T>, Vec<T>) {
|
||||
let (cx, cy, cw, ch) = crop;
|
||||
let ctb = 1usize << self.log2_ctb;
|
||||
let (y, u, v) = out;
|
||||
y.reserve_exact(cw * ch);
|
||||
for r in cy..cy + ch {
|
||||
let row = self.row(r / ctb);
|
||||
let o = (r % ctb) * self.width + cx;
|
||||
put(y, &row.y[o..o + cw]);
|
||||
let (mut y, mut u, mut v) = (Vec::new(), Vec::new(), Vec::new());
|
||||
let put = |o: &mut Vec<T>, plane: &[S], off: usize, n: usize| {
|
||||
if let Some(s) = plane.get(off..off.saturating_add(n)) {
|
||||
o.extend(s.iter().map(|&v| conv(v)));
|
||||
}
|
||||
};
|
||||
y.reserve_exact(cw.saturating_mul(ch));
|
||||
for r in cy..cy.saturating_add(ch) {
|
||||
let Some(row) = self.row::<S>(r / ctb) else { break };
|
||||
put(&mut y, &row.y, (r % ctb) * self.width + cx, cw);
|
||||
}
|
||||
let (ccx, ccy, ccw, cch) = (cx / 2, cy / 2, cw.div_ceil(2), ch.div_ceil(2));
|
||||
let cctb = ctb / 2;
|
||||
u.reserve_exact(ccw * cch);
|
||||
v.reserve_exact(ccw * cch);
|
||||
for r in ccy..ccy + cch {
|
||||
let row = self.row(r / cctb);
|
||||
u.reserve_exact(ccw.saturating_mul(cch));
|
||||
v.reserve_exact(ccw.saturating_mul(cch));
|
||||
for r in ccy..ccy.saturating_add(cch) {
|
||||
let Some(row) = self.row::<S>(r / cctb) else { break };
|
||||
let o = (r % cctb) * self.cwidth + ccx;
|
||||
put(u, &row.cb[o..o + ccw]);
|
||||
put(v, &row.cr[o..o + ccw]);
|
||||
put(&mut u, &row.cb, o, ccw);
|
||||
put(&mut v, &row.cr, o, ccw);
|
||||
}
|
||||
(y, u, v)
|
||||
}
|
||||
|
||||
/// A frame with every row published from full-picture planes and no motion (used to replace missing
|
||||
/// reference pictures).
|
||||
pub fn from_planes(id: u32, poc: i32, like: &Frame, y: &[u16], cb: &[u16], cr: &[u16]) -> Frame {
|
||||
let f = Frame::new(id, poc, like.width, like.height, like.log2_ctb, like.bit_depth, like.bit_depth_c);
|
||||
let ctb = 1usize << f.log2_ctb;
|
||||
let w16 = f.width16();
|
||||
for r in 0..f.num_rows() {
|
||||
let lines = f.row_lines(r);
|
||||
let (y0, y1) = (r * ctb * f.width, (r * ctb + lines) * f.width);
|
||||
let clines = lines.div_ceil(2);
|
||||
let (c0, c1) = (r * ctb / 2 * f.cwidth, (r * ctb / 2 + clines) * f.cwidth);
|
||||
f.publish(
|
||||
r,
|
||||
FrameRow { y: y[y0..y1].into(), cb: cb[c0..c1].into(), cr: cr[c0..c1].into(), col: vec![ColMv::default(); w16 * lines.div_ceil(16)].into() },
|
||||
);
|
||||
/// Publish every row not published yet as mid-grey without motion (a missing reference picture,
|
||||
/// or a picture whose decoding failed: readers never block on it).
|
||||
pub fn publish_gray(&self) {
|
||||
fn fill<S: Sample>(f: &Frame) {
|
||||
let (gy, gc) = (S::from_u16(1 << (f.bit_depth.clamp(1, 16) - 1)), S::from_u16(1 << (f.bit_depth_c.clamp(1, 16) - 1)));
|
||||
let w16 = f.width16();
|
||||
for r in 0..f.num_rows() {
|
||||
if f.is_published(r) {
|
||||
continue;
|
||||
}
|
||||
let lines = f.row_lines(r);
|
||||
let clines = lines.div_ceil(2);
|
||||
f.publish(
|
||||
r,
|
||||
FrameRow::<S> {
|
||||
y: vec![gy; lines * f.width].into(),
|
||||
cb: vec![gc; clines * f.cwidth].into(),
|
||||
cr: vec![gc; clines * f.cwidth].into(),
|
||||
col: vec![ColMv::default(); w16 * lines.div_ceil(16)].into(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
match &self.rows {
|
||||
Rows::U8(_) => fill::<u8>(self),
|
||||
Rows::U16(_) => fill::<u16>(self),
|
||||
}
|
||||
f
|
||||
}
|
||||
|
||||
/// A mid-grey frame with the geometry of `like`.
|
||||
pub fn gray(id: u32, poc: i32, like: &Frame) -> Frame {
|
||||
let y = vec![1u16 << (like.bit_depth - 1); like.width * like.height];
|
||||
let c = vec![1u16 << (like.bit_depth_c - 1); like.cwidth * like.cheight];
|
||||
Frame::from_planes(id, poc, like, &y, &c, &c)
|
||||
let f = Frame::new(id, poc, like.width, like.height, like.log2_ctb, like.bit_depth, like.bit_depth_c);
|
||||
f.publish_gray();
|
||||
f
|
||||
}
|
||||
|
||||
/// A frame with every row published from full-picture planes and no motion.
|
||||
#[cfg(test)]
|
||||
pub fn from_planes(id: u32, poc: i32, like: &Frame, y: &[u16], cb: &[u16], cr: &[u16]) -> Frame {
|
||||
fn fill<S: Sample>(f: &Frame, y: &[u16], cb: &[u16], cr: &[u16]) {
|
||||
let ctb = 1usize << f.log2_ctb;
|
||||
let w16 = f.width16();
|
||||
let conv = |s: &[u16]| -> Box<[S]> { s.iter().map(|&v| S::from_u16(v)).collect() };
|
||||
for r in 0..f.num_rows() {
|
||||
let lines = f.row_lines(r);
|
||||
let (y0, y1) = (r * ctb * f.width, (r * ctb + lines) * f.width);
|
||||
let clines = lines.div_ceil(2);
|
||||
let (c0, c1) = (r * ctb / 2 * f.cwidth, (r * ctb / 2 + clines) * f.cwidth);
|
||||
f.publish(
|
||||
r,
|
||||
FrameRow::<S> {
|
||||
y: conv(&y[y0..y1]),
|
||||
cb: conv(&cb[c0..c1]),
|
||||
cr: conv(&cr[c0..c1]),
|
||||
col: vec![ColMv::default(); w16 * lines.div_ceil(16)].into(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
let f = Frame::new(id, poc, like.width, like.height, like.log2_ctb, like.bit_depth, like.bit_depth_c);
|
||||
match &f.rows {
|
||||
Rows::U8(_) => fill::<u8>(&f, y, cb, cr),
|
||||
Rows::U16(_) => fill::<u16>(&f, y, cb, cr),
|
||||
}
|
||||
f
|
||||
}
|
||||
}
|
||||
|
||||
/// `dst.copy_from_slice(src)` for the run lengths of prediction blocks as fixed-size copies: a
|
||||
/// libc `memcpy` call per short block line costs more than the copy.
|
||||
#[inline(always)]
|
||||
fn copy_run(src: &[u16], dst: &mut [u16]) {
|
||||
fn copy_run<S: Sample>(src: &[S], dst: &mut [S]) {
|
||||
#[inline(always)]
|
||||
fn fixed<const N: usize>(src: &[u16], dst: &mut [u16]) {
|
||||
fn fixed<S: Sample, const N: usize>(src: &[S], dst: &mut [S]) {
|
||||
if let (Some(s), Some(d)) = (src.first_chunk::<N>(), dst.first_chunk_mut::<N>()) {
|
||||
*d = *s;
|
||||
}
|
||||
}
|
||||
match dst.len() {
|
||||
2 => fixed::<2>(src, dst),
|
||||
4 => fixed::<4>(src, dst),
|
||||
6 => fixed::<6>(src, dst),
|
||||
8 => fixed::<8>(src, dst),
|
||||
12 => fixed::<12>(src, dst),
|
||||
16 => fixed::<16>(src, dst),
|
||||
24 => fixed::<24>(src, dst),
|
||||
32 => fixed::<32>(src, dst),
|
||||
48 => fixed::<48>(src, dst),
|
||||
64 => fixed::<64>(src, dst),
|
||||
2 => fixed::<S, 2>(src, dst),
|
||||
4 => fixed::<S, 4>(src, dst),
|
||||
6 => fixed::<S, 6>(src, dst),
|
||||
8 => fixed::<S, 8>(src, dst),
|
||||
12 => fixed::<S, 12>(src, dst),
|
||||
16 => fixed::<S, 16>(src, dst),
|
||||
24 => fixed::<S, 24>(src, dst),
|
||||
32 => fixed::<S, 32>(src, dst),
|
||||
48 => fixed::<S, 48>(src, dst),
|
||||
64 => fixed::<S, 64>(src, dst),
|
||||
n => {
|
||||
if let Some(s) = src.get(..n) {
|
||||
dst.copy_from_slice(s);
|
||||
@@ -262,44 +454,58 @@ fn copy_run(src: &[u16], dst: &mut [u16]) {
|
||||
|
||||
/// Copy `out.len()` samples of `line` starting at x0, replicating edge samples outside the line.
|
||||
#[inline(always)]
|
||||
fn copy_line(line: &[u16], x0: i32, out: &mut [i16]) {
|
||||
fn copy_line<S: Sample>(line: &[S], x0: i32, out: &mut [i16]) {
|
||||
let w = out.len();
|
||||
if x0 >= 0 && x0 as usize + w <= line.len() {
|
||||
let src = &line[x0 as usize..x0 as usize + w];
|
||||
// fixed-size copies for the window widths (block width + 7 luma, + 3 chroma): see copy_run
|
||||
#[inline(always)]
|
||||
fn fixed<const N: usize>(src: &[u16], out: &mut [i16]) {
|
||||
fn fixed<S: Sample, const N: usize>(src: &[S], out: &mut [i16]) {
|
||||
if let (Some(s), Some(o)) = (src.first_chunk::<N>(), out.first_chunk_mut::<N>()) {
|
||||
for j in 0..N {
|
||||
o[j] = s[j] as i16;
|
||||
o[j] = s[j].to_i16();
|
||||
}
|
||||
}
|
||||
}
|
||||
match w {
|
||||
5 => fixed::<5>(src, out),
|
||||
7 => fixed::<7>(src, out),
|
||||
9 => fixed::<9>(src, out),
|
||||
11 => fixed::<11>(src, out),
|
||||
15 => fixed::<15>(src, out),
|
||||
19 => fixed::<19>(src, out),
|
||||
23 => fixed::<23>(src, out),
|
||||
27 => fixed::<27>(src, out),
|
||||
31 => fixed::<31>(src, out),
|
||||
35 => fixed::<35>(src, out),
|
||||
39 => fixed::<39>(src, out),
|
||||
55 => fixed::<55>(src, out),
|
||||
71 => fixed::<71>(src, out),
|
||||
5 => fixed::<S, 5>(src, out),
|
||||
7 => fixed::<S, 7>(src, out),
|
||||
9 => fixed::<S, 9>(src, out),
|
||||
11 => fixed::<S, 11>(src, out),
|
||||
15 => fixed::<S, 15>(src, out),
|
||||
19 => fixed::<S, 19>(src, out),
|
||||
23 => fixed::<S, 23>(src, out),
|
||||
27 => fixed::<S, 27>(src, out),
|
||||
31 => fixed::<S, 31>(src, out),
|
||||
35 => fixed::<S, 35>(src, out),
|
||||
39 => fixed::<S, 39>(src, out),
|
||||
55 => fixed::<S, 55>(src, out),
|
||||
71 => fixed::<S, 71>(src, out),
|
||||
_ => {
|
||||
for (o, &s) in out.iter_mut().zip(src) {
|
||||
*o = s as i16;
|
||||
*o = s.to_i16();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let max = line.len() as i32 - 1;
|
||||
for (c, o) in out.iter_mut().enumerate() {
|
||||
*o = line[(x0 + c as i32).clamp(0, max) as usize] as i16;
|
||||
// the window crosses the left and / or right picture edge: the edge samples repeated
|
||||
// outside, the samples inside copied as a run
|
||||
let (Some(&first), Some(&last)) = (line.first(), line.last()) else { return };
|
||||
let n = line.len() as i64;
|
||||
let (x0, w) = (x0 as i64, w as i64);
|
||||
// out[i] is inside the line for i in lo..hi
|
||||
let lo = (-x0).clamp(0, w) as usize;
|
||||
let hi = (n - x0).clamp(0, w) as usize;
|
||||
let (left, rest) = out.split_at_mut(lo);
|
||||
left.fill(first.to_i16());
|
||||
let (mid, right) = rest.split_at_mut(hi.saturating_sub(lo));
|
||||
let start = (x0 + lo as i64).max(0) as usize;
|
||||
if let Some(src) = line.get(start..start + mid.len()) {
|
||||
for (o, &s) in mid.iter_mut().zip(src) {
|
||||
*o = s.to_i16();
|
||||
}
|
||||
}
|
||||
right.fill(last.to_i16());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,3 +513,88 @@ pub type FrameRef = Arc<Frame>;
|
||||
|
||||
/// An entry of RefPicList0/1 of the current slice.
|
||||
pub type RefPic = crate::dpb::Ref<FrameRef>;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn planes(w: usize, h: usize, bd: u32, seed: u64) -> (Vec<u16>, Vec<u16>, Vec<u16>) {
|
||||
let mut x = seed;
|
||||
let mut next = move || {
|
||||
x ^= x << 13;
|
||||
x ^= x >> 7;
|
||||
x ^= x << 17;
|
||||
(x % (1 << bd)) as u16
|
||||
};
|
||||
let y = (0..w * h).map(|_| next()).collect();
|
||||
let cb = (0..w * h / 4).map(|_| next()).collect();
|
||||
let cr = (0..w * h / 4).map(|_| next()).collect();
|
||||
(y, cb, cr)
|
||||
}
|
||||
|
||||
/// 8-bit pictures are stored as `u8`, deeper ones as `u16`, and both crop back to exactly the
|
||||
/// samples they were given.
|
||||
#[test]
|
||||
fn storage_type_follows_the_bit_depth_and_crops_exactly() {
|
||||
let (w, h) = (40usize, 36usize);
|
||||
let crop = (2usize, 4usize, 33usize, 27usize);
|
||||
for bd in [8u32, 10] {
|
||||
let (y, cb, cr) = planes(w, h, bd, 0x9e37_79b9_7f4a_7c15 + bd as u64);
|
||||
let like = Frame::new(0, 0, w, h, 4, bd, bd);
|
||||
let f = Frame::from_planes(1, 0, &like, &y, &cb, &cr);
|
||||
assert_eq!(matches!(f.rows, Rows::U8(_)), bd == 8);
|
||||
assert!(f.is_complete());
|
||||
let (py, pu, pv) = f.cropped_planes(crop, bd == 8);
|
||||
let (cx, cy, cw, ch) = crop;
|
||||
let want_y: Vec<u16> = (cy..cy + ch).flat_map(|r| y[r * w + cx..r * w + cx + cw].to_vec()).collect();
|
||||
let (ccw, cch) = (cw.div_ceil(2), ch.div_ceil(2));
|
||||
let want_c = |p: &[u16]| -> Vec<u16> { (cy / 2..cy / 2 + cch).flat_map(|r| p[r * w / 2 + cx / 2..r * w / 2 + cx / 2 + ccw].to_vec()).collect() };
|
||||
let got = |p: &Plane| -> Vec<u16> { (0..p.len()).map(|i| p.get(i)).collect() };
|
||||
assert_eq!(got(&py), want_y, "luma bd {bd}");
|
||||
assert_eq!(got(&pu), want_c(&cb), "cb bd {bd}");
|
||||
assert_eq!(got(&pv), want_c(&cr), "cr bd {bd}");
|
||||
assert_eq!(py.as_u8().is_some(), bd == 8);
|
||||
}
|
||||
}
|
||||
|
||||
/// Window lines crossing the left and / or right picture edge repeat the edge samples, as the
|
||||
/// per-sample clamp of 8-228 does.
|
||||
#[test]
|
||||
fn edge_replicated_lines_match_the_clamped_reads() {
|
||||
let line: Vec<u8> = (0..23).map(|i| (i * 11 % 256) as u8).collect();
|
||||
for n in [1usize, 2, 23] {
|
||||
let line = &line[..n];
|
||||
for w in [1usize, 5, 7, 11, 15, 19, 23, 39, 71] {
|
||||
for x0 in -80i32..80 {
|
||||
let mut out = vec![-1i16; w];
|
||||
copy_line(line, x0, &mut out);
|
||||
let want: Vec<i16> = (0..w as i32).map(|i| line[(x0 + i).clamp(0, n as i32 - 1) as usize] as i16).collect();
|
||||
assert_eq!(out, want, "n {n} w {w} x0 {x0}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Reading or publishing a frame with the other sample type (never done by the decoder) gives
|
||||
/// nothing instead of a panic, and a grey frame of either type is complete.
|
||||
#[test]
|
||||
fn wrong_sample_type_is_harmless() {
|
||||
let f = Frame::new(0, 0, 32, 32, 4, 8, 8);
|
||||
f.publish(0, FrameRow::<u16> { y: vec![1; 16 * 32].into(), cb: vec![1; 8 * 16].into(), cr: vec![1; 8 * 16].into(), col: Box::new([]) });
|
||||
assert!(!f.is_published(0));
|
||||
f.publish_gray();
|
||||
assert!(f.is_complete());
|
||||
assert!(f.row::<u16>(0).is_none());
|
||||
assert_eq!(f.row::<u8>(1).map(|r| r.y[0]), Some(128));
|
||||
let mut win = [7i16; 64];
|
||||
f.luma_window::<u16>(-3, -3, 8, 8, &mut win, 8);
|
||||
assert!(win.iter().all(|&v| v == 7));
|
||||
let mut dst = [5u16; 16];
|
||||
f.copy_block::<u16>(0, 0, 0, 4, 4, &mut dst, 4);
|
||||
assert!(dst.iter().all(|&v| v == 5));
|
||||
let g = Frame::gray(1, 0, &Frame::new(0, 0, 16, 8, 4, 10, 10));
|
||||
assert!(g.is_complete());
|
||||
assert_eq!(g.row::<u16>(0).map(|r| r.cb[0]), Some(512));
|
||||
assert_eq!(g.col_mv(15, 7), ColMv::default());
|
||||
}
|
||||
}
|
||||
|
||||
+39
-21
@@ -8,7 +8,7 @@ use crate::error::{Result, ensure, invalid};
|
||||
use crate::inter;
|
||||
use crate::intra::{self, Refs};
|
||||
use crate::params::{Layout, Pps, Sps};
|
||||
use crate::picture::{FrameRef, RefPic};
|
||||
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};
|
||||
@@ -95,7 +95,7 @@ pub struct SliceJob {
|
||||
}
|
||||
|
||||
/// Per-picture decoding state.
|
||||
pub struct PicState {
|
||||
pub struct PicState<S> {
|
||||
pub frame: FrameRef,
|
||||
#[allow(dead_code)]
|
||||
pub sps: Arc<Sps>,
|
||||
@@ -114,7 +114,7 @@ pub struct PicState {
|
||||
pub bd_y: u32,
|
||||
pub bd_c: u32,
|
||||
/// Reconstructed (pre-SAO) planes.
|
||||
pub planes: [Vec<u16>; 3],
|
||||
pub planes: [Vec<S>; 3],
|
||||
pub blk: Vec<Blk>,
|
||||
pub mvf: Vec<MvField>,
|
||||
/// Index into `slices` per CTB (u32::MAX = not decoded).
|
||||
@@ -136,7 +136,7 @@ pub struct PicState {
|
||||
pub draft: bool,
|
||||
}
|
||||
|
||||
impl PicState {
|
||||
impl<S: Sample> PicState<S> {
|
||||
pub fn new(frame: FrameRef, sps: Arc<Sps>, pps: Arc<Pps>, layout: Arc<Layout>, scaling: Option<Arc<Vec<Vec<u8>>>>) -> Self {
|
||||
let (width, height) = (sps.width as usize, sps.height as usize);
|
||||
let (cwidth, cheight) = (width / 2, height / 2);
|
||||
@@ -144,8 +144,8 @@ impl PicState {
|
||||
let log2_ctb = sps.log2_ctb;
|
||||
let (wctb, hctb) = (sps.pic_width_in_ctbs() as usize, sps.pic_height_in_ctbs() as usize);
|
||||
let (bd_y, bd_c) = (sps.bit_depth_luma, sps.bit_depth_chroma);
|
||||
let gy = 1u16 << (bd_y - 1);
|
||||
let gc = 1u16 << (bd_c - 1);
|
||||
let gy = S::from_u16(1u16 << (bd_y - 1));
|
||||
let gc = S::from_u16(1u16 << (bd_c - 1));
|
||||
PicState {
|
||||
poc: frame.poc,
|
||||
frame,
|
||||
@@ -231,8 +231,8 @@ pub enum PartMode {
|
||||
}
|
||||
|
||||
/// Decoder of one slice segment.
|
||||
pub struct SliceDecoder<'a> {
|
||||
pub pic: &'a mut PicState,
|
||||
pub struct SliceDecoder<'a, S> {
|
||||
pub pic: &'a mut PicState<S>,
|
||||
pub sh: &'a SliceHeader,
|
||||
pub sps: &'a Sps,
|
||||
pub pps: &'a Pps,
|
||||
@@ -272,7 +272,7 @@ pub struct SliceDecoder<'a> {
|
||||
pub ctb_y: i32,
|
||||
}
|
||||
|
||||
impl<'a> SliceDecoder<'a> {
|
||||
impl<'a, S: Sample> SliceDecoder<'a, S> {
|
||||
#[inline(always)]
|
||||
fn dd(&mut self, ctx: usize) -> u32 {
|
||||
self.c.decode_decision(ctx)
|
||||
@@ -294,7 +294,7 @@ impl<'a> SliceDecoder<'a> {
|
||||
}
|
||||
|
||||
/// Decode the slice segment `job` into `pic`.
|
||||
pub fn decode(pic: &'a mut PicState, job: &'a SliceJob) -> Result<()> {
|
||||
pub fn decode(pic: &'a mut PicState<S>, job: &'a SliceJob) -> Result<()> {
|
||||
let sh = &job.sh;
|
||||
let (sps, pps) = (&*job.sps, &*job.pps);
|
||||
ensure!(sh.data_offset < job.rbsp.len(), "empty slice segment data");
|
||||
@@ -802,7 +802,7 @@ impl<'a> SliceDecoder<'a> {
|
||||
for y in 0..n {
|
||||
for x in 0..n {
|
||||
let v = r.read_bits(bl)? as u16;
|
||||
self.pic.planes[0][(y0 as usize + y) * w + x0 as usize + x] = v << sy;
|
||||
self.pic.planes[0][(y0 as usize + y) * w + x0 as usize + x] = S::from_u16(v << sy);
|
||||
}
|
||||
}
|
||||
let cw = self.pic.cwidth;
|
||||
@@ -810,7 +810,7 @@ impl<'a> SliceDecoder<'a> {
|
||||
for y in 0..n / 2 {
|
||||
for x in 0..n / 2 {
|
||||
let v = r.read_bits(bc)? as u16;
|
||||
self.pic.planes[c][(y0 as usize / 2 + y) * cw + x0 as usize / 2 + x] = v << sc;
|
||||
self.pic.planes[c][(y0 as usize / 2 + y) * cw + x0 as usize / 2 + x] = S::from_u16(v << sc);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -997,14 +997,14 @@ impl<'a> SliceDecoder<'a> {
|
||||
let mut at = [false; 129];
|
||||
let addr = self.slice_addr;
|
||||
let avail =
|
||||
|pic: &PicState, xn: i32, yn: i32| -> bool { pic.zavail(xl, yl, xn, yn, addr) && (!constrained || pic.blk_at(xn, yn).flags & F_INTRA != 0) };
|
||||
|pic: &PicState<S>, xn: i32, yn: i32| -> bool { pic.zavail(xl, yl, xn, yn, addr) && (!constrained || pic.blk_at(xn, yn).flags & F_INTRA != 0) };
|
||||
let plane = &self.pic.planes[c];
|
||||
let r = &mut self.refs_buf;
|
||||
// corner
|
||||
if avail(self.pic, xl - 1, yl - 1) {
|
||||
al[0] = true;
|
||||
at[0] = true;
|
||||
let v = plane[(y0 - 1) as usize * stride + (x0 - 1) as usize];
|
||||
let v = plane[(y0 - 1) as usize * stride + (x0 - 1) as usize].to_u16();
|
||||
r.left[0] = v;
|
||||
r.top[0] = v;
|
||||
}
|
||||
@@ -1014,7 +1014,7 @@ impl<'a> SliceDecoder<'a> {
|
||||
if avail(self.pic, xl - 1, yn * sc) {
|
||||
for i in 0..unit {
|
||||
al[1 + k + i] = true;
|
||||
r.left[1 + k + i] = plane[(yn as usize + i) * stride + (x0 - 1) as usize];
|
||||
r.left[1 + k + i] = plane[(yn as usize + i) * stride + (x0 - 1) as usize].to_u16();
|
||||
}
|
||||
}
|
||||
let xn = x0 + k as i32;
|
||||
@@ -1022,7 +1022,7 @@ impl<'a> SliceDecoder<'a> {
|
||||
let o = (y0 - 1) as usize * stride + xn as usize;
|
||||
for i in 0..unit {
|
||||
at[1 + k + i] = true;
|
||||
r.top[1 + k + i] = plane[o + i];
|
||||
r.top[1 + k + i] = plane[o + i].to_u16();
|
||||
}
|
||||
}
|
||||
k += unit;
|
||||
@@ -1318,7 +1318,7 @@ impl<'a> SliceDecoder<'a> {
|
||||
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 as i32 + coeffs[y * n + x]).clamp(0, max) as u16;
|
||||
*s = S::from_i32((s.to_i32() + coeffs[y * n + x]).clamp(0, max));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -1446,11 +1446,17 @@ impl<'a> SliceDecoder<'a> {
|
||||
SliceType::I => false,
|
||||
};
|
||||
let bi = f.pred(0) && f.pred(1);
|
||||
let pwt = self.sh.pwt.as_ref().filter(|_| weighted);
|
||||
for c in 0..3 {
|
||||
let (x, y, bw, bh, bd) = if c == 0 { (xp, yp, w, h, self.pic.bd_y) } else { (xp / 2, yp / 2, w / 2, h / 2, self.pic.bd_c) };
|
||||
// Explicit weights equal to the default ones (weight 2^denom and offset 0 for every
|
||||
// list used, as signalled for most slices of a weighted-prediction stream) give exactly
|
||||
// the default prediction: with log2WD = denom + shift1 the weighted sums of 8-265 /
|
||||
// 8-266 are those of 8-262 / 8-263 multiplied by 2^denom.
|
||||
let pwt = pwt.filter(|pwt| !default_weights(pwt, f, c));
|
||||
// Uni-prediction at a full-sample position without weighting: the prediction
|
||||
// ((s << shift3) + off) >> shift3 (8-228, 8-262) is the reference sample itself.
|
||||
if !bi && !weighted {
|
||||
if !bi && pwt.is_none() {
|
||||
let l = if f.pred(0) { 0 } else { 1 };
|
||||
let frac = if c == 0 { 3 } else { 7 };
|
||||
let mv = f.mv[l];
|
||||
@@ -1472,14 +1478,14 @@ impl<'a> SliceDecoder<'a> {
|
||||
let frame = &self.refs[l][f.ref_idx[l] as usize].frame;
|
||||
let buf = if l == 0 || !bi { &mut self.pred0 } else { &mut self.pred1 };
|
||||
if c == 0 {
|
||||
inter::mc_luma(frame, x, y, f.mv[l], bw, bh, buf, &mut self.mc);
|
||||
inter::mc_luma::<S>(frame, x, y, f.mv[l], bw, bh, buf, &mut self.mc);
|
||||
} else {
|
||||
inter::mc_chroma(frame, c - 1, x, y, f.mv[l], bw, bh, buf, &mut self.mc);
|
||||
inter::mc_chroma::<S>(frame, c - 1, x, y, f.mv[l], bw, bh, buf, &mut self.mc);
|
||||
}
|
||||
}
|
||||
let stride = if c == 0 { self.pic.width } else { self.pic.cwidth };
|
||||
let dst = &mut self.pic.planes[c][y as usize * stride + x as usize..];
|
||||
let Some(pwt) = self.sh.pwt.as_ref().filter(|_| weighted) else {
|
||||
let Some(pwt) = pwt else {
|
||||
if bi {
|
||||
inter::put_bi(&self.pred0, &self.pred1, bw, bh, bd, dst, stride);
|
||||
} else {
|
||||
@@ -1506,3 +1512,15 @@ impl<'a> SliceDecoder<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
let denom = if c == 0 { pwt.luma_log2_denom } else { pwt.chroma_log2_denom };
|
||||
(0..2).filter(|&l| f.pred(l)).all(|l| {
|
||||
pwt.l[l].get(f.ref_idx[l] as usize).is_some_and(|e| {
|
||||
let (wt, o) = if c == 0 { e.luma } else { e.chroma[c - 1] };
|
||||
wt == 1 << denom && o == 0
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user