use crate::config::Pps;
#[derive(Debug, Clone)]
#[allow(dead_code)] pub(crate) struct TileGrid {
pub(crate) cols: usize,
pub(crate) rows: usize,
pub(crate) col_bd: Vec<usize>,
pub(crate) row_bd: Vec<usize>,
pub(crate) col_width: Vec<usize>,
pub(crate) row_height: Vec<usize>,
pub(crate) rs_to_ts: Vec<usize>,
pub(crate) ts_to_rs: Vec<usize>,
pub(crate) tile_id: Vec<usize>,
pub(crate) ctb_cols: usize,
pub(crate) ctb_rows: usize,
pub(crate) loop_filter_across_tiles: bool,
}
impl TileGrid {
pub(crate) fn from_pps(pps: &Pps, ctb_cols: usize, ctb_rows: usize) -> Option<TileGrid> {
if !pps.tiles_enabled || ctb_cols == 0 || ctb_rows == 0 {
return None;
}
let cols = (pps.num_tile_columns as usize).clamp(1, ctb_cols);
let rows = (pps.num_tile_rows as usize).clamp(1, ctb_rows);
let col_width = tile_sizes(
cols,
ctb_cols,
pps.tile_uniform_spacing,
&pps.tile_column_widths,
);
let row_height = tile_sizes(
rows,
ctb_rows,
pps.tile_uniform_spacing,
&pps.tile_row_heights,
);
let mut col_bd = Vec::with_capacity(cols);
let mut acc = 0;
for &w in &col_width {
col_bd.push(acc);
acc += w;
}
let mut row_bd = Vec::with_capacity(rows);
acc = 0;
for &h in &row_height {
row_bd.push(acc);
acc += h;
}
let pic = ctb_cols * ctb_rows;
let (rs_to_ts, ts_to_rs, tile_id) = scan_tables(
ctb_cols,
ctb_rows,
cols,
rows,
&col_bd,
&row_bd,
&col_width,
&row_height,
);
debug_assert_eq!(rs_to_ts.len(), pic);
Some(TileGrid {
cols,
rows,
col_bd,
row_bd,
col_width,
row_height,
rs_to_ts,
ts_to_rs,
tile_id,
ctb_cols,
ctb_rows,
loop_filter_across_tiles: pps.loop_filter_across_tiles,
})
}
#[inline]
pub(crate) fn rs_to_ts(&self, rs: usize) -> usize {
self.rs_to_ts.get(rs).copied().unwrap_or(rs)
}
#[inline]
pub(crate) fn ts_to_rs(&self, ts: usize) -> usize {
self.ts_to_rs.get(ts).copied().unwrap_or(ts)
}
#[inline]
pub(crate) fn is_tile_start_rs(&self, rs: usize) -> bool {
let ts = self.rs_to_ts(rs);
if ts == 0 {
return true;
}
self.tile_id.get(ts) != self.tile_id.get(ts - 1)
}
#[inline]
pub(crate) fn col_of(&self, cx: usize) -> usize {
let mut c = 0;
for (i, &b) in self.col_bd.iter().enumerate() {
if b <= cx {
c = i;
} else {
break;
}
}
c
}
#[inline]
pub(crate) fn row_of(&self, cy: usize) -> usize {
let mut r = 0;
for (i, &b) in self.row_bd.iter().enumerate() {
if b <= cy {
r = i;
} else {
break;
}
}
r
}
#[inline]
pub(crate) fn tile_id_at(&self, cx: usize, cy: usize) -> usize {
self.row_of(cy) * self.cols + self.col_of(cx)
}
#[inline]
pub(crate) fn tile_col_start(&self, cx: usize) -> usize {
self.col_bd[self.col_of(cx)]
}
#[inline]
pub(crate) fn tile_row_start(&self, cy: usize) -> usize {
self.row_bd[self.row_of(cy)]
}
}
fn tile_sizes(n: usize, total_ctbs: usize, uniform: bool, explicit: &[u32]) -> Vec<usize> {
let mut out = Vec::with_capacity(n);
if uniform {
for i in 0..n {
let a = ((i + 1) * total_ctbs) / n;
let b = (i * total_ctbs) / n;
out.push(a - b);
}
} else {
let mut used = 0usize;
for i in 0..n.saturating_sub(1) {
let w = explicit.get(i).copied().unwrap_or(1).max(1) as usize;
let w = w.min(total_ctbs.saturating_sub(used).saturating_sub(n - 1 - i));
let w = w.max(1);
out.push(w);
used += w;
}
out.push(total_ctbs.saturating_sub(used).max(1));
}
out
}
#[allow(clippy::too_many_arguments)]
fn scan_tables(
ctb_cols: usize,
ctb_rows: usize,
cols: usize,
rows: usize,
col_bd: &[usize],
row_bd: &[usize],
col_width: &[usize],
row_height: &[usize],
) -> (Vec<usize>, Vec<usize>, Vec<usize>) {
let pic = ctb_cols * ctb_rows;
let mut rs_to_ts = vec![0usize; pic];
for (rs, dst) in rs_to_ts[..pic].iter_mut().enumerate() {
let tbx = rs % ctb_cols;
let tby = rs / ctb_cols;
let tile_x = col_index(col_bd, col_width, cols, tbx);
let tile_y = row_index(row_bd, row_height, rows, tby);
let mut ts = 0usize;
for &rh in row_height[..tile_y].iter() {
ts += rh * ctb_cols;
}
let rwhy = row_height[tile_y];
for &col_w in col_width[..tile_x].iter() {
ts += rwhy * col_w;
}
ts += (tby - row_bd[tile_y]) * col_width[tile_x] + (tbx - col_bd[tile_x]);
*dst = ts;
}
let mut ts_to_rs = vec![0usize; pic];
for (rs, &ts) in rs_to_ts.iter().enumerate() {
ts_to_rs[ts] = rs;
}
let mut tile_id = vec![0usize; pic];
let mut ts = 0usize;
for ty in 0..rows {
for tx in 0..cols {
let id = ty * cols + tx;
let start_x = col_bd[tx];
let start_y = row_bd[ty];
for y in start_y..start_y + row_height[ty] {
for x in start_x..start_x + col_width[tx] {
let rs = y * ctb_cols + x;
tile_id[rs_to_ts[rs]] = id;
ts += 1;
}
}
}
}
debug_assert_eq!(ts, pic);
(rs_to_ts, ts_to_rs, tile_id)
}
#[inline]
fn col_index(col_bd: &[usize], col_width: &[usize], cols: usize, cx: usize) -> usize {
let mut idx = 0;
for i in 0..cols {
if cx >= col_bd[i] && cx < col_bd[i] + col_width[i] {
idx = i;
break;
}
}
idx
}
#[inline]
fn row_index(row_bd: &[usize], row_height: &[usize], rows: usize, cy: usize) -> usize {
let mut idx = 0;
for i in 0..rows {
if cy >= row_bd[i] && cy < row_bd[i] + row_height[i] {
idx = i;
break;
}
}
idx
}
#[cfg(test)]
mod tests {
use super::*;
fn pps_tiles(cols: u32, rows: u32, uniform: bool, cw: Vec<u32>, rh: Vec<u32>) -> Pps {
let mut p = crate::config::Pps::test_default();
p.tiles_enabled = true;
p.num_tile_columns = cols;
p.num_tile_rows = rows;
p.tile_uniform_spacing = uniform;
p.tile_column_widths = cw;
p.tile_row_heights = rh;
p.loop_filter_across_tiles = false;
p
}
#[test]
fn no_tiles_returns_none() {
let p = Pps::test_default();
assert!(TileGrid::from_pps(&p, 4, 4).is_none());
}
#[test]
fn uniform_2x2_scan_order() {
let p = pps_tiles(2, 2, true, vec![], vec![]);
let g = TileGrid::from_pps(&p, 4, 4).unwrap();
assert_eq!(g.col_width, vec![2, 2]);
assert_eq!(g.row_height, vec![2, 2]);
assert_eq!(g.rs_to_ts(0), 0);
assert_eq!(g.rs_to_ts(1), 1);
assert_eq!(g.rs_to_ts(4), 2);
assert_eq!(g.rs_to_ts(5), 3);
assert_eq!(g.rs_to_ts(2), 4);
assert_eq!(g.rs_to_ts(8), 8);
for rs in 0..16 {
assert_eq!(g.ts_to_rs(g.rs_to_ts(rs)), rs);
}
assert!(g.is_tile_start_rs(2));
assert!(!g.is_tile_start_rs(1));
assert_eq!(g.tile_id_at(0, 0), 0);
assert_eq!(g.tile_id_at(2, 0), 1);
assert_eq!(g.tile_id_at(0, 2), 2);
}
#[test]
fn non_uniform_columns() {
let p = pps_tiles(2, 1, false, vec![3], vec![]);
let g = TileGrid::from_pps(&p, 5, 2).unwrap();
assert_eq!(g.col_width, vec![3, 2]);
assert_eq!(g.col_bd, vec![0, 3]);
}
#[test]
fn uniform_uneven_division() {
let p = pps_tiles(2, 1, true, vec![], vec![]);
let g = TileGrid::from_pps(&p, 5, 1).unwrap();
assert_eq!(g.col_width, vec![2, 3]);
}
#[test]
fn asymmetric_3x2_bijection_and_tile_starts() {
let p = pps_tiles(3, 2, false, vec![2, 3], vec![2]);
let g = TileGrid::from_pps(&p, 7, 5).unwrap();
assert_eq!(g.col_width, vec![2, 3, 2]);
assert_eq!(g.row_height, vec![2, 3]);
let pic = 7 * 5;
let mut seen = vec![false; pic];
for rs in 0..pic {
let ts = g.rs_to_ts(rs);
assert!(ts < pic);
assert!(!seen[ts], "ts {ts} produced twice");
seen[ts] = true;
assert_eq!(g.ts_to_rs(ts), rs);
}
let starts = (0..pic).filter(|&rs| g.is_tile_start_rs(rs)).count();
assert_eq!(starts, 6);
assert!(g.is_tile_start_rs(2)); assert!(g.is_tile_start_rs(2 * 7)); }
}