use crate::filter::FILTER_ATLAS_PADDING;
use crate::target::{LayerTextureId, TextureParity, TextureRegion};
use alloc::vec::Vec;
use vello_common::geometry::{RectU16, SizeU16};
use vello_common::multi_atlas::{AllocId, Atlas, AtlasId};
use vello_common::record::RecordedLayerKind;
#[derive(Debug, Clone, Copy)]
pub(super) struct Allocation {
pub(super) allocation: AllocatedTextureRegion,
pub(super) round_idx: usize,
}
#[derive(Debug)]
pub(super) struct Atlases {
layer_atlases: [Vec<Atlas>; 2],
scratch_texture: bool,
texture_size: SizeU16,
}
impl Atlases {
pub(super) fn new(texture_size: SizeU16) -> Self {
Self {
layer_atlases: core::array::from_fn(|_| Vec::new()),
scratch_texture: false,
texture_size,
}
}
pub(super) fn scratch_texture(&self) -> bool {
self.scratch_texture
}
pub(super) fn texture_size(&self) -> SizeU16 {
self.texture_size
}
pub(super) fn allocate_layer(
&mut self,
request: &LayerAllocationRequest,
) -> Option<AllocatedTextureRegion> {
let parity = request.texture_parity.get_parity();
for page_index in 0..u16::try_from(self.layer_atlases[parity].len()).unwrap() {
let id = LayerTextureId::new(request.texture_parity, page_index);
let atlas = &mut self.layer_atlases[parity][usize::from(page_index)];
if let Some(allocation) = atlas.allocate_region(id, request.region) {
return Some(allocation);
}
}
None
}
pub(super) fn deallocate(&mut self, texture: AllocatedTextureRegion) {
let id = texture.region.target;
let atlas =
&mut self.layer_atlases[id.texture_parity.get_parity()][usize::from(id.page_index)];
atlas.deallocate_region(texture);
}
pub(super) fn require_scratch_texture(&mut self) {
self.scratch_texture = true;
}
pub(super) fn add_layer_atlas(&mut self, texture_parity: TextureParity) {
let parity = texture_parity.get_parity();
let page_index = u16::try_from(self.layer_atlases[parity].len()).unwrap();
let size = self.texture_size;
let atlas = Atlas::new(
AtlasId::new(u32::from(page_index)),
size.width(),
size.height(),
);
self.layer_atlases[parity].push(atlas);
}
}
#[derive(Debug, Clone, Copy)]
pub(super) struct LayerAllocationRequest {
pub(super) texture_parity: TextureParity,
region: RegionProps,
}
impl LayerAllocationRequest {
pub(super) fn new(
bbox: RectU16,
kind: &RecordedLayerKind,
texture_parity: TextureParity,
) -> Self {
let padding = match kind {
RecordedLayerKind::Regular => 0,
RecordedLayerKind::Filter { .. } => FILTER_ATLAS_PADDING,
};
let region = RegionProps {
size: SizeU16::from_wh(bbox.width(), bbox.height()),
padding,
};
Self {
texture_parity,
region,
}
}
pub(super) fn allocation_size(self) -> SizeU16 {
self.region.allocation_size()
}
}
#[derive(Debug, Clone, Copy)]
struct RegionProps {
size: SizeU16,
padding: u16,
}
impl RegionProps {
fn allocation_size(self) -> SizeU16 {
self.padding
.checked_mul(2)
.and_then(|padding| self.size.checked_add(padding))
.expect("layer allocation size exceeds the u16 atlas domain")
}
}
#[derive(Debug, Clone, Copy)]
pub(super) struct AllocatedTextureRegion {
pub(super) region: TextureRegion,
padding: u16,
alloc_id: AllocId,
}
impl AllocatedTextureRegion {
fn new(region: TextureRegion, padding: u16, alloc_id: AllocId) -> Self {
Self {
region,
padding,
alloc_id,
}
}
pub(super) fn clear_region(self) -> TextureRegion {
self.region
}
fn allocation_region(self) -> RectU16 {
RectU16::new(
self.region.rect.x0 - self.padding,
self.region.rect.y0 - self.padding,
self.region.rect.x1 + self.padding,
self.region.rect.y1 + self.padding,
)
}
}
trait AtlasExt {
fn allocate_region(
&mut self,
target: LayerTextureId,
props: RegionProps,
) -> Option<AllocatedTextureRegion>;
fn deallocate_region(&mut self, texture: AllocatedTextureRegion);
}
impl AtlasExt for Atlas {
fn allocate_region(
&mut self,
target: LayerTextureId,
props: RegionProps,
) -> Option<AllocatedTextureRegion> {
let padding = props.padding;
let width = props.size.width();
let height = props.size.height();
let allocation_size = props.allocation_size();
let allocation = self.allocate(allocation_size.width(), allocation_size.height())?;
let x = allocation.x + padding;
let y = allocation.y + padding;
let region = AllocatedTextureRegion::new(
TextureRegion {
target,
rect: RectU16::new(x, y, x + width, y + height),
},
props.padding,
allocation.id,
);
Some(region)
}
fn deallocate_region(&mut self, texture: AllocatedTextureRegion) {
let allocation_region = texture.allocation_region();
self.deallocate(
texture.alloc_id,
allocation_region.width(),
allocation_region.height(),
);
}
}
#[cfg(test)]
mod tests {
use super::{AtlasExt, Atlases, FILTER_ATLAS_PADDING, LayerAllocationRequest, RegionProps};
use crate::target::{LayerTextureId, TextureParity};
use vello_common::filter::{FilterData, FilterLayerPlacement};
use vello_common::filter_effects::{Filter, FilterPrimitive};
use vello_common::geometry::{RectU16, SizeU16};
use vello_common::kurbo::Affine;
use vello_common::multi_atlas::{Atlas, AtlasId};
use vello_common::record::RecordedLayerKind;
fn request(
texture_parity: TextureParity,
size: SizeU16,
padding: u16,
) -> LayerAllocationRequest {
LayerAllocationRequest {
texture_parity,
region: RegionProps { size, padding },
}
}
#[test]
fn layer_requests() {
let bbox = RectU16::new(4, 8, 20, 32);
let regular_kind = RecordedLayerKind::Regular;
let regular = LayerAllocationRequest::new(bbox, ®ular_kind, TextureParity::Odd);
assert_eq!(regular.texture_parity, TextureParity::Odd);
assert_eq!(regular.region.size, SizeU16::from_wh(16, 24));
assert_eq!(regular.region.padding, 0);
let filter_kind = RecordedLayerKind::Filter {
filter_data: FilterData::new(
Filter::from_primitive(FilterPrimitive::Offset { dx: 1.0, dy: 2.0 }),
Affine::IDENTITY,
),
placement: FilterLayerPlacement::default(),
};
let filter = LayerAllocationRequest::new(bbox, &filter_kind, TextureParity::Odd);
assert_eq!(filter.texture_parity, TextureParity::Odd);
assert_eq!(filter.region.size, SizeU16::from_wh(16, 24));
assert_eq!(filter.region.padding, FILTER_ATLAS_PADDING);
let temporary = LayerAllocationRequest::new(
RectU16::new(10, 20, 42, 68),
&filter_kind,
TextureParity::Even,
);
assert_eq!(temporary.texture_parity, TextureParity::Even);
assert_eq!(temporary.region.size, SizeU16::from_wh(32, 48));
assert_eq!(temporary.region.padding, FILTER_ATLAS_PADDING);
}
#[test]
fn page_selection() {
let size = SizeU16::new(8);
let mut atlases = Atlases::new(size);
let even = request(TextureParity::Even, size, 0);
let odd = request(TextureParity::Odd, size, 0);
atlases.add_layer_atlas(TextureParity::Even);
let even_0 = atlases.allocate_layer(&even).unwrap();
assert_eq!(
even_0.region.target,
LayerTextureId::new(TextureParity::Even, 0)
);
assert!(atlases.allocate_layer(&even).is_none());
atlases.add_layer_atlas(TextureParity::Even);
let even_1 = atlases.allocate_layer(&even).unwrap();
assert_eq!(
even_1.region.target,
LayerTextureId::new(TextureParity::Even, 1)
);
atlases.add_layer_atlas(TextureParity::Odd);
let odd_0 = atlases.allocate_layer(&odd).unwrap();
assert_eq!(
odd_0.region.target,
LayerTextureId::new(TextureParity::Odd, 0)
);
atlases.deallocate(even_0);
let reused = atlases.allocate_layer(&even).unwrap();
assert_eq!(
reused.region.target,
LayerTextureId::new(TextureParity::Even, 0)
);
}
#[test]
fn page_capacity() {
let mut atlases = Atlases::new(SizeU16::from_wh(16, 8));
let request = request(TextureParity::Even, SizeU16::new(8), 0);
atlases.add_layer_atlas(TextureParity::Even);
let first = atlases.allocate_layer(&request).unwrap();
let second = atlases.allocate_layer(&request).unwrap();
assert_eq!(
first.region.target,
LayerTextureId::new(TextureParity::Even, 0)
);
assert_eq!(
second.region.target,
LayerTextureId::new(TextureParity::Even, 0)
);
assert!(atlases.allocate_layer(&request).is_none());
atlases.add_layer_atlas(TextureParity::Even);
let third = atlases.allocate_layer(&request).unwrap();
assert_eq!(
third.region.target,
LayerTextureId::new(TextureParity::Even, 1)
);
}
#[test]
fn padded_reuse() {
let mut atlas = Atlas::new(AtlasId::new(0), 8, 8);
let target = LayerTextureId::new(TextureParity::Even, 0);
let request = RegionProps {
size: SizeU16::new(6),
padding: 1,
};
let allocation = atlas.allocate_region(target, request).unwrap();
assert_eq!(allocation.region.rect, RectU16::new(1, 1, 7, 7));
assert_eq!(allocation.clear_region().rect, RectU16::new(1, 1, 7, 7));
assert_eq!(allocation.allocation_region(), RectU16::new(0, 0, 8, 8));
assert!(atlas.allocate_region(target, request).is_none());
atlas.deallocate_region(allocation);
let reused = atlas.allocate_region(target, request).unwrap();
assert_eq!(reused.region.rect, RectU16::new(1, 1, 7, 7));
}
#[test]
fn oversized_regions() {
let mut atlas = Atlas::new(AtlasId::new(0), 8, 8);
let target = LayerTextureId::new(TextureParity::Even, 0);
assert!(
atlas
.allocate_region(
target,
RegionProps {
size: SizeU16::from_wh(9, 8),
padding: 0,
},
)
.is_none()
);
assert!(
atlas
.allocate_region(
target,
RegionProps {
size: SizeU16::new(8),
padding: 1,
},
)
.is_none()
);
}
}