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 }