| 1 | // Copyright (c) 2019-2022, The rav1e contributors. All rights reserved |
| 2 | // |
| 3 | // This source code is subject to the terms of the BSD 2 Clause License and |
| 4 | // the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License |
| 5 | // was not distributed with this source code in the LICENSE file, you can |
| 6 | // obtain it at www.aomedia.org/license/software. If the Alliance for Open |
| 7 | // Media Patent License 1.0 was not distributed with this source code in the |
| 8 | // PATENTS file, you can obtain it at www.aomedia.org/license/patent. |
| 9 | |
| 10 | use super::*; |
| 11 | |
| 12 | use crate::context::*; |
| 13 | use crate::encoder::*; |
| 14 | use crate::me::WriteGuardMEStats; |
| 15 | use crate::util::*; |
| 16 | |
| 17 | use std::iter::FusedIterator; |
| 18 | use std::marker::PhantomData; |
| 19 | use std::ops::DerefMut; |
| 20 | |
| 21 | pub const MAX_TILE_WIDTH: usize = 4096; |
| 22 | pub const MAX_TILE_AREA: usize = 4096 * 2304; |
| 23 | pub const MAX_TILE_COLS: usize = 64; |
| 24 | pub const MAX_TILE_ROWS: usize = 64; |
| 25 | pub const MAX_TILE_RATE: f64 = 4096f64 * 2176f64 * 60f64 * 1.1; |
| 26 | |
| 27 | /// Tiling information |
| 28 | /// |
| 29 | /// This stores everything necessary to split a frame into tiles, and write |
| 30 | /// headers fields into the bitstream. |
| 31 | /// |
| 32 | /// The method `tile_iter_mut()` actually provides tiled views of `FrameState` |
| 33 | /// and `FrameBlocks`. |
| 34 | #[derive (Debug, Clone, Copy)] |
| 35 | pub struct TilingInfo { |
| 36 | pub frame_width: usize, |
| 37 | pub frame_height: usize, |
| 38 | pub tile_width_sb: usize, |
| 39 | pub tile_height_sb: usize, |
| 40 | pub cols: usize, // number of columns of tiles within the whole frame |
| 41 | pub rows: usize, // number of rows of tiles within the whole frame |
| 42 | pub tile_cols_log2: usize, |
| 43 | pub tile_rows_log2: usize, |
| 44 | pub min_tile_cols_log2: usize, |
| 45 | pub max_tile_cols_log2: usize, |
| 46 | pub min_tile_rows_log2: usize, |
| 47 | pub max_tile_rows_log2: usize, |
| 48 | pub sb_size_log2: usize, |
| 49 | pub min_tiles_log2: usize, |
| 50 | } |
| 51 | |
| 52 | impl TilingInfo { |
| 53 | /// # Panics |
| 54 | /// |
| 55 | /// Panics if the resulting tile sizes would be too large. |
| 56 | pub fn from_target_tiles( |
| 57 | sb_size_log2: usize, frame_width: usize, frame_height: usize, |
| 58 | frame_rate: f64, tile_cols_log2: usize, tile_rows_log2: usize, |
| 59 | is_422_p: bool, |
| 60 | ) -> Self { |
| 61 | // <https://aomediacodec.github.io/av1-spec/#tile-info-syntax> |
| 62 | |
| 63 | // Frame::new() aligns to the next multiple of 8 |
| 64 | let frame_width = frame_width.align_power_of_two(3); |
| 65 | let frame_height = frame_height.align_power_of_two(3); |
| 66 | let frame_width_sb = |
| 67 | frame_width.align_power_of_two_and_shift(sb_size_log2); |
| 68 | let frame_height_sb = |
| 69 | frame_height.align_power_of_two_and_shift(sb_size_log2); |
| 70 | let sb_cols = frame_width.align_power_of_two_and_shift(sb_size_log2); |
| 71 | let sb_rows = frame_height.align_power_of_two_and_shift(sb_size_log2); |
| 72 | |
| 73 | // these are bitstream-defined values and must not be changed |
| 74 | let max_tile_width_sb = MAX_TILE_WIDTH >> sb_size_log2; |
| 75 | let max_tile_area_sb = MAX_TILE_AREA >> (2 * sb_size_log2); |
| 76 | let min_tile_cols_log2 = |
| 77 | Self::tile_log2(max_tile_width_sb, sb_cols).unwrap(); |
| 78 | let max_tile_cols_log2 = |
| 79 | Self::tile_log2(1, sb_cols.min(MAX_TILE_COLS)).unwrap(); |
| 80 | let max_tile_rows_log2 = |
| 81 | Self::tile_log2(1, sb_rows.min(MAX_TILE_ROWS)).unwrap(); |
| 82 | let min_tiles_log2 = min_tile_cols_log2 |
| 83 | .max(Self::tile_log2(max_tile_area_sb, sb_cols * sb_rows).unwrap()); |
| 84 | |
| 85 | // Implements restriction in Annex A of the spec. |
| 86 | // Unlike the other restrictions, this one does not change |
| 87 | // the header coding of the tile rows/cols. |
| 88 | let min_tiles_ratelimit_log2 = min_tiles_log2.max( |
| 89 | ((frame_width * frame_height) as f64 * frame_rate / MAX_TILE_RATE) |
| 90 | .ceil() |
| 91 | .log2() |
| 92 | .ceil() as usize, |
| 93 | ); |
| 94 | |
| 95 | let tile_cols_log2 = |
| 96 | tile_cols_log2.clamp(min_tile_cols_log2, max_tile_cols_log2); |
| 97 | let tile_width_sb_pre = |
| 98 | sb_cols.align_power_of_two_and_shift(tile_cols_log2); |
| 99 | |
| 100 | // If this is 4:2:2, our UV horizontal is subsampled but not our |
| 101 | // vertical. Loop Restoration Units must be square, so they |
| 102 | // will always have an even number of horizontal superblocks. For |
| 103 | // tiles and LRUs to align, tile_width_sb must be even in 4:2:2 |
| 104 | // video. |
| 105 | |
| 106 | // This is only relevant when doing loop restoration RDO inline |
| 107 | // with block/superblock encoding, that is, where tiles are |
| 108 | // relevant. If (when) we introduce optionally delaying loop-filter |
| 109 | // encode to after the partitioning loop, we won't need to make |
| 110 | // any 4:2:2 adjustment. |
| 111 | |
| 112 | let tile_width_sb = if is_422_p { |
| 113 | (tile_width_sb_pre + 1) >> 1 << 1 |
| 114 | } else { |
| 115 | tile_width_sb_pre |
| 116 | }; |
| 117 | |
| 118 | let cols = (frame_width_sb + tile_width_sb - 1) / tile_width_sb; |
| 119 | |
| 120 | // Adjust tile_cols_log2 in case of rounding tile_width_sb to even. |
| 121 | let tile_cols_log2 = Self::tile_log2(1, cols).unwrap(); |
| 122 | assert!(tile_cols_log2 >= min_tile_cols_log2); |
| 123 | |
| 124 | let min_tile_rows_log2 = if min_tiles_log2 > tile_cols_log2 { |
| 125 | min_tiles_log2 - tile_cols_log2 |
| 126 | } else { |
| 127 | 0 |
| 128 | }; |
| 129 | let min_tile_rows_ratelimit_log2 = |
| 130 | if min_tiles_ratelimit_log2 > tile_cols_log2 { |
| 131 | min_tiles_ratelimit_log2 - tile_cols_log2 |
| 132 | } else { |
| 133 | 0 |
| 134 | }; |
| 135 | let tile_rows_log2 = tile_rows_log2 |
| 136 | .max(min_tile_rows_log2) |
| 137 | .clamp(min_tile_rows_ratelimit_log2, max_tile_rows_log2); |
| 138 | let tile_height_sb = sb_rows.align_power_of_two_and_shift(tile_rows_log2); |
| 139 | |
| 140 | let rows = (frame_height_sb + tile_height_sb - 1) / tile_height_sb; |
| 141 | |
| 142 | Self { |
| 143 | frame_width, |
| 144 | frame_height, |
| 145 | tile_width_sb, |
| 146 | tile_height_sb, |
| 147 | cols, |
| 148 | rows, |
| 149 | tile_cols_log2, |
| 150 | tile_rows_log2, |
| 151 | min_tile_cols_log2, |
| 152 | max_tile_cols_log2, |
| 153 | min_tile_rows_log2, |
| 154 | max_tile_rows_log2, |
| 155 | sb_size_log2, |
| 156 | min_tiles_log2, |
| 157 | } |
| 158 | } |
| 159 | |
| 160 | /// Return the smallest value for `k` such that `blkSize << k` is greater than |
| 161 | /// or equal to `target`. |
| 162 | /// |
| 163 | /// <https://aomediacodec.github.io/av1-spec/#tile-size-calculation-function> |
| 164 | pub fn tile_log2(blk_size: usize, target: usize) -> Option<usize> { |
| 165 | let mut k = 0; |
| 166 | while (blk_size.checked_shl(k)?) < target { |
| 167 | k += 1; |
| 168 | } |
| 169 | Some(k as usize) |
| 170 | } |
| 171 | |
| 172 | #[inline (always)] |
| 173 | pub const fn tile_count(&self) -> usize { |
| 174 | self.cols * self.rows |
| 175 | } |
| 176 | |
| 177 | /// Split frame-level structures into tiles |
| 178 | /// |
| 179 | /// Provide mutable tiled views of frame-level structures. |
| 180 | pub fn tile_iter_mut<'a, T: Pixel>( |
| 181 | &self, fs: &'a mut FrameState<T>, fb: &'a mut FrameBlocks, |
| 182 | ) -> TileContextIterMut<'a, T> { |
| 183 | let afs = fs as *mut _; |
| 184 | let afb = fb as *mut _; |
| 185 | let frame_me_stats = fs.frame_me_stats.write().expect("poisoned lock" ); |
| 186 | TileContextIterMut { ti: *self, fs: afs, fb: afb, next: 0, frame_me_stats } |
| 187 | } |
| 188 | } |
| 189 | |
| 190 | /// Container for all tiled views |
| 191 | pub struct TileContextMut<'a, T: Pixel> { |
| 192 | pub ts: TileStateMut<'a, T>, |
| 193 | pub tb: TileBlocksMut<'a>, |
| 194 | } |
| 195 | |
| 196 | /// Iterator over tiled views |
| 197 | pub struct TileContextIterMut<'a, T: Pixel> { |
| 198 | ti: TilingInfo, |
| 199 | fs: *mut FrameState<T>, |
| 200 | fb: *mut FrameBlocks, |
| 201 | frame_me_stats: WriteGuardMEStats<'a>, |
| 202 | next: usize, |
| 203 | } |
| 204 | |
| 205 | impl<'a, T: Pixel> Iterator for TileContextIterMut<'a, T> { |
| 206 | type Item = TileContextMut<'a, T>; |
| 207 | |
| 208 | fn next(&mut self) -> Option<Self::Item> { |
| 209 | if self.next < self.ti.rows * self.ti.cols { |
| 210 | let tile_col = self.next % self.ti.cols; |
| 211 | let tile_row = self.next / self.ti.cols; |
| 212 | let ctx = TileContextMut { |
| 213 | ts: { |
| 214 | // SAFETY: Multiple tiles mutably access this struct. |
| 215 | // The dimensions must be configured correctly to ensure |
| 216 | // the tiles do not overlap. |
| 217 | let fs = unsafe { &mut *self.fs }; |
| 218 | // SAFETY: ditto |
| 219 | let frame_me_stats = unsafe { |
| 220 | let len = self.frame_me_stats.len(); |
| 221 | let ptr = self.frame_me_stats.as_mut_ptr(); |
| 222 | std::slice::from_raw_parts_mut(ptr, len) |
| 223 | }; |
| 224 | let sbo = PlaneSuperBlockOffset(SuperBlockOffset { |
| 225 | x: tile_col * self.ti.tile_width_sb, |
| 226 | y: tile_row * self.ti.tile_height_sb, |
| 227 | }); |
| 228 | let x = sbo.0.x << self.ti.sb_size_log2; |
| 229 | let y = sbo.0.y << self.ti.sb_size_log2; |
| 230 | let tile_width = self.ti.tile_width_sb << self.ti.sb_size_log2; |
| 231 | let tile_height = self.ti.tile_height_sb << self.ti.sb_size_log2; |
| 232 | let width = tile_width.min(self.ti.frame_width - x); |
| 233 | let height = tile_height.min(self.ti.frame_height - y); |
| 234 | TileStateMut::new( |
| 235 | fs, |
| 236 | sbo, |
| 237 | self.ti.sb_size_log2, |
| 238 | width, |
| 239 | height, |
| 240 | frame_me_stats, |
| 241 | ) |
| 242 | }, |
| 243 | tb: { |
| 244 | // SAFETY: Multiple tiles mutably access this struct. |
| 245 | // The dimensions must be configured correctly to ensure |
| 246 | // the tiles do not overlap. |
| 247 | let fb = unsafe { &mut *self.fb }; |
| 248 | let tile_width_mi = |
| 249 | self.ti.tile_width_sb << (self.ti.sb_size_log2 - MI_SIZE_LOG2); |
| 250 | let tile_height_mi = |
| 251 | self.ti.tile_height_sb << (self.ti.sb_size_log2 - MI_SIZE_LOG2); |
| 252 | let x = tile_col * tile_width_mi; |
| 253 | let y = tile_row * tile_height_mi; |
| 254 | let cols = tile_width_mi.min(fb.cols - x); |
| 255 | let rows = tile_height_mi.min(fb.rows - y); |
| 256 | TileBlocksMut::new(fb, x, y, cols, rows) |
| 257 | }, |
| 258 | }; |
| 259 | self.next += 1; |
| 260 | Some(ctx) |
| 261 | } else { |
| 262 | None |
| 263 | } |
| 264 | } |
| 265 | |
| 266 | fn size_hint(&self) -> (usize, Option<usize>) { |
| 267 | let remaining = self.ti.cols * self.ti.rows - self.next; |
| 268 | (remaining, Some(remaining)) |
| 269 | } |
| 270 | } |
| 271 | |
| 272 | impl<T: Pixel> ExactSizeIterator for TileContextIterMut<'_, T> {} |
| 273 | impl<T: Pixel> FusedIterator for TileContextIterMut<'_, T> {} |
| 274 | |
| 275 | #[cfg (test)] |
| 276 | pub mod test { |
| 277 | use super::*; |
| 278 | use crate::api::*; |
| 279 | use crate::lrf::*; |
| 280 | use crate::mc::MotionVector; |
| 281 | use crate::predict::PredictionMode; |
| 282 | use std::sync::Arc; |
| 283 | |
| 284 | #[test ] |
| 285 | fn test_tiling_info_from_tile_count() { |
| 286 | let sb_size_log2 = 6; |
| 287 | let (width, height) = (160, 144); |
| 288 | let frame_rate = 25f64; |
| 289 | |
| 290 | let ti = TilingInfo::from_target_tiles( |
| 291 | sb_size_log2, |
| 292 | width, |
| 293 | height, |
| 294 | frame_rate, |
| 295 | 0, |
| 296 | 0, |
| 297 | false, |
| 298 | ); |
| 299 | assert_eq!(1, ti.cols); |
| 300 | assert_eq!(1, ti.rows); |
| 301 | assert_eq!(3, ti.tile_width_sb); |
| 302 | assert_eq!(3, ti.tile_height_sb); |
| 303 | |
| 304 | let ti = TilingInfo::from_target_tiles( |
| 305 | sb_size_log2, |
| 306 | width, |
| 307 | height, |
| 308 | frame_rate, |
| 309 | 1, |
| 310 | 1, |
| 311 | false, |
| 312 | ); |
| 313 | assert_eq!(2, ti.cols); |
| 314 | assert_eq!(2, ti.rows); |
| 315 | assert_eq!(2, ti.tile_width_sb); |
| 316 | assert_eq!(2, ti.tile_height_sb); |
| 317 | |
| 318 | let ti = TilingInfo::from_target_tiles( |
| 319 | sb_size_log2, |
| 320 | width, |
| 321 | height, |
| 322 | frame_rate, |
| 323 | 2, |
| 324 | 2, |
| 325 | false, |
| 326 | ); |
| 327 | assert_eq!(3, ti.cols); |
| 328 | assert_eq!(3, ti.rows); |
| 329 | assert_eq!(1, ti.tile_width_sb); |
| 330 | assert_eq!(1, ti.tile_height_sb); |
| 331 | |
| 332 | // cannot split more than superblocks |
| 333 | let ti = TilingInfo::from_target_tiles( |
| 334 | sb_size_log2, |
| 335 | width, |
| 336 | height, |
| 337 | frame_rate, |
| 338 | 10, |
| 339 | 8, |
| 340 | false, |
| 341 | ); |
| 342 | assert_eq!(3, ti.cols); |
| 343 | assert_eq!(3, ti.rows); |
| 344 | assert_eq!(1, ti.tile_width_sb); |
| 345 | assert_eq!(1, ti.tile_height_sb); |
| 346 | |
| 347 | let ti = TilingInfo::from_target_tiles( |
| 348 | sb_size_log2, |
| 349 | 1024, |
| 350 | 1024, |
| 351 | frame_rate, |
| 352 | 0, |
| 353 | 0, |
| 354 | false, |
| 355 | ); |
| 356 | assert_eq!(1, ti.cols); |
| 357 | assert_eq!(1, ti.rows); |
| 358 | assert_eq!(16, ti.tile_width_sb); |
| 359 | assert_eq!(16, ti.tile_height_sb); |
| 360 | } |
| 361 | |
| 362 | fn setup( |
| 363 | width: usize, height: usize, |
| 364 | ) -> (FrameInvariants<u16>, FrameState<u16>, FrameBlocks, f64) { |
| 365 | // FrameInvariants aligns to the next multiple of 8, so using other values could make tests confusing |
| 366 | assert!(width & 7 == 0); |
| 367 | assert!(height & 7 == 0); |
| 368 | // We test only for 420 for now |
| 369 | let chroma_sampling = ChromaSampling::Cs420; |
| 370 | let config = Arc::new(EncoderConfig { |
| 371 | width, |
| 372 | height, |
| 373 | bit_depth: 8, |
| 374 | chroma_sampling, |
| 375 | ..Default::default() |
| 376 | }); |
| 377 | let mut sequence = Sequence::new(&config); |
| 378 | // These tests are all assuming SB-sized LRUs, so set that. |
| 379 | sequence.enable_large_lru = false; |
| 380 | let frame_rate = config.frame_rate(); |
| 381 | let fi = FrameInvariants::new(config, Arc::new(sequence)); |
| 382 | let fs = FrameState::new(&fi); |
| 383 | let fb = FrameBlocks::new(fi.w_in_b, fi.h_in_b); |
| 384 | |
| 385 | (fi, fs, fb, frame_rate) |
| 386 | } |
| 387 | |
| 388 | #[test ] |
| 389 | fn test_tile_iter_len() { |
| 390 | // frame size 160x144, 40x36 in 4x4-blocks |
| 391 | let (fi, mut fs, mut fb, frame_rate) = setup(160, 144); |
| 392 | |
| 393 | { |
| 394 | // 2x2 tiles |
| 395 | let ti = TilingInfo::from_target_tiles( |
| 396 | fi.sb_size_log2(), |
| 397 | fi.width, |
| 398 | fi.height, |
| 399 | frame_rate, |
| 400 | 1, |
| 401 | 1, |
| 402 | false, |
| 403 | ); |
| 404 | let mut iter = ti.tile_iter_mut(&mut fs, &mut fb); |
| 405 | assert_eq!(4, iter.len()); |
| 406 | assert!(iter.next().is_some()); |
| 407 | assert_eq!(3, iter.len()); |
| 408 | assert!(iter.next().is_some()); |
| 409 | assert_eq!(2, iter.len()); |
| 410 | assert!(iter.next().is_some()); |
| 411 | assert_eq!(1, iter.len()); |
| 412 | assert!(iter.next().is_some()); |
| 413 | assert_eq!(0, iter.len()); |
| 414 | assert!(iter.next().is_none()); |
| 415 | } |
| 416 | |
| 417 | { |
| 418 | // 4x4 tiles requested, will actually get 3x3 tiles |
| 419 | let ti = TilingInfo::from_target_tiles( |
| 420 | fi.sb_size_log2(), |
| 421 | fi.width, |
| 422 | fi.height, |
| 423 | frame_rate, |
| 424 | 2, |
| 425 | 2, |
| 426 | false, |
| 427 | ); |
| 428 | let mut iter = ti.tile_iter_mut(&mut fs, &mut fb); |
| 429 | assert_eq!(9, iter.len()); |
| 430 | assert!(iter.next().is_some()); |
| 431 | assert_eq!(8, iter.len()); |
| 432 | assert!(iter.next().is_some()); |
| 433 | assert_eq!(7, iter.len()); |
| 434 | assert!(iter.next().is_some()); |
| 435 | assert_eq!(6, iter.len()); |
| 436 | assert!(iter.next().is_some()); |
| 437 | assert_eq!(5, iter.len()); |
| 438 | assert!(iter.next().is_some()); |
| 439 | assert_eq!(4, iter.len()); |
| 440 | assert!(iter.next().is_some()); |
| 441 | assert_eq!(3, iter.len()); |
| 442 | assert!(iter.next().is_some()); |
| 443 | assert_eq!(2, iter.len()); |
| 444 | assert!(iter.next().is_some()); |
| 445 | assert_eq!(1, iter.len()); |
| 446 | assert!(iter.next().is_some()); |
| 447 | assert_eq!(0, iter.len()); |
| 448 | assert!(iter.next().is_none()); |
| 449 | } |
| 450 | } |
| 451 | |
| 452 | #[inline ] |
| 453 | fn rect<T: Pixel>( |
| 454 | region: &PlaneRegionMut<'_, T>, |
| 455 | ) -> (isize, isize, usize, usize) { |
| 456 | let &Rect { x, y, width, height } = region.rect(); |
| 457 | (x, y, width, height) |
| 458 | } |
| 459 | |
| 460 | #[test ] |
| 461 | fn test_tile_area() { |
| 462 | let (fi, mut fs, mut fb, frame_rate) = setup(160, 144); |
| 463 | |
| 464 | // 4x4 tiles requested, will actually get 3x3 tiles |
| 465 | let ti = TilingInfo::from_target_tiles( |
| 466 | fi.sb_size_log2(), |
| 467 | fi.width, |
| 468 | fi.height, |
| 469 | frame_rate, |
| 470 | 2, |
| 471 | 2, |
| 472 | false, |
| 473 | ); |
| 474 | let iter = ti.tile_iter_mut(&mut fs, &mut fb); |
| 475 | let tile_states = iter.map(|ctx| ctx.ts).collect::<Vec<_>>(); |
| 476 | |
| 477 | // the frame must be split into 9 tiles: |
| 478 | // |
| 479 | // luma (Y) chroma (U) chroma (V) |
| 480 | // 64x64 64x64 32x64 32x32 32x32 16x32 32x32 32x32 16x32 |
| 481 | // 64x64 64x64 32x64 32x32 32x32 16x32 32x32 32x32 16x32 |
| 482 | // 64x16 64x16 32x16 32x 8 32x 8 16x 8 32x 8 32x 8 16x 8 |
| 483 | |
| 484 | assert_eq!(9, tile_states.len()); |
| 485 | |
| 486 | let tile = &tile_states[0].rec; // the top-left tile |
| 487 | assert_eq!((0, 0, 64, 64), rect(&tile.planes[0])); |
| 488 | assert_eq!((0, 0, 32, 32), rect(&tile.planes[1])); |
| 489 | assert_eq!((0, 0, 32, 32), rect(&tile.planes[2])); |
| 490 | |
| 491 | let tile = &tile_states[1].rec; // the top-middle tile |
| 492 | assert_eq!((64, 0, 64, 64), rect(&tile.planes[0])); |
| 493 | assert_eq!((32, 0, 32, 32), rect(&tile.planes[1])); |
| 494 | assert_eq!((32, 0, 32, 32), rect(&tile.planes[2])); |
| 495 | |
| 496 | let tile = &tile_states[2].rec; // the top-right tile |
| 497 | assert_eq!((128, 0, 64, 64), rect(&tile.planes[0])); |
| 498 | assert_eq!((64, 0, 32, 32), rect(&tile.planes[1])); |
| 499 | assert_eq!((64, 0, 32, 32), rect(&tile.planes[2])); |
| 500 | |
| 501 | let tile = &tile_states[3].rec; // the middle-left tile |
| 502 | assert_eq!((0, 64, 64, 64), rect(&tile.planes[0])); |
| 503 | assert_eq!((0, 32, 32, 32), rect(&tile.planes[1])); |
| 504 | assert_eq!((0, 32, 32, 32), rect(&tile.planes[2])); |
| 505 | |
| 506 | let tile = &tile_states[4].rec; // the center tile |
| 507 | assert_eq!((64, 64, 64, 64), rect(&tile.planes[0])); |
| 508 | assert_eq!((32, 32, 32, 32), rect(&tile.planes[1])); |
| 509 | assert_eq!((32, 32, 32, 32), rect(&tile.planes[2])); |
| 510 | |
| 511 | let tile = &tile_states[5].rec; // the middle-right tile |
| 512 | assert_eq!((128, 64, 64, 64), rect(&tile.planes[0])); |
| 513 | assert_eq!((64, 32, 32, 32), rect(&tile.planes[1])); |
| 514 | assert_eq!((64, 32, 32, 32), rect(&tile.planes[2])); |
| 515 | |
| 516 | let tile = &tile_states[6].rec; // the bottom-left tile |
| 517 | assert_eq!((0, 128, 64, 64), rect(&tile.planes[0])); |
| 518 | assert_eq!((0, 64, 32, 32), rect(&tile.planes[1])); |
| 519 | assert_eq!((0, 64, 32, 32), rect(&tile.planes[2])); |
| 520 | |
| 521 | let tile = &tile_states[7].rec; // the bottom-middle tile |
| 522 | assert_eq!((64, 128, 64, 64), rect(&tile.planes[0])); |
| 523 | assert_eq!((32, 64, 32, 32), rect(&tile.planes[1])); |
| 524 | assert_eq!((32, 64, 32, 32), rect(&tile.planes[2])); |
| 525 | |
| 526 | let tile = &tile_states[8].rec; // the bottom-right tile |
| 527 | assert_eq!((128, 128, 64, 64), rect(&tile.planes[0])); |
| 528 | assert_eq!((64, 64, 32, 32), rect(&tile.planes[1])); |
| 529 | assert_eq!((64, 64, 32, 32), rect(&tile.planes[2])); |
| 530 | } |
| 531 | |
| 532 | #[inline ] |
| 533 | const fn b_area(region: &TileBlocksMut<'_>) -> (usize, usize, usize, usize) { |
| 534 | (region.x(), region.y(), region.cols(), region.rows()) |
| 535 | } |
| 536 | |
| 537 | #[test ] |
| 538 | fn test_tile_blocks_area() { |
| 539 | let (fi, mut fs, mut fb, frame_rate) = setup(160, 144); |
| 540 | |
| 541 | // 4x4 tiles requested, will actually get 3x3 tiles |
| 542 | let ti = TilingInfo::from_target_tiles( |
| 543 | fi.sb_size_log2(), |
| 544 | fi.width, |
| 545 | fi.height, |
| 546 | frame_rate, |
| 547 | 2, |
| 548 | 2, |
| 549 | false, |
| 550 | ); |
| 551 | let iter = ti.tile_iter_mut(&mut fs, &mut fb); |
| 552 | let tbs = iter.map(|ctx| ctx.tb).collect::<Vec<_>>(); |
| 553 | |
| 554 | // the FrameBlocks must be split into 9 TileBlocks: |
| 555 | // |
| 556 | // 16x16 16x16 8x16 |
| 557 | // 16x16 16x16 8x16 |
| 558 | // 16x 4 16x4 8x 4 |
| 559 | |
| 560 | assert_eq!(9, tbs.len()); |
| 561 | |
| 562 | assert_eq!((0, 0, 16, 16), b_area(&tbs[0])); |
| 563 | assert_eq!((16, 0, 16, 16), b_area(&tbs[1])); |
| 564 | assert_eq!((32, 0, 8, 16), b_area(&tbs[2])); |
| 565 | |
| 566 | assert_eq!((0, 16, 16, 16), b_area(&tbs[3])); |
| 567 | assert_eq!((16, 16, 16, 16), b_area(&tbs[4])); |
| 568 | assert_eq!((32, 16, 8, 16), b_area(&tbs[5])); |
| 569 | |
| 570 | assert_eq!((0, 32, 16, 4), b_area(&tbs[6])); |
| 571 | assert_eq!((16, 32, 16, 4), b_area(&tbs[7])); |
| 572 | assert_eq!((32, 32, 8, 4), b_area(&tbs[8])); |
| 573 | } |
| 574 | |
| 575 | #[test ] |
| 576 | fn test_tile_write() { |
| 577 | let (fi, mut fs, mut fb, frame_rate) = setup(160, 144); |
| 578 | |
| 579 | { |
| 580 | // 4x4 tiles requested, will actually get 3x3 tiles |
| 581 | let ti = TilingInfo::from_target_tiles( |
| 582 | fi.sb_size_log2(), |
| 583 | fi.width, |
| 584 | fi.height, |
| 585 | frame_rate, |
| 586 | 2, |
| 587 | 2, |
| 588 | false, |
| 589 | ); |
| 590 | let iter = ti.tile_iter_mut(&mut fs, &mut fb); |
| 591 | let mut tile_states = iter.map(|ctx| ctx.ts).collect::<Vec<_>>(); |
| 592 | |
| 593 | { |
| 594 | // row 12 of Y-plane of the top-left tile |
| 595 | let tile_plane = &mut tile_states[0].rec.planes[0]; |
| 596 | let row = &mut tile_plane[12]; |
| 597 | assert_eq!(64, row.len()); |
| 598 | row[35..41].copy_from_slice(&[4, 42, 12, 18, 15, 31]); |
| 599 | } |
| 600 | |
| 601 | { |
| 602 | // row 8 of U-plane of the middle-right tile |
| 603 | let tile_plane = &mut tile_states[5].rec.planes[1]; |
| 604 | let row = &mut tile_plane[8]; |
| 605 | assert_eq!(32, row.len()); |
| 606 | row[..4].copy_from_slice(&[14, 121, 1, 3]); |
| 607 | } |
| 608 | |
| 609 | { |
| 610 | // row 1 of V-plane of the bottom-middle tile |
| 611 | let tile_plane = &mut tile_states[7].rec.planes[2]; |
| 612 | let row = &mut tile_plane[1]; |
| 613 | assert_eq!(32, row.len()); |
| 614 | row[11..16].copy_from_slice(&[6, 5, 2, 11, 8]); |
| 615 | } |
| 616 | } |
| 617 | |
| 618 | // check that writes on tiles correctly affected the underlying frame |
| 619 | |
| 620 | let plane = &fs.rec.planes[0]; |
| 621 | let y = plane.cfg.yorigin + 12; |
| 622 | let x = plane.cfg.xorigin + 35; |
| 623 | let idx = y * plane.cfg.stride + x; |
| 624 | assert_eq!(&[4, 42, 12, 18, 15, 31], &plane.data[idx..idx + 6]); |
| 625 | |
| 626 | let plane = &fs.rec.planes[1]; |
| 627 | let offset = (64, 32); // middle-right tile, chroma plane |
| 628 | let y = plane.cfg.yorigin + offset.1 + 8; |
| 629 | let x = plane.cfg.xorigin + offset.0; |
| 630 | let idx = y * plane.cfg.stride + x; |
| 631 | assert_eq!(&[14, 121, 1, 3], &plane.data[idx..idx + 4]); |
| 632 | |
| 633 | let plane = &fs.rec.planes[2]; |
| 634 | let offset = (32, 64); // bottom-middle tile, chroma plane |
| 635 | let y = plane.cfg.yorigin + offset.1 + 1; |
| 636 | let x = plane.cfg.xorigin + offset.0 + 11; |
| 637 | let idx = y * plane.cfg.stride + x; |
| 638 | assert_eq!(&[6, 5, 2, 11, 8], &plane.data[idx..idx + 5]); |
| 639 | } |
| 640 | |
| 641 | #[test ] |
| 642 | fn test_tile_restoration_edges() { |
| 643 | let (fi, mut fs, mut fb, frame_rate) = setup(64, 80); |
| 644 | |
| 645 | let ti = TilingInfo::from_target_tiles( |
| 646 | fi.sb_size_log2(), |
| 647 | fi.width, |
| 648 | fi.height, |
| 649 | frame_rate, |
| 650 | 2, |
| 651 | 2, |
| 652 | false, |
| 653 | ); |
| 654 | let iter = ti.tile_iter_mut(&mut fs, &mut fb); |
| 655 | let mut tile_states = iter.map(|ctx| ctx.ts).collect::<Vec<_>>(); |
| 656 | |
| 657 | assert_eq!(tile_states.len(), 2); |
| 658 | |
| 659 | { |
| 660 | let trs = &mut tile_states[0].restoration; |
| 661 | let units = &trs.planes[0].units; |
| 662 | assert_eq!(units.x(), 0); |
| 663 | assert_eq!(units.y(), 0); |
| 664 | assert_eq!(units.cols(), 1); |
| 665 | assert_eq!(units.rows(), 1); |
| 666 | } |
| 667 | |
| 668 | { |
| 669 | let trs = &mut tile_states[1].restoration; |
| 670 | let units = &trs.planes[0].units; |
| 671 | assert_eq!(units.x(), 0); |
| 672 | assert_eq!(units.y(), 1); |
| 673 | // no units, the tile is too small (less than 1/2 super-block) |
| 674 | assert_eq!(units.cols() * units.rows(), 0); |
| 675 | } |
| 676 | } |
| 677 | |
| 678 | #[test ] |
| 679 | fn test_tile_restoration_write() { |
| 680 | let (fi, mut fs, mut fb, frame_rate) = setup(256, 256); |
| 681 | |
| 682 | { |
| 683 | // 2x2 tiles, each one containing 2×2 restoration units (1 super-block per restoration unit) |
| 684 | let ti = TilingInfo::from_target_tiles( |
| 685 | fi.sb_size_log2(), |
| 686 | fi.width, |
| 687 | fi.height, |
| 688 | frame_rate, |
| 689 | 1, |
| 690 | 1, |
| 691 | false, |
| 692 | ); |
| 693 | let iter = ti.tile_iter_mut(&mut fs, &mut fb); |
| 694 | let mut tile_states = iter.map(|ctx| ctx.ts).collect::<Vec<_>>(); |
| 695 | |
| 696 | { |
| 697 | // unit (1, 0) of Y-plane of the top-left tile |
| 698 | let units = &mut tile_states[0].restoration.planes[0].units; |
| 699 | units[0][1].filter = |
| 700 | RestorationFilter::Wiener { coeffs: [[1, 2, 3], [4, 5, 6]] }; |
| 701 | } |
| 702 | |
| 703 | { |
| 704 | // unit (0, 1) of U-plane of the bottom-right tile |
| 705 | let units = &mut tile_states[3].restoration.planes[1].units; |
| 706 | units[1][0].filter = |
| 707 | RestorationFilter::Sgrproj { set: 42, xqd: [10, 20] }; |
| 708 | } |
| 709 | |
| 710 | { |
| 711 | // unit (1, 1) of V-plane of the bottom-left tile |
| 712 | let units = &mut tile_states[2].restoration.planes[2].units; |
| 713 | units[1][1].filter = |
| 714 | RestorationFilter::Sgrproj { set: 5, xqd: [1, 2] }; |
| 715 | } |
| 716 | } |
| 717 | |
| 718 | // check that writes on tiles correctly affected the underlying restoration units |
| 719 | |
| 720 | let units = &mut fs.restoration.planes[0].units; |
| 721 | assert_eq!( |
| 722 | units[0][1].filter, |
| 723 | RestorationFilter::Wiener { coeffs: [[1, 2, 3], [4, 5, 6]] } |
| 724 | ); |
| 725 | |
| 726 | let units = &mut fs.restoration.planes[1].units; |
| 727 | assert_eq!( |
| 728 | units[3][2].filter, |
| 729 | RestorationFilter::Sgrproj { set: 42, xqd: [10, 20] } |
| 730 | ); |
| 731 | |
| 732 | let units = &mut fs.restoration.planes[2].units; |
| 733 | assert_eq!( |
| 734 | units[3][1].filter, |
| 735 | RestorationFilter::Sgrproj { set: 5, xqd: [1, 2] } |
| 736 | ); |
| 737 | } |
| 738 | |
| 739 | #[test ] |
| 740 | fn test_tile_motion_vectors_write() { |
| 741 | let (fi, mut fs, mut fb, frame_rate) = setup(160, 144); |
| 742 | |
| 743 | { |
| 744 | // 4x4 tiles requested, will actually get 3x3 tiles |
| 745 | let ti = TilingInfo::from_target_tiles( |
| 746 | fi.sb_size_log2(), |
| 747 | fi.width, |
| 748 | fi.height, |
| 749 | frame_rate, |
| 750 | 2, |
| 751 | 2, |
| 752 | false, |
| 753 | ); |
| 754 | let iter = ti.tile_iter_mut(&mut fs, &mut fb); |
| 755 | let mut tile_states = iter.map(|ctx| ctx.ts).collect::<Vec<_>>(); |
| 756 | |
| 757 | { |
| 758 | // block (8, 5) of the top-left tile (of the first ref frame) |
| 759 | let me_stats = &mut tile_states[0].me_stats[0]; |
| 760 | me_stats[5][8].mv = MotionVector { col: 42, row: 38 }; |
| 761 | println!("{:?}" , me_stats[5][8].mv); |
| 762 | } |
| 763 | |
| 764 | { |
| 765 | // block (4, 2) of the middle-right tile (of ref frame 2) |
| 766 | let me_stats = &mut tile_states[5].me_stats[2]; |
| 767 | me_stats[2][3].mv = MotionVector { col: 2, row: 14 }; |
| 768 | } |
| 769 | } |
| 770 | |
| 771 | // check that writes on tiled views affected the underlying motion vectors |
| 772 | |
| 773 | let me_stats = &fs.frame_me_stats.read().unwrap()[0]; |
| 774 | assert_eq!(MotionVector { col: 42, row: 38 }, me_stats[5][8].mv); |
| 775 | |
| 776 | let me_stats = &fs.frame_me_stats.read().unwrap()[2]; |
| 777 | let mix = (128 >> MI_SIZE_LOG2) + 3; |
| 778 | let miy = (64 >> MI_SIZE_LOG2) + 2; |
| 779 | assert_eq!(MotionVector { col: 2, row: 14 }, me_stats[miy][mix].mv); |
| 780 | } |
| 781 | |
| 782 | #[test ] |
| 783 | fn test_tile_blocks_write() { |
| 784 | let (fi, mut fs, mut fb, frame_rate) = setup(160, 144); |
| 785 | |
| 786 | { |
| 787 | // 4x4 tiles requested, will actually get 3x3 tiles |
| 788 | let ti = TilingInfo::from_target_tiles( |
| 789 | fi.sb_size_log2(), |
| 790 | fi.width, |
| 791 | fi.height, |
| 792 | frame_rate, |
| 793 | 2, |
| 794 | 2, |
| 795 | false, |
| 796 | ); |
| 797 | let iter = ti.tile_iter_mut(&mut fs, &mut fb); |
| 798 | let mut tbs = iter.map(|ctx| ctx.tb).collect::<Vec<_>>(); |
| 799 | |
| 800 | { |
| 801 | // top-left tile |
| 802 | let tb = &mut tbs[0]; |
| 803 | // block (4, 3) |
| 804 | tb[3][4].n4_w = 42; |
| 805 | // block (8, 5) |
| 806 | tb[5][8].segmentation_idx = 14; |
| 807 | } |
| 808 | |
| 809 | { |
| 810 | // middle-right tile |
| 811 | let tb = &mut tbs[5]; |
| 812 | // block (0, 1) |
| 813 | tb[1][0].n4_h = 11; |
| 814 | // block (7, 5) |
| 815 | tb[5][7].cdef_index = 3; |
| 816 | } |
| 817 | |
| 818 | { |
| 819 | // bottom-middle tile |
| 820 | let tb = &mut tbs[7]; |
| 821 | // block (3, 2) |
| 822 | tb[2][3].mode = PredictionMode::PAETH_PRED; |
| 823 | // block (1, 1) |
| 824 | tb[1][1].n4_w = 8; |
| 825 | } |
| 826 | } |
| 827 | |
| 828 | // check that writes on tiles correctly affected the underlying blocks |
| 829 | |
| 830 | assert_eq!(42, fb[3][4].n4_w); |
| 831 | assert_eq!(14, fb[5][8].segmentation_idx); |
| 832 | |
| 833 | assert_eq!(11, fb[17][32].n4_h); |
| 834 | assert_eq!(3, fb[21][39].cdef_index); |
| 835 | |
| 836 | assert_eq!(PredictionMode::PAETH_PRED, fb[34][19].mode); |
| 837 | assert_eq!(8, fb[33][17].n4_w); |
| 838 | } |
| 839 | |
| 840 | #[test ] |
| 841 | fn tile_log2_overflow() { |
| 842 | assert_eq!(TilingInfo::tile_log2(1, usize::MAX), None); |
| 843 | } |
| 844 | |
| 845 | #[test ] |
| 846 | fn from_target_tiles_422() { |
| 847 | let sb_size_log2 = 6; |
| 848 | let is_422_p = true; |
| 849 | let frame_rate = 60.; |
| 850 | let sb_size = 1 << sb_size_log2; |
| 851 | |
| 852 | for frame_height in (sb_size..4352).step_by(sb_size) { |
| 853 | for tile_rows_log2 in |
| 854 | 0..=TilingInfo::tile_log2(1, frame_height >> sb_size_log2).unwrap() |
| 855 | { |
| 856 | for frame_width in (sb_size..7680).step_by(sb_size) { |
| 857 | for tile_cols_log2 in |
| 858 | 0..=TilingInfo::tile_log2(1, frame_width >> sb_size_log2).unwrap() |
| 859 | { |
| 860 | let ti = TilingInfo::from_target_tiles( |
| 861 | sb_size_log2, |
| 862 | frame_width, |
| 863 | frame_height, |
| 864 | frame_rate, |
| 865 | tile_cols_log2, |
| 866 | tile_rows_log2, |
| 867 | is_422_p, |
| 868 | ); |
| 869 | assert_eq!( |
| 870 | ti.tile_cols_log2, |
| 871 | TilingInfo::tile_log2(1, ti.cols).unwrap() |
| 872 | ); |
| 873 | assert_eq!( |
| 874 | ti.tile_rows_log2, |
| 875 | TilingInfo::tile_log2(1, ti.rows).unwrap() |
| 876 | ); |
| 877 | } |
| 878 | } |
| 879 | } |
| 880 | } |
| 881 | } |
| 882 | } |
| 883 | |