#[cfg(feature = "profiling")]
use core::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Phase {
Clip,
Color,
Glyphs,
Image,
Shading,
PathPrep,
Cull,
RectTest,
ZeroScan,
PathXform,
Patches,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Site {
ClipVec,
ClipPath,
GlyphVec,
OptionsClone,
CtxClone,
PathGeometry,
}
#[cfg(feature = "profiling")]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Profile {
pub phase_time: [Duration; 11],
pub phase_calls: [u64; 11],
pub site_count: [u64; 6],
pub site_bytes: [u64; 6],
}
impl Phase {
#[cfg(feature = "profiling")]
#[must_use]
pub const fn index(self) -> usize {
match self {
Phase::Clip => 0,
Phase::Color => 1,
Phase::Glyphs => 2,
Phase::Image => 3,
Phase::Shading => 4,
Phase::PathPrep => 5,
Phase::Cull => 6,
Phase::RectTest => 7,
Phase::ZeroScan => 8,
Phase::PathXform => 9,
Phase::Patches => 10,
}
}
#[cfg(feature = "profiling")]
pub const ALL: [Phase; 11] = [
Phase::Clip,
Phase::Color,
Phase::Glyphs,
Phase::Image,
Phase::Shading,
Phase::PathPrep,
Phase::Cull,
Phase::RectTest,
Phase::ZeroScan,
Phase::PathXform,
Phase::Patches,
];
#[cfg(feature = "profiling")]
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Phase::Clip => "clip",
Phase::Color => "color",
Phase::Glyphs => "glyphs",
Phase::Image => "image",
Phase::Shading => "shading",
Phase::PathPrep => "path prep",
Phase::Cull => "cull",
Phase::RectTest => "rect test",
Phase::ZeroScan => "zero scan",
Phase::PathXform => "path xform",
Phase::Patches => "patches",
}
}
}
impl Site {
#[cfg(feature = "profiling")]
#[must_use]
pub const fn index(self) -> usize {
match self {
Site::ClipVec => 0,
Site::ClipPath => 1,
Site::GlyphVec => 2,
Site::OptionsClone => 3,
Site::CtxClone => 4,
Site::PathGeometry => 5,
}
}
#[cfg(feature = "profiling")]
pub const ALL: [Site; 6] = [
Site::ClipVec,
Site::ClipPath,
Site::GlyphVec,
Site::OptionsClone,
Site::CtxClone,
Site::PathGeometry,
];
#[cfg(feature = "profiling")]
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Site::ClipVec => "clip Vec<Clip>",
Site::ClipPath => "clip BezPath",
Site::GlyphVec => "Vec<PlacedGlyph>",
Site::OptionsClone => "RenderOptions clone",
Site::CtxClone => "RenderCtx clone",
Site::PathGeometry => "draw_path BezPath",
}
}
}
#[cfg(feature = "profiling")]
mod imp {
use core::cell::Cell;
use core::time::Duration;
use super::{Phase, Profile, Site};
thread_local! {
static PROFILE: Cell<Profile> = const { Cell::new(Profile {
phase_time: [Duration::ZERO; 11],
phase_calls: [0; 11],
site_count: [0; 6],
site_bytes: [0; 6],
}) };
}
pub fn take() -> Profile {
PROFILE.replace(Profile::default())
}
pub fn alloc(site: Site, bytes: usize) {
let mut p = PROFILE.get();
let i = site.index();
if let (Some(c), Some(b)) = (p.site_count.get_mut(i), p.site_bytes.get_mut(i)) {
*c = c.saturating_add(1);
*b = b.saturating_add(bytes as u64);
}
PROFILE.set(p);
}
pub fn phase<T>(phase: Phase, body: impl FnOnce() -> T) -> T {
let started = std::time::Instant::now();
let out = body();
let elapsed = started.elapsed();
let mut p = PROFILE.get();
let i = phase.index();
if let (Some(t), Some(c)) = (p.phase_time.get_mut(i), p.phase_calls.get_mut(i)) {
*t = t.saturating_add(elapsed);
*c = c.saturating_add(1);
}
PROFILE.set(p);
out
}
#[derive(Debug, Clone, Copy)]
pub struct Started(std::time::Instant);
impl Started {
pub fn end(self, phase: Phase) {
let elapsed = self.0.elapsed();
let mut p = PROFILE.get();
let i = phase.index();
if let (Some(t), Some(c)) = (p.phase_time.get_mut(i), p.phase_calls.get_mut(i)) {
*t = t.saturating_add(elapsed);
*c = c.saturating_add(1);
}
PROFILE.set(p);
}
}
pub fn phase_start() -> Started {
Started(std::time::Instant::now())
}
}
#[cfg(not(feature = "profiling"))]
mod imp {
#[cfg(feature = "profiling")]
use super::Profile;
use super::{Phase, Site};
#[cfg(feature = "profiling")]
#[inline]
pub fn take() -> Profile {
Profile::default()
}
#[inline]
pub fn alloc(_site: Site, _bytes: usize) {}
#[inline]
pub fn phase<T>(_phase: Phase, body: impl FnOnce() -> T) -> T {
body()
}
#[derive(Debug, Clone, Copy)]
pub struct Started;
impl Started {
#[inline]
#[expect(
clippy::unused_self,
reason = "the feature-on twin takes `self` — the `Instant` it holds — and the two must have one signature"
)]
pub fn end(self, _phase: Phase) {}
}
#[inline]
pub fn phase_start() -> Started {
Started
}
}
#[cfg(feature = "profiling")]
pub use imp::take;
pub use imp::{alloc, phase, phase_start};
#[inline]
pub fn alloc_items(site: Site, items: usize, item_size: usize) {
alloc(site, items.saturating_mul(item_size));
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "profiling")]
#[test]
fn the_indices_are_dense_and_distinct() {
let phases: Vec<usize> = Phase::ALL.iter().map(|p| p.index()).collect();
assert_eq!(phases, (0..Phase::ALL.len()).collect::<Vec<_>>());
let sites: Vec<usize> = Site::ALL.iter().map(|s| s.index()).collect();
assert_eq!(sites, (0..Site::ALL.len()).collect::<Vec<_>>());
}
#[test]
fn the_phase_wrapper_returns_the_body_s_value_either_way() {
assert_eq!(phase(Phase::Color, || 7_u32), 7);
alloc_items(Site::ClipVec, 3, 8);
}
#[cfg(feature = "profiling")]
#[test]
fn taking_the_profile_clears_it() {
let _ = take();
alloc_items(Site::CtxClone, 4, 16);
let first = take();
assert_eq!(first.site_count.get(Site::CtxClone.index()), Some(&1));
assert_eq!(first.site_bytes.get(Site::CtxClone.index()), Some(&64));
assert_eq!(take(), Profile::default());
}
}