use crate::bitreader::BitReader;
use crate::Error;
pub const MAX_TILE_WIDTH: u32 = 4096;
pub const MAX_TILE_AREA: u32 = 4096 * 2304;
pub const MAX_TILE_ROWS: u32 = 64;
pub const MAX_TILE_COLS: u32 = 64;
fn tile_log2(blk_size: u32, target: u32) -> u32 {
let mut k = 0u32;
while (blk_size << k) < target {
k += 1;
}
k
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TileInfo {
pub uniform_tile_spacing_flag: bool,
pub tile_cols: u32,
pub tile_rows: u32,
pub tile_cols_log2: u32,
pub tile_rows_log2: u32,
pub context_update_tile_id: u32,
pub tile_size_bytes: u8,
pub mi_col_starts: Vec<u32>,
pub mi_row_starts: Vec<u32>,
}
impl TileInfo {
pub fn is_single_tile(&self) -> bool {
self.tile_cols == 1 && self.tile_rows == 1
}
#[must_use]
pub fn min_uniform_layout(
mi_cols: u32,
mi_rows: u32,
use_128x128_superblock: bool,
) -> Option<(u32, u32)> {
if mi_cols == 0 || mi_rows == 0 {
return None;
}
let (sb_cols, sb_rows, sb_shift) = if use_128x128_superblock {
((mi_cols + 31) >> 5, (mi_rows + 31) >> 5, 5u32)
} else {
((mi_cols + 15) >> 4, (mi_rows + 15) >> 4, 4u32)
};
let sb_size = sb_shift + 2;
let max_tile_width_sb = MAX_TILE_WIDTH >> sb_size;
let max_tile_area_sb = MAX_TILE_AREA >> (2 * sb_size);
let min_log2_tile_cols = tile_log2(max_tile_width_sb, sb_cols);
let min_log2_tiles = min_log2_tile_cols.max(tile_log2(max_tile_area_sb, sb_rows * sb_cols));
Some((
min_log2_tile_cols,
min_log2_tiles.saturating_sub(min_log2_tile_cols),
))
}
#[must_use]
pub fn uniform_layout(
mi_cols: u32,
mi_rows: u32,
use_128x128_superblock: bool,
tile_cols_log2: u32,
tile_rows_log2: u32,
) -> Option<TileInfo> {
if mi_cols == 0 || mi_rows == 0 {
return None;
}
let (sb_cols, sb_rows, sb_shift) = if use_128x128_superblock {
((mi_cols + 31) >> 5, (mi_rows + 31) >> 5, 5u32)
} else {
((mi_cols + 15) >> 4, (mi_rows + 15) >> 4, 4u32)
};
let sb_size = sb_shift + 2;
let max_tile_width_sb = MAX_TILE_WIDTH >> sb_size;
let max_tile_area_sb = MAX_TILE_AREA >> (2 * sb_size);
let min_log2_tile_cols = tile_log2(max_tile_width_sb, sb_cols);
let max_log2_tile_cols = tile_log2(1, sb_cols.min(MAX_TILE_COLS));
let max_log2_tile_rows = tile_log2(1, sb_rows.min(MAX_TILE_ROWS));
let min_log2_tiles = min_log2_tile_cols.max(tile_log2(max_tile_area_sb, sb_rows * sb_cols));
if tile_cols_log2 < min_log2_tile_cols || tile_cols_log2 > max_log2_tile_cols {
return None;
}
let min_log2_tile_rows = min_log2_tiles.saturating_sub(tile_cols_log2);
if tile_rows_log2 < min_log2_tile_rows || tile_rows_log2 > max_log2_tile_rows {
return None;
}
let tile_width_sb = (sb_cols + (1u32 << tile_cols_log2) - 1) >> tile_cols_log2;
let mut mi_col_starts: Vec<u32> = Vec::new();
let mut start_sb: u32 = 0;
while start_sb < sb_cols {
mi_col_starts.push(start_sb << sb_shift);
start_sb += tile_width_sb;
}
let tile_cols = mi_col_starts.len() as u32;
mi_col_starts.push(mi_cols);
let tile_height_sb = (sb_rows + (1u32 << tile_rows_log2) - 1) >> tile_rows_log2;
let mut mi_row_starts: Vec<u32> = Vec::new();
let mut start_sb: u32 = 0;
while start_sb < sb_rows {
mi_row_starts.push(start_sb << sb_shift);
start_sb += tile_height_sb;
}
let tile_rows = mi_row_starts.len() as u32;
mi_row_starts.push(mi_rows);
Some(TileInfo {
uniform_tile_spacing_flag: true,
tile_cols,
tile_rows,
tile_cols_log2,
tile_rows_log2,
context_update_tile_id: 0,
tile_size_bytes: 4,
mi_col_starts,
mi_row_starts,
})
}
#[must_use]
pub fn explicit_layout(
mi_cols: u32,
mi_rows: u32,
use_128x128_superblock: bool,
widths_sb: &[u32],
heights_sb: &[u32],
) -> Option<TileInfo> {
if mi_cols == 0 || mi_rows == 0 || widths_sb.is_empty() || heights_sb.is_empty() {
return None;
}
let (sb_cols, sb_rows, sb_shift) = if use_128x128_superblock {
((mi_cols + 31) >> 5, (mi_rows + 31) >> 5, 5u32)
} else {
((mi_cols + 15) >> 4, (mi_rows + 15) >> 4, 4u32)
};
let sb_size = sb_shift + 2;
let max_tile_width_sb = MAX_TILE_WIDTH >> sb_size;
let max_tile_area_sb = MAX_TILE_AREA >> (2 * sb_size);
let min_log2_tile_cols = tile_log2(max_tile_width_sb, sb_cols);
let min_log2_tiles = min_log2_tile_cols.max(tile_log2(max_tile_area_sb, sb_rows * sb_cols));
if widths_sb.len() as u32 > MAX_TILE_COLS || heights_sb.len() as u32 > MAX_TILE_ROWS {
return None;
}
let mut mi_col_starts: Vec<u32> = Vec::with_capacity(widths_sb.len() + 1);
let mut widest_tile_sb: u32 = 0;
let mut start_sb: u32 = 0;
for &w in widths_sb {
if start_sb >= sb_cols {
return None;
}
let max_width = (sb_cols - start_sb).min(max_tile_width_sb);
if w == 0 || w > max_width {
return None;
}
mi_col_starts.push(start_sb << sb_shift);
widest_tile_sb = widest_tile_sb.max(w);
start_sb += w;
}
if start_sb != sb_cols {
return None;
}
mi_col_starts.push(mi_cols);
let tile_cols = widths_sb.len() as u32;
let max_tile_area_sb_local = if min_log2_tiles > 0 {
(sb_rows * sb_cols) >> (min_log2_tiles + 1)
} else {
sb_rows * sb_cols
};
let max_tile_height_sb = (max_tile_area_sb_local / widest_tile_sb).max(1);
let mut mi_row_starts: Vec<u32> = Vec::with_capacity(heights_sb.len() + 1);
let mut start_sb: u32 = 0;
for &h in heights_sb {
if start_sb >= sb_rows {
return None;
}
let max_height = (sb_rows - start_sb).min(max_tile_height_sb);
if h == 0 || h > max_height {
return None;
}
mi_row_starts.push(start_sb << sb_shift);
start_sb += h;
}
if start_sb != sb_rows {
return None;
}
mi_row_starts.push(mi_rows);
let tile_rows = heights_sb.len() as u32;
Some(TileInfo {
uniform_tile_spacing_flag: false,
tile_cols,
tile_rows,
tile_cols_log2: tile_log2(1, tile_cols),
tile_rows_log2: tile_log2(1, tile_rows),
context_update_tile_id: 0,
tile_size_bytes: 4,
mi_col_starts,
mi_row_starts,
})
}
#[must_use]
pub fn tile_geometry(&self, tile_num: u32) -> Option<crate::cdf::TileGeometry> {
if tile_num >= self.tile_cols * self.tile_rows {
return None;
}
let tile_row = (tile_num / self.tile_cols) as usize;
let tile_col = (tile_num % self.tile_cols) as usize;
Some(crate::cdf::TileGeometry {
mi_row_start: *self.mi_row_starts.get(tile_row)?,
mi_row_end: *self.mi_row_starts.get(tile_row + 1)?,
mi_col_start: *self.mi_col_starts.get(tile_col)?,
mi_col_end: *self.mi_col_starts.get(tile_col + 1)?,
})
}
}
pub fn parse_tile_info(
payload: &[u8],
mi_cols: u32,
mi_rows: u32,
use_128x128_superblock: bool,
) -> Result<(TileInfo, usize), Error> {
let mut br = BitReader::new(payload);
let ti = read_tile_info(&mut br, mi_cols, mi_rows, use_128x128_superblock)?;
Ok((ti, br.position()))
}
pub(crate) fn read_tile_info(
br: &mut BitReader<'_>,
mi_cols: u32,
mi_rows: u32,
use_128x128_superblock: bool,
) -> Result<TileInfo, Error> {
let (sb_cols, sb_rows, sb_shift) = if use_128x128_superblock {
((mi_cols + 31) >> 5, (mi_rows + 31) >> 5, 5u32)
} else {
((mi_cols + 15) >> 4, (mi_rows + 15) >> 4, 4u32)
};
if sb_cols == 0 || sb_rows == 0 {
return Err(Error::PartitionWalkOutOfRange);
}
let sb_size = sb_shift + 2;
let max_tile_width_sb = MAX_TILE_WIDTH >> sb_size;
let max_tile_area_sb = MAX_TILE_AREA >> (2 * sb_size);
let min_log2_tile_cols = tile_log2(max_tile_width_sb, sb_cols);
let max_log2_tile_cols = tile_log2(1, sb_cols.min(MAX_TILE_COLS));
let max_log2_tile_rows = tile_log2(1, sb_rows.min(MAX_TILE_ROWS));
let min_log2_tiles = min_log2_tile_cols.max(tile_log2(max_tile_area_sb, sb_rows * sb_cols));
let uniform_tile_spacing_flag = br.f(1)? == 1;
let mut mi_col_starts: Vec<u32> = Vec::new();
let mut mi_row_starts: Vec<u32> = Vec::new();
let tile_cols;
let tile_rows;
let tile_cols_log2;
let tile_rows_log2;
if uniform_tile_spacing_flag {
let mut tile_cols_log2_local = min_log2_tile_cols;
while tile_cols_log2_local < max_log2_tile_cols {
let increment = br.f(1)? == 1;
if increment {
tile_cols_log2_local += 1;
} else {
break;
}
}
let tile_width_sb = (sb_cols + (1u32 << tile_cols_log2_local) - 1) >> tile_cols_log2_local;
let mut i: u32 = 0;
let mut start_sb: u32 = 0;
while start_sb < sb_cols {
mi_col_starts.push(start_sb << sb_shift);
i += 1;
start_sb += tile_width_sb;
}
mi_col_starts.push(mi_cols);
tile_cols = i;
let min_log2_tile_rows = min_log2_tiles.saturating_sub(tile_cols_log2_local);
let mut tile_rows_log2_local = min_log2_tile_rows;
while tile_rows_log2_local < max_log2_tile_rows {
let increment = br.f(1)? == 1;
if increment {
tile_rows_log2_local += 1;
} else {
break;
}
}
let tile_height_sb = (sb_rows + (1u32 << tile_rows_log2_local) - 1) >> tile_rows_log2_local;
let mut i: u32 = 0;
let mut start_sb: u32 = 0;
while start_sb < sb_rows {
mi_row_starts.push(start_sb << sb_shift);
i += 1;
start_sb += tile_height_sb;
}
mi_row_starts.push(mi_rows);
tile_rows = i;
tile_cols_log2 = tile_cols_log2_local;
tile_rows_log2 = tile_rows_log2_local;
} else {
let mut widest_tile_sb: u32 = 0;
let mut start_sb: u32 = 0;
let mut i: u32 = 0;
while start_sb < sb_cols {
mi_col_starts.push(start_sb << sb_shift);
let max_width = (sb_cols - start_sb).min(max_tile_width_sb);
let width_in_sbs_minus_1 = br.ns(max_width.max(1))?;
let size_sb = width_in_sbs_minus_1 + 1;
widest_tile_sb = widest_tile_sb.max(size_sb);
start_sb += size_sb;
i += 1;
}
mi_col_starts.push(mi_cols);
tile_cols = i;
let tile_cols_log2_local = tile_log2(1, tile_cols);
let max_tile_area_sb_local = if min_log2_tiles > 0 {
(sb_rows * sb_cols) >> (min_log2_tiles + 1)
} else {
sb_rows * sb_cols
};
let max_tile_height_sb = (max_tile_area_sb_local / widest_tile_sb).max(1);
let mut start_sb: u32 = 0;
let mut i: u32 = 0;
while start_sb < sb_rows {
mi_row_starts.push(start_sb << sb_shift);
let max_height = (sb_rows - start_sb).min(max_tile_height_sb);
let height_in_sbs_minus_1 = br.ns(max_height.max(1))?;
let size_sb = height_in_sbs_minus_1 + 1;
start_sb += size_sb;
i += 1;
}
mi_row_starts.push(mi_rows);
tile_rows = i;
let tile_rows_log2_local = tile_log2(1, tile_rows);
tile_cols_log2 = tile_cols_log2_local;
tile_rows_log2 = tile_rows_log2_local;
}
let (context_update_tile_id, tile_size_bytes) = if tile_cols_log2 > 0 || tile_rows_log2 > 0 {
let n = tile_rows_log2 + tile_cols_log2;
let id = br.f(n)? as u32;
if id >= tile_cols * tile_rows {
return Err(Error::PartitionWalkOutOfRange);
}
let tsb_minus_1 = br.f(2)? as u8;
(id, tsb_minus_1 + 1)
} else {
(0, 1)
};
Ok(TileInfo {
uniform_tile_spacing_flag,
tile_cols,
tile_rows,
tile_cols_log2,
tile_rows_log2,
context_update_tile_id,
tile_size_bytes,
mi_col_starts,
mi_row_starts,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tile_log2_smallest_k() {
assert_eq!(tile_log2(1, 1), 0);
assert_eq!(tile_log2(1, 2), 1);
assert_eq!(tile_log2(1, 3), 2);
assert_eq!(tile_log2(4, 8), 1);
assert_eq!(tile_log2(64, 4), 0);
}
#[test]
fn tiny_16x16_single_tile() {
let payload = [0b1000_0000u8];
let (ti, bits) = parse_tile_info(&payload, 4, 4, false).expect("parses");
assert!(ti.uniform_tile_spacing_flag);
assert_eq!(ti.tile_cols, 1);
assert_eq!(ti.tile_rows, 1);
assert_eq!(ti.tile_cols_log2, 0);
assert_eq!(ti.tile_rows_log2, 0);
assert_eq!(ti.context_update_tile_id, 0);
assert_eq!(ti.tile_size_bytes, 1);
assert_eq!(bits, 1);
assert!(ti.is_single_tile());
assert_eq!(ti.mi_col_starts, vec![0, 4]);
assert_eq!(ti.mi_row_starts, vec![0, 4]);
}
#[test]
fn synthetic_256x64_two_tile_columns() {
let payload = [0b1100_0000u8];
let (ti, bits) = parse_tile_info(&payload, 64, 16, false).expect("parses");
assert!(ti.uniform_tile_spacing_flag);
assert_eq!(ti.tile_cols, 2);
assert_eq!(ti.tile_rows, 1);
assert_eq!(ti.tile_cols_log2, 1);
assert_eq!(ti.tile_rows_log2, 0);
assert_eq!(ti.context_update_tile_id, 0);
assert_eq!(ti.tile_size_bytes, 1);
assert_eq!(bits, 6);
assert_eq!(ti.mi_col_starts, vec![0, 32, 64]);
assert_eq!(ti.mi_row_starts, vec![0, 16]);
}
#[test]
fn frame_64x64_single_superblock() {
let payload = [0b1000_0000u8];
let (ti, bits) = parse_tile_info(&payload, 16, 16, false).expect("parses");
assert_eq!(ti.tile_cols, 1);
assert_eq!(ti.tile_rows, 1);
assert_eq!(bits, 1);
assert_eq!(ti.mi_col_starts, vec![0, 16]);
assert_eq!(ti.mi_row_starts, vec![0, 16]);
}
#[test]
fn frame_128x128_with_128_superblock() {
let payload = [0b1000_0000u8];
let (ti, bits) = parse_tile_info(&payload, 32, 32, true).expect("parses");
assert_eq!(ti.tile_cols, 1);
assert_eq!(ti.tile_rows, 1);
assert_eq!(bits, 1);
assert_eq!(ti.mi_col_starts, vec![0, 32]);
assert_eq!(ti.mi_row_starts, vec![0, 32]);
}
#[test]
fn non_uniform_two_tile_columns() {
let payload = [0b0011_0000u8];
let (ti, bits) = parse_tile_info(&payload, 64, 16, false).expect("parses");
assert!(!ti.uniform_tile_spacing_flag);
assert_eq!(ti.tile_cols, 2);
assert_eq!(ti.tile_rows, 1);
assert_eq!(ti.tile_cols_log2, 1);
assert_eq!(ti.tile_rows_log2, 0);
assert_eq!(ti.context_update_tile_id, 0);
assert_eq!(ti.tile_size_bytes, 1);
assert_eq!(bits, 7);
assert_eq!(ti.mi_col_starts, vec![0, 32, 64]);
assert_eq!(ti.mi_row_starts, vec![0, 16]);
}
#[test]
fn truncated_payload_returns_unexpected_end() {
let err = parse_tile_info(&[], 4, 4, false).expect_err("empty payload");
assert_eq!(err, Error::UnexpectedEnd);
}
#[test]
fn context_update_tile_id_out_of_range_rejected() {
let payload = [0b1111_1000u8];
let err = parse_tile_info(&payload, 48, 16, false)
.expect_err("context_update_tile_id == 3 must be rejected (TileCols*TileRows == 3)");
assert_eq!(err, Error::PartitionWalkOutOfRange);
}
#[test]
fn context_update_tile_id_max_in_range_accepted() {
let payload = [0b1111_0000u8];
let (ti, bits) = parse_tile_info(&payload, 48, 16, false).expect("id == 2 < 3 parses");
assert_eq!(ti.tile_cols, 3);
assert_eq!(ti.tile_rows, 1);
assert_eq!(ti.tile_cols_log2, 2);
assert_eq!(ti.tile_rows_log2, 0);
assert_eq!(ti.context_update_tile_id, 2);
assert_eq!(ti.tile_size_bytes, 1);
assert_eq!(bits, 7);
}
#[test]
fn uniform_layout_matches_parser_on_256x128() {
for cols_log2 in 0..=2u32 {
for rows_log2 in 0..=1u32 {
let ti = TileInfo::uniform_layout(64, 32, false, cols_log2, rows_log2)
.expect("legal pair");
assert!(ti.uniform_tile_spacing_flag);
assert_eq!(ti.tile_cols_log2, cols_log2);
assert_eq!(ti.tile_rows_log2, rows_log2);
assert_eq!(ti.tile_cols, 1 << cols_log2.min(2));
assert_eq!(ti.tile_rows, 1 << rows_log2.min(1));
assert_eq!(*ti.mi_col_starts.last().unwrap(), 64);
assert_eq!(*ti.mi_row_starts.last().unwrap(), 32);
let mut covered = vec![false; 64 * 32];
for t in 0..ti.tile_cols * ti.tile_rows {
let g = ti.tile_geometry(t).expect("in range");
for r in g.mi_row_start..g.mi_row_end {
for c in g.mi_col_start..g.mi_col_end {
let cell = &mut covered[(r * 64 + c) as usize];
assert!(!*cell, "tile {t} overlaps at ({r},{c})");
*cell = true;
}
}
}
assert!(covered.iter().all(|&b| b), "tiles must cover the frame");
}
}
assert!(TileInfo::uniform_layout(64, 32, false, 3, 0).is_none());
assert!(TileInfo::uniform_layout(64, 32, false, 0, 2).is_none());
}
#[test]
fn uniform_layout_round_trips_through_the_writer() {
use crate::encoder::bitwriter::BitWriter;
for (mi_cols, mi_rows, cl2, rl2) in
[(64u32, 32u32, 1u32, 1u32), (64, 32, 2, 0), (32, 32, 1, 1)]
{
let ti = TileInfo::uniform_layout(mi_cols, mi_rows, false, cl2, rl2).expect("legal");
let mut bw = BitWriter::new();
crate::encoder::frame_obu::encode_tile_info(&mut bw, &ti);
bw.byte_align();
let bytes = bw.finish();
let (parsed, _) = parse_tile_info(&bytes, mi_cols, mi_rows, false).expect("parses");
assert_eq!(parsed, ti, "layout ({mi_cols}x{mi_rows}, {cl2},{rl2})");
}
}
#[test]
fn zero_frame_dimensions_rejected_not_divide_by_zero() {
for (mi_cols, mi_rows) in [(0u32, 4u32), (4, 0), (0, 0)] {
for sb128 in [false, true] {
let err = parse_tile_info(&[0x00], mi_cols, mi_rows, sb128)
.expect_err("zero-dimension frame must be rejected");
assert_eq!(err, Error::PartitionWalkOutOfRange);
}
}
}
}