#![allow(
dead_code,
reason = "the run-routing half is consumed by `super::glyph_atlas_policy`'s \
caller; the per-glyph half — `collect_glyph`, `slot`, \
`GlyphUpload` and `AtlasPass::uploads` — is host-tested but \
unreachable while `glifo` owns the allocation, since it keys and \
rasterizes every cached glyph itself (see `atlas_mut`); and the \
readings of the budget — `entry_ceiling_at`, `max_entries`, \
`texel_budget`, `resident_texels` — are reporting rather than \
routing, so only the cases read them"
)]
use std::collections::{HashMap, HashSet};
use std::hash::{BuildHasherDefault, Hasher};
use glifo::{
AtlasConfig, AtlasSlot, FontEmbolden, GlyphAtlas, GlyphCacheConfig, GlyphCacheKey, ImageCache,
PendingClearRect, RasterMetrics,
};
use kurbo::Affine;
use peniko::color::palette::css::BLACK;
use peniko::color::{AlphaColor, Srgb};
pub(crate) const SIZE_QUANTUM: f32 = 0.25;
pub(crate) const SETTLE_FRAMES: u64 = 8;
pub(crate) const MAX_ENTRY_AGE: u64 = 64;
pub(crate) const EVICTION_FREQUENCY: u64 = 64;
pub(crate) const MAX_CACHED_FONT_SIZE: f32 = 128.0;
const NEARLY_ZERO: f64 = 1.0 / 4096.0;
const GLYPH_ATLAS_SHARE: u64 = 2;
const TYPICAL_GLYPH_TEXELS: u64 = 18 * 18;
const TYPICAL_GLYPH_SIZE: f32 = 16.0;
const MIN_GLYPH_ENTRIES: usize = 256;
const MAX_GLYPH_ENTRIES: usize = 65_536;
#[must_use]
pub(crate) fn glyph_texel_budget(pages: AtlasConfig) -> u64 {
let (width, height) = pages.atlas_size;
let layers = u64::try_from(pages.max_atlases).unwrap_or(u64::MAX).max(1);
let texels = u64::from(width)
.saturating_mul(u64::from(height))
.saturating_mul(layers);
(texels / GLYPH_ATLAS_SHARE).clamp(
MIN_GLYPH_ENTRIES as u64 * TYPICAL_GLYPH_TEXELS,
MAX_GLYPH_ENTRIES as u64 * TYPICAL_GLYPH_TEXELS,
)
}
#[must_use]
pub(crate) fn glyph_entry_ceiling(pages: AtlasConfig) -> usize {
let entries = glyph_texel_budget(pages) / TYPICAL_GLYPH_TEXELS;
usize::try_from(entries)
.unwrap_or(MAX_GLYPH_ENTRIES)
.clamp(MIN_GLYPH_ENTRIES, MAX_GLYPH_ENTRIES)
}
#[must_use]
pub(crate) fn glyph_texels_at(size: f32) -> u64 {
debug_assert!(
size.is_finite(),
"a non-finite size never reaches a charge: `quantize_font_size` refuses \
it and `absorbed_scale` refuses the transforms that could produce it"
);
let scale = f64::from(size.max(TYPICAL_GLYPH_SIZE)) / f64::from(TYPICAL_GLYPH_SIZE);
let texels = TYPICAL_GLYPH_TEXELS as f64 * scale * scale;
texels as u64
}
#[must_use]
pub(crate) fn entry_ceiling_at(entries: usize, size: f32) -> usize {
let cost = glyph_texels_at(size);
let texels = u64::try_from(entries)
.unwrap_or(u64::MAX)
.saturating_mul(TYPICAL_GLYPH_TEXELS);
usize::try_from(texels / cost.max(1))
.unwrap_or(MAX_GLYPH_ENTRIES)
.max(1)
}
#[must_use]
pub(crate) fn absorbed_scale(transform: Affine) -> Option<f64> {
let [a, b, c, d, _, _] = transform.as_coeffs();
if !(a.is_finite() && b.is_finite() && c.is_finite() && d.is_finite()) {
return None;
}
let uniform = (a - d).abs() <= NEARLY_ZERO && a > 0.0 && d > 0.0;
let skewed = b.abs() > NEARLY_ZERO || c.abs() > NEARLY_ZERO;
(uniform && !skewed).then_some(d)
}
#[must_use]
pub(crate) fn device_font_size(size: f32, transform: Affine) -> Option<f32> {
absorbed_scale(transform).map(|scale| (f64::from(size) * scale) as f32)
}
#[must_use]
pub(crate) fn glyph_cache_config() -> GlyphCacheConfig {
GlyphCacheConfig {
max_entry_age: MAX_ENTRY_AGE,
eviction_frequency: EVICTION_FREQUENCY,
max_cached_font_size: MAX_CACHED_FONT_SIZE,
}
}
#[must_use]
pub(crate) fn quantize_font_size(size: f32) -> Option<f32> {
if !size.is_finite() || size <= 0.0 {
return None;
}
let quantized = (size / SIZE_QUANTUM).round() * SIZE_QUANTUM;
(quantized > 0.0 && quantized.is_finite()).then_some(quantized)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum OutlineReason {
Disabled,
SizeAnimating,
SizeTooLarge,
UnusableSize,
TransformUncacheable,
ColorFont,
ResidencyFull,
NotCollecting,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct AtlasRun {
font_id: u64,
font_index: u32,
size: f32,
distinct_glyphs: u32,
hinted: bool,
context_color: AlphaColor<Srgb>,
context_color_packed: u32,
}
impl AtlasRun {
#[must_use]
pub(crate) fn size(&self) -> f32 {
self.size
}
#[must_use]
pub(crate) fn distinct_glyphs(&self) -> u32 {
self.distinct_glyphs
}
#[must_use]
pub(crate) fn key(&self, glyph_id: u32, fractional_x: f32) -> GlyphCacheKey {
GlyphCacheKey::new(
self.font_id,
self.font_index,
glyph_id,
self.size,
self.hinted,
fractional_x,
self.context_color,
self.context_color_packed,
FontEmbolden::default(),
&[],
)
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum RunRoute {
Atlas(AtlasRun),
Outline(OutlineReason),
}
impl RunRoute {
#[must_use]
pub(crate) fn atlas(&self) -> Option<&AtlasRun> {
match self {
Self::Atlas(run) => Some(run),
Self::Outline(_) => None,
}
}
#[must_use]
pub(crate) fn outline_reason(&self) -> Option<OutlineReason> {
match self {
Self::Atlas(_) => None,
Self::Outline(reason) => Some(*reason),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum GlyphRoute {
Cached,
Pending,
Outline(OutlineReason),
}
#[derive(Debug, Clone)]
pub(crate) struct GlyphUpload {
pub(crate) key: GlyphCacheKey,
pub(crate) slot: AtlasSlot,
}
#[derive(Debug, Default)]
pub(crate) struct AtlasPass {
pub(crate) clears: Vec<PendingClearRect>,
pub(crate) uploads: Vec<GlyphUpload>,
pub(crate) refused: u32,
}
impl AtlasPass {
#[must_use]
pub(crate) fn is_empty(&self) -> bool {
self.clears.is_empty() && self.uploads.is_empty()
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct RunKey {
pub(crate) font_id: u64,
pub(crate) font_index: u32,
pub(crate) font_size: f32,
pub(crate) distinct_glyphs: u32,
pub(crate) transform: Affine,
pub(crate) hinted: bool,
pub(crate) color_font: bool,
pub(crate) context_color: AlphaColor<Srgb>,
}
impl RunKey {
#[must_use]
pub(crate) fn for_run(
run: &frust_scene::GlyphRun,
transform: Affine,
hinted: bool,
color_font: bool,
context_color: AlphaColor<Srgb>,
seen: &mut impl DistinctGlyphs,
) -> Self {
let font = run.font.font();
let distinct = seen.count_distinct(run.glyphs.iter().map(|glyph| glyph.id));
Self {
font_id: font.data.id(),
font_index: font.index,
font_size: run.font_size,
distinct_glyphs: u32::try_from(distinct).unwrap_or(u32::MAX),
transform,
hinted,
color_font,
context_color,
}
}
}
pub(crate) trait DistinctGlyphs {
fn count_distinct(&mut self, ids: impl Iterator<Item = u32>) -> usize;
}
impl DistinctGlyphs for Vec<u32> {
fn count_distinct(&mut self, ids: impl Iterator<Item = u32>) -> usize {
self.clear();
self.extend(ids);
self.sort_unstable();
self.dedup();
self.len()
}
}
impl DistinctGlyphs for HashSet<u32> {
fn count_distinct(&mut self, ids: impl Iterator<Item = u32>) -> usize {
self.clear();
self.extend(ids);
self.len()
}
}
type FontKey = (u64, u32);
#[derive(Debug, Default)]
struct FontSizes {
animating_until: u64,
sizes: HashMap<u32, u64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Phase {
Idle,
Collect,
Draw,
}
#[derive(Debug)]
pub(crate) struct AtlasPolicy {
atlas: GlyphAtlas,
pages: AtlasConfig,
texel_budget: u64,
max_entries: usize,
resident_texels: u64,
charged_entries: usize,
admitting_size: f32,
disabled: bool,
frame: u64,
fonts: HashMap<FontKey, FontSizes>,
pending: Vec<GlyphCacheKey>,
claimed: HashSet<GlyphCacheKey>,
clears: Vec<PendingClearRect>,
offered_clears: usize,
misses_at_frame_start: u64,
probe_misses: u64,
unreplayed_since: Option<u64>,
serial: u64,
phase: Phase,
last_eviction_serial: u64,
resident: ResidentRuns,
#[cfg(feature = "perf-trace")]
trace: AtlasTrace,
}
#[cfg(feature = "perf-trace")]
#[derive(Debug, Default)]
pub(crate) struct AtlasTrace {
run_keys: HashSet<(u64, u32, u32, bool)>,
pub(crate) glyph_misses: u32,
pub(crate) glyph_evicted: u32,
}
#[cfg(feature = "perf-trace")]
impl AtlasTrace {
pub(crate) fn run_keys(&self) -> u32 {
u32::try_from(self.run_keys.len()).unwrap_or(u32::MAX)
}
}
impl AtlasPolicy {
#[must_use]
pub(crate) fn new(images: &ImageCache, disabled: bool) -> Self {
let pages = *images.atlas_manager().config();
Self {
atlas: GlyphAtlas::with_config(glyph_cache_config()),
pages,
texel_budget: glyph_texel_budget(pages),
max_entries: glyph_entry_ceiling(pages),
resident_texels: 0,
charged_entries: 0,
admitting_size: TYPICAL_GLYPH_SIZE,
disabled,
frame: 0,
fonts: HashMap::new(),
pending: Vec::new(),
claimed: HashSet::new(),
clears: Vec::new(),
offered_clears: 0,
misses_at_frame_start: 0,
probe_misses: 0,
unreplayed_since: None,
serial: 0,
phase: Phase::Idle,
last_eviction_serial: 0,
resident: ResidentRuns::default(),
#[cfg(feature = "perf-trace")]
trace: AtlasTrace::default(),
}
}
#[cfg(feature = "perf-trace")]
#[must_use]
pub(crate) fn trace(&self) -> &AtlasTrace {
&self.trace
}
#[must_use]
pub(crate) fn is_enabled(&self) -> bool {
!self.disabled
}
#[must_use]
pub(crate) fn pages(&self) -> AtlasConfig {
self.pages
}
#[must_use]
pub(crate) fn frame(&self) -> u64 {
self.frame
}
pub(crate) fn atlas_mut(&mut self) -> &mut GlyphAtlas {
&mut self.atlas
}
#[must_use]
pub(crate) fn atlas(&self) -> &GlyphAtlas {
&self.atlas
}
#[must_use]
pub(crate) fn entry_count(&self) -> usize {
self.atlas.len()
}
#[must_use]
pub(crate) fn tracked_sizes(&self) -> usize {
self.fonts.values().map(|font| font.sizes.len()).sum()
}
#[must_use]
pub(crate) fn max_entries(&self) -> usize {
self.max_entries
}
#[must_use]
pub(crate) fn texel_budget(&self) -> u64 {
self.texel_budget
}
#[must_use]
pub(crate) fn resident_texels(&self) -> u64 {
self.resident_texels
}
#[must_use]
pub(crate) fn replay_pending(&self) -> bool {
self.unreplayed_since.is_some()
}
pub(crate) fn acknowledge_replay(&mut self) {
self.unreplayed_since = None;
}
#[must_use]
pub(crate) fn has_pending_clears(&self) -> bool {
!self.clears.is_empty()
}
pub(crate) fn acknowledge_clears(&mut self) {
let offered = self.offered_clears.min(self.clears.len());
self.clears.drain(..offered);
self.offered_clears = 0;
}
pub(crate) fn begin_frame(&mut self) {
self.frame = self.frame.saturating_add(1);
if self.atlas.len() < self.resident.expected_entries {
self.resident.forget_all();
}
self.pending.clear();
self.claimed.clear();
self.misses_at_frame_start = self.atlas.cache_misses();
self.probe_misses = 0;
#[cfg(feature = "perf-trace")]
{
self.trace.run_keys.clear();
self.trace.glyph_misses = 0;
self.trace.glyph_evicted = 0;
}
self.phase = Phase::Collect;
}
#[must_use]
fn frame_misses(&self) -> u64 {
self.atlas
.cache_misses()
.saturating_sub(self.misses_at_frame_start)
.saturating_sub(self.probe_misses)
}
pub(crate) fn classify_run(&mut self, run: &RunKey) -> RunRoute {
if self.disabled {
return RunRoute::Outline(OutlineReason::Disabled);
}
if self.phase != Phase::Collect {
return RunRoute::Outline(OutlineReason::NotCollecting);
}
if run.color_font {
return RunRoute::Outline(OutlineReason::ColorFont);
}
let Some(device_size) = device_font_size(run.font_size, run.transform) else {
return RunRoute::Outline(OutlineReason::TransformUncacheable);
};
let Some(size) = quantize_font_size(device_size) else {
return RunRoute::Outline(OutlineReason::UnusableSize);
};
#[cfg(feature = "perf-trace")]
self.trace.run_keys.insert((
run.font_id,
run.font_index,
run.font_size.to_bits(),
run.hinted,
));
let animating = self.observe_size((run.font_id, run.font_index), size);
if device_size > MAX_CACHED_FONT_SIZE {
return RunRoute::Outline(OutlineReason::SizeTooLarge);
}
if animating {
return RunRoute::Outline(OutlineReason::SizeAnimating);
}
if self.would_overspend(size, run.distinct_glyphs) {
return RunRoute::Outline(OutlineReason::ResidencyFull);
}
RunRoute::Atlas(AtlasRun {
font_id: run.font_id,
font_index: run.font_index,
size,
distinct_glyphs: run.distinct_glyphs,
hinted: run.hinted,
context_color: run.context_color,
context_color_packed: pack_context_color(run.context_color),
})
}
#[must_use]
pub(crate) fn admit_run(&mut self, route: RunRoute) -> RunRoute {
self.charge_admissions();
match route {
RunRoute::Atlas(run) if self.would_overspend(run.size(), run.distinct_glyphs()) => {
RunRoute::Outline(OutlineReason::ResidencyFull)
}
RunRoute::Atlas(run) => {
self.admitting_size = run.size();
RunRoute::Atlas(run)
}
route => route,
}
}
#[must_use]
fn would_overspend(&self, size: f32, entries: u32) -> bool {
let demand = glyph_texels_at(size).saturating_mul(u64::from(entries.max(1)));
self.resident_texels.saturating_add(demand) > self.texel_budget
}
fn charge_admissions(&mut self) {
let live = self.atlas.len();
let Some(added) = live.checked_sub(self.charged_entries) else {
self.discharge_evictions();
return;
};
if added == 0 {
return;
}
let cost = glyph_texels_at(self.admitting_size);
let added = u64::try_from(added).unwrap_or(u64::MAX);
self.resident_texels = self
.resident_texels
.saturating_add(cost.saturating_mul(added));
self.charged_entries = live;
}
fn discharge_evictions(&mut self) {
let live = self.atlas.len();
if live >= self.charged_entries {
return;
}
self.resident_texels = if self.charged_entries == 0 {
0
} else {
let scaled =
u128::from(self.resident_texels) * live as u128 / self.charged_entries as u128;
u64::try_from(scaled).unwrap_or(u64::MAX)
};
self.charged_entries = live;
}
pub(crate) fn collect_glyph(
&mut self,
run: &AtlasRun,
glyph_id: u32,
fractional_x: f32,
) -> GlyphRoute {
if self.phase != Phase::Collect {
return GlyphRoute::Outline(OutlineReason::NotCollecting);
}
let key = run.key(glyph_id, fractional_x);
if self.atlas.get(&key).is_some() {
return GlyphRoute::Cached;
}
if self.claimed.insert(key.clone()) {
self.pending.push(key);
}
GlyphRoute::Pending
}
pub(crate) fn build<F>(&mut self, images: &mut ImageCache, mut raster: F) -> AtlasPass
where
F: FnMut(&GlyphCacheKey) -> Option<RasterMetrics>,
{
self.offered_clears = self.clears.len();
let mut pass = AtlasPass {
clears: self.clears.clone(),
uploads: Vec::new(),
refused: 0,
};
let mut pending = std::mem::take(&mut self.pending);
for key in pending.drain(..) {
let Some(metrics) = raster(&key) else {
pass.refused = pass.refused.saturating_add(1);
continue;
};
match self.atlas.insert_entry(images, key.clone(), metrics) {
Some(slot) => {
let size = f32::from_bits(key.size_bits);
self.resident_texels =
self.resident_texels.saturating_add(glyph_texels_at(size));
self.charged_entries = self.charged_entries.saturating_add(1);
pass.uploads.push(GlyphUpload { key, slot });
}
None => pass.refused = pass.refused.saturating_add(1),
}
}
self.pending = pending;
self.claimed.clear();
self.phase = Phase::Draw;
pass
}
pub(crate) fn slot(&mut self, key: &GlyphCacheKey) -> Option<AtlasSlot> {
if self.phase != Phase::Draw {
return None;
}
self.atlas.get(key)
}
pub(crate) fn end_frame(&mut self, images: &mut ImageCache) {
self.charge_admissions();
#[cfg(feature = "perf-trace")]
{
self.trace.glyph_misses = u32::try_from(self.frame_misses()).unwrap_or(u32::MAX);
}
if self.frame_misses() > 0 && self.unreplayed_since.is_none() {
self.unreplayed_since = Some(self.serial);
}
if self.eviction_deferred() {
self.prune_sizes();
self.resident.expected_entries = self.atlas.len();
self.phase = Phase::Idle;
return;
}
let evicting = self
.serial
.saturating_add(1)
.saturating_sub(self.last_eviction_serial)
>= EVICTION_FREQUENCY;
if evicting {
self.resident.touch(&mut self.atlas, self.serial);
}
let entries_before = self.atlas.len();
self.atlas.maintain(images);
self.serial = self.serial.saturating_add(1);
if evicting {
self.last_eviction_serial = self.serial;
}
self.discharge_evictions();
let evicted: Vec<PendingClearRect> = self.atlas.drain_pending_clear_rects().collect();
if !evicting && (!evicted.is_empty() || self.atlas.len() < entries_before) {
self.resident.forget_all();
}
self.resident.expected_entries = self.atlas.len();
#[cfg(feature = "perf-trace")]
{
self.trace.glyph_evicted = self
.trace
.glyph_evicted
.saturating_add(u32::try_from(evicted.len()).unwrap_or(u32::MAX));
}
self.clears.extend(evicted);
self.prune_sizes();
self.phase = Phase::Idle;
}
#[must_use]
fn eviction_deferred(&self) -> bool {
self.unreplayed_since
.is_some_and(|since| self.serial.saturating_sub(since) >= MAX_ENTRY_AGE)
}
pub(crate) fn resolve_resident_run(
&mut self,
identity: ResidentIdentity,
placed: &[PlacedGlyph],
slots: &mut Vec<AtlasSlot>,
) -> ResidentAnswer {
slots.clear();
if self.phase != Phase::Draw || placed.is_empty() {
return ResidentAnswer::Bypassed;
}
let serial = self.serial;
let black = self.resident.black;
let hash = run_hash(&identity, placed);
let Some(run) = self.resident.open(hash, identity, placed, self.frame) else {
return ResidentAnswer::Bypassed;
};
if run.refused {
return ResidentAnswer::Bypassed;
}
run.used = serial;
run.frame = self.frame;
for (glyph, record) in placed.iter().zip(run.glyphs.iter_mut()) {
let bucket = usize::from(glyph.bucket);
let Some(known) = record.slots.get(bucket).copied() else {
return ResidentAnswer::Bypassed;
};
let slot = match known {
Some(slot) => slot,
None => {
let key = outline_key(&identity, glyph.id, glyph.bucket, black);
let Some(slot) = self.atlas.get(&key) else {
self.probe_misses = self.probe_misses.saturating_add(1);
return ResidentAnswer::Missed(hash);
};
record.slots[bucket] = Some(slot);
slot
}
};
record.used[bucket] = serial;
slots.push(slot);
}
ResidentAnswer::Resolved
}
pub(crate) fn refuse_resident_run(&mut self, handle: u64) {
self.resident.refuse(handle);
}
#[must_use]
pub(crate) fn resident_runs(&self) -> usize {
self.resident.runs.len()
}
#[must_use]
pub(crate) fn resident_run_bytes(&self) -> usize {
self.resident.bytes
}
fn observe_size(&mut self, font: FontKey, size: f32) -> bool {
let frame = self.frame;
let bits = size.to_bits();
let entry = self.fonts.entry(font).or_default();
if !entry.sizes.contains_key(&bits) && !entry.sizes.is_empty() {
entry.animating_until = frame.saturating_add(SETTLE_FRAMES);
}
entry.sizes.insert(bits, frame);
frame <= entry.animating_until
}
fn prune_sizes(&mut self) {
let frame = self.frame;
self.fonts.retain(|_, font| {
font.sizes
.retain(|_, last_seen| frame.saturating_sub(*last_seen) <= SETTLE_FRAMES);
!font.sizes.is_empty() || frame <= font.animating_until
});
}
}
#[must_use]
fn pack_context_color(color: AlphaColor<Srgb>) -> u32 {
color.premultiply().to_rgba8().to_u32()
}
const SUBPIXEL_BUCKETS: u8 = 4;
#[must_use]
pub(crate) fn subpixel_bucket(fractional_x: f32) -> u8 {
let normalized = fractional_x.fract();
let normalized = if normalized < 0.0 {
normalized + 1.0
} else {
normalized
};
let bucket = (normalized * f32::from(SUBPIXEL_BUCKETS)).round() as u8;
bucket.min(SUBPIXEL_BUCKETS - 1)
}
pub(crate) const MAX_RESIDENT_RUNS: usize = 1024;
pub(crate) const MAX_RESIDENT_RUN_BYTES: usize = 2 * 1024 * 1024;
const NEVER: u64 = u64::MAX;
#[derive(Debug, Clone, Copy)]
pub(crate) struct ResidentIdentity {
pub(crate) font_id: u64,
pub(crate) font_index: u32,
pub(crate) size: f32,
pub(crate) hinted: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct PlacedGlyph {
pub(crate) id: u32,
pub(crate) bucket: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ResidentAnswer {
Resolved,
Missed(u64),
Bypassed,
}
#[derive(Debug, Clone, Copy)]
struct ResidentGlyph {
slots: [Option<AtlasSlot>; SUBPIXEL_BUCKETS as usize],
used: [u64; SUBPIXEL_BUCKETS as usize],
}
impl ResidentGlyph {
const EMPTY: Self = Self {
slots: [None; SUBPIXEL_BUCKETS as usize],
used: [NEVER; SUBPIXEL_BUCKETS as usize],
};
}
#[derive(Debug)]
struct ResidentRun {
identity: ResidentIdentity,
ids: Box<[u32]>,
glyphs: Box<[ResidentGlyph]>,
used: u64,
frame: u64,
refused: bool,
}
impl ResidentRun {
fn new(identity: ResidentIdentity, placed: &[PlacedGlyph], frame: u64) -> Self {
Self {
identity,
ids: placed.iter().map(|glyph| glyph.id).collect(),
glyphs: vec![ResidentGlyph::EMPTY; placed.len()].into_boxed_slice(),
used: NEVER,
frame,
refused: false,
}
}
fn matches(&self, identity: &ResidentIdentity, placed: &[PlacedGlyph]) -> bool {
self.identity.font_id == identity.font_id
&& self.identity.font_index == identity.font_index
&& self.identity.size.to_bits() == identity.size.to_bits()
&& self.identity.hinted == identity.hinted
&& self.ids.len() == placed.len()
&& self
.ids
.iter()
.zip(placed)
.all(|(id, glyph)| *id == glyph.id)
}
fn bytes(&self) -> usize {
std::mem::size_of::<Self>()
+ std::mem::size_of_val(&*self.ids)
+ std::mem::size_of_val(&*self.glyphs)
}
}
const RUN_HASH_MULTIPLIER: u64 = 0x517c_c1b7_2722_0a95;
fn mix(hash: u64, word: u64) -> u64 {
(hash.rotate_left(5) ^ word).wrapping_mul(RUN_HASH_MULTIPLIER)
}
fn run_hash(identity: &ResidentIdentity, placed: &[PlacedGlyph]) -> u64 {
let mut hash = mix(0, identity.font_id);
hash = mix(hash, u64::from(identity.font_index));
hash = mix(hash, u64::from(identity.size.to_bits()));
hash = mix(hash, u64::from(identity.hinted));
hash = mix(hash, placed.len() as u64);
for glyph in placed {
hash = mix(hash, u64::from(glyph.id));
}
hash
}
#[derive(Debug, Default)]
struct RunHashHasher(u64);
impl Hasher for RunHashHasher {
fn finish(&self) -> u64 {
self.0
}
fn write(&mut self, bytes: &[u8]) {
for byte in bytes {
self.0 = mix(self.0, u64::from(*byte));
}
}
fn write_u64(&mut self, value: u64) {
self.0 = mix(self.0, value);
}
}
fn outline_key(
identity: &ResidentIdentity,
glyph_id: u32,
bucket: u8,
black: u32,
) -> GlyphCacheKey {
GlyphCacheKey::new(
identity.font_id,
identity.font_index,
glyph_id,
identity.size,
identity.hinted,
glifo::atlas::key::subpixel_offset(bucket),
BLACK,
black,
FontEmbolden::default(),
&[],
)
}
#[derive(Debug)]
pub(crate) struct ResidentRuns {
runs: HashMap<u64, ResidentRun, BuildHasherDefault<RunHashHasher>>,
bytes: usize,
expected_entries: usize,
order: Vec<(u64, u64)>,
black: u32,
}
impl Default for ResidentRuns {
fn default() -> Self {
Self {
runs: HashMap::default(),
bytes: 0,
expected_entries: 0,
order: Vec::new(),
black: pack_context_color(BLACK),
}
}
}
impl ResidentRuns {
fn open(
&mut self,
hash: u64,
identity: ResidentIdentity,
placed: &[PlacedGlyph],
frame: u64,
) -> Option<&mut ResidentRun> {
let known = self
.runs
.get(&hash)
.is_some_and(|run| run.matches(&identity, placed));
if !known {
if let Some(previous) = self.runs.remove(&hash) {
self.bytes = self.bytes.saturating_sub(previous.bytes());
}
let run = ResidentRun::new(identity, placed, frame);
let bytes = run.bytes();
if !self.make_room(bytes, frame) {
return None;
}
self.bytes = self.bytes.saturating_add(bytes);
self.runs.insert(hash, run);
}
self.runs.get_mut(&hash)
}
fn make_room(&mut self, bytes: usize, frame: u64) -> bool {
let fits = |runs: &Self| {
runs.runs.len() < MAX_RESIDENT_RUNS
&& runs.bytes.saturating_add(bytes) <= MAX_RESIDENT_RUN_BYTES
};
if bytes > MAX_RESIDENT_RUN_BYTES / 4 {
return false;
}
if fits(self) {
return true;
}
let mut order = std::mem::take(&mut self.order);
order.clear();
order.extend(
self.runs
.iter()
.filter(|(_, run)| run.frame != frame)
.map(|(hash, run)| (run.frame, *hash)),
);
order.sort_unstable();
let run_target = MAX_RESIDENT_RUNS / 4 * 3;
let byte_target = (MAX_RESIDENT_RUN_BYTES / 4 * 3).saturating_sub(bytes);
for (_, hash) in order.drain(..) {
if self.runs.len() <= run_target && self.bytes <= byte_target {
break;
}
if let Some(run) = self.runs.remove(&hash) {
self.bytes = self.bytes.saturating_sub(run.bytes());
}
}
self.order = order;
fits(self)
}
fn refuse(&mut self, hash: u64) {
if let Some(run) = self.runs.get_mut(&hash) {
run.refused = true;
}
}
fn forget_all(&mut self) {
self.runs.clear();
self.bytes = 0;
}
fn touch(&mut self, atlas: &mut GlyphAtlas, serial: u64) {
let horizon = serial.saturating_add(1);
let fresh = |used: u64| used != NEVER && horizon.saturating_sub(used) <= MAX_ENTRY_AGE;
let black = self.black;
let mut freed = 0_usize;
self.runs.retain(|_, run| {
if !fresh(run.used) {
freed = freed.saturating_add(run.bytes());
return false;
}
for (id, glyph) in run.ids.iter().zip(run.glyphs.iter_mut()) {
for bucket in 0..SUBPIXEL_BUCKETS {
let index = usize::from(bucket);
if glyph.slots[index].is_none() {
continue;
}
let current = if fresh(glyph.used[index]) {
atlas.get(&outline_key(&run.identity, *id, bucket, black))
} else {
None
};
glyph.slots[index] = current;
if current.is_none() {
glyph.used[index] = NEVER;
}
}
}
true
});
self.bytes = self.bytes.saturating_sub(freed);
}
}
#[cfg(test)]
mod distinct_glyph_tests {
use super::*;
#[test]
fn the_sorting_scratch_counts_what_a_set_counts() {
let runs: [&[u32]; 5] = [
&[],
&[7],
&[5, 6, 7, 7, 8],
&[9, 9, 9, 9],
&[3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5],
];
let mut sorted: Vec<u32> = Vec::new();
let mut set: HashSet<u32> = HashSet::new();
for ids in runs.iter().chain(runs.iter().rev()) {
let expected = ids.iter().collect::<HashSet<_>>().len();
assert_eq!(
sorted.count_distinct(ids.iter().copied()),
expected,
"{ids:?}"
);
assert_eq!(set.count_distinct(ids.iter().copied()), expected, "{ids:?}");
}
}
}
#[cfg(test)]
mod resident_run_tests {
use super::*;
#[test]
fn the_restated_bucket_is_glifos_own() {
let identity = ResidentIdentity {
font_id: 1,
font_index: 0,
size: 16.0,
hinted: false,
};
let black = pack_context_color(BLACK);
for step in -4096_i32..4096 {
let origin = f64::from(step) / 1024.0 + 0.000_7;
let fraction = origin.fract() as f32;
let theirs = GlyphCacheKey::new(
1,
0,
7,
16.0,
false,
fraction,
BLACK,
black,
FontEmbolden::default(),
&[],
);
let bucket = subpixel_bucket(fraction);
assert_eq!(bucket, theirs.subpixel_x, "fraction {fraction}");
assert_eq!(
outline_key(&identity, 7, bucket, black),
theirs,
"fraction {fraction}: the probe key is the key `glifo` builds"
);
}
}
#[test]
fn a_resident_probe_miss_is_not_a_frame_miss() {
use vello_common::multi_atlas::AllocationStrategy;
let mut images = ImageCache::new_with_config(AtlasConfig {
initial_atlas_count: 0,
max_atlases: 1,
atlas_size: (256, 256),
auto_grow: true,
allocation_strategy: AllocationStrategy::FirstFit,
});
let mut policy = AtlasPolicy::new(&images, false);
policy.begin_frame();
let _ = policy.build(&mut images, |_| None);
let identity = ResidentIdentity {
font_id: 9,
font_index: 0,
size: 16.0,
hinted: false,
};
let placed = [PlacedGlyph { id: 3, bucket: 1 }];
let mut slots = Vec::new();
assert!(matches!(
policy.resolve_resident_run(identity, &placed, &mut slots),
ResidentAnswer::Missed(_)
));
assert_eq!(policy.frame_misses(), 0);
let key = outline_key(&identity, 3, 1, policy.resident.black);
assert!(policy.atlas_mut().get(&key).is_none());
assert_eq!(policy.frame_misses(), 1, "`glifo`'s own lookup counts");
}
#[test]
fn the_bound_sheds_the_least_recently_drawn_first() {
let mut runs = ResidentRuns::default();
let identity = ResidentIdentity {
font_id: 1,
font_index: 0,
size: 16.0,
hinted: false,
};
for serial in 0..(MAX_RESIDENT_RUNS as u32 * 3) {
let frame = u64::from(serial / 10);
let placed: Vec<PlacedGlyph> = (0..3)
.map(|digit| PlacedGlyph {
id: serial.wrapping_mul(7).wrapping_add(digit),
bucket: 0,
})
.collect();
let hash = run_hash(&identity, &placed);
assert!(runs.open(hash, identity, &placed, frame).is_some());
assert!(runs.runs.len() <= MAX_RESIDENT_RUNS);
assert!(runs.bytes <= MAX_RESIDENT_RUN_BYTES);
let current = runs.runs.values().filter(|run| run.frame == frame).count();
assert_eq!(
current as u32,
serial % 10 + 1,
"this frame's runs are all kept"
);
}
let total: usize = runs.runs.values().map(ResidentRun::bytes).sum();
assert_eq!(runs.bytes, total, "the byte count is the records' own");
let huge: Vec<PlacedGlyph> = (0..MAX_RESIDENT_RUN_BYTES as u32 / 64)
.map(|id| PlacedGlyph { id, bucket: 0 })
.collect();
let hash = run_hash(&identity, &huge);
assert!(runs.open(hash, identity, &huge, 10_000).is_none());
}
}
#[cfg(all(test, feature = "perf-trace"))]
mod trace_tests {
use super::*;
use glifo::RasterMetrics;
use peniko::color::{AlphaColor, Srgb};
use vello_common::multi_atlas::AllocationStrategy;
const GLYPHS: [u32; 3] = [11, 12, 13];
fn images() -> ImageCache {
ImageCache::new_with_config(AtlasConfig {
initial_atlas_count: 0,
max_atlases: 4,
atlas_size: (1024, 1024),
auto_grow: true,
allocation_strategy: AllocationStrategy::FirstFit,
})
}
fn run_key() -> RunKey {
RunKey {
font_id: 0x7ace_0001,
font_index: 0,
font_size: 16.0,
distinct_glyphs: GLYPHS.len() as u32,
transform: Affine::IDENTITY,
hinted: false,
color_font: false,
context_color: AlphaColor::<Srgb>::new([0.0, 0.0, 0.0, 1.0]),
}
}
fn frame(policy: &mut AtlasPolicy, images: &mut ImageCache) -> u64 {
policy.begin_frame();
let route = policy.classify_run(&run_key());
let route = policy.admit_run(route);
let Some(run) = route.atlas().copied() else {
return 0;
};
policy.build(images, |_| None);
for glyph in GLYPHS {
let key = run.key(glyph, 0.0);
if policy.atlas_mut().get(&key).is_none() {
let metrics = RasterMetrics {
width: 8,
height: 10,
bearing_x: 0,
bearing_y: 10,
};
let inserted = policy.atlas_mut().insert(images, key, metrics).is_some();
assert!(inserted, "the atlas has room");
}
}
let misses = policy.frame_misses();
policy.acknowledge_clears();
policy.end_frame(images);
policy.acknowledge_replay();
misses
}
#[test]
fn trace_glyph_misses_reports_the_production_frame_miss_count() {
let mut images = images();
let mut policy = AtlasPolicy::new(&images, false);
let first = frame(&mut policy, &mut images);
assert!(first > 0, "a cold run must miss");
assert_eq!(u64::from(policy.trace().glyph_misses), first);
let second = frame(&mut policy, &mut images);
assert_eq!(second, 0, "the resident run must hit");
assert_eq!(policy.trace().glyph_misses, 0);
}
}