blob: 485f262c73940bf21ac2a64c05ba1db5dd4f34c0 [file]
// Copyright 2025 the Vello Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
use crate::tile::{Tile, TILE_WIDTH};
/// A footprint represents in a compact fashion the range of pixels covered by a tile.
/// We represent this as a u32 so that we can work with bit-shifting for better performance.
pub(crate) struct Footprint(pub(crate) u32);
impl Footprint {
/// Create a new, empty footprint.
pub(crate) fn empty() -> Self {
Self(0)
}
/// Create a new footprint from a single index, i.e. [i, i + 1).
pub(crate) fn from_index(index: u8) -> Self {
Self(1 << index)
}
/// Create a new footprint from a single index, i.e. [start, end).
pub(crate) fn from_range(start: u8, end: u8) -> Self {
Self((1 << end) - (1 << start))
}
/// The start point of the covered range (inclusive).
pub(crate) fn x0(&self) -> u32 {
self.0.trailing_zeros()
}
/// The end point of the covered range (exclusive).
pub(crate) fn x1(&self) -> u32 {
32 - self.0.leading_zeros()
}
/// Extend the range with a single index.
pub(crate) fn extend(&mut self, index: u8) {
self.0 |= (1 << index) as u32;
}
/// Merge another footprint with the current one.
pub(crate) fn merge(&mut self, fp: &Self) {
self.0 |= fp.0;
}
}
impl Tile {
// TODO: Profiling shows that this method takes up quite a lot of time in AVX SIMD, investigate
// if it can be improved.
pub(crate) fn footprint(&self) -> Footprint {
let x0 = self.p0.x;
let x1 = self.p1.x;
let x_min = x0.min(x1).floor();
let x_max = x0.max(x1).ceil();
let start_i = x_min as u32;
let end_i = (start_i + 1).max(x_max as u32).min(TILE_WIDTH);
Footprint::from_range(start_i as u8, end_i as u8)
}
}
#[cfg(test)]
mod tests {
use crate::footprint::Footprint;
#[test]
fn footprint_empty() {
let fp1 = Footprint::empty();
// Not optimal behavior, but currently how it is.
assert_eq!(fp1.x0(), 32);
assert_eq!(fp1.x1(), 0);
}
#[test]
fn footprint_from_index() {
let fp1 = Footprint::from_index(0);
assert_eq!(fp1.x0(), 0);
assert_eq!(fp1.x1(), 1);
let fp2 = Footprint::from_index(3);
assert_eq!(fp2.x0(), 3);
assert_eq!(fp2.x1(), 4);
let fp3 = Footprint::from_index(6);
assert_eq!(fp3.x0(), 6);
assert_eq!(fp3.x1(), 7);
}
#[test]
fn footprint_from_range() {
let fp1 = Footprint::from_range(1, 3);
assert_eq!(fp1.x0(), 1);
assert_eq!(fp1.x1(), 3);
// Same comment as for empty.
let fp2 = Footprint::from_range(2, 2);
assert_eq!(fp2.x0(), 32);
assert_eq!(fp2.x1(), 0);
let fp3 = Footprint::from_range(3, 7);
assert_eq!(fp3.x0(), 3);
assert_eq!(fp3.x1(), 7);
}
#[test]
fn footprint_extend() {
let mut fp = Footprint::empty();
fp.extend(5);
assert_eq!(fp.x0(), 5);
assert_eq!(fp.x1(), 6);
fp.extend(3);
assert_eq!(fp.x0(), 3);
assert_eq!(fp.x1(), 6);
fp.extend(8);
assert_eq!(fp.x0(), 3);
assert_eq!(fp.x1(), 9);
fp.extend(0);
assert_eq!(fp.x0(), 0);
assert_eq!(fp.x1(), 9);
fp.extend(9);
assert_eq!(fp.x0(), 0);
assert_eq!(fp.x1(), 10);
}
#[test]
fn footprint_merge() {
let mut fp1 = Footprint::from_range(2, 4);
let fp2 = Footprint::from_range(5, 6);
fp1.merge(&fp2);
assert_eq!(fp1.x0(), 2);
assert_eq!(fp1.x1(), 6);
let mut fp3 = Footprint::from_range(5, 9);
let fp4 = Footprint::from_range(7, 10);
fp3.merge(&fp4);
assert_eq!(fp3.x0(), 5);
assert_eq!(fp3.x1(), 10);
}
}