Source code

Revision control

Other Tools

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