webrender/
image_tiling.rs

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
5use crate::api::TileSize;
6use crate::api::units::*;
7use crate::segment::EdgeAaSegmentMask;
8use euclid::{point2, size2};
9use std::i32;
10use 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.
17pub 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
34pub struct Repetition {
35    pub origin: LayoutPoint,
36    pub edge_flags: EdgeAaSegmentMask,
37}
38
39pub 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
50impl RepetitionIterator {
51    pub fn num_repetitions(&self) -> usize {
52        (self.y_count * self.x_count) as usize
53    }
54}
55
56impl 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
97pub 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)]
160pub struct Tile {
161    pub rect: LayoutRect,
162    pub offset: TileOffset,
163    pub edge_flags: EdgeAaSegmentMask,
164}
165
166#[derive(Debug)]
167pub 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)]
183pub struct TileIterator {
184    current_tile: TileOffset,
185    x: TileIteratorExtent,
186    y: TileIteratorExtent,
187    regular_tile_size: LayoutSize,
188}
189
190impl 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
252pub 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.
353fn 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/// ```
431fn 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.
455fn 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.
483fn 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
507pub 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
525fn 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.
539pub 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
551fn 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
573pub 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
587pub 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
598pub 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)]
639mod 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}