use crate::loop_filter::PlaneBuffer;
pub const FILTER_BITS: u32 = 7;
pub const SUPERRES_FILTER_BITS: u32 = 6;
pub const SUPERRES_FILTER_SHIFTS: usize = 1 << SUPERRES_FILTER_BITS;
pub const SUPERRES_FILTER_TAPS: usize = 8;
pub const SUPERRES_FILTER_OFFSET: i32 = 3;
pub const SUPERRES_SCALE_BITS: u32 = 14;
pub const SUPERRES_SCALE_MASK: i32 = (1 << SUPERRES_SCALE_BITS) - 1;
pub const SUPERRES_EXTRA_BITS: u32 = SUPERRES_SCALE_BITS - SUPERRES_FILTER_BITS;
#[rustfmt::skip]
pub const UPSCALE_FILTER: [[i32; SUPERRES_FILTER_TAPS]; SUPERRES_FILTER_SHIFTS] = [
[ 0, 0, 0, 128, 0, 0, 0, 0],
[ 0, 0, -1, 128, 2, -1, 0, 0],
[ 0, 1, -3, 127, 4, -2, 1, 0],
[ 0, 1, -4, 127, 6, -3, 1, 0],
[ 0, 2, -6, 126, 8, -3, 1, 0],
[ 0, 2, -7, 125, 11, -4, 1, 0],
[-1, 2, -8, 125, 13, -5, 2, 0],
[-1, 3, -9, 124, 15, -6, 2, 0],
[-1, 3, -10, 123, 18, -6, 2, -1],
[-1, 3, -11, 122, 20, -7, 3, -1],
[-1, 4, -12, 121, 22, -8, 3, -1],
[-1, 4, -13, 120, 25, -9, 3, -1],
[-1, 4, -14, 118, 28, -9, 3, -1],
[-1, 4, -15, 117, 30, -10, 4, -1],
[-1, 5, -16, 116, 32, -11, 4, -1],
[-1, 5, -16, 114, 35, -12, 4, -1],
[-1, 5, -17, 112, 38, -12, 4, -1],
[-1, 5, -18, 111, 40, -13, 5, -1],
[-1, 5, -18, 109, 43, -14, 5, -1],
[-1, 6, -19, 107, 45, -14, 5, -1],
[-1, 6, -19, 105, 48, -15, 5, -1],
[-1, 6, -19, 103, 51, -16, 5, -1],
[-1, 6, -20, 101, 53, -16, 6, -1],
[-1, 6, -20, 99, 56, -17, 6, -1],
[-1, 6, -20, 97, 58, -17, 6, -1],
[-1, 6, -20, 95, 61, -18, 6, -1],
[-2, 7, -20, 93, 64, -18, 6, -2],
[-2, 7, -20, 91, 66, -19, 6, -1],
[-2, 7, -20, 88, 69, -19, 6, -1],
[-2, 7, -20, 86, 71, -19, 6, -1],
[-2, 7, -20, 84, 74, -20, 7, -2],
[-2, 7, -20, 81, 76, -20, 7, -1],
[-2, 7, -20, 79, 79, -20, 7, -2],
[-1, 7, -20, 76, 81, -20, 7, -2],
[-2, 7, -20, 74, 84, -20, 7, -2],
[-1, 6, -19, 71, 86, -20, 7, -2],
[-1, 6, -19, 69, 88, -20, 7, -2],
[-1, 6, -19, 66, 91, -20, 7, -2],
[-2, 6, -18, 64, 93, -20, 7, -2],
[-1, 6, -18, 61, 95, -20, 6, -1],
[-1, 6, -17, 58, 97, -20, 6, -1],
[-1, 6, -17, 56, 99, -20, 6, -1],
[-1, 6, -16, 53, 101, -20, 6, -1],
[-1, 5, -16, 51, 103, -19, 6, -1],
[-1, 5, -15, 48, 105, -19, 6, -1],
[-1, 5, -14, 45, 107, -19, 6, -1],
[-1, 5, -14, 43, 109, -18, 5, -1],
[-1, 5, -13, 40, 111, -18, 5, -1],
[-1, 4, -12, 38, 112, -17, 5, -1],
[-1, 4, -12, 35, 114, -16, 5, -1],
[-1, 4, -11, 32, 116, -16, 5, -1],
[-1, 4, -10, 30, 117, -15, 4, -1],
[-1, 3, -9, 28, 118, -14, 4, -1],
[-1, 3, -9, 25, 120, -13, 4, -1],
[-1, 3, -8, 22, 121, -12, 4, -1],
[-1, 3, -7, 20, 122, -11, 3, -1],
[-1, 2, -6, 18, 123, -10, 3, -1],
[ 0, 2, -6, 15, 124, -9, 3, -1],
[ 0, 2, -5, 13, 125, -8, 2, -1],
[ 0, 1, -4, 11, 125, -7, 2, 0],
[ 0, 1, -3, 8, 126, -6, 2, 0],
[ 0, 1, -3, 6, 127, -4, 1, 0],
[ 0, 1, -2, 4, 127, -3, 1, 0],
[ 0, 0, -1, 2, 128, -1, 0, 0],
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SuperresError {
UpscaledNotLarger,
PlaneShapeMismatch,
PlaneCountMismatch,
}
#[derive(Debug, Clone, Copy)]
pub struct SuperresFrameContext {
pub use_superres: bool,
pub frame_width: u32,
pub upscaled_width: u32,
pub frame_height: u32,
pub mi_cols: u32,
pub num_planes: u8,
pub bit_depth: u8,
pub subsampling_x: u8,
pub subsampling_y: u8,
}
pub fn upscale_frame(
ctx: &SuperresFrameContext,
input_planes: &[PlaneBuffer<'_>],
output_planes: &mut [PlaneBuffer<'_>],
) -> Result<(), SuperresError> {
let num_planes = ctx.num_planes as usize;
if input_planes.len() < num_planes || output_planes.len() < num_planes {
return Err(SuperresError::PlaneCountMismatch);
}
if !ctx.use_superres || ctx.frame_width == ctx.upscaled_width {
for plane in 0..ctx.num_planes {
copy_plane(input_planes, output_planes, plane)?;
}
return Ok(());
}
if ctx.upscaled_width <= ctx.frame_width {
return Err(SuperresError::UpscaledNotLarger);
}
for plane in 0..ctx.num_planes {
upscale_plane(
ctx,
plane,
&input_planes[plane as usize],
&mut output_planes[plane as usize],
)?;
}
Ok(())
}
pub fn upscale_plane(
ctx: &SuperresFrameContext,
plane: u8,
input: &PlaneBuffer<'_>,
output: &mut PlaneBuffer<'_>,
) -> Result<(), SuperresError> {
let (sub_x, sub_y) = subsampling_for_plane(plane, ctx.subsampling_x, ctx.subsampling_y);
let downscaled_plane_w = round2(ctx.frame_width, sub_x);
let upscaled_plane_w = round2(ctx.upscaled_width, sub_x);
let plane_h = round2(ctx.frame_height, sub_y);
if input.rows < plane_h
|| input.cols < downscaled_plane_w
|| output.rows != plane_h
|| output.cols != upscaled_plane_w
{
return Err(SuperresError::PlaneShapeMismatch);
}
if upscaled_plane_w == 0 || plane_h == 0 {
return Ok(());
}
if upscaled_plane_w <= downscaled_plane_w {
return Err(SuperresError::UpscaledNotLarger);
}
let stepx_num: i64 =
(i64::from(downscaled_plane_w) << SUPERRES_SCALE_BITS) + i64::from(upscaled_plane_w / 2);
let step_x: i32 = (stepx_num / i64::from(upscaled_plane_w)) as i32;
let err: i32 = (i64::from(upscaled_plane_w) * i64::from(step_x)
- (i64::from(downscaled_plane_w) << SUPERRES_SCALE_BITS)) as i32;
let diff: i64 = i64::from(upscaled_plane_w) - i64::from(downscaled_plane_w);
let num: i64 = -(diff << (SUPERRES_SCALE_BITS - 1)) + i64::from(upscaled_plane_w / 2);
let division: i32 = (num / i64::from(upscaled_plane_w)) as i32;
let mut initial_subpel_x: i32 = division + (1 << (SUPERRES_EXTRA_BITS - 1)) - err / 2;
initial_subpel_x &= SUPERRES_SCALE_MASK;
let mi_w = ctx.mi_cols >> sub_x;
let min_x: i32 = 0;
let max_x: i32 = (mi_w as i32) * (crate::cdf::MI_SIZE as i32) - 1;
let bit_depth = ctx.bit_depth;
let in_cols = input.cols as usize;
let out_cols = output.cols as usize;
for y in 0..plane_h {
let in_row_off = (y as usize) * in_cols;
let in_row = &input.samples[in_row_off..in_row_off + in_cols];
let out_row_off = (y as usize) * out_cols;
for x in 0..upscaled_plane_w {
let src_x: i32 =
-(1i32 << SUPERRES_SCALE_BITS) + initial_subpel_x + (x as i32) * step_x;
let src_x_px: i32 = src_x >> SUPERRES_SCALE_BITS;
let src_x_subpel: i32 = (src_x & SUPERRES_SCALE_MASK) >> SUPERRES_EXTRA_BITS;
let sample = upscale_sample(
in_row,
src_x_px,
src_x_subpel as usize,
min_x,
max_x,
bit_depth,
);
output.samples[out_row_off + (x as usize)] = sample;
}
}
Ok(())
}
pub fn upscale_sample(
in_row: &[i32],
src_x_px: i32,
src_x_subpel: usize,
min_x: i32,
max_x: i32,
bit_depth: u8,
) -> i32 {
let taps = &UPSCALE_FILTER[src_x_subpel & (SUPERRES_FILTER_SHIFTS - 1)];
let mut sum: i32 = 0;
for (k, &tap) in taps.iter().enumerate() {
let sample_x = clip3(min_x, max_x, src_x_px + (k as i32 - SUPERRES_FILTER_OFFSET));
let idx = sample_x.clamp(0, in_row.len() as i32 - 1) as usize;
sum += in_row[idx] * tap;
}
clip1(bit_depth, round2_i32(sum, FILTER_BITS))
}
#[inline]
fn round2(x: u32, n: u8) -> u32 {
if n == 0 {
x
} else {
(x + (1u32 << (n - 1))) >> n
}
}
#[inline]
fn round2_i32(x: i32, n: u32) -> i32 {
if n == 0 {
x
} else {
(x + (1i32 << (n - 1))) >> n
}
}
#[inline]
fn clip3(a: i32, b: i32, x: i32) -> i32 {
if x < a {
a
} else if x > b {
b
} else {
x
}
}
#[inline]
fn clip1(bit_depth: u8, x: i32) -> i32 {
let hi = (1i32 << bit_depth) - 1;
clip3(0, hi, x)
}
#[inline]
fn subsampling_for_plane(plane: u8, sub_x: u8, sub_y: u8) -> (u8, u8) {
if plane == 0 {
(0, 0)
} else {
(sub_x, sub_y)
}
}
fn copy_plane(
input_planes: &[PlaneBuffer<'_>],
output_planes: &mut [PlaneBuffer<'_>],
plane: u8,
) -> Result<(), SuperresError> {
let p = plane as usize;
let src = &input_planes[p];
let dst = &mut output_planes[p];
if src.rows != dst.rows || src.cols != dst.cols {
return Err(SuperresError::PlaneShapeMismatch);
}
dst.samples.copy_from_slice(src.samples);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn make_plane(rows: u32, cols: u32, fill: i32) -> Vec<i32> {
vec![fill; (rows * cols) as usize]
}
#[test]
fn upscale_filter_table_sums_to_128() {
for (i, row) in UPSCALE_FILTER.iter().enumerate() {
let sum: i32 = row.iter().sum();
assert_eq!(sum, 128, "row {i} sums to {sum}, expected 128");
}
}
#[test]
fn upscale_filter_table_geometry() {
assert_eq!(UPSCALE_FILTER.len(), SUPERRES_FILTER_SHIFTS);
assert_eq!(UPSCALE_FILTER[0].len(), SUPERRES_FILTER_TAPS);
assert_eq!(
UPSCALE_FILTER[0],
[0, 0, 0, 128, 0, 0, 0, 0],
"phase 0 of Upscale_Filter must be identity"
);
}
#[test]
fn upscale_constants_match_spec() {
assert_eq!(SUPERRES_FILTER_SHIFTS, 1 << SUPERRES_FILTER_BITS);
assert_eq!(
SUPERRES_EXTRA_BITS,
SUPERRES_SCALE_BITS - SUPERRES_FILTER_BITS
);
assert_eq!(SUPERRES_SCALE_MASK, (1 << SUPERRES_SCALE_BITS) - 1);
}
#[test]
fn use_superres_false_copies_planes_verbatim() {
let mut src = make_plane(16, 16, 137);
let mut dst = make_plane(16, 16, 0);
let src_buf = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut src,
};
let mut dst_buf = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut dst,
};
let ctx = SuperresFrameContext {
use_superres: false,
frame_width: 16,
upscaled_width: 16,
frame_height: 16,
mi_cols: 4,
num_planes: 1,
bit_depth: 8,
subsampling_x: 0,
subsampling_y: 0,
};
upscale_frame(
&ctx,
std::slice::from_ref(&src_buf),
std::slice::from_mut(&mut dst_buf),
)
.unwrap();
assert!(dst_buf.samples.iter().all(|&v| v == 137));
}
#[test]
fn equal_widths_copies_planes_verbatim() {
let mut src = make_plane(8, 24, 99);
let mut dst = make_plane(8, 24, 0);
let src_buf = PlaneBuffer {
rows: 8,
cols: 24,
samples: &mut src,
};
let mut dst_buf = PlaneBuffer {
rows: 8,
cols: 24,
samples: &mut dst,
};
let ctx = SuperresFrameContext {
use_superres: true,
frame_width: 24,
upscaled_width: 24,
frame_height: 8,
mi_cols: 6,
num_planes: 1,
bit_depth: 8,
subsampling_x: 0,
subsampling_y: 0,
};
upscale_frame(
&ctx,
std::slice::from_ref(&src_buf),
std::slice::from_mut(&mut dst_buf),
)
.unwrap();
assert!(dst_buf.samples.iter().all(|&v| v == 99));
}
#[test]
fn flat_input_yields_flat_output() {
let frame_w = 8u32;
let upscaled_w = 16u32;
let frame_h = 4u32;
let mut src = make_plane(frame_h, frame_w, 80);
let mut dst = make_plane(frame_h, upscaled_w, 0);
let src_buf = PlaneBuffer {
rows: frame_h,
cols: frame_w,
samples: &mut src,
};
let mut dst_buf = PlaneBuffer {
rows: frame_h,
cols: upscaled_w,
samples: &mut dst,
};
let ctx = SuperresFrameContext {
use_superres: true,
frame_width: frame_w,
upscaled_width: upscaled_w,
frame_height: frame_h,
mi_cols: 4, num_planes: 1,
bit_depth: 8,
subsampling_x: 0,
subsampling_y: 0,
};
upscale_frame(
&ctx,
std::slice::from_ref(&src_buf),
std::slice::from_mut(&mut dst_buf),
)
.unwrap();
for (i, &v) in dst_buf.samples.iter().enumerate() {
assert_eq!(v, 80, "pixel {i} = {v}, expected 80");
}
}
#[test]
fn upscale_clips_to_bit_depth() {
let row: [i32; 8] = [255, 0, 255, 0, 255, 0, 255, 0];
let upscaled_w = 24u32;
let mut src = Vec::with_capacity(8);
src.extend_from_slice(&row);
let mut dst = vec![0i32; upscaled_w as usize];
let src_buf = PlaneBuffer {
rows: 1,
cols: 8,
samples: &mut src,
};
let mut dst_buf = PlaneBuffer {
rows: 1,
cols: upscaled_w,
samples: &mut dst,
};
let ctx = SuperresFrameContext {
use_superres: true,
frame_width: 8,
upscaled_width: upscaled_w,
frame_height: 1,
mi_cols: 6, num_planes: 1,
bit_depth: 8,
subsampling_x: 0,
subsampling_y: 0,
};
upscale_frame(
&ctx,
std::slice::from_ref(&src_buf),
std::slice::from_mut(&mut dst_buf),
)
.unwrap();
for &v in dst_buf.samples.iter() {
assert!((0..=255).contains(&v), "sample {v} escaped 8-bit Clip1");
}
}
#[test]
fn upscaled_smaller_than_downscaled_is_rejected() {
let mut src = make_plane(4, 16, 0);
let mut dst = make_plane(4, 8, 0);
let src_buf = PlaneBuffer {
rows: 4,
cols: 16,
samples: &mut src,
};
let mut dst_buf = PlaneBuffer {
rows: 4,
cols: 8,
samples: &mut dst,
};
let ctx = SuperresFrameContext {
use_superres: true,
frame_width: 16,
upscaled_width: 8,
frame_height: 4,
mi_cols: 4,
num_planes: 1,
bit_depth: 8,
subsampling_x: 0,
subsampling_y: 0,
};
let r = upscale_frame(
&ctx,
std::slice::from_ref(&src_buf),
std::slice::from_mut(&mut dst_buf),
);
assert_eq!(r, Err(SuperresError::UpscaledNotLarger));
}
#[test]
fn upscale_sample_phase_zero_is_passthrough() {
let row: Vec<i32> = (0..16).collect();
for src_x_px in 3..13 {
let v = upscale_sample(&row, src_x_px, 0, 0, 15, 8);
assert_eq!(v, src_x_px, "phase 0 at px {src_x_px} returned {v}");
}
}
#[test]
fn upscale_sample_edge_replicates_via_clip3() {
let row = vec![10, 0, 0, 0, 0, 0, 0, 0];
let v = upscale_sample(&row, 0, 0, 0, 7, 8);
assert_eq!(v, 10);
}
#[test]
fn upscale_plane_geometry_mismatch_is_rejected() {
let mut src = make_plane(4, 8, 0);
let mut dst = make_plane(4, 10, 0);
let src_buf = PlaneBuffer {
rows: 4,
cols: 8,
samples: &mut src,
};
let mut dst_buf = PlaneBuffer {
rows: 4,
cols: 10,
samples: &mut dst,
};
let ctx = SuperresFrameContext {
use_superres: true,
frame_width: 8,
upscaled_width: 16,
frame_height: 4,
mi_cols: 4,
num_planes: 1,
bit_depth: 8,
subsampling_x: 0,
subsampling_y: 0,
};
let r = upscale_frame(
&ctx,
std::slice::from_ref(&src_buf),
std::slice::from_mut(&mut dst_buf),
);
assert_eq!(r, Err(SuperresError::PlaneShapeMismatch));
}
#[test]
fn chroma_subsampling_halves_widths() {
let frame_w = 16u32;
let upscaled_w = 24u32;
let frame_h = 8u32;
let chroma_in_w = frame_w.div_ceil(2); let chroma_out_w = upscaled_w.div_ceil(2); let chroma_h = frame_h.div_ceil(2);
let mut y_in = make_plane(frame_h, frame_w, 64);
let mut u_in = make_plane(chroma_h, chroma_in_w, 128);
let mut v_in = make_plane(chroma_h, chroma_in_w, 192);
let mut y_out = make_plane(frame_h, upscaled_w, 0);
let mut u_out = make_plane(chroma_h, chroma_out_w, 0);
let mut v_out = make_plane(chroma_h, chroma_out_w, 0);
let inputs = [
PlaneBuffer {
rows: frame_h,
cols: frame_w,
samples: &mut y_in,
},
PlaneBuffer {
rows: chroma_h,
cols: chroma_in_w,
samples: &mut u_in,
},
PlaneBuffer {
rows: chroma_h,
cols: chroma_in_w,
samples: &mut v_in,
},
];
let mut outputs = [
PlaneBuffer {
rows: frame_h,
cols: upscaled_w,
samples: &mut y_out,
},
PlaneBuffer {
rows: chroma_h,
cols: chroma_out_w,
samples: &mut u_out,
},
PlaneBuffer {
rows: chroma_h,
cols: chroma_out_w,
samples: &mut v_out,
},
];
let ctx = SuperresFrameContext {
use_superres: true,
frame_width: frame_w,
upscaled_width: upscaled_w,
frame_height: frame_h,
mi_cols: 6, num_planes: 3,
bit_depth: 8,
subsampling_x: 1,
subsampling_y: 1,
};
upscale_frame(&ctx, &inputs, &mut outputs).unwrap();
assert!(outputs[0].samples.iter().all(|&v| v == 64));
assert!(outputs[1].samples.iter().all(|&v| v == 128));
assert!(outputs[2].samples.iter().all(|&v| v == 192));
}
}