use super::*;
pub(super) fn tile_grid_for(
tile_cols: usize,
tile_rows: usize,
width: usize,
height: usize,
) -> Option<(usize, usize)> {
let log2 = |n: usize| {
let mut k = 0;
while (1usize << k) < n {
k += 1;
}
k
};
let (mut lc, mut lr) = (log2(tile_cols.max(1)), log2(tile_rows.max(1)));
let floor_log2 = |n: usize| usize::BITS as usize - 1 - n.max(1).leading_zeros() as usize;
let full_sbs = |dim: usize| ((dim + 7) >> 6).clamp(1, 64);
lc = lc.min(floor_log2(full_sbs(width)));
lr = lr.min(floor_log2(full_sbs(height)));
if lc == 0 && lr == 0 {
return None;
}
Some((lc, lr))
}
pub(super) fn extract_subplane(
p: &[f32],
width: usize,
x0: usize,
y0: usize,
tw: usize,
th: usize,
) -> Vec<f32> {
let mut o = vec![0f32; tw * th];
for (dst_row, src_row) in o
.chunks_exact_mut(tw)
.zip(rect_rows(p, width, y0, x0, tw, th))
{
dst_row.copy_from_slice(src_row);
}
o
}
pub(super) fn pad_plane_pixels<T: Pixel>(
src: &[T],
width: usize,
height: usize,
padded_width: usize,
padded_height: usize,
) -> Vec<T> {
let mut out = vec![T::default(); padded_width * padded_height];
for y in 0..padded_height {
let sy = y.min(height - 1);
for x in 0..padded_width {
out[y * padded_width + x] = src[sy * width + x.min(width - 1)];
}
}
out
}
pub(super) fn extract_subplane_pixels<T: Pixel>(
src: &[T],
stride: usize,
x0: usize,
y0: usize,
width: usize,
height: usize,
) -> Vec<T> {
let mut out = vec![T::default(); width * height];
for row in 0..height {
out[row * width..(row + 1) * width]
.copy_from_slice(&src[(y0 + row) * stride + x0..(y0 + row) * stride + x0 + width]);
}
out
}
pub(super) fn lossless_tile_payload(
frame: &Av2Frame,
config: &Config,
width: usize,
height: usize,
) -> Vec<u8> {
let (_, _, obu) = frame.split_obus();
let mut leb_len = 0usize;
while obu[leb_len] & 0x80 != 0 {
leb_len += 1;
}
leb_len += 1;
let payload = &obu[leb_len + 1..]; let header_len = frame_header(config, width as u32, height as u32, (0, 0, 1)).len();
payload[header_len..].to_vec()
}
fn tile_starts(dim_px: usize, log2: usize) -> Vec<usize> {
let tile_count = 1usize << log2;
let mi = 2 * ((dim_px + 7) >> 3);
let ceil_sbs = (mi + 15) >> 4;
let full_sbs = mi >> 4;
let tile_sbs = full_sbs >> log2;
let extra_sbs = if tile_sbs == 0 {
ceil_sbs
} else {
full_sbs - (tile_sbs << log2)
};
let mut starts = Vec::with_capacity(tile_count.min(ceil_sbs) + 1);
let mut start_sb = 0usize;
for i in 0..tile_count {
if start_sb >= ceil_sbs {
break;
}
starts.push(start_sb);
start_sb += tile_sbs + if i < extra_sbs { 1 } else { 0 };
}
starts.push(ceil_sbs); starts
}
pub(super) fn tile_specs(
width: usize,
height: usize,
log2c: usize,
log2r: usize,
) -> Vec<(usize, usize, usize, usize)> {
let col_starts = tile_starts(width, log2c);
let row_starts = tile_starts(height, log2r);
let mut specs = Vec::new();
for tr in 0..row_starts.len() - 1 {
let (r0, r1) = (row_starts[tr], row_starts[tr + 1]);
let (y0, th) = (r0 * 64, (r1 * 64).min(height) - r0 * 64);
for tc in 0..col_starts.len() - 1 {
let (c0, c1) = (col_starts[tc], col_starts[tc + 1]);
let (x0, tw) = (c0 * 64, (c1 * 64).min(width) - c0 * 64);
specs.push((x0, y0, tw, th));
}
}
specs
}
#[derive(Clone)]
pub(super) struct TileRefCtx {
pub(super) planes: std::sync::Arc<Vec<Vec<f32>>>, pub(super) luma_stride: usize,
pub(super) chroma_stride: usize,
pub(super) x0: usize, pub(super) y0: usize,
}
#[derive(Clone, Copy)]
pub(super) struct TiledEncodeRequest<'a> {
pub(super) y: &'a [f32],
pub(super) u: &'a [f32],
pub(super) v: &'a [f32],
pub(super) width: usize,
pub(super) height: usize,
pub(super) config: &'a Config,
pub(super) color: &'a Cicp,
pub(super) log2_cols: usize,
pub(super) log2_rows: usize,
pub(super) threads: usize,
}
#[derive(Clone, Copy)]
pub(super) struct MultitileAssembly<'a> {
pub(super) config: &'a Config,
pub(super) coded_width: usize,
pub(super) coded_height: usize,
pub(super) display_width: usize,
pub(super) display_height: usize,
pub(super) color: &'a Cicp,
pub(super) log2_cols: usize,
pub(super) log2_rows: usize,
pub(super) bit_depth: u8,
pub(super) chroma_format: ChromaFormat,
}
pub(super) fn assemble_multitile(
assembly: &MultitileAssembly<'_>,
tiles_bytes: &[Vec<u8>],
) -> Av2Frame {
let MultitileAssembly {
config,
coded_width: sig_w,
coded_height: sig_h,
display_width: disp_w,
display_height: disp_h,
color,
log2_cols: log2c,
log2_rows: log2r,
bit_depth,
chroma_format,
} = *assembly;
let n = tiles_bytes.len();
let tsb = 4usize;
let mut frame = frame_header(config, sig_w as u32, sig_h as u32, (log2c, log2r, tsb));
for (i, t) in tiles_bytes.iter().enumerate() {
if i + 1 < n {
let v = t.len() - 1; for b in 0..tsb {
frame.push(((v >> (8 * b)) & 0xff) as u8);
}
}
frame.extend(t);
}
let mut data = vec![];
data.extend(obu(2, &[]));
data.extend(obu(1, &sequence_header(config, sig_w as u32, sig_h as u32)));
data.extend(obu(4, &frame));
Av2Frame {
data,
width: disp_w,
height: disp_h,
coded_width: sig_w,
coded_height: sig_h,
bit_depth,
color: *color,
chroma_format,
tile_grid: (log2c, log2r, tsb),
recon: Vec::new(),
allow_intrabc: config.allow_intrabc,
video_config: config.clone(),
}
}
#[cfg(test)]
mod tests {
use super::{tile_grid_for, tile_specs, tile_starts};
#[test]
fn uniform_spacing_keeps_partial_final_superblock() {
assert_eq!(tile_starts(484, 3), (0usize..=8).collect::<Vec<_>>());
assert_eq!(tile_grid_for(8, 1, 484, 64), Some((2, 0)));
}
#[test]
fn uniform_spacing_matches_full_sb_remainder_rule() {
assert_eq!(tile_starts(4000, 2), vec![0, 16, 32, 47, 63]);
}
#[test]
fn requested_grid_and_emitted_tile_count_stay_in_sync() {
let grid = tile_grid_for(8, 8, 484, 484).unwrap();
assert_eq!(grid, (2, 2));
assert_eq!(tile_specs(484, 484, grid.0, grid.1).len(), 16);
}
#[test]
fn excessive_request_clamps_to_complete_superblocks() {
let grid = tile_grid_for(8, 8, 453, 604).unwrap();
assert_eq!(grid, (2, 3));
let specs = tile_specs(453, 604, grid.0, grid.1);
assert_eq!(specs.len(), 32);
assert!(specs.iter().all(|&(_, _, w, h)| w != 0 && h != 0));
}
}