use std::collections::{HashMap, HashSet};
use std::sync::Arc;
#[cfg(feature = "perf-trace")]
use std::sync::atomic::{AtomicBool, Ordering};
use frust_gpu::{DownlevelProfile, TierCaps};
use peniko::color::PremulRgba8;
use peniko::{ImageData, ImageFormat};
use thiserror::Error;
use vello_common::image_cache::ImageCache;
use vello_common::multi_atlas::{AllocationStrategy, AtlasConfig};
use vello_common::paint::{ImageId, ImageSource};
use vello_common::pixmap::Pixmap;
use crate::config;
pub const MAX_UNSEEN_FRAMES: u64 = 60;
pub const ATLAS_PADDING: u16 = 0;
pub const MAX_IMAGE_DIMENSION: u32 = u16::MAX as u32;
pub const MAX_ATLAS_LAYERS: usize = 256;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
pub enum ImageSkip {
#[error("image residency is disabled by FRUST_ENGINE_NO_ATLAS")]
AtlasDisabled,
#[error("unsupported image format {0:?}: only Rgba8 and Bgra8 are uploaded")]
UnsupportedFormat(ImageFormat),
#[error("degenerate image extent {width}x{height}")]
DegenerateExtent {
width: u32,
height: u32,
},
#[error("image {width}x{height} exceeds the {max}-pixel residency ceiling")]
TooLarge {
width: u32,
height: u32,
max: u32,
},
#[error("image pixel buffer is {actual} bytes; {width}x{height} needs exactly {expected}")]
MalformedPixels {
width: u32,
height: u32,
expected: usize,
actual: usize,
},
#[error("image paint transform is singular or non-finite")]
SingularTransform,
#[error("image {width}x{height} does not fit the {atlas_width}x{atlas_height} atlas budget")]
NoAtlasSpace {
width: u32,
height: u32,
atlas_width: u32,
atlas_height: u32,
},
#[error(
"image {width}x{height} already resolved this frame at a different extent; the first \
extent's texels are kept until the next frame"
)]
SameFrameExtentConflict {
width: u32,
height: u32,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AtlasBudget {
pub atlas_size: (u32, u32),
pub max_atlases: usize,
}
impl AtlasBudget {
pub const MOBILE: Self = Self {
atlas_size: (1024, 1024),
max_atlases: 4,
};
pub const DESKTOP: Self = Self {
atlas_size: (2048, 2048),
max_atlases: 8,
};
#[must_use]
pub fn for_caps(caps: &TierCaps) -> Self {
let tier = if is_mobile_tier(caps) {
Self::MOBILE
} else {
Self::DESKTOP
};
let resolved = match config::atlas_size() {
None => tier.clamped(caps),
Some(atlas_size) => Self {
atlas_size,
max_atlases: tier.max_atlases,
}
.within_total_bytes(tier.total_bytes())
.clamped(caps),
};
#[cfg(feature = "perf-trace")]
ATLAS_TIER_LOG.emit(caps, resolved);
resolved
}
#[must_use]
pub fn within_total_bytes(self, total_bytes: u64) -> Self {
let allowance = total_bytes.max(ATLAS_FORMAT_BYTES);
let max_texels = allowance / ATLAS_FORMAT_BYTES;
let (width, height) = self.atlas_size;
let texels = u64::from(width).saturating_mul(u64::from(height));
let atlas_size = if texels <= max_texels {
(width, height)
} else {
let scale = (max_texels as f64 / texels.max(1) as f64).sqrt();
let scaled_w = ((f64::from(width) * scale) as u32).max(1);
let scaled_h = ((f64::from(height) * scale) as u32).max(1);
let fitted_w = fit_axis(scaled_w, max_texels / u64::from(scaled_h));
let fitted_h = fit_axis(scaled_h, max_texels / u64::from(fitted_w));
(fitted_w, fitted_h)
};
let layer_bytes = u64::from(atlas_size.0)
.saturating_mul(u64::from(atlas_size.1))
.saturating_mul(ATLAS_FORMAT_BYTES)
.max(1);
let layers = usize::try_from(allowance / layer_bytes).unwrap_or(MAX_ATLAS_LAYERS);
Self {
atlas_size,
max_atlases: self.max_atlases.min(layers).max(1),
}
}
#[must_use]
pub fn contains(self, region: AtlasRegion, layers: u32) -> bool {
!region.is_empty()
&& region.layer < layers
&& region.offset[0].saturating_add(region.size[0]) <= self.atlas_size.0
&& region.offset[1].saturating_add(region.size[1]) <= self.atlas_size.1
}
#[must_use]
pub fn clamped(self, caps: &TierCaps) -> Self {
let edge = caps.max_texture_dimension_2d.max(1);
let layers = usize::try_from(caps.max_texture_array_layers).unwrap_or(MAX_ATLAS_LAYERS);
Self {
atlas_size: (
self.atlas_size.0.clamp(1, edge),
self.atlas_size.1.clamp(1, edge),
),
max_atlases: self.max_atlases.clamp(1, layers.min(MAX_ATLAS_LAYERS)),
}
}
#[must_use]
pub fn config(self) -> AtlasConfig {
AtlasConfig {
initial_atlas_count: 0,
max_atlases: self.max_atlases,
atlas_size: self.atlas_size,
auto_grow: true,
allocation_strategy: AllocationStrategy::FirstFit,
}
}
#[must_use]
pub fn layer_bytes(self) -> u64 {
u64::from(self.atlas_size.0)
.saturating_mul(u64::from(self.atlas_size.1))
.saturating_mul(ATLAS_FORMAT_BYTES)
}
#[must_use]
pub fn total_bytes(self) -> u64 {
self.layer_bytes()
.saturating_mul(self.max_atlases.try_into().unwrap_or(u64::MAX))
}
}
pub const ATLAS_FORMAT_BYTES: u64 = 4;
#[must_use]
pub fn is_mobile_tier(caps: &TierCaps) -> bool {
caps.downlevel_profile != DownlevelProfile::Full || caps.transient_saves_memory
}
#[cfg(feature = "perf-trace")]
static ATLAS_TIER_LOG: TierLogOnce = TierLogOnce::new();
#[cfg(feature = "perf-trace")]
#[derive(Debug, Default)]
pub struct TierLogOnce {
emitted: AtomicBool,
}
#[cfg(feature = "perf-trace")]
impl TierLogOnce {
#[must_use]
pub const fn new() -> Self {
Self {
emitted: AtomicBool::new(false),
}
}
pub fn emit(&self, caps: &TierCaps, budget: AtlasBudget) -> bool {
if self.emitted.swap(true, Ordering::Relaxed) {
return false;
}
log::info!("{}", atlas_tier_line(caps, budget));
true
}
}
#[cfg(feature = "perf-trace")]
#[must_use]
pub fn atlas_tier_line(caps: &TierCaps, budget: AtlasBudget) -> String {
let tier = if is_mobile_tier(caps) {
"mobile"
} else {
"desktop"
};
format!(
"frust-perf atlas tier={tier} budget={}x{}x{} downlevel={:?} \
transient_saves_memory={} adapter={}",
budget.atlas_size.0,
budget.atlas_size.1,
budget.max_atlases,
caps.downlevel_profile,
caps.transient_saves_memory,
caps.adapter_name,
)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct AtlasRegion {
pub layer: u32,
pub offset: [u32; 2],
pub size: [u32; 2],
}
impl AtlasRegion {
#[must_use]
pub fn byte_len(self) -> usize {
(self.size[0] as usize)
.saturating_mul(self.size[1] as usize)
.saturating_mul(ATLAS_FORMAT_BYTES as usize)
}
#[must_use]
pub fn bytes_per_row(self) -> u32 {
self.size[0].saturating_mul(ATLAS_FORMAT_BYTES as u32)
}
#[must_use]
pub fn is_empty(self) -> bool {
self.size[0] == 0 || self.size[1] == 0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ResidentImage {
pub id: ImageId,
pub region: AtlasRegion,
pub natural: [u32; 2],
pub padding: u32,
pub may_have_transparency: bool,
}
impl ResidentImage {
#[must_use]
pub fn source(&self) -> ImageSource {
ImageSource::opaque_id_with_transparency_hint(self.id, self.may_have_transparency)
}
#[must_use]
pub fn minify_scale(&self) -> Option<(f32, f32)> {
if self.region.size == self.natural {
return None;
}
let natural_w = self.natural[0].max(1) as f32;
let natural_h = self.natural[1].max(1) as f32;
Some((
self.region.size[0] as f32 / natural_w,
self.region.size[1] as f32 / natural_h,
))
}
}
#[derive(Debug, Clone)]
pub struct ImageUpload {
pub id: ImageId,
pub region: AtlasRegion,
pub natural: [u32; 2],
pub pixels: Arc<Pixmap>,
pub may_have_transparency: bool,
}
#[derive(Debug, Clone, Copy)]
struct Entry {
id: ImageId,
region: AtlasRegion,
natural: [u32; 2],
may_have_transparency: bool,
last_seen: u64,
}
#[derive(Debug)]
pub struct ImageResidency {
cache: ImageCache,
budget: AtlasBudget,
entries: HashMap<u64, Entry>,
frame: u64,
disabled: bool,
uploads: Vec<ImageUpload>,
evictions: Vec<AtlasRegion>,
eviction_set: HashSet<AtlasRegion>,
skipped: u64,
pressure_evictions: u64,
frame_pressure_evictions: u64,
minified: u64,
reported_minify: HashSet<u64>,
reaped: Vec<u64>,
pressure_rects: Vec<PressureRect>,
pressure_order: Vec<usize>,
pressure_free_area: Vec<u64>,
pressure_layers: Vec<u32>,
pressure_plan: Vec<u64>,
}
impl ImageResidency {
#[must_use]
pub fn new(budget: AtlasBudget) -> Self {
Self::with_enabled(budget, !config::atlas_disabled())
}
#[must_use]
pub fn disabled(budget: AtlasBudget) -> Self {
Self::with_enabled(budget, false)
}
fn with_enabled(budget: AtlasBudget, enabled: bool) -> Self {
Self {
cache: ImageCache::new_with_config(budget.config()),
budget,
entries: HashMap::new(),
frame: 0,
disabled: !enabled,
uploads: Vec::new(),
evictions: Vec::new(),
eviction_set: HashSet::new(),
skipped: 0,
pressure_evictions: 0,
frame_pressure_evictions: 0,
minified: 0,
reported_minify: HashSet::new(),
reaped: Vec::new(),
pressure_rects: Vec::new(),
pressure_order: Vec::new(),
pressure_free_area: Vec::new(),
pressure_layers: Vec::new(),
pressure_plan: Vec::new(),
}
}
#[must_use]
pub fn for_caps(caps: &TierCaps) -> Self {
Self::new(AtlasBudget::for_caps(caps))
}
#[must_use]
pub fn budget(&self) -> AtlasBudget {
self.budget
}
#[must_use]
pub fn allocator(&self) -> &ImageCache {
&self.cache
}
#[must_use]
pub fn allocator_mut(&mut self) -> &mut ImageCache {
&mut self.cache
}
#[must_use]
pub fn is_disabled(&self) -> bool {
self.disabled
}
#[must_use]
pub fn entry_count(&self) -> usize {
self.entries.len()
}
#[must_use]
pub fn frame(&self) -> u64 {
self.frame
}
#[must_use]
pub fn layers(&self) -> u32 {
u32::try_from(self.cache.atlas_count()).unwrap_or(u32::MAX)
}
#[must_use]
pub fn skipped(&self) -> u64 {
self.skipped
}
#[must_use]
pub fn pressure_evictions(&self) -> u64 {
self.pressure_evictions
}
#[must_use]
pub fn frame_pressure_evictions(&self) -> u64 {
self.frame_pressure_evictions
}
#[must_use]
pub fn minified(&self) -> u64 {
self.minified
}
#[must_use]
pub fn evictions(&self) -> &[AtlasRegion] {
&self.evictions
}
#[must_use]
pub fn uploads(&self) -> &[ImageUpload] {
&self.uploads
}
#[must_use]
pub fn plan(&self) -> (Vec<AtlasRegion>, Vec<ImageUpload>) {
(self.evictions.clone(), self.uploads.clone())
}
#[must_use]
pub fn has_pending_plan(&self) -> bool {
!self.evictions.is_empty() || !self.uploads.is_empty()
}
pub fn acknowledge_plan(&mut self) {
self.evictions.clear();
self.eviction_set.clear();
self.uploads.clear();
}
pub fn begin_frame(&mut self) {
self.frame = self.frame.saturating_add(1);
self.frame_pressure_evictions = 0;
self.reap();
}
pub fn resolve(&mut self, data: &ImageData) -> Result<ResidentImage, ImageSkip> {
let result = self.resolve_inner(data);
if result.is_err() {
self.skipped = self.skipped.saturating_add(1);
}
result
}
fn resolve_inner(&mut self, data: &ImageData) -> Result<ResidentImage, ImageSkip> {
if self.disabled {
return Err(ImageSkip::AtlasDisabled);
}
check_supported(data)?;
let key = data.data.id();
if let Some(entry) = self.entries.get_mut(&key)
&& entry.natural == [data.width, data.height]
{
entry.last_seen = self.frame;
return Ok(ResidentImage {
id: entry.id,
region: entry.region,
natural: entry.natural,
padding: u32::from(ATLAS_PADDING),
may_have_transparency: entry.may_have_transparency,
});
}
if self
.entries
.get(&key)
.is_some_and(|entry| entry.last_seen == self.frame)
{
let (width, height) = fit_extent([data.width, data.height], self.budget.atlas_size);
return Err(ImageSkip::SameFrameExtentConflict { width, height });
}
self.release(key);
let ImageSource::Pixmap(pixels) = ImageSource::from_peniko_image_data(data) else {
return Err(ImageSkip::UnsupportedFormat(data.format));
};
let natural = [data.width, data.height];
let (width, height) = fit_extent(natural, self.budget.atlas_size);
let pixels = if [width, height] == natural {
pixels
} else {
self.note_minify(key, natural, [width, height]);
Arc::new(minify(&pixels, width, height))
};
let no_space = ImageSkip::NoAtlasSpace {
width,
height,
atlas_width: self.budget.atlas_size.0,
atlas_height: self.budget.atlas_size.1,
};
let id = match self.cache.allocate(width, height, ATLAS_PADDING) {
Ok(id) => id,
Err(_) => self
.allocate_under_pressure(width, height)
.ok_or(no_space)?,
};
let Some(resource) = self.cache.get(id) else {
return Err(no_space);
};
let region = AtlasRegion {
layer: resource.atlas_id.as_u32(),
offset: resource.offsets(),
size: resource.size(),
};
let may_have_transparency = pixels.may_have_transparency();
self.entries.insert(
key,
Entry {
id,
region,
natural,
may_have_transparency,
last_seen: self.frame,
},
);
self.uploads.push(ImageUpload {
id,
region,
natural,
pixels,
may_have_transparency,
});
Ok(ResidentImage {
id,
region,
natural,
padding: u32::from(ATLAS_PADDING),
may_have_transparency,
})
}
fn allocate_under_pressure(&mut self, width: u32, height: u32) -> Option<ImageId> {
if !self
.entries
.values()
.any(|entry| entry.last_seen < self.frame)
{
return None;
}
let mut rects = std::mem::take(&mut self.pressure_rects);
let mut order = std::mem::take(&mut self.pressure_order);
let mut free_area = std::mem::take(&mut self.pressure_free_area);
let mut layers = std::mem::take(&mut self.pressure_layers);
let mut plan = std::mem::take(&mut self.pressure_plan);
self.model_layers(&mut rects, &mut order, &mut free_area);
let fit = plan_pressure_eviction(
width,
height,
self.budget.atlas_size,
&mut rects,
&order,
&mut free_area,
&mut layers,
);
plan.clear();
if let Some(layer) = fit {
plan.extend(
rects
.iter()
.filter(|rect| rect.freed && rect.layer == layer)
.map(|rect| rect.key),
);
}
let allocated = if plan.is_empty() {
None
} else {
for key in &plan {
self.release(*key);
self.pressure_evictions = self.pressure_evictions.saturating_add(1);
self.frame_pressure_evictions = self.frame_pressure_evictions.saturating_add(1);
}
self.cache.allocate(width, height, ATLAS_PADDING).ok()
};
rects.clear();
order.clear();
free_area.clear();
layers.clear();
plan.clear();
self.pressure_rects = rects;
self.pressure_order = order;
self.pressure_free_area = free_area;
self.pressure_layers = layers;
self.pressure_plan = plan;
allocated
}
fn model_layers(
&self,
rects: &mut Vec<PressureRect>,
order: &mut Vec<usize>,
free_area: &mut Vec<u64>,
) {
rects.clear();
rects.extend(self.entries.iter().map(|(key, entry)| {
let region = padded(entry.region, ATLAS_PADDING);
PressureRect {
key: *key,
layer: region.layer,
x0: region.offset[0],
y0: region.offset[1],
x1: region.offset[0].saturating_add(region.size[0]),
y1: region.offset[1].saturating_add(region.size[1]),
area: u64::from(region.size[0]).saturating_mul(u64::from(region.size[1])),
last_seen: entry.last_seen,
slot: entry.id.as_u32(),
freed: false,
}
}));
rects.sort_unstable_by_key(|rect| rect.layer);
order.clear();
order.extend(
rects
.iter()
.enumerate()
.filter_map(|(index, rect)| (rect.last_seen < self.frame).then_some(index)),
);
order.sort_unstable_by_key(|index| {
let rect = &rects[*index];
(rect.last_seen, rect.slot, rect.key)
});
free_area.clear();
free_area.resize(self.cache.atlas_count(), 0);
for (id, stats) in self.cache.atlas_manager().atlas_stats() {
if let Some(free) = free_area.get_mut(id.as_u32() as usize) {
*free = u64::from(stats.total_area.saturating_sub(stats.allocated_area));
}
}
}
fn note_minify(&mut self, key: u64, natural: [u32; 2], fitted: [u32; 2]) {
self.minified = self.minified.saturating_add(1);
if !self.reported_minify.insert(key) {
return;
}
log::warn!(
"image {}x{} is larger than the {}x{} atlas budget; uploading a {}x{} box-filtered \
copy instead (reported once per image)",
natural[0],
natural[1],
self.budget.atlas_size.0,
self.budget.atlas_size.1,
fitted[0],
fitted[1],
);
}
fn reap(&mut self) {
let mut reaped = std::mem::take(&mut self.reaped);
reaped.clear();
reaped.extend(self.entries.iter().filter_map(|(key, entry)| {
(self.frame.saturating_sub(entry.last_seen) > MAX_UNSEEN_FRAMES).then_some(*key)
}));
for key in reaped.drain(..) {
self.release(key);
}
self.reaped = reaped;
}
fn release(&mut self, key: u64) {
let Some(entry) = self.entries.remove(&key) else {
return;
};
self.cache.deallocate(entry.id);
self.uploads.retain(|upload| upload.id != entry.id);
let region = padded(entry.region, ATLAS_PADDING);
if self.eviction_set.insert(region) {
self.evictions.push(region);
}
}
}
const MAX_PRESSURE_EVICTION_CANDIDATES: usize = 64;
const PRESSURE_EVICTION_AREA_BUDGET: u64 = 4;
#[derive(Debug, Clone, Copy)]
struct PressureRect {
key: u64,
layer: u32,
x0: u32,
y0: u32,
x1: u32,
y1: u32,
area: u64,
last_seen: u64,
slot: u32,
freed: bool,
}
fn plan_pressure_eviction(
width: u32,
height: u32,
atlas_size: (u32, u32),
rects: &mut [PressureRect],
order: &[usize],
free_area: &mut [u64],
layers: &mut Vec<u32>,
) -> Option<u32> {
let padding = u32::from(ATLAS_PADDING).saturating_mul(2);
let request_w = width.saturating_add(padding);
let request_h = height.saturating_add(padding);
let request_area = u64::from(request_w).saturating_mul(u64::from(request_h));
let area_budget = request_area.saturating_mul(PRESSURE_EVICTION_AREA_BUDGET);
layers.clear();
for &index in order {
let Some(rect) = rects.get(index) else {
continue;
};
if !layers.contains(&rect.layer) {
layers.push(rect.layer);
}
}
for &layer in layers.iter() {
let mut layer_freed_area = 0_u64;
let mut step = 0_usize;
for &index in order {
let on_this_layer = rects.get(index).is_some_and(|rect| rect.layer == layer);
if !on_this_layer {
continue;
}
if step >= MAX_PRESSURE_EVICTION_CANDIDATES || layer_freed_area > area_budget {
break;
}
step += 1;
let area = {
let Some(rect) = rects.get_mut(index) else {
continue;
};
rect.freed = true;
rect.area
};
layer_freed_area = layer_freed_area.saturating_add(area);
let Some(free) = free_area.get_mut(layer as usize) else {
continue;
};
*free = free.saturating_add(area);
if *free >= request_area && layer_admits(rects, layer, request_w, request_h, atlas_size)
{
return Some(layer);
}
}
}
None
}
fn layer_admits(
rects: &[PressureRect],
layer: u32,
width: u32,
height: u32,
atlas_size: (u32, u32),
) -> bool {
if width > atlas_size.0 || height > atlas_size.1 {
return false;
}
let start = rects.partition_point(|rect| rect.layer < layer);
let end = rects.partition_point(|rect| rect.layer <= layer);
rects[start..end]
.iter()
.filter(|rect| !rect.freed)
.all(|rect| {
width <= rect.x0
|| width <= atlas_size.0.saturating_sub(rect.x1)
|| height <= rect.y0
|| height <= atlas_size.1.saturating_sub(rect.y1)
})
}
#[must_use]
pub fn fit_extent(natural: [u32; 2], atlas: (u32, u32)) -> (u32, u32) {
let (width, height) = (natural[0], natural[1]);
if width <= atlas.0 && height <= atlas.1 {
return (width, height);
}
let scale = (f64::from(atlas.0) / f64::from(width.max(1)))
.min(f64::from(atlas.1) / f64::from(height.max(1)));
let axis = |value: u32, ceiling: u32| {
let scaled = (f64::from(value) * scale).floor();
(scaled as u32).clamp(1, ceiling.max(1))
};
(axis(width, atlas.0), axis(height, atlas.1))
}
#[must_use]
pub fn minify(source: &Pixmap, width: u32, height: u32) -> Pixmap {
let source_w = u32::from(source.width());
let source_h = u32::from(source.height());
let width = width.clamp(1, source_w.max(1));
let height = height.clamp(1, source_h.max(1));
let texels = source.data();
let mut out = Vec::with_capacity((width as usize).saturating_mul(height as usize));
let mut may_have_transparency = false;
for y in 0..height {
let y0 = (u64::from(y) * u64::from(source_h) / u64::from(height)) as u32;
let y1 = ((u64::from(y) + 1) * u64::from(source_h) / u64::from(height)) as u32;
let y1 = y1.max(y0.saturating_add(1)).min(source_h);
for x in 0..width {
let x0 = (u64::from(x) * u64::from(source_w) / u64::from(width)) as u32;
let x1 = ((u64::from(x) + 1) * u64::from(source_w) / u64::from(width)) as u32;
let x1 = x1.max(x0.saturating_add(1)).min(source_w);
let mut sum = [0_u64; 4];
let mut count = 0_u64;
for row in y0..y1 {
let base = (row as usize).saturating_mul(source_w as usize);
for column in x0..x1 {
let Some(texel) = texels.get(base.saturating_add(column as usize)) else {
continue;
};
sum[0] += u64::from(texel.r);
sum[1] += u64::from(texel.g);
sum[2] += u64::from(texel.b);
sum[3] += u64::from(texel.a);
count += 1;
}
}
let divisor = count.max(1);
let mean = |channel: usize| {
((sum[channel] + divisor / 2) / divisor).min(u64::from(u8::MAX)) as u8
};
let texel = PremulRgba8 {
r: mean(0),
g: mean(1),
b: mean(2),
a: mean(3),
};
may_have_transparency |= texel.a != u8::MAX;
out.push(texel);
}
}
Pixmap::from_parts_with_opacity(
out,
width.min(u32::from(u16::MAX)) as u16,
height.min(u32::from(u16::MAX)) as u16,
may_have_transparency,
)
}
fn fit_axis(axis: u32, ceiling: u64) -> u32 {
axis.min(u32::try_from(ceiling).unwrap_or(u32::MAX)).max(1)
}
fn padded(region: AtlasRegion, padding: u16) -> AtlasRegion {
let padding = u32::from(padding);
let pad_x = padding.min(region.offset[0]);
let pad_y = padding.min(region.offset[1]);
AtlasRegion {
layer: region.layer,
offset: [region.offset[0] - pad_x, region.offset[1] - pad_y],
size: [
region.size[0].saturating_add(pad_x + padding),
region.size[1].saturating_add(pad_y + padding),
],
}
}
fn check_supported(data: &ImageData) -> Result<(), ImageSkip> {
match data.format {
ImageFormat::Rgba8 | ImageFormat::Bgra8 => {}
format => return Err(ImageSkip::UnsupportedFormat(format)),
}
if data.width == 0 || data.height == 0 {
return Err(ImageSkip::DegenerateExtent {
width: data.width,
height: data.height,
});
}
if data.width > MAX_IMAGE_DIMENSION || data.height > MAX_IMAGE_DIMENSION {
return Err(ImageSkip::TooLarge {
width: data.width,
height: data.height,
max: MAX_IMAGE_DIMENSION,
});
}
let actual = data.data.data().len();
let expected = data
.format
.size_in_bytes(data.width, data.height)
.unwrap_or(usize::MAX);
if actual != expected {
return Err(ImageSkip::MalformedPixels {
width: data.width,
height: data.height,
expected,
actual,
});
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use peniko::{Blob, ImageAlphaType};
fn image(width: u32, height: u32) -> ImageData {
let len = (width as usize) * (height as usize) * 4;
ImageData {
data: Blob::new(Arc::new(vec![255_u8; len])),
format: ImageFormat::Rgba8,
alpha_type: ImageAlphaType::Alpha,
width,
height,
}
}
fn residency() -> ImageResidency {
ImageResidency::new(AtlasBudget {
atlas_size: (64, 64),
max_atlases: 2,
})
}
#[test]
fn a_mobile_budget_is_never_the_vello_default() {
let default = AtlasConfig::default();
for budget in [AtlasBudget::MOBILE, AtlasBudget::DESKTOP] {
assert_ne!(budget.atlas_size, default.atlas_size);
assert!(budget.total_bytes() < 4096 * 4096 * ATLAS_FORMAT_BYTES * 8);
}
assert_eq!(AtlasBudget::MOBILE.total_bytes(), 16 << 20);
assert_eq!(AtlasBudget::DESKTOP.total_bytes(), 128 << 20);
}
#[test]
fn a_downlevel_adapter_takes_the_mobile_budget() {
let webgl2 = TierCaps::fake(DownlevelProfile::WebGl2);
assert!(is_mobile_tier(&webgl2));
let mut tiler = TierCaps::fake(DownlevelProfile::Full);
assert!(!is_mobile_tier(&tiler));
tiler.transient_saves_memory = true;
assert!(is_mobile_tier(&tiler));
}
#[test]
fn a_budget_is_clamped_to_the_adapters_own_ceilings() {
let mut caps = TierCaps::fake(DownlevelProfile::Full);
caps.max_texture_dimension_2d = 1024;
caps.max_texture_array_layers = 2;
let budget = AtlasBudget::DESKTOP.clamped(&caps);
assert_eq!(budget.atlas_size, (1024, 1024));
assert_eq!(budget.max_atlases, 2);
caps.max_texture_array_layers = 4096;
let budget = AtlasBudget {
atlas_size: (256, 256),
max_atlases: 4096,
}
.clamped(&caps);
assert_eq!(budget.max_atlases, MAX_ATLAS_LAYERS);
}
#[test]
fn the_first_layer_is_created_lazily() {
let residency = residency();
assert_eq!(residency.layers(), 0);
assert_eq!(residency.entry_count(), 0);
}
#[test]
fn one_image_over_many_frames_uploads_exactly_once() {
let mut residency = residency();
let data = image(8, 8);
let mut uploads = 0;
for _ in 0..60 {
residency.begin_frame();
residency.resolve(&data).expect("8x8 fits a 64x64 atlas");
uploads += residency.uploads().len();
residency.acknowledge_plan();
}
assert_eq!(uploads, 1, "residency survives every frame that draws it");
assert_eq!(residency.entry_count(), 1);
assert_eq!(residency.layers(), 1);
}
#[test]
fn an_unseen_image_is_reaped_and_its_padded_region_cleared() {
let mut residency = residency();
let data = image(8, 8);
residency.begin_frame();
let resident = residency.resolve(&data).expect("fits");
residency.acknowledge_plan();
for _ in 0..MAX_UNSEEN_FRAMES {
residency.begin_frame();
assert!(residency.evictions().is_empty(), "still inside the window");
}
residency.begin_frame();
assert_eq!(residency.entry_count(), 0);
assert_eq!(
residency.evictions(),
&[padded(resident.region, ATLAS_PADDING)]
);
residency.resolve(&data).expect("fits");
assert_eq!(residency.uploads().len(), 1);
}
#[test]
fn an_image_larger_than_the_atlas_is_minified_to_fit_rather_than_skipped() {
let mut residency = residency();
residency.begin_frame();
let resident = residency.resolve(&image(128, 8)).expect("minified to fit");
assert_eq!(resident.region.size, [64, 4]);
assert_eq!(resident.natural, [128, 8]);
assert_eq!(resident.minify_scale(), Some((0.5, 0.5)));
assert_eq!(residency.minified(), 1);
assert_eq!(residency.skipped(), 0, "a big image is not a skipped one");
assert_eq!(residency.uploads().len(), 1);
let upload = &residency.uploads()[0];
assert_eq!(
(upload.pixels.width(), upload.pixels.height()),
(64, 4),
"the upload carries exactly the region's texels"
);
}
#[test]
fn an_atlas_with_no_room_left_is_still_a_skip() {
let mut residency = ImageResidency::new(AtlasBudget {
atlas_size: (64, 64),
max_atlases: 1,
});
residency.begin_frame();
residency
.resolve(&image(64, 64))
.expect("fills the one layer");
let skip = residency
.resolve(&image(32, 32))
.expect_err("nothing is left to allocate from");
assert!(matches!(skip, ImageSkip::NoAtlasSpace { .. }));
assert_eq!(residency.skipped(), 1);
assert_eq!(residency.minified(), 0);
}
#[test]
fn a_minified_image_stays_resident_across_frames_at_its_declared_extent() {
let mut residency = residency();
let data = image(128, 8);
for _ in 0..8 {
residency.begin_frame();
let resident = residency.resolve(&data).expect("minified to fit");
assert_eq!(resident.natural, [128, 8]);
residency.acknowledge_plan();
}
assert_eq!(residency.entry_count(), 1);
assert_eq!(
residency.minified(),
1,
"downsampled once, not once a frame"
);
assert!(
residency.uploads().is_empty(),
"no re-upload after the first"
);
}
#[test]
fn a_fit_extent_preserves_the_aspect_ratio_and_never_grows() {
let atlas = (64, 64);
assert_eq!(fit_extent([64, 64], atlas), (64, 64));
assert_eq!(fit_extent([8, 4], atlas), (8, 4));
assert_eq!(fit_extent([5000, 5000], (2048, 2048)), (2048, 2048));
assert_eq!(fit_extent([128, 8], atlas), (64, 4));
assert_eq!(fit_extent([8, 128], atlas), (4, 64));
assert_eq!(fit_extent([1000, 10], atlas), (64, 1));
assert_eq!(fit_extent([400, 400], (100, 50)), (50, 50));
}
#[test]
fn an_extreme_aspect_ratio_still_fits_at_one_texel_rather_than_none() {
let fitted = fit_extent([10_000, 1], (64, 64));
assert_eq!(fitted, (64, 1));
let mut residency = residency();
residency.begin_frame();
let resident = residency
.resolve(&image(10_000, 1))
.expect("a sliver still becomes a rectangle");
assert_eq!(resident.region.size, [64, 1]);
}
#[test]
fn a_uniform_source_minifies_to_exactly_its_own_colour() {
let texel = PremulRgba8 {
r: 255,
g: 0,
b: 255,
a: 255,
};
let source = Pixmap::from_parts_with_opacity(vec![texel; 100 * 100], 100, 100, false);
let small = minify(&source, 7, 3);
assert_eq!((small.width(), small.height()), (7, 3));
assert!(small.data().iter().all(|out| *out == texel));
assert!(
!small.may_have_transparency(),
"an opaque source stays opaque"
);
}
#[test]
fn a_box_filter_averages_the_source_texels_each_output_covers() {
let black = PremulRgba8 {
r: 0,
g: 0,
b: 0,
a: 255,
};
let white = PremulRgba8 {
r: 255,
g: 255,
b: 255,
a: 255,
};
let source = Pixmap::from_parts_with_opacity(vec![black, white], 2, 1, false);
let small = minify(&source, 1, 1);
assert_eq!((small.width(), small.height()), (1, 1));
assert_eq!(small.data()[0].r, 128);
assert_eq!(small.data()[0].a, 255);
let clear = PremulRgba8 {
r: 0,
g: 0,
b: 0,
a: 0,
};
let mixed = Pixmap::from_parts_with_opacity(vec![white, clear], 2, 1, true);
let small = minify(&mixed, 1, 1);
assert_eq!(small.data()[0].a, 128);
assert!(small.may_have_transparency());
}
#[test]
fn minify_never_magnifies() {
let texel = PremulRgba8 {
r: 1,
g: 2,
b: 3,
a: 255,
};
let source = Pixmap::from_parts_with_opacity(vec![texel; 4], 2, 2, false);
let same = minify(&source, 8, 8);
assert_eq!((same.width(), same.height()), (2, 2));
assert!(same.data().iter().all(|out| *out == texel));
}
#[test]
fn a_malformed_or_degenerate_image_is_refused_before_the_conversion() {
let mut short = image(4, 4);
short.data = Blob::new(Arc::new(vec![0_u8; 8]));
assert!(matches!(
check_supported(&short),
Err(ImageSkip::MalformedPixels { .. })
));
assert!(matches!(
check_supported(&image(0, 4)),
Err(ImageSkip::DegenerateExtent { .. })
));
let mut huge = image(1, 1);
huge.width = MAX_IMAGE_DIMENSION + 1;
assert!(matches!(
check_supported(&huge),
Err(ImageSkip::TooLarge { .. })
));
}
#[test]
fn two_images_share_a_layer_and_hold_distinct_rectangles() {
let mut residency = residency();
residency.begin_frame();
let first = residency.resolve(&image(8, 8)).expect("fits");
let second = residency.resolve(&image(16, 16)).expect("fits");
assert_ne!(first.id, second.id);
assert_eq!(first.region.layer, second.region.layer);
assert_ne!(first.region.offset, second.region.offset);
assert_eq!(residency.uploads().len(), 2);
assert_eq!(residency.layers(), 1);
}
#[test]
fn a_second_layer_is_created_when_the_first_is_full() {
let mut residency = residency();
residency.begin_frame();
let first = residency.resolve(&image(64, 48)).expect("fits a layer");
let second = residency.resolve(&image(64, 48)).expect("fits the next");
assert_eq!(first.region.layer, 0);
assert_eq!(second.region.layer, 1);
assert_eq!(residency.layers(), 2);
}
#[test]
fn a_slot_taken_through_the_shared_allocator_is_a_distinct_id_on_the_same_budget() {
let mut residency = residency();
residency.begin_frame();
let resident = residency.resolve(&image(64, 48)).expect("fills a layer");
let borrowed = residency
.allocator_mut()
.allocate(64, 48, ATLAS_PADDING)
.expect("the second layer is free");
assert_ne!(
resident.id, borrowed,
"one cache never hands the same slot index to two live occupants"
);
assert_eq!(
residency.layers(),
2,
"a layer created by the other class still has to be in the array"
);
assert_eq!(
residency
.allocator()
.get(resident.id)
.map(|resource| resource.size()),
Some(resident.region.size),
"and neither allocation disturbed the other's rectangle"
);
}
#[test]
fn a_disabled_residency_refuses_every_image_and_touches_no_atlas() {
let mut residency = ImageResidency::disabled(AtlasBudget::MOBILE);
assert!(residency.is_disabled());
residency.begin_frame();
let skip = residency
.resolve(&image(8, 8))
.expect_err("the kill switch refuses every image");
assert_eq!(skip, ImageSkip::AtlasDisabled);
assert_eq!(residency.entry_count(), 0);
assert_eq!(residency.layers(), 0, "no atlas layer is ever created");
assert!(residency.uploads().is_empty());
assert_eq!(residency.skipped(), 1);
}
#[test]
fn an_enabled_residency_is_what_an_unset_kill_switch_produces() {
assert!(!ImageResidency::new(AtlasBudget::MOBILE).is_disabled());
}
#[test]
fn reading_the_plan_leaves_it_pending_and_acknowledging_it_clears_it() {
let mut residency = residency();
residency.begin_frame();
residency.resolve(&image(8, 8)).expect("fits");
let (evictions, uploads) = residency.plan();
assert!(evictions.is_empty());
assert_eq!(uploads.len(), 1);
assert!(
residency.has_pending_plan(),
"reading the plan is not servicing it"
);
residency.acknowledge_plan();
assert!(!residency.has_pending_plan());
assert!(residency.uploads().is_empty());
assert!(residency.evictions().is_empty());
}
#[test]
fn an_unacknowledged_upload_is_re_emitted_until_it_is_serviced() {
let mut residency = residency();
let data = image(8, 8);
residency.begin_frame();
let first = residency.resolve(&data).expect("fits");
let region = residency.uploads()[0].region;
for _ in 0..4 {
residency.begin_frame();
let again = residency.resolve(&data).expect("still resident");
assert_eq!(again.region, first.region, "residency does not move");
assert_eq!(
residency.uploads().len(),
1,
"the same upload is re-offered, never duplicated"
);
assert_eq!(residency.uploads()[0].region, region);
}
residency.acknowledge_plan();
residency.begin_frame();
residency.resolve(&data).expect("still resident");
assert!(
residency.uploads().is_empty(),
"a serviced upload is never offered again"
);
}
#[test]
fn reaping_an_unacknowledged_entry_withdraws_its_upload_with_it() {
let mut residency = residency();
let data = image(8, 8);
residency.begin_frame();
let resident = residency.resolve(&data).expect("fits");
assert_eq!(residency.uploads().len(), 1);
for _ in 0..=MAX_UNSEEN_FRAMES {
residency.begin_frame();
}
assert_eq!(residency.entry_count(), 0);
assert!(
residency.uploads().is_empty(),
"the reaped entry's unserviced upload goes with it"
);
assert_eq!(
residency.evictions(),
&[padded(resident.region, ATLAS_PADDING)]
);
}
#[test]
fn every_pending_upload_lies_inside_the_atlas_its_layer_count_asks_for() {
let mut residency = residency();
residency.begin_frame();
residency.resolve(&image(64, 48)).expect("fills a layer");
residency.resolve(&image(64, 48)).expect("takes the next");
let budget = residency.budget();
let layers = residency.layers();
assert_eq!(layers, 2);
for upload in residency.uploads() {
assert!(
budget.contains(upload.region, layers),
"{:?} is outside a {layers}-layer {budget:?}",
upload.region
);
}
}
fn two_slot_residency() -> ImageResidency {
ImageResidency::new(AtlasBudget {
atlas_size: (64, 64),
max_atlases: 2,
})
}
#[test]
fn a_working_set_larger_than_the_atlas_re_uploads_rather_than_skipping() {
let mut residency = two_slot_residency();
let images: Vec<ImageData> = (0..12).map(|_| image(64, 64)).collect();
for data in &images {
residency.begin_frame();
residency
.resolve(data)
.expect("a full atlas gives a rectangle back rather than refusing");
residency.acknowledge_plan();
}
assert_eq!(residency.skipped(), 0, "no draw was ever dropped");
assert_eq!(
residency.pressure_evictions(),
10,
"the ten images past the two slots each displaced one"
);
assert_eq!(residency.entry_count(), 2, "and residency stays bounded");
}
#[test]
fn an_image_resolved_this_frame_is_never_the_one_evicted() {
let mut residency = two_slot_residency();
residency.begin_frame();
residency.resolve(&image(64, 64)).expect("takes layer 0");
residency.resolve(&image(64, 64)).expect("takes layer 1");
let skip = residency
.resolve(&image(64, 64))
.expect_err("nothing this frame drew is evictable");
assert!(matches!(skip, ImageSkip::NoAtlasSpace { .. }));
assert_eq!(residency.skipped(), 1);
assert_eq!(residency.pressure_evictions(), 0);
assert_eq!(residency.entry_count(), 2, "both occupants stayed put");
assert!(
residency.evictions().is_empty(),
"no region a frame is about to sample was scheduled for clearing"
);
}
#[test]
fn the_least_recently_seen_image_is_the_one_displaced() {
let mut residency = two_slot_residency();
let old = image(64, 64);
let refreshed = image(64, 64);
residency.begin_frame();
let refreshed_region = residency.resolve(&refreshed).expect("takes a layer").region;
residency.begin_frame();
let old_region = residency.resolve(&old).expect("takes the other").region;
residency.acknowledge_plan();
residency.begin_frame();
residency.resolve(&refreshed).expect("still resident");
let arrival = residency.resolve(&image(64, 64)).expect("displaces one");
assert_eq!(residency.pressure_evictions(), 1);
assert_eq!(
arrival.region, old_region,
"the rectangle taken is the least recently seen one"
);
assert_eq!(
residency.resolve(&refreshed).map(|again| again.region),
Ok(refreshed_region),
"the image drawn this frame kept its own"
);
}
#[test]
fn a_pressure_eviction_reports_its_rectangle_once_and_withdraws_its_upload() {
let mut residency = two_slot_residency();
let displaced = image(64, 64);
residency.begin_frame();
let displaced_region = residency.resolve(&displaced).expect("takes a layer").region;
residency.resolve(&image(64, 64)).expect("takes the other");
assert_eq!(residency.uploads().len(), 2);
residency.begin_frame();
let arrival = residency
.resolve(&image(64, 64))
.expect("displaces the oldest");
assert_eq!(
residency.evictions(),
&[padded(displaced_region, ATLAS_PADDING)],
"exactly one rectangle, reported exactly once"
);
assert_eq!(
arrival.region, displaced_region,
"and the freed rectangle is what the new occupant took"
);
assert_eq!(
residency.uploads().len(),
2,
"the displaced image's unserviced upload went with its entry — \
writing it would put an evicted image back over its successor"
);
}
#[test]
fn an_age_reap_and_a_pressure_eviction_are_counted_separately() {
let mut residency = two_slot_residency();
let data = image(64, 64);
residency.begin_frame();
residency.resolve(&data).expect("fits");
for _ in 0..=MAX_UNSEEN_FRAMES {
residency.begin_frame();
}
assert_eq!(residency.entry_count(), 0, "the age reap ran");
assert_eq!(
residency.pressure_evictions(),
0,
"an age reap is not a pressure eviction"
);
assert_eq!(residency.frame_pressure_evictions(), 0);
}
#[test]
fn the_per_frame_eviction_count_is_this_frames_alone() {
let mut residency = two_slot_residency();
residency.begin_frame();
residency.resolve(&image(64, 64)).expect("takes a layer");
residency.resolve(&image(64, 64)).expect("takes the other");
residency.begin_frame();
residency.resolve(&image(64, 64)).expect("displaces one");
assert_eq!(residency.frame_pressure_evictions(), 1);
assert_eq!(residency.pressure_evictions(), 1);
residency.begin_frame();
assert_eq!(
residency.frame_pressure_evictions(),
0,
"a new frame starts from nothing"
);
assert_eq!(
residency.pressure_evictions(),
1,
"while the lifetime total keeps counting"
);
}
fn four_slot_residency() -> ImageResidency {
ImageResidency::new(AtlasBudget {
atlas_size: (64, 64),
max_atlases: 1,
})
}
#[test]
fn a_request_no_candidate_set_can_place_is_refused_before_anything_is_freed() {
let mut residency = four_slot_residency();
let resident: Vec<ImageData> = (0..4).map(|_| image(32, 32)).collect();
residency.begin_frame();
for data in &resident {
residency
.resolve(data)
.expect("the four cells take four images");
}
residency.acknowledge_plan();
residency.begin_frame();
residency.resolve(&resident[0]).expect("still resident");
let skip = residency
.resolve(&image(48, 48))
.expect_err("no arrangement of the three candidates admits a 48-square");
assert!(matches!(skip, ImageSkip::NoAtlasSpace { .. }));
assert_eq!(
residency.pressure_evictions(),
0,
"a request that could not have been placed displaced nothing"
);
assert_eq!(residency.entry_count(), 4, "the residency is untouched");
assert!(
residency.evictions().is_empty(),
"and nothing was scheduled for clearing on the way to failing"
);
assert!(residency.uploads().is_empty());
}
#[test]
fn a_request_larger_than_every_candidate_together_is_refused_on_area_alone() {
let mut residency = four_slot_residency();
let resident: Vec<ImageData> = (0..4).map(|_| image(32, 32)).collect();
residency.begin_frame();
for data in &resident {
residency
.resolve(data)
.expect("the four cells take four images");
}
residency.acknowledge_plan();
residency.begin_frame();
residency.resolve(&resident[0]).expect("still resident");
residency
.resolve(&image(64, 64))
.expect_err("a whole layer is more than the three candidates hold");
assert_eq!(residency.pressure_evictions(), 0);
assert_eq!(residency.entry_count(), 4);
assert!(residency.evictions().is_empty());
}
#[test]
fn a_satisfiable_request_under_pressure_still_evicts_the_minimum() {
let mut residency = four_slot_residency();
let resident: Vec<ImageData> = (0..4).map(|_| image(32, 32)).collect();
residency.begin_frame();
for data in &resident {
residency
.resolve(data)
.expect("the four cells take four images");
}
residency.acknowledge_plan();
residency.begin_frame();
residency.resolve(&image(32, 32)).expect("displaces one");
assert_eq!(
residency.pressure_evictions(),
1,
"the minimum, not the lot"
);
assert_eq!(residency.entry_count(), 4);
assert_eq!(residency.evictions().len(), 1);
}
#[test]
fn a_blob_redrawn_at_a_new_extent_never_frees_what_this_frame_resolved() {
let square = image(16, 8);
let transposed = ImageData {
data: square.data.clone(),
format: square.format,
alpha_type: square.alpha_type,
width: 8,
height: 16,
};
let mut residency = residency();
residency.begin_frame();
let first = residency.resolve(&square).expect("takes a rectangle");
let skip = residency
.resolve(&transposed)
.expect_err("the second extent cannot take the first one's rectangle");
assert!(
matches!(skip, ImageSkip::SameFrameExtentConflict { .. }),
"a same-frame ordering conflict, not an atlas-capacity refusal: {skip:?}"
);
assert!(
residency.evictions().is_empty(),
"no clear over a rectangle this frame already sampled"
);
assert_eq!(residency.entry_count(), 1);
assert_eq!(residency.uploads().len(), 1);
residency.acknowledge_plan();
residency.begin_frame();
let second = residency.resolve(&transposed).expect("the new extent fits");
assert_eq!(second.natural, [8, 16]);
assert_eq!(
residency.evictions(),
&[padded(first.region, ATLAS_PADDING)],
"the rectangle the old extent held is reported for clearing"
);
}
#[test]
fn a_layer_admits_only_what_every_occupant_leaves_room_for() {
let corner = PressureRect {
key: 1,
layer: 0,
x0: 0,
y0: 0,
x1: 32,
y1: 32,
area: 1024,
last_seen: 0,
slot: 0,
freed: false,
};
let rects = [corner];
assert!(!layer_admits(&rects, 0, 48, 48, (64, 64)));
assert!(layer_admits(&rects, 0, 32, 32, (64, 64)));
assert!(layer_admits(&rects, 0, 32, 64, (64, 64)));
assert!(layer_admits(
&[PressureRect {
freed: true,
..corner
}],
0,
64,
64,
(64, 64)
));
assert!(!layer_admits(&[], 0, 65, 64, (64, 64)));
assert!(layer_admits(
&[PressureRect { layer: 1, ..corner }],
0,
64,
64,
(64, 64)
));
}
#[test]
fn a_plan_that_would_outrun_its_budget_frees_nothing() {
let mut rects: Vec<PressureRect> = (0..MAX_PRESSURE_EVICTION_CANDIDATES + 8)
.map(|index| {
let index = u32::try_from(index).expect("fits");
PressureRect {
key: u64::from(index),
layer: 0,
x0: index,
y0: 0,
x1: index + 1,
y1: 1,
area: 1,
last_seen: 0,
slot: index,
freed: false,
}
})
.collect();
let order: Vec<usize> = (0..rects.len()).collect();
let mut free_area = vec![0_u64];
let mut layers = Vec::new();
assert_eq!(
plan_pressure_eviction(
64,
64,
(64, 64),
&mut rects,
&order,
&mut free_area,
&mut layers
),
None,
"a walk that cannot fit the request inside its budget plans nothing"
);
assert!(
rects.iter().filter(|rect| rect.freed).count() <= MAX_PRESSURE_EVICTION_CANDIDATES + 1,
"and it stopped walking rather than marking the whole residency"
);
}
#[test]
fn a_satisfiable_layer_is_not_starved_by_a_hopeless_ones_candidates() {
let mut residency = ImageResidency::new(AtlasBudget {
atlas_size: (64, 64),
max_atlases: 4,
});
let anchor = image(64, 40);
let fillers: Vec<ImageData> = (0..MAX_PRESSURE_EVICTION_CANDIDATES + 6)
.map(|_| image(1, 1))
.collect();
let quadrants: Vec<ImageData> = (0..4).map(|_| image(32, 32)).collect();
let layer2_filler = image(64, 64);
let layer3_filler = image(64, 64);
residency.begin_frame();
residency
.resolve(&anchor)
.expect("the anchor takes layer 0's top");
for filler in &fillers {
residency
.resolve(filler)
.expect("a one-pixel filler always finds room in the leftover strip");
}
for quadrant in &quadrants {
residency
.resolve(quadrant)
.expect("a 32-square quadrant packs layer 1's empty corners");
}
residency
.resolve(&layer2_filler)
.expect("a whole-layer image grows into a fresh layer 2");
residency
.resolve(&layer3_filler)
.expect("a whole-layer image grows into a fresh layer 3");
residency.acknowledge_plan();
assert_eq!(
residency.layers(),
4,
"fixture precondition: all four layers exist"
);
residency.begin_frame();
residency.resolve(&anchor).expect("still resident");
residency.resolve(&quadrants[1]).expect("still resident");
residency.resolve(&quadrants[2]).expect("still resident");
residency.resolve(&quadrants[3]).expect("still resident");
residency.resolve(&layer2_filler).expect("still resident");
residency.resolve(&layer3_filler).expect("still resident");
let arrival = residency
.resolve(&image(32, 32))
.expect("a per-layer budget reaches layer 1's satisfiable candidate");
assert_eq!(
residency.pressure_evictions(),
1,
"exactly the one quadrant needed, not layer 0's hopeless fillers"
);
assert_eq!(residency.evictions().len(), 1);
assert_eq!(
residency.evictions()[0].layer,
arrival.region.layer,
"the eviction and the arrival land on the same, single layer"
);
assert_ne!(
arrival.region.layer, 0,
"the winning layer is not the one full of hopeless fillers"
);
}
#[cfg(feature = "perf-trace")]
#[test]
fn the_resolved_tier_line_names_the_tier_and_is_written_once() {
let mobile = TierCaps::fake(DownlevelProfile::WebGl2);
assert_eq!(
atlas_tier_line(&mobile, AtlasBudget::MOBILE),
"frust-perf atlas tier=mobile budget=1024x1024x4 downlevel=WebGl2 \
transient_saves_memory=false adapter=fake-webgl2"
);
let mut tiler = TierCaps::fake(DownlevelProfile::Full);
tiler.adapter_name = "Adreno (TM) 660".to_string();
assert_eq!(
atlas_tier_line(&tiler, AtlasBudget::DESKTOP),
"frust-perf atlas tier=desktop budget=2048x2048x8 downlevel=Full \
transient_saves_memory=false adapter=Adreno (TM) 660"
);
tiler.transient_saves_memory = true;
assert!(atlas_tier_line(&tiler, AtlasBudget::MOBILE).contains("tier=mobile"));
assert!(
atlas_tier_line(&tiler, AtlasBudget::MOBILE).contains("transient_saves_memory=true")
);
let latch = TierLogOnce::new();
assert!(
latch.emit(&mobile, AtlasBudget::MOBILE),
"the first call writes the line"
);
assert!(
!latch.emit(&mobile, AtlasBudget::MOBILE),
"and no later one repeats it"
);
}
#[test]
fn an_override_redistributes_a_tiers_memory_but_never_exceeds_it() {
let tier = AtlasBudget::DESKTOP;
let huge = AtlasBudget {
atlas_size: (16_384, 16_384),
max_atlases: tier.max_atlases,
}
.within_total_bytes(tier.total_bytes());
assert!(huge.total_bytes() <= tier.total_bytes());
assert_eq!(huge.atlas_size.0, huge.atlas_size.1, "aspect preserved");
assert!(huge.max_atlases >= 1);
let modest = AtlasBudget {
atlas_size: (1024, 1024),
max_atlases: tier.max_atlases,
}
.within_total_bytes(tier.total_bytes());
assert_eq!(modest.atlas_size, (1024, 1024));
assert_eq!(modest.max_atlases, tier.max_atlases);
let sliver = AtlasBudget {
atlas_size: (65_535, 4),
max_atlases: 1,
}
.within_total_bytes(64);
assert!(sliver.atlas_size.0 >= 1 && sliver.atlas_size.1 >= 1);
assert!(sliver.total_bytes() <= 64);
}
}