Skip to main content

oximedia_codec/av1/
tile.rs

1//! AV1 tile processing.
2//!
3//! AV1 divides frames into rectangular tiles for parallel processing.
4//! Each tile can be decoded independently, enabling efficient
5//! multi-threaded decoding.
6//!
7//! # Tile Structure
8//!
9//! - Tiles are arranged in a grid pattern
10//! - Minimum tile size is 256x256 luma samples (for level 5.0+: 128x128)
11//! - Maximum of 64 tile columns and 64 tile rows
12//! - Each tile contains superblocks (64x64 or 128x128)
13//!
14//! # Tile Groups
15//!
16//! Tile groups allow tiles to be packaged into separate OBUs
17//! for flexible delivery and error resilience.
18//!
19//! # Reference
20//!
21//! See AV1 Specification Section 5.9.15 for tile info syntax and
22//! Section 6.7.10 for tile info semantics.
23
24#![forbid(unsafe_code)]
25#![allow(dead_code)]
26#![allow(clippy::doc_markdown)]
27#![allow(clippy::unused_self)]
28#![allow(clippy::cast_possible_truncation)]
29#![allow(clippy::trivially_copy_pass_by_ref)]
30#![allow(clippy::match_same_arms)]
31#![allow(clippy::manual_div_ceil)]
32#![allow(clippy::missing_errors_doc)]
33
34use super::frame_header::FrameSize;
35use super::sequence::SequenceHeader;
36use crate::error::{CodecError, CodecResult};
37use oximedia_io::BitReader;
38
39// =============================================================================
40// Constants
41// =============================================================================
42
43/// Maximum number of tile columns.
44pub const MAX_TILE_COLS: usize = 64;
45
46/// Maximum number of tile rows.
47pub const MAX_TILE_ROWS: usize = 64;
48
49/// Maximum number of tiles.
50pub const MAX_TILE_COUNT: usize = MAX_TILE_COLS * MAX_TILE_ROWS;
51
52/// Maximum tile area in superblocks.
53pub const MAX_TILE_AREA_SB: usize = 4096;
54
55/// Minimum tile width in superblocks (for uniform tiles).
56pub const MIN_TILE_WIDTH_SB: usize = 1;
57
58/// Maximum tile width in superblocks.
59pub const MAX_TILE_WIDTH_SB: usize = 64;
60
61/// Minimum tile height in superblocks.
62pub const MIN_TILE_HEIGHT_SB: usize = 1;
63
64/// Maximum tile height in superblocks.
65pub const MAX_TILE_HEIGHT_SB: usize = 64;
66
67/// Tile size bytes field length (for non-last tiles).
68pub const TILE_SIZE_BYTES_MINUS_1_BITS: u8 = 2;
69
70// =============================================================================
71// Structures
72// =============================================================================
73
74/// Tile information from frame header.
75#[derive(Clone, Debug, Default)]
76pub struct TileInfo {
77    /// Number of tile columns.
78    pub tile_cols: u32,
79    /// Number of tile rows.
80    pub tile_rows: u32,
81    /// Tile column start positions (in superblocks).
82    pub tile_col_starts: Vec<u32>,
83    /// Tile row start positions (in superblocks).
84    pub tile_row_starts: Vec<u32>,
85    /// Context update tile ID.
86    pub context_update_tile_id: u32,
87    /// Number of bytes to read for tile size.
88    pub tile_size_bytes: u8,
89    /// Uniform tile spacing flag.
90    pub uniform_tile_spacing: bool,
91    /// Tile columns log2.
92    pub tile_cols_log2: u8,
93    /// Tile rows log2.
94    pub tile_rows_log2: u8,
95    /// Minimum tile columns log2.
96    pub min_tile_cols_log2: u8,
97    /// Maximum tile columns log2.
98    pub max_tile_cols_log2: u8,
99    /// Minimum tile rows log2.
100    pub min_tile_rows_log2: u8,
101    /// Maximum tile rows log2.
102    pub max_tile_rows_log2: u8,
103    /// Superblock columns.
104    pub sb_cols: u32,
105    /// Superblock rows.
106    pub sb_rows: u32,
107    /// Superblock size (64 or 128).
108    pub sb_size: u32,
109}
110
111impl TileInfo {
112    /// Create a new tile info with default values.
113    #[must_use]
114    pub fn new() -> Self {
115        Self::default()
116    }
117
118    /// Parse tile info from the bitstream.
119    ///
120    /// # Errors
121    ///
122    /// Returns error if the bitstream is malformed.
123    #[allow(clippy::too_many_lines, clippy::cast_possible_truncation)]
124    pub fn parse(
125        reader: &mut BitReader<'_>,
126        _seq: &SequenceHeader,
127        frame_size: &FrameSize,
128    ) -> CodecResult<Self> {
129        let mut tile_info = Self::new();
130
131        // Determine superblock size
132        tile_info.sb_size = 64; // Simplified: would come from sequence header
133
134        // Calculate superblock dimensions
135        tile_info.sb_cols = frame_size.sb_cols(tile_info.sb_size);
136        tile_info.sb_rows = frame_size.sb_rows(tile_info.sb_size);
137
138        // Calculate tile limits
139        let max_tile_width_sb = MAX_TILE_WIDTH_SB.min(tile_info.sb_cols as usize);
140        let max_tile_area_sb = MAX_TILE_AREA_SB;
141
142        tile_info.min_tile_cols_log2 = Self::tile_log2(max_tile_width_sb as u32, tile_info.sb_cols);
143        tile_info.max_tile_cols_log2 =
144            Self::tile_log2(1, tile_info.sb_cols.min(MAX_TILE_COLS as u32));
145        tile_info.max_tile_rows_log2 =
146            Self::tile_log2(1, tile_info.sb_rows.min(MAX_TILE_ROWS as u32));
147
148        // Calculate min_log2_tile_rows
149        let min_log2_tiles = Self::tile_log2(
150            max_tile_area_sb as u32,
151            tile_info.sb_cols * tile_info.sb_rows,
152        );
153        tile_info.min_tile_rows_log2 = min_log2_tiles.saturating_sub(tile_info.max_tile_cols_log2);
154
155        // Parse uniform tile spacing flag
156        tile_info.uniform_tile_spacing = reader.read_bit().map_err(CodecError::Core)? != 0;
157
158        if tile_info.uniform_tile_spacing {
159            // Parse tile columns log2
160            tile_info.tile_cols_log2 = tile_info.min_tile_cols_log2;
161            while tile_info.tile_cols_log2 < tile_info.max_tile_cols_log2 {
162                let increment = reader.read_bit().map_err(CodecError::Core)?;
163                if increment != 0 {
164                    tile_info.tile_cols_log2 += 1;
165                } else {
166                    break;
167                }
168            }
169
170            // Calculate tile column starts
171            let tile_width_sb = (tile_info.sb_cols + (1 << tile_info.tile_cols_log2) - 1)
172                >> tile_info.tile_cols_log2;
173            let mut start_sb = 0u32;
174            tile_info.tile_col_starts.clear();
175            while start_sb < tile_info.sb_cols {
176                tile_info.tile_col_starts.push(start_sb);
177                start_sb += tile_width_sb;
178            }
179            tile_info.tile_col_starts.push(tile_info.sb_cols);
180            tile_info.tile_cols = (tile_info.tile_col_starts.len() - 1) as u32;
181
182            // Parse tile rows log2
183            tile_info.tile_rows_log2 = tile_info.min_tile_rows_log2;
184            while tile_info.tile_rows_log2 < tile_info.max_tile_rows_log2 {
185                let increment = reader.read_bit().map_err(CodecError::Core)?;
186                if increment != 0 {
187                    tile_info.tile_rows_log2 += 1;
188                } else {
189                    break;
190                }
191            }
192
193            // Calculate tile row starts
194            let tile_height_sb = (tile_info.sb_rows + (1 << tile_info.tile_rows_log2) - 1)
195                >> tile_info.tile_rows_log2;
196            let mut start_sb = 0u32;
197            tile_info.tile_row_starts.clear();
198            while start_sb < tile_info.sb_rows {
199                tile_info.tile_row_starts.push(start_sb);
200                start_sb += tile_height_sb;
201            }
202            tile_info.tile_row_starts.push(tile_info.sb_rows);
203            tile_info.tile_rows = (tile_info.tile_row_starts.len() - 1) as u32;
204        } else {
205            // Non-uniform tile spacing
206            let mut widest_tile_sb = 0u32;
207            let mut start_sb = 0u32;
208            tile_info.tile_col_starts.clear();
209
210            while start_sb < tile_info.sb_cols {
211                tile_info.tile_col_starts.push(start_sb);
212                let max_width = tile_info.sb_cols - start_sb;
213                let width_in_sbs_minus_1 = Self::ns(reader, max_width)?;
214                let size_sb = width_in_sbs_minus_1 + 1;
215                widest_tile_sb = widest_tile_sb.max(size_sb);
216                start_sb += size_sb;
217            }
218            tile_info.tile_col_starts.push(tile_info.sb_cols);
219            tile_info.tile_cols = (tile_info.tile_col_starts.len() - 1) as u32;
220            tile_info.tile_cols_log2 = Self::tile_log2(1, tile_info.tile_cols);
221
222            // Tile row starts (non-uniform)
223            let mut start_sb = 0u32;
224            tile_info.tile_row_starts.clear();
225
226            while start_sb < tile_info.sb_rows {
227                tile_info.tile_row_starts.push(start_sb);
228                let max_height = tile_info.sb_rows - start_sb;
229                let height_in_sbs_minus_1 = Self::ns(reader, max_height)?;
230                let size_sb = height_in_sbs_minus_1 + 1;
231                start_sb += size_sb;
232            }
233            tile_info.tile_row_starts.push(tile_info.sb_rows);
234            tile_info.tile_rows = (tile_info.tile_row_starts.len() - 1) as u32;
235            tile_info.tile_rows_log2 = Self::tile_log2(1, tile_info.tile_rows);
236        }
237
238        // Context update tile ID
239        if tile_info.tile_cols_log2 > 0 || tile_info.tile_rows_log2 > 0 {
240            let tile_bits = tile_info.tile_cols_log2 + tile_info.tile_rows_log2;
241            tile_info.context_update_tile_id =
242                reader.read_bits(tile_bits).map_err(CodecError::Core)? as u32;
243            tile_info.tile_size_bytes = reader
244                .read_bits(TILE_SIZE_BYTES_MINUS_1_BITS)
245                .map_err(CodecError::Core)? as u8
246                + 1;
247        } else {
248            tile_info.context_update_tile_id = 0;
249            tile_info.tile_size_bytes = 1;
250        }
251
252        Ok(tile_info)
253    }
254
255    /// Calculate tile_log2 value.
256    #[must_use]
257    fn tile_log2(blk_size: u32, target: u32) -> u8 {
258        let mut k = 0u8;
259        while (blk_size << k) < target {
260            k += 1;
261        }
262        k
263    }
264
265    /// Read an ns() (non-symmetric) value from the bitstream.
266    #[allow(clippy::cast_possible_truncation)]
267    fn ns(reader: &mut BitReader<'_>, n: u32) -> CodecResult<u32> {
268        if n <= 1 {
269            return Ok(0);
270        }
271
272        let w = 32 - (n - 1).leading_zeros();
273        let m = (1u32 << w) - n;
274
275        let v = reader.read_bits(w as u8 - 1).map_err(CodecError::Core)? as u32;
276        if v < m {
277            Ok(v)
278        } else {
279            let extra_bit = u32::from(reader.read_bit().map_err(CodecError::Core)?);
280            Ok((v << 1) - m + extra_bit)
281        }
282    }
283
284    /// Get total number of tiles.
285    #[must_use]
286    pub const fn tile_count(&self) -> u32 {
287        self.tile_cols * self.tile_rows
288    }
289
290    /// Get tile dimensions in superblocks for a specific tile.
291    #[must_use]
292    pub fn tile_size_sb(&self, tile_col: u32, tile_row: u32) -> (u32, u32) {
293        let col_start = self
294            .tile_col_starts
295            .get(tile_col as usize)
296            .copied()
297            .unwrap_or(0);
298        let col_end = self
299            .tile_col_starts
300            .get(tile_col as usize + 1)
301            .copied()
302            .unwrap_or(col_start);
303        let row_start = self
304            .tile_row_starts
305            .get(tile_row as usize)
306            .copied()
307            .unwrap_or(0);
308        let row_end = self
309            .tile_row_starts
310            .get(tile_row as usize + 1)
311            .copied()
312            .unwrap_or(row_start);
313
314        (col_end - col_start, row_end - row_start)
315    }
316
317    /// Get tile dimensions in pixels for a specific tile.
318    #[must_use]
319    pub fn tile_size_pixels(&self, tile_col: u32, tile_row: u32) -> (u32, u32) {
320        let (width_sb, height_sb) = self.tile_size_sb(tile_col, tile_row);
321        (width_sb * self.sb_size, height_sb * self.sb_size)
322    }
323
324    /// Get the tile index for a given (col, row) position.
325    #[must_use]
326    pub const fn tile_index(&self, tile_col: u32, tile_row: u32) -> u32 {
327        tile_row * self.tile_cols + tile_col
328    }
329
330    /// Get the (col, row) position for a tile index.
331    #[must_use]
332    pub const fn tile_position(&self, tile_idx: u32) -> (u32, u32) {
333        (tile_idx % self.tile_cols, tile_idx / self.tile_cols)
334    }
335
336    /// Get the starting superblock column for a tile column.
337    #[must_use]
338    pub fn tile_col_start_sb(&self, tile_col: u32) -> u32 {
339        self.tile_col_starts
340            .get(tile_col as usize)
341            .copied()
342            .unwrap_or(0)
343    }
344
345    /// Get the starting superblock row for a tile row.
346    #[must_use]
347    pub fn tile_row_start_sb(&self, tile_row: u32) -> u32 {
348        self.tile_row_starts
349            .get(tile_row as usize)
350            .copied()
351            .unwrap_or(0)
352    }
353
354    /// Get the pixel position for the start of a tile.
355    #[must_use]
356    pub fn tile_start_pixels(&self, tile_col: u32, tile_row: u32) -> (u32, u32) {
357        (
358            self.tile_col_start_sb(tile_col) * self.sb_size,
359            self.tile_row_start_sb(tile_row) * self.sb_size,
360        )
361    }
362
363    /// Check if this is a single-tile frame.
364    #[must_use]
365    pub const fn is_single_tile(&self) -> bool {
366        self.tile_cols == 1 && self.tile_rows == 1
367    }
368
369    /// Check if a tile is at the left edge.
370    #[must_use]
371    pub const fn is_left_edge(&self, tile_col: u32) -> bool {
372        tile_col == 0
373    }
374
375    /// Check if a tile is at the right edge.
376    #[must_use]
377    pub fn is_right_edge(&self, tile_col: u32) -> bool {
378        tile_col == self.tile_cols - 1
379    }
380
381    /// Check if a tile is at the top edge.
382    #[must_use]
383    pub const fn is_top_edge(&self, tile_row: u32) -> bool {
384        tile_row == 0
385    }
386
387    /// Check if a tile is at the bottom edge.
388    #[must_use]
389    pub fn is_bottom_edge(&self, tile_row: u32) -> bool {
390        tile_row == self.tile_rows - 1
391    }
392}
393
394/// Tile configuration for backward compatibility.
395pub type TileConfig = TileInfo;
396
397/// Tile data reference for decoding.
398#[derive(Clone, Debug)]
399pub struct TileData {
400    /// Tile column index.
401    pub tile_col: u32,
402    /// Tile row index.
403    pub tile_row: u32,
404    /// Tile data offset in bitstream.
405    pub offset: usize,
406    /// Tile data size in bytes.
407    pub size: usize,
408    /// Tile index in scan order.
409    pub tile_idx: u32,
410}
411
412impl TileData {
413    /// Create a new tile data reference.
414    #[must_use]
415    pub fn new(tile_col: u32, tile_row: u32, offset: usize, size: usize, tile_cols: u32) -> Self {
416        Self {
417            tile_col,
418            tile_row,
419            offset,
420            size,
421            tile_idx: tile_row * tile_cols + tile_col,
422        }
423    }
424
425    /// Check if this tile data is valid.
426    #[must_use]
427    pub const fn is_valid(&self) -> bool {
428        self.size > 0
429    }
430}
431
432/// Tile group header and data.
433#[derive(Clone, Debug)]
434pub struct TileGroup {
435    /// Starting tile index (inclusive).
436    pub tile_start: u32,
437    /// Ending tile index (inclusive).
438    pub tile_end: u32,
439    /// Tile data entries.
440    pub tiles: Vec<TileData>,
441    /// Number of tiles signaled.
442    pub num_tiles: u32,
443}
444
445impl TileGroup {
446    /// Create a new tile group.
447    #[must_use]
448    pub fn new(tile_start: u32, tile_end: u32) -> Self {
449        Self {
450            tile_start,
451            tile_end,
452            tiles: Vec::new(),
453            num_tiles: tile_end - tile_start + 1,
454        }
455    }
456
457    /// Get number of tiles in this group.
458    #[must_use]
459    pub fn tile_count(&self) -> u32 {
460        self.tile_end - self.tile_start + 1
461    }
462
463    /// Add a tile to this group.
464    pub fn add_tile(&mut self, tile: TileData) {
465        self.tiles.push(tile);
466    }
467
468    /// Get a tile by its index within the group.
469    #[must_use]
470    pub fn get_tile(&self, idx: usize) -> Option<&TileData> {
471        self.tiles.get(idx)
472    }
473
474    /// Check if this group contains a specific tile index.
475    #[must_use]
476    pub const fn contains_tile(&self, tile_idx: u32) -> bool {
477        tile_idx >= self.tile_start && tile_idx <= self.tile_end
478    }
479
480    /// Check if this is a single-tile group.
481    #[must_use]
482    pub const fn is_single_tile(&self) -> bool {
483        self.tile_start == self.tile_end
484    }
485}
486
487/// Tile group OBU parser.
488#[derive(Clone, Debug)]
489pub struct TileGroupObu {
490    /// Tile info from frame header.
491    pub tile_info: TileInfo,
492    /// Tile groups in this OBU.
493    pub groups: Vec<TileGroup>,
494}
495
496impl TileGroupObu {
497    /// Create a new tile group OBU parser.
498    #[must_use]
499    pub fn new(tile_info: TileInfo) -> Self {
500        Self {
501            tile_info,
502            groups: Vec::new(),
503        }
504    }
505
506    /// Parse tile group data from OBU payload.
507    ///
508    /// # Errors
509    ///
510    /// Returns error if the bitstream is malformed.
511    #[allow(clippy::cast_possible_truncation)]
512    pub fn parse(&mut self, data: &[u8]) -> CodecResult<()> {
513        let mut reader = BitReader::new(data);
514        let num_tiles = self.tile_info.tile_count();
515
516        // Parse tile group header
517        let (tile_start, tile_end) = if num_tiles > 1 {
518            let tile_bits = self.tile_info.tile_cols_log2 + self.tile_info.tile_rows_log2;
519            let tile_start = reader.read_bits(tile_bits).map_err(CodecError::Core)? as u32;
520            let tile_end = reader.read_bits(tile_bits).map_err(CodecError::Core)? as u32;
521            (tile_start, tile_end)
522        } else {
523            (0, 0)
524        };
525
526        let mut group = TileGroup::new(tile_start, tile_end);
527
528        // Byte align
529        reader.byte_align();
530
531        // Parse tile data
532        let header_bytes = reader.bits_read().div_ceil(8);
533        let mut offset = header_bytes;
534
535        for tile_idx in tile_start..=tile_end {
536            let tile_size = if tile_idx < tile_end {
537                // Read tile size (little-endian, tile_size_bytes bytes)
538                let size_bytes = self.tile_info.tile_size_bytes as usize;
539                let mut size = 0u32;
540                for i in 0..size_bytes {
541                    if offset + i >= data.len() {
542                        return Err(CodecError::InvalidBitstream(
543                            "Tile data truncated".to_string(),
544                        ));
545                    }
546                    size |= u32::from(data[offset + i]) << (8 * i);
547                }
548                offset += size_bytes;
549                (size + 1) as usize
550            } else {
551                // Last tile uses remaining data
552                data.len() - offset
553            };
554
555            let (tile_col, tile_row) = self.tile_info.tile_position(tile_idx);
556            let tile_data = TileData::new(
557                tile_col,
558                tile_row,
559                offset,
560                tile_size,
561                self.tile_info.tile_cols,
562            );
563
564            group.add_tile(tile_data);
565            offset += tile_size;
566        }
567
568        self.groups.push(group);
569        Ok(())
570    }
571
572    /// Get total number of tiles across all groups.
573    #[must_use]
574    pub fn total_tiles(&self) -> usize {
575        self.groups.iter().map(|g| g.tiles.len()).sum()
576    }
577
578    /// Get a specific tile by global index.
579    #[must_use]
580    pub fn get_tile(&self, tile_idx: u32) -> Option<&TileData> {
581        for group in &self.groups {
582            if group.contains_tile(tile_idx) {
583                let local_idx = (tile_idx - group.tile_start) as usize;
584                return group.get_tile(local_idx);
585            }
586        }
587        None
588    }
589
590    /// Check if all tiles have been received.
591    #[must_use]
592    pub fn is_complete(&self) -> bool {
593        let total_expected = self.tile_info.tile_count() as usize;
594        self.total_tiles() == total_expected
595    }
596}
597
598/// Tile decoder state for a single tile.
599#[derive(Clone, Debug, Default)]
600pub struct TileDecoderState {
601    /// Tile column.
602    pub tile_col: u32,
603    /// Tile row.
604    pub tile_row: u32,
605    /// Current superblock column within tile.
606    pub sb_col: u32,
607    /// Current superblock row within tile.
608    pub sb_row: u32,
609    /// Tile width in superblocks.
610    pub tile_width_sb: u32,
611    /// Tile height in superblocks.
612    pub tile_height_sb: u32,
613    /// Decoding completed for this tile.
614    pub completed: bool,
615}
616
617impl TileDecoderState {
618    /// Create a new tile decoder state.
619    #[must_use]
620    pub fn new(tile_info: &TileInfo, tile_col: u32, tile_row: u32) -> Self {
621        let (width, height) = tile_info.tile_size_sb(tile_col, tile_row);
622        Self {
623            tile_col,
624            tile_row,
625            sb_col: 0,
626            sb_row: 0,
627            tile_width_sb: width,
628            tile_height_sb: height,
629            completed: false,
630        }
631    }
632
633    /// Advance to the next superblock.
634    pub fn advance(&mut self) {
635        self.sb_col += 1;
636        if self.sb_col >= self.tile_width_sb {
637            self.sb_col = 0;
638            self.sb_row += 1;
639            if self.sb_row >= self.tile_height_sb {
640                self.completed = true;
641            }
642        }
643    }
644
645    /// Check if decoding is complete.
646    #[must_use]
647    pub const fn is_complete(&self) -> bool {
648        self.completed
649    }
650
651    /// Get the current position as (`sb_col`, `sb_row`).
652    #[must_use]
653    pub const fn position(&self) -> (u32, u32) {
654        (self.sb_col, self.sb_row)
655    }
656
657    /// Get total superblocks in this tile.
658    #[must_use]
659    pub const fn total_superblocks(&self) -> u32 {
660        self.tile_width_sb * self.tile_height_sb
661    }
662
663    /// Get number of decoded superblocks.
664    #[must_use]
665    pub fn decoded_superblocks(&self) -> u32 {
666        self.sb_row * self.tile_width_sb + self.sb_col
667    }
668}
669
670/// Multi-tile decoder coordinator.
671#[derive(Clone, Debug)]
672pub struct TileDecoder {
673    /// Tile info.
674    pub tile_info: TileInfo,
675    /// Tile states.
676    pub tile_states: Vec<TileDecoderState>,
677}
678
679impl TileDecoder {
680    /// Create a new tile decoder.
681    #[must_use]
682    pub fn new(tile_info: TileInfo) -> Self {
683        let mut tile_states = Vec::with_capacity(tile_info.tile_count() as usize);
684        for row in 0..tile_info.tile_rows {
685            for col in 0..tile_info.tile_cols {
686                tile_states.push(TileDecoderState::new(&tile_info, col, row));
687            }
688        }
689        Self {
690            tile_info,
691            tile_states,
692        }
693    }
694
695    /// Get a tile state by index.
696    #[must_use]
697    pub fn get_state(&self, idx: usize) -> Option<&TileDecoderState> {
698        self.tile_states.get(idx)
699    }
700
701    /// Get a mutable tile state by index.
702    pub fn get_state_mut(&mut self, idx: usize) -> Option<&mut TileDecoderState> {
703        self.tile_states.get_mut(idx)
704    }
705
706    /// Check if all tiles are complete.
707    #[must_use]
708    pub fn all_complete(&self) -> bool {
709        self.tile_states.iter().all(TileDecoderState::is_complete)
710    }
711
712    /// Get number of complete tiles.
713    #[must_use]
714    pub fn complete_count(&self) -> usize {
715        self.tile_states.iter().filter(|s| s.is_complete()).count()
716    }
717
718    /// Reset all tile states.
719    pub fn reset(&mut self) {
720        for state in &mut self.tile_states {
721            state.sb_col = 0;
722            state.sb_row = 0;
723            state.completed = false;
724        }
725    }
726}
727
728// =============================================================================
729// Tests
730// =============================================================================
731
732#[cfg(test)]
733mod tests {
734    use super::*;
735
736    fn create_test_tile_info() -> TileInfo {
737        TileInfo {
738            tile_cols: 2,
739            tile_rows: 2,
740            tile_col_starts: vec![0, 15, 30],
741            tile_row_starts: vec![0, 8, 17],
742            context_update_tile_id: 0,
743            tile_size_bytes: 4,
744            uniform_tile_spacing: true,
745            tile_cols_log2: 1,
746            tile_rows_log2: 1,
747            min_tile_cols_log2: 0,
748            max_tile_cols_log2: 2,
749            min_tile_rows_log2: 0,
750            max_tile_rows_log2: 2,
751            sb_cols: 30,
752            sb_rows: 17,
753            sb_size: 64,
754        }
755    }
756
757    #[test]
758    fn test_tile_info_default() {
759        let tile_info = TileInfo::default();
760        assert_eq!(tile_info.tile_cols, 0);
761        assert_eq!(tile_info.tile_rows, 0);
762    }
763
764    #[test]
765    fn test_tile_count() {
766        let tile_info = create_test_tile_info();
767        assert_eq!(tile_info.tile_count(), 4);
768    }
769
770    #[test]
771    fn test_tile_size_sb() {
772        let tile_info = create_test_tile_info();
773        assert_eq!(tile_info.tile_size_sb(0, 0), (15, 8));
774        assert_eq!(tile_info.tile_size_sb(1, 0), (15, 8));
775        assert_eq!(tile_info.tile_size_sb(0, 1), (15, 9));
776        assert_eq!(tile_info.tile_size_sb(1, 1), (15, 9));
777    }
778
779    #[test]
780    fn test_tile_size_pixels() {
781        let tile_info = create_test_tile_info();
782        let (width, height) = tile_info.tile_size_pixels(0, 0);
783        assert_eq!(width, 15 * 64);
784        assert_eq!(height, 8 * 64);
785    }
786
787    #[test]
788    fn test_tile_index() {
789        let tile_info = create_test_tile_info();
790        assert_eq!(tile_info.tile_index(0, 0), 0);
791        assert_eq!(tile_info.tile_index(1, 0), 1);
792        assert_eq!(tile_info.tile_index(0, 1), 2);
793        assert_eq!(tile_info.tile_index(1, 1), 3);
794    }
795
796    #[test]
797    fn test_tile_position() {
798        let tile_info = create_test_tile_info();
799        assert_eq!(tile_info.tile_position(0), (0, 0));
800        assert_eq!(tile_info.tile_position(1), (1, 0));
801        assert_eq!(tile_info.tile_position(2), (0, 1));
802        assert_eq!(tile_info.tile_position(3), (1, 1));
803    }
804
805    #[test]
806    fn test_tile_start_sb() {
807        let tile_info = create_test_tile_info();
808        assert_eq!(tile_info.tile_col_start_sb(0), 0);
809        assert_eq!(tile_info.tile_col_start_sb(1), 15);
810        assert_eq!(tile_info.tile_row_start_sb(0), 0);
811        assert_eq!(tile_info.tile_row_start_sb(1), 8);
812    }
813
814    #[test]
815    fn test_tile_start_pixels() {
816        let tile_info = create_test_tile_info();
817        assert_eq!(tile_info.tile_start_pixels(0, 0), (0, 0));
818        assert_eq!(tile_info.tile_start_pixels(1, 0), (15 * 64, 0));
819        assert_eq!(tile_info.tile_start_pixels(0, 1), (0, 8 * 64));
820    }
821
822    #[test]
823    fn test_is_single_tile() {
824        let mut tile_info = TileInfo::default();
825        tile_info.tile_cols = 1;
826        tile_info.tile_rows = 1;
827        assert!(tile_info.is_single_tile());
828
829        tile_info.tile_cols = 2;
830        assert!(!tile_info.is_single_tile());
831    }
832
833    #[test]
834    fn test_tile_edges() {
835        let tile_info = create_test_tile_info();
836
837        assert!(tile_info.is_left_edge(0));
838        assert!(!tile_info.is_left_edge(1));
839        assert!(!tile_info.is_right_edge(0));
840        assert!(tile_info.is_right_edge(1));
841        assert!(tile_info.is_top_edge(0));
842        assert!(!tile_info.is_top_edge(1));
843        assert!(!tile_info.is_bottom_edge(0));
844        assert!(tile_info.is_bottom_edge(1));
845    }
846
847    #[test]
848    fn test_tile_log2() {
849        assert_eq!(TileInfo::tile_log2(1, 1), 0);
850        assert_eq!(TileInfo::tile_log2(1, 2), 1);
851        assert_eq!(TileInfo::tile_log2(1, 4), 2);
852        assert_eq!(TileInfo::tile_log2(2, 4), 1);
853    }
854
855    #[test]
856    fn test_tile_data() {
857        let tile_data = TileData::new(1, 2, 100, 500, 4);
858        assert_eq!(tile_data.tile_col, 1);
859        assert_eq!(tile_data.tile_row, 2);
860        assert_eq!(tile_data.offset, 100);
861        assert_eq!(tile_data.size, 500);
862        assert_eq!(tile_data.tile_idx, 2 * 4 + 1);
863        assert!(tile_data.is_valid());
864
865        let empty_tile = TileData::new(0, 0, 0, 0, 1);
866        assert!(!empty_tile.is_valid());
867    }
868
869    #[test]
870    fn test_tile_group() {
871        let mut group = TileGroup::new(0, 3);
872        assert_eq!(group.tile_count(), 4);
873        assert!(!group.is_single_tile());
874
875        assert!(group.contains_tile(0));
876        assert!(group.contains_tile(3));
877        assert!(!group.contains_tile(4));
878
879        group.add_tile(TileData::new(0, 0, 0, 100, 2));
880        assert_eq!(group.tiles.len(), 1);
881        assert!(group.get_tile(0).is_some());
882    }
883
884    #[test]
885    fn test_tile_group_single() {
886        let group = TileGroup::new(5, 5);
887        assert_eq!(group.tile_count(), 1);
888        assert!(group.is_single_tile());
889    }
890
891    #[test]
892    fn test_tile_group_obu() {
893        let tile_info = create_test_tile_info();
894        let obu = TileGroupObu::new(tile_info);
895
896        assert_eq!(obu.total_tiles(), 0);
897        assert!(!obu.is_complete());
898    }
899
900    #[test]
901    fn test_tile_decoder_state() {
902        let tile_info = create_test_tile_info();
903        let mut state = TileDecoderState::new(&tile_info, 0, 0);
904
905        assert_eq!(state.tile_col, 0);
906        assert_eq!(state.tile_row, 0);
907        assert_eq!(state.position(), (0, 0));
908        assert!(!state.is_complete());
909        assert_eq!(state.tile_width_sb, 15);
910        assert_eq!(state.tile_height_sb, 8);
911        assert_eq!(state.total_superblocks(), 120);
912
913        state.advance();
914        assert_eq!(state.position(), (1, 0));
915        assert_eq!(state.decoded_superblocks(), 1);
916    }
917
918    #[test]
919    fn test_tile_decoder_state_wrap() {
920        let tile_info = TileInfo {
921            tile_cols: 1,
922            tile_rows: 1,
923            tile_col_starts: vec![0, 2],
924            tile_row_starts: vec![0, 2],
925            sb_cols: 2,
926            sb_rows: 2,
927            sb_size: 64,
928            ..Default::default()
929        };
930
931        let mut state = TileDecoderState::new(&tile_info, 0, 0);
932        assert_eq!(state.tile_width_sb, 2);
933        assert_eq!(state.tile_height_sb, 2);
934
935        state.advance();
936        assert_eq!(state.position(), (1, 0));
937
938        state.advance();
939        assert_eq!(state.position(), (0, 1));
940
941        state.advance();
942        assert_eq!(state.position(), (1, 1));
943
944        state.advance();
945        assert!(state.is_complete());
946    }
947
948    #[test]
949    fn test_tile_decoder() {
950        let tile_info = create_test_tile_info();
951        let decoder = TileDecoder::new(tile_info);
952
953        assert_eq!(decoder.tile_states.len(), 4);
954        assert!(!decoder.all_complete());
955        assert_eq!(decoder.complete_count(), 0);
956
957        assert!(decoder.get_state(0).is_some());
958        assert!(decoder.get_state(3).is_some());
959        assert!(decoder.get_state(4).is_none());
960    }
961
962    #[test]
963    fn test_tile_decoder_reset() {
964        let tile_info = create_test_tile_info();
965        let mut decoder = TileDecoder::new(tile_info);
966
967        // Advance some tiles
968        if let Some(state) = decoder.get_state_mut(0) {
969            state.advance();
970            state.advance();
971        }
972
973        decoder.reset();
974
975        for state in &decoder.tile_states {
976            assert_eq!(state.position(), (0, 0));
977            assert!(!state.is_complete());
978        }
979    }
980
981    #[test]
982    fn test_constants() {
983        assert_eq!(MAX_TILE_COLS, 64);
984        assert_eq!(MAX_TILE_ROWS, 64);
985        assert_eq!(MAX_TILE_COUNT, 4096);
986        assert_eq!(MAX_TILE_AREA_SB, 4096);
987    }
988}