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