Coverage Report

Created: 2026-02-26 07:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/av-scenechange-0.14.1/src/data/tile.rs
Line
Count
Source
1
use std::iter::FusedIterator;
2
3
use v_frame::{
4
    frame::Frame,
5
    math::Fixed,
6
    pixel::Pixel,
7
    plane::{Plane, PlaneOffset},
8
};
9
10
use crate::data::{
11
    block::BlockOffset,
12
    frame::{FrameState, MAX_PLANES},
13
    motion::{FrameMEStats, TileMEStatsMut, WriteGuardMEStats},
14
    plane::{PlaneBlockOffset, PlaneRegion, Rect},
15
    superblock::{PlaneSuperBlockOffset, SuperBlockOffset, MI_SIZE, MI_SIZE_LOG2, SB_SIZE_LOG2},
16
};
17
18
pub const MAX_TILE_WIDTH: usize = 4096;
19
pub const MAX_TILE_AREA: usize = 4096 * 2304;
20
pub const MAX_TILE_COLS: usize = 64;
21
pub const MAX_TILE_ROWS: usize = 64;
22
pub const MAX_TILE_RATE: f64 = 4096f64 * 2176f64 * 60f64 * 1.1;
23
24
/// Tiled view of a frame
25
#[derive(Debug)]
26
pub struct Tile<'a, T: Pixel> {
27
    pub planes: [PlaneRegion<'a, T>; MAX_PLANES],
28
}
29
30
// common impl for Tile and TileMut
31
macro_rules! tile_common {
32
  // $name: Tile or TileMut
33
  // $pr_type: PlaneRegion or PlaneRegionMut
34
  // $iter: iter or iter_mut
35
  //opt_mut: nothing or mut
36
  ($name:ident, $pr_type:ident, $iter:ident $(,$opt_mut:tt)?) => {
37
    impl<'a, T: Pixel> $name<'a, T> {
38
39
0
      pub fn new(
40
0
        frame: &'a $($opt_mut)? Frame<T>,
41
0
        luma_rect: TileRect,
42
0
      ) -> Self {
43
0
        let mut planes_iter = frame.planes.$iter();
44
0
        Self {
45
0
          planes: [
46
0
            {
47
0
              let plane = planes_iter.next().unwrap();
48
0
              $pr_type::new(plane, luma_rect.into())
49
0
            },
50
0
            {
51
0
              let plane = planes_iter.next().unwrap();
52
0
              let rect = luma_rect.decimated(plane.cfg.xdec, plane.cfg.ydec);
53
0
              $pr_type::new(plane, rect.into())
54
0
            },
55
0
            {
56
0
              let plane = planes_iter.next().unwrap();
57
0
              let rect = luma_rect.decimated(plane.cfg.xdec, plane.cfg.ydec);
58
0
              $pr_type::new(plane, rect.into())
59
0
            },
60
0
          ],
61
0
        }
62
0
      }
Unexecuted instantiation: <av_scenechange::data::tile::Tile<u16>>::new
Unexecuted instantiation: <av_scenechange::data::tile::Tile<u8>>::new
Unexecuted instantiation: <av_scenechange::data::tile::Tile<_>>::new
63
    }
64
  }
65
}
66
67
tile_common!(Tile, PlaneRegion, iter);
68
69
/// Rectangle of a tile, in pixels
70
///
71
/// This is similar to Rect, but with unsigned (x, y) for convenience.
72
#[derive(Debug, Clone, Copy)]
73
pub struct TileRect {
74
    pub x: usize,
75
    pub y: usize,
76
    pub width: usize,
77
    pub height: usize,
78
}
79
80
impl TileRect {
81
0
    pub const fn decimated(self, xdec: usize, ydec: usize) -> Self {
82
0
        Self {
83
0
            x: self.x >> xdec,
84
0
            y: self.y >> ydec,
85
0
            width: self.width >> xdec,
86
0
            height: self.height >> ydec,
87
0
        }
88
0
    }
89
90
0
    pub const fn to_frame_plane_offset(self, tile_po: PlaneOffset) -> PlaneOffset {
91
0
        PlaneOffset {
92
0
            x: self.x as isize + tile_po.x,
93
0
            y: self.y as isize + tile_po.y,
94
0
        }
95
0
    }
96
}
97
98
impl From<TileRect> for Rect {
99
0
    fn from(tile_rect: TileRect) -> Rect {
100
0
        Rect {
101
0
            x: tile_rect.x as isize,
102
0
            y: tile_rect.y as isize,
103
0
            width: tile_rect.width,
104
0
            height: tile_rect.height,
105
0
        }
106
0
    }
107
}
108
109
/// Tiled view of `FrameState`
110
///
111
/// Contrary to `PlaneRegionMut` and `TileMut`, there is no const version:
112
///  - in practice, we don't need it;
113
///  - it would require to instantiate a const version of every of its inner
114
///    tiled views recursively.
115
///
116
/// # `TileState` fields
117
///
118
/// The way the `FrameState` fields are mapped depend on how they are accessed
119
/// tile-wise and frame-wise.
120
///
121
/// Some fields (like `qc`) are only used during tile-encoding, so they are only
122
/// stored in `TileState`.
123
///
124
/// Some other fields (like `input` or `segmentation`) are not written
125
/// tile-wise, so they just reference the matching field in `FrameState`.
126
///
127
/// Some others (like `rec`) are written tile-wise, but must be accessible
128
/// frame-wise once the tile views vanish (e.g. for deblocking).
129
#[derive(Debug)]
130
pub struct TileStateMut<'a, T: Pixel> {
131
    pub sbo: PlaneSuperBlockOffset,
132
    pub sb_width: usize,
133
    pub sb_height: usize,
134
    pub mi_width: usize,
135
    pub mi_height: usize,
136
    pub width: usize,
137
    pub height: usize,
138
    pub input_tile: Tile<'a, T>, // the current tile
139
    pub input_hres: &'a Plane<T>,
140
    pub input_qres: &'a Plane<T>,
141
    pub me_stats: Vec<TileMEStatsMut<'a>>,
142
}
143
144
impl<'a, T: Pixel> TileStateMut<'a, T> {
145
0
    pub fn new(
146
0
        fs: &'a mut FrameState<T>,
147
0
        sbo: PlaneSuperBlockOffset,
148
0
        width: usize,
149
0
        height: usize,
150
0
        frame_me_stats: &'a mut [FrameMEStats],
151
0
    ) -> Self {
152
0
        debug_assert!(
153
0
            width % MI_SIZE == 0,
154
0
            "Tile width must be a multiple of MI_SIZE"
155
        );
156
0
        debug_assert!(
157
0
            height % MI_SIZE == 0,
158
0
            "Tile width must be a multiple of MI_SIZE"
159
        );
160
161
0
        let sb_rounded_width = width.align_power_of_two(SB_SIZE_LOG2);
162
0
        let sb_rounded_height = height.align_power_of_two(SB_SIZE_LOG2);
163
164
0
        let luma_rect = TileRect {
165
0
            x: sbo.0.x << SB_SIZE_LOG2,
166
0
            y: sbo.0.y << SB_SIZE_LOG2,
167
0
            width: sb_rounded_width,
168
0
            height: sb_rounded_height,
169
0
        };
170
0
        let sb_width = width.align_power_of_two_and_shift(SB_SIZE_LOG2);
171
0
        let sb_height = height.align_power_of_two_and_shift(SB_SIZE_LOG2);
172
173
        Self {
174
0
            sbo,
175
0
            sb_width,
176
0
            sb_height,
177
0
            mi_width: width >> MI_SIZE_LOG2,
178
0
            mi_height: height >> MI_SIZE_LOG2,
179
0
            width,
180
0
            height,
181
0
            input_tile: Tile::new(&fs.input, luma_rect),
182
0
            input_hres: &fs.input_hres,
183
0
            input_qres: &fs.input_qres,
184
0
            me_stats: frame_me_stats
185
0
                .iter_mut()
186
0
                .map(|fmvs| {
187
0
                    TileMEStatsMut::new(
188
0
                        fmvs,
189
0
                        sbo.0.x << (SB_SIZE_LOG2 - MI_SIZE_LOG2),
190
0
                        sbo.0.y << (SB_SIZE_LOG2 - MI_SIZE_LOG2),
191
0
                        width >> MI_SIZE_LOG2,
192
0
                        height >> MI_SIZE_LOG2,
193
                    )
194
0
                })
Unexecuted instantiation: <av_scenechange::data::tile::TileStateMut<u16>>::new::{closure#0}
Unexecuted instantiation: <av_scenechange::data::tile::TileStateMut<u8>>::new::{closure#0}
Unexecuted instantiation: <av_scenechange::data::tile::TileStateMut<_>>::new::{closure#0}
195
0
                .collect(),
196
        }
197
0
    }
Unexecuted instantiation: <av_scenechange::data::tile::TileStateMut<u16>>::new
Unexecuted instantiation: <av_scenechange::data::tile::TileStateMut<u8>>::new
Unexecuted instantiation: <av_scenechange::data::tile::TileStateMut<_>>::new
198
199
0
    pub fn to_frame_block_offset(&self, tile_bo: TileBlockOffset) -> PlaneBlockOffset {
200
0
        let bx = self.sbo.0.x << (SB_SIZE_LOG2 - MI_SIZE_LOG2);
201
0
        let by = self.sbo.0.y << (SB_SIZE_LOG2 - MI_SIZE_LOG2);
202
0
        PlaneBlockOffset(BlockOffset {
203
0
            x: bx + tile_bo.0.x,
204
0
            y: by + tile_bo.0.y,
205
0
        })
206
0
    }
Unexecuted instantiation: <av_scenechange::data::tile::TileStateMut<u16>>::to_frame_block_offset
Unexecuted instantiation: <av_scenechange::data::tile::TileStateMut<u8>>::to_frame_block_offset
Unexecuted instantiation: <av_scenechange::data::tile::TileStateMut<_>>::to_frame_block_offset
207
}
208
209
/// Absolute offset in blocks inside a tile, where a block is defined
210
/// to be an `N*N` square where `N == (1 << BLOCK_TO_PLANE_SHIFT)`.
211
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
212
pub struct TileBlockOffset(pub BlockOffset);
213
214
impl TileBlockOffset {
215
    /// Convert to plane offset without decimation.
216
0
    pub const fn to_luma_plane_offset(self) -> PlaneOffset {
217
0
        self.0.to_luma_plane_offset()
218
0
    }
219
220
0
    pub fn with_offset(self, col_offset: isize, row_offset: isize) -> TileBlockOffset {
221
0
        Self(self.0.with_offset(col_offset, row_offset))
222
0
    }
223
}
224
225
/// Tiling information
226
///
227
/// This stores everything necessary to split a frame into tiles, and write
228
/// headers fields into the bitstream.
229
///
230
/// The method `tile_iter_mut()` actually provides tiled views of `FrameState`
231
/// and `FrameBlocks`.
232
#[derive(Debug, Clone, Copy)]
233
pub struct TilingInfo {
234
    pub frame_width: usize,
235
    pub frame_height: usize,
236
    pub tile_width_sb: usize,
237
    pub tile_height_sb: usize,
238
    pub cols: usize, // number of columns of tiles within the whole frame
239
    pub rows: usize, // number of rows of tiles within the whole frame
240
}
241
242
impl TilingInfo {
243
    /// # Panics
244
    ///
245
    /// Panics if the resulting tile sizes would be too large.
246
0
    pub fn from_target_tiles(
247
0
        frame_width: usize,
248
0
        frame_height: usize,
249
0
        frame_rate: f64,
250
0
        tile_cols_log2: usize,
251
0
        tile_rows_log2: usize,
252
0
        is_422_p: bool,
253
0
    ) -> Self {
254
        // <https://aomediacodec.github.io/av1-spec/#tile-info-syntax>
255
256
        // Frame::new() aligns to the next multiple of 8
257
0
        let frame_width = frame_width.align_power_of_two(3);
258
0
        let frame_height = frame_height.align_power_of_two(3);
259
0
        let frame_width_sb = frame_width.align_power_of_two_and_shift(SB_SIZE_LOG2);
260
0
        let frame_height_sb = frame_height.align_power_of_two_and_shift(SB_SIZE_LOG2);
261
0
        let sb_cols = frame_width.align_power_of_two_and_shift(SB_SIZE_LOG2);
262
0
        let sb_rows = frame_height.align_power_of_two_and_shift(SB_SIZE_LOG2);
263
264
        // these are bitstream-defined values and must not be changed
265
0
        let max_tile_width_sb = MAX_TILE_WIDTH >> SB_SIZE_LOG2;
266
0
        let max_tile_area_sb = MAX_TILE_AREA >> (2 * SB_SIZE_LOG2);
267
0
        let min_tile_cols_log2 = Self::tile_log2(max_tile_width_sb, sb_cols).unwrap();
268
0
        let max_tile_cols_log2 = Self::tile_log2(1, sb_cols.min(MAX_TILE_COLS)).unwrap();
269
0
        let max_tile_rows_log2 = Self::tile_log2(1, sb_rows.min(MAX_TILE_ROWS)).unwrap();
270
0
        let min_tiles_log2 =
271
0
            min_tile_cols_log2.max(Self::tile_log2(max_tile_area_sb, sb_cols * sb_rows).unwrap());
272
273
        // Implements restriction in Annex A of the spec.
274
        // Unlike the other restrictions, this one does not change
275
        // the header coding of the tile rows/cols.
276
0
        let min_tiles_ratelimit_log2 = min_tiles_log2.max(
277
0
            ((frame_width * frame_height) as f64 * frame_rate / MAX_TILE_RATE)
278
0
                .ceil()
279
0
                .log2()
280
0
                .ceil() as usize,
281
        );
282
283
0
        let tile_cols_log2 = tile_cols_log2.clamp(min_tile_cols_log2, max_tile_cols_log2);
284
0
        let tile_width_sb_pre = sb_cols.align_power_of_two_and_shift(tile_cols_log2);
285
286
        // If this is 4:2:2, our UV horizontal is subsampled but not our
287
        // vertical.  Loop Restoration Units must be square, so they
288
        // will always have an even number of horizontal superblocks. For
289
        // tiles and LRUs to align, tile_width_sb must be even in 4:2:2
290
        // video.
291
292
        // This is only relevant when doing loop restoration RDO inline
293
        // with block/superblock encoding, that is, where tiles are
294
        // relevant.  If (when) we introduce optionally delaying loop-filter
295
        // encode to after the partitioning loop, we won't need to make
296
        // any 4:2:2 adjustment.
297
298
0
        let tile_width_sb = if is_422_p {
299
0
            (tile_width_sb_pre + 1) >> 1 << 1
300
        } else {
301
0
            tile_width_sb_pre
302
        };
303
304
0
        let cols = frame_width_sb.div_ceil(tile_width_sb);
305
306
        // Adjust tile_cols_log2 in case of rounding tile_width_sb to even.
307
0
        let tile_cols_log2 = Self::tile_log2(1, cols).unwrap();
308
0
        assert!(tile_cols_log2 >= min_tile_cols_log2);
309
310
0
        let min_tile_rows_log2 = min_tiles_log2.saturating_sub(tile_cols_log2);
311
0
        let min_tile_rows_ratelimit_log2 = min_tiles_ratelimit_log2.saturating_sub(tile_cols_log2);
312
0
        let tile_rows_log2 = tile_rows_log2
313
0
            .max(min_tile_rows_log2)
314
0
            .clamp(min_tile_rows_ratelimit_log2, max_tile_rows_log2);
315
0
        let tile_height_sb = sb_rows.align_power_of_two_and_shift(tile_rows_log2);
316
317
0
        let rows = frame_height_sb.div_ceil(tile_height_sb);
318
319
0
        Self {
320
0
            frame_width,
321
0
            frame_height,
322
0
            tile_width_sb,
323
0
            tile_height_sb,
324
0
            cols,
325
0
            rows,
326
0
        }
327
0
    }
328
329
    /// Return the smallest value for `k` such that `blkSize << k` is greater
330
    /// than or equal to `target`.
331
    ///
332
    /// <https://aomediacodec.github.io/av1-spec/#tile-size-calculation-function>
333
0
    pub fn tile_log2(blk_size: usize, target: usize) -> Option<usize> {
334
0
        let mut k = 0;
335
0
        while (blk_size.checked_shl(k)?) < target {
336
0
            k += 1;
337
0
        }
338
0
        Some(k as usize)
339
0
    }
340
341
    /// Split frame-level structures into tiles
342
    ///
343
    /// Provide mutable tiled views of frame-level structures.
344
0
    pub fn tile_iter_mut<'a, T: Pixel>(
345
0
        &self,
346
0
        fs: &'a mut FrameState<T>,
347
0
    ) -> TileContextIterMut<'a, T> {
348
0
        let afs = fs as *mut _;
349
0
        let frame_me_stats = fs.frame_me_stats.write().expect("poisoned lock");
350
0
        TileContextIterMut {
351
0
            ti: *self,
352
0
            fs: afs,
353
0
            next: 0,
354
0
            frame_me_stats,
355
0
        }
356
0
    }
Unexecuted instantiation: <av_scenechange::data::tile::TilingInfo>::tile_iter_mut::<u16>
Unexecuted instantiation: <av_scenechange::data::tile::TilingInfo>::tile_iter_mut::<u8>
Unexecuted instantiation: <av_scenechange::data::tile::TilingInfo>::tile_iter_mut::<_>
357
}
358
359
/// Iterator over tiled views
360
pub struct TileContextIterMut<'a, T: Pixel> {
361
    ti: TilingInfo,
362
    fs: *mut FrameState<T>,
363
    frame_me_stats: WriteGuardMEStats<'a>,
364
    next: usize,
365
}
366
367
impl<'a, T: Pixel> Iterator for TileContextIterMut<'a, T> {
368
    type Item = TileContextMut<'a, T>;
369
370
0
    fn next(&mut self) -> Option<Self::Item> {
371
0
        if self.next < self.ti.rows * self.ti.cols {
372
0
            let tile_col = self.next % self.ti.cols;
373
0
            let tile_row = self.next / self.ti.cols;
374
0
            let ctx = TileContextMut {
375
0
                ts: {
376
0
                    // SAFETY: Multiple tiles mutably access this struct.
377
0
                    // The dimensions must be configured correctly to ensure
378
0
                    // the tiles do not overlap.
379
0
                    let fs = unsafe { &mut *self.fs };
380
0
                    // SAFETY: ditto
381
0
                    let frame_me_stats = unsafe {
382
0
                        let len = self.frame_me_stats.len();
383
0
                        let ptr = self.frame_me_stats.as_mut_ptr();
384
0
                        std::slice::from_raw_parts_mut(ptr, len)
385
0
                    };
386
0
                    let sbo = PlaneSuperBlockOffset(SuperBlockOffset {
387
0
                        x: tile_col * self.ti.tile_width_sb,
388
0
                        y: tile_row * self.ti.tile_height_sb,
389
0
                    });
390
0
                    let x = sbo.0.x << SB_SIZE_LOG2;
391
0
                    let y = sbo.0.y << SB_SIZE_LOG2;
392
0
                    let tile_width = self.ti.tile_width_sb << SB_SIZE_LOG2;
393
0
                    let tile_height = self.ti.tile_height_sb << SB_SIZE_LOG2;
394
0
                    let width = tile_width.min(self.ti.frame_width - x);
395
0
                    let height = tile_height.min(self.ti.frame_height - y);
396
0
                    TileStateMut::new(fs, sbo, width, height, frame_me_stats)
397
0
                },
398
0
            };
399
0
            self.next += 1;
400
0
            Some(ctx)
401
        } else {
402
0
            None
403
        }
404
0
    }
Unexecuted instantiation: <av_scenechange::data::tile::TileContextIterMut<u16> as core::iter::traits::iterator::Iterator>::next
Unexecuted instantiation: <av_scenechange::data::tile::TileContextIterMut<u8> as core::iter::traits::iterator::Iterator>::next
Unexecuted instantiation: <av_scenechange::data::tile::TileContextIterMut<_> as core::iter::traits::iterator::Iterator>::next
405
406
0
    fn size_hint(&self) -> (usize, Option<usize>) {
407
0
        let remaining = self.ti.cols * self.ti.rows - self.next;
408
0
        (remaining, Some(remaining))
409
0
    }
Unexecuted instantiation: <av_scenechange::data::tile::TileContextIterMut<u16> as core::iter::traits::iterator::Iterator>::size_hint
Unexecuted instantiation: <av_scenechange::data::tile::TileContextIterMut<u8> as core::iter::traits::iterator::Iterator>::size_hint
Unexecuted instantiation: <av_scenechange::data::tile::TileContextIterMut<_> as core::iter::traits::iterator::Iterator>::size_hint
410
}
411
412
impl<T: Pixel> ExactSizeIterator for TileContextIterMut<'_, T> {
413
}
414
impl<T: Pixel> FusedIterator for TileContextIterMut<'_, T> {
415
}
416
417
/// Container for all tiled views
418
pub struct TileContextMut<'a, T: Pixel> {
419
    pub ts: TileStateMut<'a, T>,
420
}