tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

image_tiling.rs (28139B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 use crate::api::TileSize;
      6 use crate::api::units::*;
      7 use crate::segment::EdgeAaSegmentMask;
      8 use euclid::{point2, size2};
      9 use std::i32;
     10 use std::ops::Range;
     11 
     12 /// If repetitions are far enough apart that only one is within
     13 /// the primitive rect, then we can simplify the parameters and
     14 /// treat the primitive as not repeated.
     15 /// This can let us avoid unnecessary work later to handle some
     16 /// of the parameters.
     17 pub fn simplify_repeated_primitive(
     18    stretch_size: &LayoutSize,
     19    tile_spacing: &mut LayoutSize,
     20    prim_rect: &mut LayoutRect,
     21 ) {
     22    let stride = *stretch_size + *tile_spacing;
     23 
     24    if stride.width >= prim_rect.width() {
     25        tile_spacing.width = 0.0;
     26        prim_rect.max.x = f32::min(prim_rect.min.x + stretch_size.width, prim_rect.max.x);
     27    }
     28    if stride.height >= prim_rect.height() {
     29        tile_spacing.height = 0.0;
     30        prim_rect.max.y = f32::min(prim_rect.min.y + stretch_size.height, prim_rect.max.y);
     31    }
     32 }
     33 
     34 pub struct Repetition {
     35    pub origin: LayoutPoint,
     36    pub edge_flags: EdgeAaSegmentMask,
     37 }
     38 
     39 pub struct RepetitionIterator {
     40    current_x: i32,
     41    x_count: i32,
     42    current_y: i32,
     43    y_count: i32,
     44    row_flags: EdgeAaSegmentMask,
     45    current_origin: LayoutPoint,
     46    initial_origin: LayoutPoint,
     47    stride: LayoutSize,
     48 }
     49 
     50 impl RepetitionIterator {
     51    pub fn num_repetitions(&self) -> usize {
     52        (self.y_count * self.x_count) as usize
     53    }
     54 }
     55 
     56 impl Iterator for RepetitionIterator {
     57    type Item = Repetition;
     58 
     59    fn next(&mut self) -> Option<Self::Item> {
     60        if self.current_x == self.x_count {
     61            self.current_y += 1;
     62            if self.current_y >= self.y_count {
     63                return None;
     64            }
     65            self.current_x = 0;
     66 
     67            self.row_flags = EdgeAaSegmentMask::empty();
     68            if self.current_y == self.y_count - 1 {
     69                self.row_flags |= EdgeAaSegmentMask::BOTTOM;
     70            }
     71 
     72            self.current_origin.x = self.initial_origin.x;
     73            self.current_origin.y += self.stride.height;
     74        }
     75 
     76        let mut edge_flags = self.row_flags;
     77        if self.current_x == 0 {
     78            edge_flags |= EdgeAaSegmentMask::LEFT;
     79        }
     80 
     81        if self.current_x == self.x_count - 1 {
     82            edge_flags |= EdgeAaSegmentMask::RIGHT;
     83        }
     84 
     85        let repetition = Repetition {
     86            origin: self.current_origin,
     87            edge_flags,
     88        };
     89 
     90        self.current_origin.x += self.stride.width;
     91        self.current_x += 1;
     92 
     93        Some(repetition)
     94    }
     95 }
     96 
     97 pub fn repetitions(
     98    prim_rect: &LayoutRect,
     99    visible_rect: &LayoutRect,
    100    stride: LayoutSize,
    101 ) -> RepetitionIterator {
    102    let visible_rect = match prim_rect.intersection(&visible_rect) {
    103        Some(rect) => rect,
    104        None => {
    105            return RepetitionIterator {
    106                current_origin: LayoutPoint::zero(),
    107                initial_origin: LayoutPoint::zero(),
    108                current_x: 0,
    109                current_y: 0,
    110                x_count: 0,
    111                y_count: 0,
    112                stride,
    113                row_flags: EdgeAaSegmentMask::empty(),
    114            }
    115        }
    116    };
    117 
    118    assert!(stride.width > 0.0);
    119    assert!(stride.height > 0.0);
    120 
    121    let nx = if visible_rect.min.x > prim_rect.min.x {
    122        f32::floor((visible_rect.min.x - prim_rect.min.x) / stride.width)
    123    } else {
    124        0.0
    125    };
    126 
    127    let ny = if visible_rect.min.y > prim_rect.min.y {
    128        f32::floor((visible_rect.min.y - prim_rect.min.y) / stride.height)
    129    } else {
    130        0.0
    131    };
    132 
    133    let x0 = prim_rect.min.x + nx * stride.width;
    134    let y0 = prim_rect.min.y + ny * stride.height;
    135 
    136    let x_most = visible_rect.max.x;
    137    let y_most = visible_rect.max.y;
    138 
    139    let x_count = f32::ceil((x_most - x0) / stride.width) as i32;
    140    let y_count = f32::ceil((y_most - y0) / stride.height) as i32;
    141 
    142    let mut row_flags = EdgeAaSegmentMask::TOP;
    143    if y_count == 1 {
    144        row_flags |= EdgeAaSegmentMask::BOTTOM;
    145    }
    146 
    147    RepetitionIterator {
    148        current_origin: LayoutPoint::new(x0, y0),
    149        initial_origin: LayoutPoint::new(x0, y0),
    150        current_x: 0,
    151        current_y: 0,
    152        x_count,
    153        y_count,
    154        row_flags,
    155        stride,
    156    }
    157 }
    158 
    159 #[derive(Debug)]
    160 pub struct Tile {
    161    pub rect: LayoutRect,
    162    pub offset: TileOffset,
    163    pub edge_flags: EdgeAaSegmentMask,
    164 }
    165 
    166 #[derive(Debug)]
    167 pub struct TileIteratorExtent {
    168    /// Range of visible tiles to iterate over in number of tiles.
    169    tile_range: Range<i32>,
    170    /// Range of tiles of the full image including tiles that are culled out.
    171    image_tiles: Range<i32>,
    172    /// Size of the first tile in layout space.
    173    first_tile_layout_size: f32,
    174    /// Size of the last tile in layout space.
    175    last_tile_layout_size: f32,
    176    /// Position of blob point (0, 0) in layout space.
    177    layout_tiling_origin: f32,
    178    /// Position of the top-left corner of the primitive rect in layout space.
    179    layout_prim_start: f32,
    180 }
    181 
    182 #[derive(Debug)]
    183 pub struct TileIterator {
    184    current_tile: TileOffset,
    185    x: TileIteratorExtent,
    186    y: TileIteratorExtent,
    187    regular_tile_size: LayoutSize,
    188 }
    189 
    190 impl Iterator for TileIterator {
    191    type Item = Tile;
    192 
    193    fn next(&mut self) -> Option<Self::Item> {
    194        // If we reach the end of a row, reset to the beginning of the next row.
    195        if self.current_tile.x >= self.x.tile_range.end {
    196            self.current_tile.y += 1;
    197            self.current_tile.x = self.x.tile_range.start;
    198        }
    199 
    200        // Stop iterating if we reach the last tile. We may start here if there
    201        // were no tiles to iterate over.
    202        if self.current_tile.x >= self.x.tile_range.end || self.current_tile.y >= self.y.tile_range.end {
    203            return None;
    204        }
    205 
    206        let tile_offset = self.current_tile;
    207 
    208        let mut segment_rect = LayoutRect::from_origin_and_size(
    209            LayoutPoint::new(
    210                self.x.layout_tiling_origin + tile_offset.x as f32 * self.regular_tile_size.width,
    211                self.y.layout_tiling_origin + tile_offset.y as f32 * self.regular_tile_size.height,
    212            ),
    213            self.regular_tile_size,
    214        );
    215 
    216        let mut edge_flags = EdgeAaSegmentMask::empty();
    217 
    218        if tile_offset.x == self.x.image_tiles.start {
    219            edge_flags |= EdgeAaSegmentMask::LEFT;
    220            segment_rect.min.x = self.x.layout_prim_start;
    221            // TODO(nical) we may not need to do this.
    222            segment_rect.max.x = segment_rect.min.x + self.x.first_tile_layout_size;
    223        }
    224        if tile_offset.x == self.x.image_tiles.end - 1 {
    225            edge_flags |= EdgeAaSegmentMask::RIGHT;
    226            segment_rect.max.x = segment_rect.min.x + self.x.last_tile_layout_size;
    227        }
    228 
    229        if tile_offset.y == self.y.image_tiles.start {
    230            segment_rect.min.y = self.y.layout_prim_start;
    231            segment_rect.max.y = segment_rect.min.y + self.y.first_tile_layout_size;
    232            edge_flags |= EdgeAaSegmentMask::TOP;
    233        }
    234        if tile_offset.y == self.y.image_tiles.end - 1 {
    235            segment_rect.max.y = segment_rect.min.y + self.y.last_tile_layout_size;
    236            edge_flags |= EdgeAaSegmentMask::BOTTOM;
    237        }
    238 
    239        assert!(tile_offset.y < self.y.tile_range.end);
    240        let tile = Tile {
    241            rect: segment_rect,
    242            offset: tile_offset,
    243            edge_flags,
    244        };
    245 
    246        self.current_tile.x += 1;
    247 
    248        Some(tile)
    249    }
    250 }
    251 
    252 pub fn tiles(
    253    prim_rect: &LayoutRect,
    254    visible_rect: &LayoutRect,
    255    image_rect: &DeviceIntRect,
    256    device_tile_size: i32,
    257 ) -> TileIterator {
    258    // The image resource is tiled. We have to generate an image primitive
    259    // for each tile.
    260    // We need to do this because the image is broken up into smaller tiles in the texture
    261    // cache and the image shader is not able to work with this type of sparse representation.
    262 
    263    // The tiling logic works as follows:
    264    //
    265    //  +-#################-+  -+
    266    //  | #//|    |    |//# |   | image size
    267    //  | #//|    |    |//# |   |
    268    //  +-#--+----+----+--#-+   |  -+
    269    //  | #//|    |    |//# |   |   | regular tile size
    270    //  | #//|    |    |//# |   |   |
    271    //  +-#--+----+----+--#-+   |  -+-+
    272    //  | #//|////|////|//# |   |     | "leftover" height
    273    //  | ################# |  -+  ---+
    274    //  +----+----+----+----+
    275    //
    276    // In the ascii diagram above, a large image is split into tiles of almost regular size.
    277    // The tiles on the edges (hatched in the diagram) can be smaller than the regular tiles
    278    // and are handled separately in the code (we'll call them boundary tiles).
    279    //
    280    // Each generated segment corresponds to a tile in the texture cache, with the
    281    // assumption that the boundary tiles are sized to fit their own irregular size in the
    282    // texture cache.
    283    //
    284    // Because we can have very large virtual images we iterate over the visible portion of
    285    // the image in layer space instead of iterating over all device tiles.
    286 
    287    let visible_rect = match prim_rect.intersection(&visible_rect) {
    288        Some(rect) => rect,
    289        None => {
    290            return TileIterator {
    291                current_tile: TileOffset::zero(),
    292                x: TileIteratorExtent {
    293                    tile_range: 0..0,
    294                    image_tiles: 0..0,
    295                    first_tile_layout_size: 0.0,
    296                    last_tile_layout_size: 0.0,
    297                    layout_tiling_origin: 0.0,
    298                    layout_prim_start: prim_rect.min.x,
    299                },
    300                y: TileIteratorExtent {
    301                    tile_range: 0..0,
    302                    image_tiles: 0..0,
    303                    first_tile_layout_size: 0.0,
    304                    last_tile_layout_size: 0.0,
    305                    layout_tiling_origin: 0.0,
    306                    layout_prim_start: prim_rect.min.y,
    307                },
    308                regular_tile_size: LayoutSize::zero(),
    309            }
    310        }
    311    };
    312 
    313    // Size of regular tiles in layout space.
    314    let layout_tile_size = LayoutSize::new(
    315        device_tile_size as f32 / image_rect.width() as f32 * prim_rect.width(),
    316        device_tile_size as f32 / image_rect.height() as f32 * prim_rect.height(),
    317    );
    318 
    319    // The decomposition logic is exactly the same on each axis so we reduce
    320    // this to a 1-dimensional problem in an attempt to make the code simpler.
    321 
    322    let x_extent = tiles_1d(
    323        layout_tile_size.width,
    324        visible_rect.x_range(),
    325        prim_rect.min.x,
    326        image_rect.x_range(),
    327        device_tile_size,
    328    );
    329 
    330    let y_extent = tiles_1d(
    331        layout_tile_size.height,
    332        visible_rect.y_range(),
    333        prim_rect.min.y,
    334        image_rect.y_range(),
    335        device_tile_size,
    336    );
    337 
    338    TileIterator {
    339        current_tile: point2(
    340            x_extent.tile_range.start,
    341            y_extent.tile_range.start,
    342        ),
    343        x: x_extent,
    344        y: y_extent,
    345        regular_tile_size: layout_tile_size,
    346    }
    347 }
    348 
    349 /// Decompose tiles along an arbitrary axis.
    350 ///
    351 /// This does most of the heavy lifting needed for `tiles` but in a single dimension for
    352 /// the sake of simplicity since the problem is independent on the x and y axes.
    353 fn tiles_1d(
    354    layout_tile_size: f32,
    355    layout_visible_range: Range<f32>,
    356    layout_prim_start: f32,
    357    device_image_range: Range<i32>,
    358    device_tile_size: i32,
    359 ) -> TileIteratorExtent {
    360    // A few sanity checks.
    361    debug_assert!(layout_tile_size > 0.0);
    362    debug_assert!(layout_visible_range.end >= layout_visible_range.start);
    363    debug_assert!(device_image_range.end > device_image_range.start);
    364    debug_assert!(device_tile_size > 0);
    365 
    366    // Sizes of the boundary tiles in pixels.
    367    let first_tile_device_size = first_tile_size_1d(&device_image_range, device_tile_size);
    368    let last_tile_device_size = last_tile_size_1d(&device_image_range, device_tile_size);
    369 
    370    // [start..end[ Range of tiles of this row/column (in number of tiles) without
    371    // taking culling into account.
    372    let image_tiles = tile_range_1d(&device_image_range, device_tile_size);
    373 
    374    // Layout offset of tile (0, 0) with respect to the top-left corner of the display item.
    375    let layout_offset = device_image_range.start as f32 * layout_tile_size / device_tile_size as f32;
    376    // Position in layout space of tile (0, 0).
    377    let layout_tiling_origin = layout_prim_start - layout_offset;
    378 
    379    // [start..end[ Range of the visible tiles (because of culling).
    380    let visible_tiles_start = f32::floor((layout_visible_range.start - layout_tiling_origin) / layout_tile_size) as i32;
    381    let visible_tiles_end = f32::ceil((layout_visible_range.end - layout_tiling_origin) / layout_tile_size) as i32;
    382 
    383    // Combine the above two to get the tiles in the image that are visible this frame.
    384    let mut tiles_start = i32::max(image_tiles.start, visible_tiles_start);
    385    let tiles_end = i32::min(image_tiles.end, visible_tiles_end);
    386    if tiles_start > tiles_end {
    387        tiles_start = tiles_end;
    388    }
    389 
    390    // The size in layout space of the boundary tiles.
    391    let first_tile_layout_size = if tiles_start == image_tiles.start {
    392        first_tile_device_size as f32 * layout_tile_size / device_tile_size as f32
    393    } else {
    394        // boundary tile was culled out, so the new first tile is a regularly sized tile.
    395        layout_tile_size
    396    };
    397 
    398    // Same here.
    399    let last_tile_layout_size = if tiles_end == image_tiles.end {
    400        last_tile_device_size as f32 * layout_tile_size / device_tile_size as f32
    401    } else {
    402        layout_tile_size
    403    };
    404 
    405    TileIteratorExtent {
    406        tile_range: tiles_start..tiles_end,
    407        image_tiles,
    408        first_tile_layout_size,
    409        last_tile_layout_size,
    410        layout_tiling_origin,
    411        layout_prim_start,
    412    }
    413 }
    414 
    415 /// Compute the range of tiles (in number of tiles) that intersect the provided
    416 /// image range (in pixels) in an arbitrary dimension.
    417 ///
    418 /// ```ignore
    419 ///
    420 ///         0
    421 ///         :
    422 ///   #-+---+---+---+---+---+--#
    423 ///   # |   |   |   |   |   |  #
    424 ///   #-+---+---+---+---+---+--#
    425 /// ^       :                   ^
    426 ///
    427 ///  +------------------------+  image_range
    428 ///        +---+  regular_tile_size
    429 ///
    430 /// ```
    431 fn tile_range_1d(
    432    image_range: &Range<i32>,
    433    regular_tile_size: i32,
    434 ) -> Range<i32> {
    435    // Integer division truncates towards zero so with negative values if the first/last
    436    // tile isn't a full tile we can get offset by one which we account for here.
    437 
    438    let mut start = image_range.start / regular_tile_size;
    439    if image_range.start % regular_tile_size < 0 {
    440        start -= 1;
    441    }
    442 
    443    let mut end = image_range.end / regular_tile_size;
    444    if image_range.end % regular_tile_size > 0 {
    445        end += 1;
    446    }
    447 
    448    start..end
    449 }
    450 
    451 // Sizes of the first boundary tile in pixels.
    452 //
    453 // It can be smaller than the regular tile size if the image is not a multiple
    454 // of the regular tile size.
    455 fn first_tile_size_1d(
    456    image_range: &Range<i32>,
    457    regular_tile_size: i32,
    458 ) -> i32 {
    459    // We have to account for how the % operation behaves for negative values.
    460    let image_size = image_range.end - image_range.start;
    461    i32::min(
    462        match image_range.start % regular_tile_size {
    463            //             .      #------+------+      .
    464            //             .      #//////|      |      .
    465            0 => regular_tile_size,
    466            //   (zero) -> 0      .   #--+------+      .
    467            //             .      .   #//|      |      .
    468            // %(m):                  ~~>
    469            m if m > 0 => regular_tile_size - m,
    470            //             .      .   #--+------+      0 <- (zero)
    471            //             .      .   #//|      |      .
    472            // %(m):                  <~~
    473            m => -m,
    474        },
    475        image_size
    476    )
    477 }
    478 
    479 // Sizes of the last boundary tile in pixels.
    480 //
    481 // It can be smaller than the regular tile size if the image is not a multiple
    482 // of the regular tile size.
    483 fn last_tile_size_1d(
    484    image_range: &Range<i32>,
    485    regular_tile_size: i32,
    486 ) -> i32 {
    487    // We have to account for how the modulo operation behaves for negative values.
    488    let image_size = image_range.end - image_range.start;
    489    i32::min(
    490        match image_range.end % regular_tile_size {
    491            //                    +------+------#      .
    492            // tiles:      .      |      |//////#      .
    493            0 => regular_tile_size,
    494            //             .      +------+--#   .      0 <- (zero)
    495            //             .      |      |//#   .      .
    496            // modulo (m):                   <~~
    497            m if m < 0 => regular_tile_size + m,
    498            //   (zero) -> 0      +------+--#   .      .
    499            //             .      |      |//#   .      .
    500            // modulo (m):                ~~>
    501            m => m,
    502        },
    503        image_size,
    504    )
    505 }
    506 
    507 pub fn compute_tile_rect(
    508    image_rect: &DeviceIntRect,
    509    regular_tile_size: TileSize,
    510    tile: TileOffset,
    511 ) -> DeviceIntRect {
    512    let regular_tile_size = regular_tile_size as i32;
    513    DeviceIntRect::from_origin_and_size(
    514        point2(
    515            compute_tile_origin_1d(image_rect.x_range(), regular_tile_size, tile.x as i32),
    516            compute_tile_origin_1d(image_rect.y_range(), regular_tile_size, tile.y as i32),
    517        ),
    518        size2(
    519            compute_tile_size_1d(image_rect.x_range(), regular_tile_size, tile.x as i32),
    520            compute_tile_size_1d(image_rect.y_range(), regular_tile_size, tile.y as i32),
    521        ),
    522    )
    523 }
    524 
    525 fn compute_tile_origin_1d(
    526    img_range: Range<i32>,
    527    regular_tile_size: i32,
    528    tile_offset: i32,
    529 ) -> i32 {
    530    let tile_range = tile_range_1d(&img_range, regular_tile_size);
    531    if tile_offset == tile_range.start {
    532        img_range.start
    533    } else {
    534        tile_offset * regular_tile_size
    535    }
    536 }
    537 
    538 // Compute the width and height in pixels of a tile depending on its position in the image.
    539 pub fn compute_tile_size(
    540    image_rect: &DeviceIntRect,
    541    regular_tile_size: TileSize,
    542    tile: TileOffset,
    543 ) -> DeviceIntSize {
    544    let regular_tile_size = regular_tile_size as i32;
    545    size2(
    546        compute_tile_size_1d(image_rect.x_range(), regular_tile_size, tile.x as i32),
    547        compute_tile_size_1d(image_rect.y_range(), regular_tile_size, tile.y as i32),
    548    )
    549 }
    550 
    551 fn compute_tile_size_1d(
    552    img_range: Range<i32>,
    553    regular_tile_size: i32,
    554    tile_offset: i32,
    555 ) -> i32 {
    556    let tile_range = tile_range_1d(&img_range, regular_tile_size);
    557 
    558    // Most tiles are going to have base_size as width and height,
    559    // except for tiles around the edges that are shrunk to fit the image data.
    560    let actual_size = if tile_offset == tile_range.start {
    561        first_tile_size_1d(&img_range, regular_tile_size)
    562    } else if tile_offset == tile_range.end - 1 {
    563        last_tile_size_1d(&img_range, regular_tile_size)
    564    } else {
    565        regular_tile_size
    566    };
    567 
    568    assert!(actual_size > 0);
    569 
    570    actual_size
    571 }
    572 
    573 pub fn compute_tile_range(
    574    visible_area: &DeviceIntRect,
    575    tile_size: u16,
    576 ) -> TileRange {
    577    let tile_size = tile_size as i32;
    578    let x_range = tile_range_1d(&visible_area.x_range(), tile_size);
    579    let y_range = tile_range_1d(&visible_area.y_range(), tile_size);
    580 
    581    TileRange {
    582        min: point2(x_range.start, y_range.start),
    583        max: point2(x_range.end, y_range.end),
    584    }
    585 }
    586 
    587 pub fn for_each_tile_in_range(
    588    range: &TileRange,
    589    mut callback: impl FnMut(TileOffset),
    590 ) {
    591    for y in range.y_range() {
    592        for x in range.x_range() {
    593            callback(point2(x, y));
    594        }
    595    }
    596 }
    597 
    598 pub fn compute_valid_tiles_if_bounds_change(
    599    prev_rect: &DeviceIntRect,
    600    new_rect: &DeviceIntRect,
    601    tile_size: u16,
    602 ) -> Option<TileRange> {
    603    let intersection = match prev_rect.intersection(new_rect) {
    604        Some(rect) => rect,
    605        None => {
    606            return Some(TileRange::zero());
    607        }
    608    };
    609 
    610    let left = prev_rect.min.x != new_rect.min.x;
    611    let right = prev_rect.max.x != new_rect.max.x;
    612    let top = prev_rect.min.y != new_rect.min.y;
    613    let bottom = prev_rect.max.y != new_rect.max.y;
    614 
    615    if !left && !right && !top && !bottom {
    616        // Bounds have not changed.
    617        return None;
    618    }
    619 
    620    let tw = 1.0 / (tile_size as f32);
    621    let th = 1.0 / (tile_size as f32);
    622 
    623    let tiles = intersection
    624        .cast::<f32>()
    625        .scale(tw, th);
    626 
    627    let min_x = if left { f32::ceil(tiles.min.x) } else { f32::floor(tiles.min.x) };
    628    let min_y = if top { f32::ceil(tiles.min.y) } else { f32::floor(tiles.min.y) };
    629    let max_x = if right { f32::floor(tiles.max.x) } else { f32::ceil(tiles.max.x) };
    630    let max_y = if bottom { f32::floor(tiles.max.y) } else { f32::ceil(tiles.max.y) };
    631 
    632    Some(TileRange {
    633        min: point2(min_x as i32, min_y as i32),
    634        max: point2(max_x as i32, max_y as i32),
    635    })
    636 }
    637 
    638 #[cfg(test)]
    639 mod tests {
    640    use super::*;
    641    use std::collections::HashSet;
    642    use euclid::rect;
    643 
    644    // this checks some additional invariants
    645    fn checked_for_each_tile(
    646        prim_rect: &LayoutRect,
    647        visible_rect: &LayoutRect,
    648        device_image_rect: &DeviceIntRect,
    649        device_tile_size: i32,
    650        callback: &mut dyn FnMut(&LayoutRect, TileOffset, EdgeAaSegmentMask),
    651    ) {
    652        let mut coverage = LayoutRect::zero();
    653        let mut seen_tiles = HashSet::new();
    654        for tile in tiles(
    655            prim_rect,
    656            visible_rect,
    657            device_image_rect,
    658            device_tile_size,
    659        ) {
    660            // make sure we don't get sent duplicate tiles
    661            assert!(!seen_tiles.contains(&tile.offset));
    662            seen_tiles.insert(tile.offset);
    663            coverage = coverage.union(&tile.rect);
    664            assert!(prim_rect.contains_box(&tile.rect));
    665            callback(&tile.rect, tile.offset, tile.edge_flags);
    666        }
    667        assert!(prim_rect.contains_box(&coverage));
    668        assert!(coverage.contains_box(&visible_rect.intersection(&prim_rect).unwrap_or(LayoutRect::zero())));
    669    }
    670 
    671    #[test]
    672    fn basic() {
    673        let mut count = 0;
    674        checked_for_each_tile(&rect(0., 0., 1000., 1000.).to_box2d(),
    675            &rect(75., 75., 400., 400.).to_box2d(),
    676            &rect(0, 0, 400, 400).to_box2d(),
    677            36,
    678            &mut |_tile_rect, _tile_offset, _tile_flags| {
    679                count += 1;
    680            },
    681        );
    682        assert_eq!(count, 36);
    683    }
    684 
    685    #[test]
    686    fn empty() {
    687        let mut count = 0;
    688        checked_for_each_tile(&rect(0., 0., 74., 74.).to_box2d(),
    689            &rect(75., 75., 400., 400.).to_box2d(),
    690            &rect(0, 0, 400, 400).to_box2d(),
    691            36,
    692            &mut |_tile_rect, _tile_offset, _tile_flags| {
    693              count += 1;
    694            },
    695        );
    696        assert_eq!(count, 0);
    697    }
    698 
    699    #[test]
    700    fn test_tiles_1d() {
    701        // Exactly one full tile at positive offset.
    702        let result = tiles_1d(64.0, -10000.0..10000.0, 0.0, 0..64, 64);
    703        assert_eq!(result.tile_range.start, 0);
    704        assert_eq!(result.tile_range.end, 1);
    705        assert_eq!(result.first_tile_layout_size, 64.0);
    706        assert_eq!(result.last_tile_layout_size, 64.0);
    707 
    708        // Exactly one full tile at negative offset.
    709        let result = tiles_1d(64.0, -10000.0..10000.0, -64.0, -64..0, 64);
    710        assert_eq!(result.tile_range.start, -1);
    711        assert_eq!(result.tile_range.end, 0);
    712        assert_eq!(result.first_tile_layout_size, 64.0);
    713        assert_eq!(result.last_tile_layout_size, 64.0);
    714 
    715        // Two full tiles at negative and positive offsets.
    716        let result = tiles_1d(64.0, -10000.0..10000.0, -64.0, -64..64, 64);
    717        assert_eq!(result.tile_range.start, -1);
    718        assert_eq!(result.tile_range.end, 1);
    719        assert_eq!(result.first_tile_layout_size, 64.0);
    720        assert_eq!(result.last_tile_layout_size, 64.0);
    721 
    722        // One partial tile at positive offset, non-zero origin, culled out.
    723        let result = tiles_1d(64.0, -100.0..10.0, 64.0, 64..310, 64);
    724        assert_eq!(result.tile_range.start, result.tile_range.end);
    725 
    726        // Two tiles at negative and positive offsets, one of which is culled out.
    727        // The remaining tile is partially culled but it should still generate a full tile.
    728        let result = tiles_1d(64.0, 10.0..10000.0, -64.0, -64..64, 64);
    729        assert_eq!(result.tile_range.start, 0);
    730        assert_eq!(result.tile_range.end, 1);
    731        assert_eq!(result.first_tile_layout_size, 64.0);
    732        assert_eq!(result.last_tile_layout_size, 64.0);
    733        let result = tiles_1d(64.0, -10000.0..-10.0, -64.0, -64..64, 64);
    734        assert_eq!(result.tile_range.start, -1);
    735        assert_eq!(result.tile_range.end, 0);
    736        assert_eq!(result.first_tile_layout_size, 64.0);
    737        assert_eq!(result.last_tile_layout_size, 64.0);
    738 
    739        // Stretched tile in layout space device tile size is 64 and layout tile size is 128.
    740        // So the resulting tile sizes in layout space should be multiplied by two.
    741        let result = tiles_1d(128.0, -10000.0..10000.0, -64.0, -64..32, 64);
    742        assert_eq!(result.tile_range.start, -1);
    743        assert_eq!(result.tile_range.end, 1);
    744        assert_eq!(result.first_tile_layout_size, 128.0);
    745        assert_eq!(result.last_tile_layout_size, 64.0);
    746 
    747        // Two visible tiles (the rest is culled out).
    748        let result = tiles_1d(10.0, 0.0..20.0, 0.0, 0..64, 64);
    749        assert_eq!(result.tile_range.start, 0);
    750        assert_eq!(result.tile_range.end, 1);
    751        assert_eq!(result.first_tile_layout_size, 10.0);
    752        assert_eq!(result.last_tile_layout_size, 10.0);
    753 
    754        // Two visible tiles at negative layout offsets (the rest is culled out).
    755        let result = tiles_1d(10.0, -20.0..0.0, -20.0, 0..64, 64);
    756        assert_eq!(result.tile_range.start, 0);
    757        assert_eq!(result.tile_range.end, 1);
    758        assert_eq!(result.first_tile_layout_size, 10.0);
    759        assert_eq!(result.last_tile_layout_size, 10.0);
    760    }
    761 
    762    #[test]
    763    fn test_tile_range_1d() {
    764        assert_eq!(tile_range_1d(&(0..256), 256), 0..1);
    765        assert_eq!(tile_range_1d(&(0..257), 256), 0..2);
    766        assert_eq!(tile_range_1d(&(-1..257), 256), -1..2);
    767        assert_eq!(tile_range_1d(&(-256..256), 256), -1..1);
    768        assert_eq!(tile_range_1d(&(-20..-10), 6), -4..-1);
    769        assert_eq!(tile_range_1d(&(20..100), 256), 0..1);
    770    }
    771 
    772    #[test]
    773    fn test_first_last_tile_size_1d() {
    774        assert_eq!(first_tile_size_1d(&(0..10), 64), 10);
    775        assert_eq!(first_tile_size_1d(&(-20..0), 64), 20);
    776 
    777        assert_eq!(last_tile_size_1d(&(0..10), 64), 10);
    778        assert_eq!(last_tile_size_1d(&(-20..0), 64), 20);
    779    }
    780 
    781    #[test]
    782    fn doubly_partial_tiles() {
    783        // In the following tests the image is a single tile and none of the sides of the tile
    784        // align with the tile grid.
    785        // This can only happen when we have a single non-aligned partial tile and no regular
    786        // tiles.
    787        assert_eq!(first_tile_size_1d(&(300..310), 64), 10);
    788        assert_eq!(first_tile_size_1d(&(-20..-10), 64), 10);
    789 
    790        assert_eq!(last_tile_size_1d(&(300..310), 64), 10);
    791        assert_eq!(last_tile_size_1d(&(-20..-10), 64), 10);
    792 
    793 
    794        // One partial tile at positve offset, non-zero origin.
    795        let result = tiles_1d(64.0, -10000.0..10000.0, 0.0, 300..310, 64);
    796        assert_eq!(result.tile_range.start, 4);
    797        assert_eq!(result.tile_range.end, 5);
    798        assert_eq!(result.first_tile_layout_size, 10.0);
    799        assert_eq!(result.last_tile_layout_size, 10.0);
    800    }
    801 
    802    #[test]
    803    fn smaller_than_tile_size_at_origin() {
    804        let r = compute_tile_rect(
    805            &rect(0, 0, 80, 80).to_box2d(),
    806            256,
    807            point2(0, 0),
    808        );
    809 
    810        assert_eq!(r, rect(0, 0, 80, 80).to_box2d());
    811    }
    812 
    813    #[test]
    814    fn smaller_than_tile_size_with_offset() {
    815        let r = compute_tile_rect(
    816            &rect(20, 20, 80, 80).to_box2d(),
    817            256,
    818            point2(0, 0),
    819        );
    820 
    821        assert_eq!(r, rect(20, 20, 80, 80).to_box2d());
    822    }
    823 }