use std::iter::FusedIterator;
use v_frame::{frame::Frame, pixel::Pixel, plane::Plane};
use crate::{
data::{
block::BlockOffset,
frame::FrameState,
motion::{FrameMEStats, TileMEStatsMut, WriteGuardMEStats},
plane::{PlaneBlockOffset, PlaneOffset, PlaneRegion, Rect},
superblock::{
MI_SIZE,
MI_SIZE_LOG2,
PlaneSuperBlockOffset,
SB_SIZE_LOG2,
SuperBlockOffset,
},
},
math::Fixed,
};
pub const MAX_TILE_WIDTH: usize = 4096;
pub const MAX_TILE_AREA: usize = 4096 * 2304;
pub const MAX_TILE_COLS: usize = 64;
pub const MAX_TILE_ROWS: usize = 64;
pub const MAX_TILE_RATE: f64 = 4096f64 * 2176f64 * 60f64 * 1.1;
#[derive(Debug)]
pub struct Tile<'a, T: Pixel> {
pub y_plane: PlaneRegion<'a, T>,
}
macro_rules! tile_common {
($name:ident, $pr_type:ident, $iter:ident $(,$opt_mut:tt)?) => {
impl<'a, T: Pixel> $name<'a, T> {
pub fn new(
frame: &'a $($opt_mut)? Frame<T>,
luma_rect: TileRect,
) -> Self {
Self {
y_plane: {
let plane = &frame.y_plane;
$pr_type::new(plane, luma_rect.into())
}
}
}
}
}
}
tile_common!(Tile, PlaneRegion, iter);
#[derive(Debug, Clone, Copy)]
pub struct TileRect {
pub x: usize,
pub y: usize,
pub width: usize,
pub height: usize,
}
impl TileRect {
pub const fn to_frame_plane_offset(self, tile_po: PlaneOffset) -> PlaneOffset {
PlaneOffset {
x: self.x as isize + tile_po.x,
y: self.y as isize + tile_po.y,
}
}
}
impl From<TileRect> for Rect {
fn from(tile_rect: TileRect) -> Rect {
Rect {
x: tile_rect.x as isize,
y: tile_rect.y as isize,
width: tile_rect.width,
height: tile_rect.height,
}
}
}
pub struct TileStateMut<'a, T: Pixel> {
pub sbo: PlaneSuperBlockOffset,
pub sb_width: usize,
pub sb_height: usize,
pub mi_width: usize,
pub mi_height: usize,
pub width: usize,
pub height: usize,
pub input_tile: Tile<'a, T>, pub input_hres: Option<&'a Plane<T>>,
pub input_qres: Option<&'a Plane<T>>,
pub me_stats: Vec<TileMEStatsMut<'a>>,
}
impl<'a, T: Pixel> TileStateMut<'a, T> {
pub fn new(
fs: &'a FrameState<T>,
sbo: PlaneSuperBlockOffset,
width: usize,
height: usize,
frame_me_stats: &'a mut [FrameMEStats],
) -> Self {
debug_assert!(
width.is_multiple_of(MI_SIZE),
"Tile width must be a multiple of MI_SIZE"
);
debug_assert!(
height.is_multiple_of(MI_SIZE),
"Tile width must be a multiple of MI_SIZE"
);
let sb_rounded_width = width.align_power_of_two(SB_SIZE_LOG2);
let sb_rounded_height = height.align_power_of_two(SB_SIZE_LOG2);
let luma_rect = TileRect {
x: sbo.0.x << SB_SIZE_LOG2,
y: sbo.0.y << SB_SIZE_LOG2,
width: sb_rounded_width,
height: sb_rounded_height,
};
let sb_width = width.align_power_of_two_and_shift(SB_SIZE_LOG2);
let sb_height = height.align_power_of_two_and_shift(SB_SIZE_LOG2);
Self {
sbo,
sb_width,
sb_height,
mi_width: width >> MI_SIZE_LOG2,
mi_height: height >> MI_SIZE_LOG2,
width,
height,
input_tile: Tile::new(&fs.input, luma_rect),
input_hres: fs.input_hres.as_deref(),
input_qres: fs.input_qres.as_deref(),
me_stats: frame_me_stats
.iter_mut()
.map(|fmvs| {
TileMEStatsMut::new(
fmvs,
sbo.0.x << (SB_SIZE_LOG2 - MI_SIZE_LOG2),
sbo.0.y << (SB_SIZE_LOG2 - MI_SIZE_LOG2),
width >> MI_SIZE_LOG2,
height >> MI_SIZE_LOG2,
)
})
.collect(),
}
}
pub fn to_frame_block_offset(&self, tile_bo: TileBlockOffset) -> PlaneBlockOffset {
let bx = self.sbo.0.x << (SB_SIZE_LOG2 - MI_SIZE_LOG2);
let by = self.sbo.0.y << (SB_SIZE_LOG2 - MI_SIZE_LOG2);
PlaneBlockOffset(BlockOffset {
x: bx + tile_bo.0.x,
y: by + tile_bo.0.y,
})
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct TileBlockOffset(pub BlockOffset);
impl TileBlockOffset {
pub const fn to_luma_plane_offset(self) -> PlaneOffset {
self.0.to_luma_plane_offset()
}
pub fn with_offset(self, col_offset: isize, row_offset: isize) -> TileBlockOffset {
Self(self.0.with_offset(col_offset, row_offset))
}
}
#[derive(Debug, Clone, Copy)]
pub struct TilingInfo {
pub frame_width: usize,
pub frame_height: usize,
pub tile_width_sb: usize,
pub tile_height_sb: usize,
pub cols: usize, pub rows: usize, }
impl TilingInfo {
pub fn from_target_tiles(
frame_width: usize,
frame_height: usize,
frame_rate: f64,
tile_cols_log2: usize,
tile_rows_log2: usize,
is_422_p: bool,
) -> Self {
let frame_width = frame_width.align_power_of_two(3);
let frame_height = frame_height.align_power_of_two(3);
let frame_width_sb = frame_width.align_power_of_two_and_shift(SB_SIZE_LOG2);
let frame_height_sb = frame_height.align_power_of_two_and_shift(SB_SIZE_LOG2);
let sb_cols = frame_width.align_power_of_two_and_shift(SB_SIZE_LOG2);
let sb_rows = frame_height.align_power_of_two_and_shift(SB_SIZE_LOG2);
let max_tile_width_sb = MAX_TILE_WIDTH >> SB_SIZE_LOG2;
let max_tile_area_sb = MAX_TILE_AREA >> (2 * SB_SIZE_LOG2);
let min_tile_cols_log2 =
Self::tile_log2(max_tile_width_sb, sb_cols).expect("invalid tile_log2 count");
let max_tile_cols_log2 =
Self::tile_log2(1, sb_cols.min(MAX_TILE_COLS)).expect("invalid tile_log2 count");
let max_tile_rows_log2 =
Self::tile_log2(1, sb_rows.min(MAX_TILE_ROWS)).expect("invalid tile_log2 count");
let min_tiles_log2 = min_tile_cols_log2.max(
Self::tile_log2(max_tile_area_sb, sb_cols * sb_rows).expect("invalid tile_log2 count"),
);
let min_tiles_ratelimit_log2 = min_tiles_log2.max(
((frame_width * frame_height) as f64 * frame_rate / MAX_TILE_RATE)
.ceil()
.log2()
.ceil() as usize,
);
let tile_cols_log2 = tile_cols_log2.clamp(min_tile_cols_log2, max_tile_cols_log2);
let tile_width_sb_pre = sb_cols.align_power_of_two_and_shift(tile_cols_log2);
let tile_width_sb = if is_422_p {
(tile_width_sb_pre + 1) >> 1 << 1
} else {
tile_width_sb_pre
};
let cols = frame_width_sb.div_ceil(tile_width_sb);
let tile_cols_log2 = Self::tile_log2(1, cols).expect("invalid tile_log2 count");
assert!(tile_cols_log2 >= min_tile_cols_log2);
let min_tile_rows_log2 = min_tiles_log2.saturating_sub(tile_cols_log2);
let min_tile_rows_ratelimit_log2 = min_tiles_ratelimit_log2.saturating_sub(tile_cols_log2);
let tile_rows_log2 = tile_rows_log2
.max(min_tile_rows_log2)
.clamp(min_tile_rows_ratelimit_log2, max_tile_rows_log2);
let tile_height_sb = sb_rows.align_power_of_two_and_shift(tile_rows_log2);
let rows = frame_height_sb.div_ceil(tile_height_sb);
Self {
frame_width,
frame_height,
tile_width_sb,
tile_height_sb,
cols,
rows,
}
}
pub fn tile_log2(blk_size: usize, target: usize) -> Option<usize> {
let mut k = 0;
while (blk_size.checked_shl(k)?) < target {
k += 1;
}
Some(k as usize)
}
#[expect(clippy::needless_pass_by_ref_mut)]
pub fn tile_iter_mut<'a, T: Pixel>(
&mut self,
fs: &'a mut FrameState<T>,
) -> TileContextIterMut<'a, T> {
let afs = fs as *mut _;
let frame_me_stats = fs.frame_me_stats.write().expect("poisoned lock");
TileContextIterMut {
ti: *self,
fs: afs,
next: 0,
frame_me_stats,
}
}
}
pub struct TileContextIterMut<'a, T: Pixel> {
ti: TilingInfo,
fs: *mut FrameState<T>,
frame_me_stats: WriteGuardMEStats<'a>,
next: usize,
}
impl<'a, T: Pixel> Iterator for TileContextIterMut<'a, T> {
type Item = TileContextMut<'a, T>;
fn next(&mut self) -> Option<Self::Item> {
(self.next < self.ti.rows * self.ti.cols).then(|| {
let tile_col = self.next % self.ti.cols;
let tile_row = self.next / self.ti.cols;
let ctx = TileContextMut {
ts: {
let fs = unsafe { &mut *self.fs };
let frame_me_stats = unsafe {
let len = self.frame_me_stats.len();
let ptr = self.frame_me_stats.as_mut_ptr();
std::slice::from_raw_parts_mut(ptr, len)
};
let sbo = PlaneSuperBlockOffset(SuperBlockOffset {
x: tile_col * self.ti.tile_width_sb,
y: tile_row * self.ti.tile_height_sb,
});
let x = sbo.0.x << SB_SIZE_LOG2;
let y = sbo.0.y << SB_SIZE_LOG2;
let tile_width = self.ti.tile_width_sb << SB_SIZE_LOG2;
let tile_height = self.ti.tile_height_sb << SB_SIZE_LOG2;
let width = tile_width.min(self.ti.frame_width - x);
let height = tile_height.min(self.ti.frame_height - y);
TileStateMut::new(fs, sbo, width, height, frame_me_stats)
},
};
self.next += 1;
ctx
})
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.ti.cols * self.ti.rows - self.next;
(remaining, Some(remaining))
}
}
impl<T: Pixel> ExactSizeIterator for TileContextIterMut<'_, T> {
}
impl<T: Pixel> FusedIterator for TileContextIterMut<'_, T> {
}
pub struct TileContextMut<'a, T: Pixel> {
pub ts: TileStateMut<'a, T>,
}