/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
usecrate::api::TileSize; usecrate::api::units::*; usecrate::segment::EdgeMask; use euclid::{point2, size2}; use std::i32; use std::ops::Range;
/// If repetitions are far enough apart that only one is within /// the primitive rect, then we can simplify the parameters and /// treat the primitive as not repeated. /// This can let us avoid unnecessary work later to handle some /// of the parameters. pubfn simplify_repeated_primitive(
stretch_size: &LayoutSize,
tile_spacing: &mut LayoutSize,
prim_rect: &mut LayoutRect,
) { let stride = *stretch_size + *tile_spacing;
// Sanity-check that we don't have anything that may cause the iterator // to run indefinitely. let valid = x_count.is_finite()
& y_count.is_finite()
& stride.is_finite();
if !valid {
x_count = 0.0;
y_count = 0.0;
}
letmut row_flags = EdgeMask::TOP; if y_count as i32 == 1 {
row_flags |= EdgeMask::BOTTOM;
}
#[derive(Debug)] pubstruct TileIteratorExtent { /// Range of visible tiles to iterate over in number of tiles.
tile_range: Range<i32>, /// Range of tiles of the full image including tiles that are culled out.
image_tiles: Range<i32>, /// Size of the first tile in layout space.
first_tile_layout_size: f32, /// Size of the last tile in layout space.
last_tile_layout_size: f32, /// Position of blob point (0, 0) in layout space.
layout_tiling_origin: f32, /// Position of the top-left corner of the primitive rect in layout space.
layout_prim_start: f32,
}
impl Iterator for TileIterator { type Item = Tile;
fn next(&mutself) -> Option<Self::Item> { // If we reach the end of a row, reset to the beginning of the next row. ifself.current_tile.x >= self.x.tile_range.end { self.current_tile.y += 1; self.current_tile.x = self.x.tile_range.start;
}
// Stop iterating if we reach the last tile. We may start here if there // were no tiles to iterate over. ifself.current_tile.x >= self.x.tile_range.end || self.current_tile.y >= self.y.tile_range.end { return None;
}
pubfn tiles(
prim_rect: &LayoutRect,
visible_rect: &LayoutRect,
image_rect: &DeviceIntRect,
device_tile_size: i32,
) -> TileIterator { // The image resource is tiled. We have to generate an image primitive // for each tile. // We need to do this because the image is broken up into smaller tiles in the texture // cache and the image shader is not able to work with this type of sparse representation.
// The tiling logic works as follows: // // +-#################-+ -+ // | #//| | |//# | | image size // | #//| | |//# | | // +-#--+----+----+--#-+ | -+ // | #//| | |//# | | | regular tile size // | #//| | |//# | | | // +-#--+----+----+--#-+ | -+-+ // | #//|////|////|//# | | | "leftover" height // | ################# | -+ ---+ // +----+----+----+----+ // // In the ascii diagram above, a large image is split into tiles of almost regular size. // The tiles on the edges (hatched in the diagram) can be smaller than the regular tiles // and are handled separately in the code (we'll call them boundary tiles). // // Each generated segment corresponds to a tile in the texture cache, with the // assumption that the boundary tiles are sized to fit their own irregular size in the // texture cache. // // Because we can have very large virtual images we iterate over the visible portion of // the image in layer space instead of iterating over all device tiles.
// Size of regular tiles in layout space. let layout_tile_size = LayoutSize::new(
device_tile_size as f32 / image_rect.width() as f32 * prim_rect.width(),
device_tile_size as f32 / image_rect.height() as f32 * prim_rect.height(),
);
// The decomposition logic is exactly the same on each axis so we reduce // this to a 1-dimensional problem in an attempt to make the code simpler.
let x_extent = tiles_1d(
layout_tile_size.width,
visible_rect.x_range(),
prim_rect.min.x,
image_rect.x_range(),
device_tile_size,
);
let y_extent = tiles_1d(
layout_tile_size.height,
visible_rect.y_range(),
prim_rect.min.y,
image_rect.y_range(),
device_tile_size,
);
/// Decompose tiles along an arbitrary axis. /// /// This does most of the heavy lifting needed for `tiles` but in a single dimension for /// the sake of simplicity since the problem is independent on the x and y axes. fn tiles_1d(
layout_tile_size: f32,
layout_visible_range: Range<f32>,
layout_prim_start: f32,
device_image_range: Range<i32>,
device_tile_size: i32,
) -> TileIteratorExtent { // A few sanity checks.
debug_assert!(layout_tile_size > 0.0);
debug_assert!(layout_visible_range.end >= layout_visible_range.start);
debug_assert!(device_image_range.end > device_image_range.start);
debug_assert!(device_tile_size > 0);
// Sizes of the boundary tiles in pixels. let first_tile_device_size = first_tile_size_1d(&device_image_range, device_tile_size); let last_tile_device_size = last_tile_size_1d(&device_image_range, device_tile_size);
// [start..end[ Range of tiles of this row/column (in number of tiles) without // taking culling into account. let image_tiles = tile_range_1d(&device_image_range, device_tile_size);
// Layout offset of tile (0, 0) with respect to the top-left corner of the display item. let layout_offset = device_image_range.start as f32 * layout_tile_size / device_tile_size as f32; // Position in layout space of tile (0, 0). let layout_tiling_origin = layout_prim_start - layout_offset;
// [start..end[ Range of the visible tiles (because of culling). let visible_tiles_start = f32::floor((layout_visible_range.start - layout_tiling_origin) / layout_tile_size) as i32; let visible_tiles_end = f32::ceil((layout_visible_range.end - layout_tiling_origin) / layout_tile_size) as i32;
// Combine the above two to get the tiles in the image that are visible this frame. letmut tiles_start = i32::max(image_tiles.start, visible_tiles_start); let tiles_end = i32::min(image_tiles.end, visible_tiles_end); if tiles_start > tiles_end {
tiles_start = tiles_end;
}
// The size in layout space of the boundary tiles. let first_tile_layout_size = if tiles_start == image_tiles.start {
first_tile_device_size as f32 * layout_tile_size / device_tile_size as f32
} else { // boundary tile was culled out, so the new first tile is a regularly sized tile.
layout_tile_size
};
// Same here. let last_tile_layout_size = if tiles_end == image_tiles.end {
last_tile_device_size as f32 * layout_tile_size / device_tile_size as f32
} else {
layout_tile_size
};
/// Compute the range of tiles (in number of tiles) that intersect the provided /// image range (in pixels) in an arbitrary dimension. /// /// ```ignore /// /// 0 /// : /// #-+---+---+---+---+---+--# /// # | | | | | | # /// #-+---+---+---+---+---+--# /// ^ : ^ /// /// +------------------------+ image_range /// +---+ regular_tile_size /// /// ``` fn tile_range_1d(
image_range: &Range<i32>,
regular_tile_size: i32,
) -> Range<i32> { // Integer division truncates towards zero so with negative values if the first/last // tile isn't a full tile we can get offset by one which we account for here.
letmut end = image_range.end / regular_tile_size; if image_range.end % regular_tile_size > 0 {
end += 1;
}
start..end
}
// Sizes of the first boundary tile in pixels. // // It can be smaller than the regular tile size if the image is not a multiple // of the regular tile size. fn first_tile_size_1d(
image_range: &Range<i32>,
regular_tile_size: i32,
) -> i32 { // We have to account for how the % operation behaves for negative values. let image_size = image_range.end - image_range.start;
i32::min( match image_range.start % regular_tile_size { // . #------+------+ . // . #//////| | . 0 => regular_tile_size, // (zero) -> 0 . #--+------+ . // . . #//| | . // %(m): ~~>
m if m > 0 => regular_tile_size - m, // . . #--+------+ 0 <- (zero) // . . #//| | . // %(m): <~~
m => -m,
},
image_size
)
}
// Sizes of the last boundary tile in pixels. // // It can be smaller than the regular tile size if the image is not a multiple // of the regular tile size. fn last_tile_size_1d(
image_range: &Range<i32>,
regular_tile_size: i32,
) -> i32 { // We have to account for how the modulo operation behaves for negative values. let image_size = image_range.end - image_range.start;
i32::min( match image_range.end % regular_tile_size { // +------+------# . // tiles: . | |//////# . 0 => regular_tile_size, // . +------+--# . 0 <- (zero) // . | |//# . . // modulo (m): <~~
m if m < 0 => regular_tile_size + m, // (zero) -> 0 +------+--# . . // . | |//# . . // modulo (m): ~~>
m => m,
},
image_size,
)
}
pubfn compute_tile_rect(
image_rect: &DeviceIntRect,
regular_tile_size: TileSize,
tile: TileOffset,
) -> DeviceIntRect { let regular_tile_size = regular_tile_size as i32;
DeviceIntRect::from_origin_and_size(
point2(
compute_tile_origin_1d(image_rect.x_range(), regular_tile_size, tile.x as i32),
compute_tile_origin_1d(image_rect.y_range(), regular_tile_size, tile.y as i32),
),
size2(
compute_tile_size_1d(image_rect.x_range(), regular_tile_size, tile.x as i32),
compute_tile_size_1d(image_rect.y_range(), regular_tile_size, tile.y as i32),
),
)
}
// Compute the width and height in pixels of a tile depending on its position in the image. pubfn compute_tile_size(
image_rect: &DeviceIntRect,
regular_tile_size: TileSize,
tile: TileOffset,
) -> DeviceIntSize { let regular_tile_size = regular_tile_size as i32;
size2(
compute_tile_size_1d(image_rect.x_range(), regular_tile_size, tile.x as i32),
compute_tile_size_1d(image_rect.y_range(), regular_tile_size, tile.y as i32),
)
}
// Most tiles are going to have base_size as width and height, // except for tiles around the edges that are shrunk to fit the image data. let actual_size = if tile_offset == tile_range.start {
first_tile_size_1d(&img_range, regular_tile_size)
} elseif tile_offset == tile_range.end - 1 {
last_tile_size_1d(&img_range, regular_tile_size)
} else {
regular_tile_size
};
assert!(actual_size > 0);
actual_size
}
pubfn compute_tile_range(
visible_area: &DeviceIntRect,
tile_size: u16,
) -> TileRange { let tile_size = tile_size as i32; let x_range = tile_range_1d(&visible_area.x_range(), tile_size); let y_range = tile_range_1d(&visible_area.y_range(), tile_size);
pubfn for_each_tile_in_range(
range: &TileRange, mut callback: impl FnMut(TileOffset),
) { for y in range.y_range() { for x in range.x_range() {
callback(point2(x, y));
}
}
}
let left = prev_rect.min.x != new_rect.min.x; let right = prev_rect.max.x != new_rect.max.x; let top = prev_rect.min.y != new_rect.min.y; let bottom = prev_rect.max.y != new_rect.max.y;
if !left && !right && !top && !bottom { // Bounds have not changed. return None;
}
let tw = 1.0 / (tile_size as f32); let th = 1.0 / (tile_size as f32);
let tiles = intersection
.cast::<f32>()
.scale(tw, th);
let min_x = if left { f32::ceil(tiles.min.x) } else { f32::floor(tiles.min.x) }; let min_y = if top { f32::ceil(tiles.min.y) } else { f32::floor(tiles.min.y) }; let max_x = if right { f32::floor(tiles.max.x) } else { f32::ceil(tiles.max.x) }; let max_y = if bottom { f32::floor(tiles.max.y) } else { f32::ceil(tiles.max.y) };
Some(TileRange {
min: point2(min_x as i32, min_y as i32),
max: point2(max_x as i32, max_y as i32),
})
}
#[cfg(test)] mod tests { usesuper::*; use std::collections::HashSet; use euclid::rect;
// this checks some additional invariants fn checked_for_each_tile(
prim_rect: &LayoutRect,
visible_rect: &LayoutRect,
device_image_rect: &DeviceIntRect,
device_tile_size: i32,
callback: &mutdyn FnMut(&LayoutRect, TileOffset, EdgeMask),
) { letmut coverage = LayoutRect::zero(); letmut seen_tiles = HashSet::new(); for tile in tiles(
prim_rect,
visible_rect,
device_image_rect,
device_tile_size,
) { // make sure we don't get sent duplicate tiles
assert!(!seen_tiles.contains(&tile.offset));
seen_tiles.insert(tile.offset);
coverage = coverage.union(&tile.rect);
assert!(prim_rect.contains_box(&tile.rect));
callback(&tile.rect, tile.offset, tile.edge_flags);
}
assert!(prim_rect.contains_box(&coverage));
assert!(coverage.contains_box(&visible_rect.intersection(&prim_rect).unwrap_or(LayoutRect::zero())));
}
#[test] fn test_tiles_1d() { // Exactly one full tile at positive offset. let result = tiles_1d(64.0, -10000.0..10000.0, 0.0, 0..64, 64);
assert_eq!(result.tile_range.start, 0);
assert_eq!(result.tile_range.end, 1);
assert_eq!(result.first_tile_layout_size, 64.0);
assert_eq!(result.last_tile_layout_size, 64.0);
// Exactly one full tile at negative offset. let result = tiles_1d(64.0, -10000.0..10000.0, -64.0, -64..0, 64);
assert_eq!(result.tile_range.start, -1);
assert_eq!(result.tile_range.end, 0);
assert_eq!(result.first_tile_layout_size, 64.0);
assert_eq!(result.last_tile_layout_size, 64.0);
// Two full tiles at negative and positive offsets. let result = tiles_1d(64.0, -10000.0..10000.0, -64.0, -64..64, 64);
assert_eq!(result.tile_range.start, -1);
assert_eq!(result.tile_range.end, 1);
assert_eq!(result.first_tile_layout_size, 64.0);
assert_eq!(result.last_tile_layout_size, 64.0);
// One partial tile at positive offset, non-zero origin, culled out. let result = tiles_1d(64.0, -100.0..10.0, 64.0, 64..310, 64);
assert_eq!(result.tile_range.start, result.tile_range.end);
// Two tiles at negative and positive offsets, one of which is culled out. // The remaining tile is partially culled but it should still generate a full tile. let result = tiles_1d(64.0, 10.0..10000.0, -64.0, -64..64, 64);
assert_eq!(result.tile_range.start, 0);
assert_eq!(result.tile_range.end, 1);
assert_eq!(result.first_tile_layout_size, 64.0);
assert_eq!(result.last_tile_layout_size, 64.0); let result = tiles_1d(64.0, -10000.0..-10.0, -64.0, -64..64, 64);
assert_eq!(result.tile_range.start, -1);
assert_eq!(result.tile_range.end, 0);
assert_eq!(result.first_tile_layout_size, 64.0);
assert_eq!(result.last_tile_layout_size, 64.0);
// Stretched tile in layout space device tile size is 64 and layout tile size is 128. // So the resulting tile sizes in layout space should be multiplied by two. let result = tiles_1d(128.0, -10000.0..10000.0, -64.0, -64..32, 64);
assert_eq!(result.tile_range.start, -1);
assert_eq!(result.tile_range.end, 1);
assert_eq!(result.first_tile_layout_size, 128.0);
assert_eq!(result.last_tile_layout_size, 64.0);
// Two visible tiles (the rest is culled out). let result = tiles_1d(10.0, 0.0..20.0, 0.0, 0..64, 64);
assert_eq!(result.tile_range.start, 0);
assert_eq!(result.tile_range.end, 1);
assert_eq!(result.first_tile_layout_size, 10.0);
assert_eq!(result.last_tile_layout_size, 10.0);
// Two visible tiles at negative layout offsets (the rest is culled out). let result = tiles_1d(10.0, -20.0..0.0, -20.0, 0..64, 64);
assert_eq!(result.tile_range.start, 0);
assert_eq!(result.tile_range.end, 1);
assert_eq!(result.first_tile_layout_size, 10.0);
assert_eq!(result.last_tile_layout_size, 10.0);
}
#[test] fn doubly_partial_tiles() { // In the following tests the image is a single tile and none of the sides of the tile // align with the tile grid. // This can only happen when we have a single non-aligned partial tile and no regular // tiles.
assert_eq!(first_tile_size_1d(&(300..310), 64), 10);
assert_eq!(first_tile_size_1d(&(-20..-10), 64), 10);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.