pub mod blur_rrect;
pub mod clear;
pub mod clip;
pub(crate) mod cull;
pub mod layers;
pub mod paint;
pub mod draw;
pub mod external;
pub(crate) mod parallel;
pub use clear::ClearPunch;
pub use clip::ClipStack;
pub use draw::{DepthCounter, EngineDraw};
pub use external::{ExternalExtents, ExternalSkip};
pub use layers::{
GroupStack, LayerLowering, RetainedBracket, RetainedKind, RetainedPlacement, RetainedStack,
};
pub use parallel::{DEFAULT_PARALLEL_MIN_COST, DEFAULT_PARALLEL_WORKERS, MAX_PARALLEL_WORKERS};
pub const MAX_RESIDENT_GLYPH_RUNS: usize = crate::text::atlas_policy::MAX_RESIDENT_RUNS;
use std::sync::Once;
use std::time::Duration;
use kurbo::{Affine, BezPath, Cap, Join, PathEl, Rect, RoundedRectRadii, Shape, Stroke};
use peniko::{Brush, Color, Fill, ImageData};
use frust_gpu::{SceneTextureId, TierCaps};
use frust_scene::{Command, CornerRadii, DashPattern, GlyphRun, PathStyle, Scene};
use glifo::{GlyphAtlas, GlyphPrepCache, PendingClearRect};
use vello_common::encode::EncodedPaint;
use vello_common::fearless_simd::Level;
use vello_common::geometry::RectU16;
use vello_common::paint::ImageId;
use vello_common::record::CommandRecorder;
use vello_common::strip_generator::{GenerationMode, StripGenerator, StripStorage};
use vello_common::tile::Tile;
use vello_common::util::is_axis_aligned;
use crate::cache::images::{
AtlasBudget, AtlasRegion, ImageResidency, ImageSkip, ImageUpload, is_mobile_tier,
};
use crate::compile::blur_rrect::encode_blurred_rounded_rect;
use crate::compile::clear::StagedPunch;
use crate::compile::external::encode_scene_texture;
use crate::compile::paint::{LutRequest, encode_brush, encode_image_brush, encode_image_command};
use crate::compile::parallel::{Claim, Geometry, Merge, Pool};
use crate::config;
use crate::error::EngineError;
use crate::filters::{Backdrop, MAX_BACKDROPS_PER_FRAME, backdrop_region, backdrop_sigma};
use crate::text::{
AtlasPolicy, FontFacts, GlyphQuadScratch, GlyphQuadTargets, GlyphRunTargets, RunKey, RunRoute,
context_paint, glyph_atlas_policy, lower_glyph_run,
};
pub(crate) const FLATTEN_TOLERANCE: f64 = 0.1;
static IMAGE_SKIP_WARNING: Once = Once::new();
static FONT_SKIP_WARNING: Once = Once::new();
static SHADER_QUAD_SKIP_WARNING: Once = Once::new();
static SHADER_EFFECTS_DISABLED_WARNING: Once = Once::new();
#[derive(Debug, Clone, Copy)]
pub(crate) struct PhaseClock {
#[cfg(feature = "perf-trace")]
last: std::time::Instant,
}
impl PhaseClock {
#[must_use]
pub(crate) fn start() -> Self {
Self {
#[cfg(feature = "perf-trace")]
last: std::time::Instant::now(),
}
}
#[must_use]
pub(crate) fn lap(&mut self) -> Duration {
#[cfg(feature = "perf-trace")]
{
let now = std::time::Instant::now();
let span = now.saturating_duration_since(self.last);
self.last = now;
span
}
#[cfg(not(feature = "perf-trace"))]
Duration::ZERO
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct CompileSpans {
pub validate: Duration,
pub prepare: Duration,
pub classify: Duration,
pub admit: Duration,
pub walk: Duration,
pub glyphs: Duration,
pub finish: Duration,
}
impl CompileSpans {
#[must_use]
pub fn total(&self) -> Duration {
[
self.prepare,
self.classify,
self.admit,
self.walk,
self.finish,
]
.iter()
.fold(self.validate, |acc, span| acc.saturating_add(*span))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct GlyphSlot {
pub id: ImageId,
pub region: AtlasRegion,
pub padding: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct GlyphQuad {
pub depth: u32,
pub x: u16,
pub y: u16,
pub width: u16,
pub height: u16,
pub atlas_x: u16,
pub atlas_y: u16,
}
#[derive(Debug)]
pub struct CompiledFrame {
pub strips: StripStorage,
pub recorder: CommandRecorder<EngineDraw>,
pub encoded_paints: Vec<EncodedPaint>,
pub clears: Vec<ClearPunch>,
pub lut_requests: Vec<LutRequest>,
pub fast_rect_draws: u32,
pub scissor_clips: u32,
pub mask_clips: u32,
pub image_evictions: Vec<AtlasRegion>,
pub image_uploads: Vec<ImageUpload>,
pub atlas_layers: u32,
pub image_draws: u32,
pub skipped_images: u32,
pub external_draws: u32,
pub skipped_externals: u32,
pub clip_mask_strips: usize,
pub glyph_draws: u32,
pub skipped_glyphs: u32,
pub atlas_glyph_draws: u32,
pub glyph_slots: Vec<GlyphSlot>,
pub glyph_quads: Vec<GlyphQuad>,
pub glyph_run_paints: Vec<usize>,
pub resident_glyph_runs: u32,
pub parallel_draws: u32,
pub glyph_clears: Vec<PendingClearRect>,
pub retained: Vec<RetainedBracket>,
pub retained_superseded: Vec<u64>,
pub backdrops: Vec<Backdrop>,
pub compile_spans: CompileSpans,
}
impl CompiledFrame {
pub fn draws(&self) -> &[EngineDraw] {
&self.recorder.draws
}
pub fn strip_buf(&self) -> &[vello_common::strip::Strip] {
&self.strips.strips
}
pub fn alphas(&self) -> &[u8] {
&self.strips.alphas
}
}
#[derive(Debug)]
pub struct SceneCompiler {
generator: StripGenerator,
level: Level,
parallel_workers: usize,
parallel_min_cost: u64,
pool: Pool,
clips: ClipStack,
groups: GroupStack,
retained_stack: RetainedStack,
retain: bool,
retained_open: Option<usize>,
punches: Vec<StagedPunch>,
images: ImageResidency,
glyphs: GlyphPrepCache,
glyph_atlas: AtlasPolicy,
run_routes: Vec<RunRoute>,
next_run: usize,
run_glyph_ids: Vec<u32>,
glyph_quads: bool,
glyph_quad_scratch: GlyphQuadScratch,
glyph_run_cache: bool,
font_facts: FontFacts,
hint_text: bool,
externals: ExternalExtents,
frame_extent: (u16, u16),
}
impl SceneCompiler {
pub fn new(width: u16, height: u16) -> Self {
Self::with_atlas_budget(width, height, AtlasBudget::MOBILE)
}
pub fn for_caps(width: u16, height: u16, caps: &TierCaps) -> Self {
let mut compiler = Self::with_atlas_budget(width, height, AtlasBudget::for_caps(caps));
compiler.hint_text = !is_mobile_tier(caps);
compiler
}
pub fn set_hint_text(&mut self, hint_text: bool) {
self.hint_text = hint_text;
}
pub fn set_glyph_quads(&mut self, enabled: bool) {
self.glyph_quads = enabled;
}
#[must_use]
pub fn glyph_quads_enabled(&self) -> bool {
self.glyph_quads
}
pub fn set_glyph_run_cache(&mut self, enabled: bool) {
self.glyph_run_cache = enabled;
}
#[must_use]
pub fn glyph_run_cache_enabled(&self) -> bool {
self.glyph_run_cache
}
pub fn set_retained_layers(&mut self, enabled: bool) {
self.retain = enabled;
}
#[must_use]
pub fn retained_layers_enabled(&self) -> bool {
self.retain
}
#[must_use]
pub fn resident_glyph_run_count(&self) -> usize {
self.glyph_atlas.resident_runs()
}
pub fn set_parallel_workers(&mut self, workers: usize) {
self.parallel_workers = workers.clamp(1, parallel::MAX_PARALLEL_WORKERS);
}
#[must_use]
pub fn parallel_workers(&self) -> usize {
self.parallel_workers
}
pub fn set_parallel_min_cost(&mut self, cost: u64) {
self.parallel_min_cost = cost;
}
#[must_use]
pub fn parallel_min_cost(&self) -> u64 {
self.parallel_min_cost
}
pub fn with_atlas_budget(width: u16, height: u16, budget: AtlasBudget) -> Self {
let level = Level::try_detect().unwrap_or(Level::baseline());
let images = ImageResidency::new(budget);
Self {
generator: StripGenerator::new(width, height, level),
level,
parallel_workers: parallel::default_workers(),
parallel_min_cost: config::parallel_min_cost().unwrap_or(DEFAULT_PARALLEL_MIN_COST),
pool: Pool::default(),
clips: ClipStack::new(),
groups: GroupStack::new(),
retained_stack: RetainedStack::new(),
retain: false,
retained_open: None,
punches: Vec::new(),
glyph_atlas: glyph_atlas_policy(&images),
images,
glyphs: GlyphPrepCache::default(),
run_routes: Vec::new(),
next_run: 0,
run_glyph_ids: Vec::new(),
glyph_quads: !config::glyph_quads_disabled(),
glyph_quad_scratch: GlyphQuadScratch::default(),
glyph_run_cache: !config::glyph_run_cache_disabled(),
font_facts: FontFacts::default(),
hint_text: false,
externals: ExternalExtents::new(),
frame_extent: (width, height),
}
}
pub fn bind_external_texture(&mut self, id: u64, size: (u32, u32)) -> bool {
self.externals.bind(id, size)
}
pub fn unbind_external_texture(&mut self, id: u64) {
self.externals.unbind(id);
}
#[must_use]
pub fn externals(&self) -> &ExternalExtents {
&self.externals
}
pub fn glyph_atlas_mut(&mut self) -> &mut GlyphAtlas {
self.glyph_atlas.atlas_mut()
}
#[must_use]
pub fn glyph_atlas_entries(&self) -> usize {
self.glyph_atlas.entry_count()
}
#[must_use]
pub fn glyph_atlas_enabled(&self) -> bool {
self.glyph_atlas.is_enabled()
}
#[cfg(feature = "perf-trace")]
pub(crate) fn glyph_atlas_trace(&self) -> &crate::text::atlas_policy::AtlasTrace {
self.glyph_atlas.trace()
}
pub fn images(&self) -> &ImageResidency {
&self.images
}
pub fn acknowledge_image_plan(&mut self) {
self.images.acknowledge_plan();
}
#[must_use]
pub fn glyph_replay_pending(&self) -> bool {
self.glyph_atlas.replay_pending()
}
pub fn acknowledge_glyph_replay(&mut self) {
self.glyph_atlas.acknowledge_replay();
}
#[must_use]
pub fn glyph_clears_pending(&self) -> bool {
self.glyph_atlas.has_pending_clears()
}
pub fn acknowledge_glyph_clears(&mut self) {
self.glyph_atlas.acknowledge_clears();
}
pub fn set_atlas_budget(&mut self, budget: AtlasBudget) {
self.set_image_residency(ImageResidency::new(budget));
}
pub fn set_image_residency(&mut self, images: ImageResidency) {
self.images = images;
self.glyph_atlas = glyph_atlas_policy(&self.images);
}
pub fn compile(
&mut self,
scene: &Scene,
root: Affine,
size: (u16, u16),
) -> Result<CompiledFrame, EngineError> {
let mut clock = PhaseClock::start();
let (width, height) = size;
check_tile_addressable(width, height)?;
check_finite(root)?;
let mut backdrops = 0_usize;
for command in scene.commands() {
if matches!(command, Command::BackdropBlur { .. }) {
backdrops = backdrops.saturating_add(1);
}
if let Some(transform) = command_transform(command) {
check_finite(root * transform)?;
}
if let Command::DrawRetained { key, .. } = command
&& !self.retain
{
return Err(EngineError::RetainedKeyMissing { key: *key });
}
check_geometry(command)?;
}
if backdrops > MAX_BACKDROPS_PER_FRAME {
return Err(EngineError::TooManyBackdrops {
recorded: backdrops,
max: MAX_BACKDROPS_PER_FRAME,
});
}
let validate = clock.lap();
self.generator.reset(width, height);
self.frame_extent = (width, height);
self.clips.reset();
self.groups.reset();
self.retained_stack.reset();
self.retained_open = None;
self.punches.clear();
self.images.begin_frame();
self.glyphs.maintain();
let prepare = clock.lap();
self.glyph_atlas.begin_frame();
self.classify_runs(scene, root);
let classify = clock.lap();
let glyph_clears = self
.glyph_atlas
.build(self.images.allocator_mut(), |_| {
None
})
.clears;
let admit = clock.lap();
let mut frame = CompiledFrame {
strips: StripStorage::new(GenerationMode::Append),
recorder: CommandRecorder::new(width, height),
encoded_paints: Vec::new(),
clears: Vec::new(),
lut_requests: Vec::new(),
fast_rect_draws: 0,
scissor_clips: 0,
mask_clips: 0,
clip_mask_strips: 0,
image_evictions: Vec::new(),
image_uploads: Vec::new(),
atlas_layers: 0,
image_draws: 0,
skipped_images: 0,
external_draws: 0,
skipped_externals: 0,
glyph_draws: 0,
skipped_glyphs: 0,
atlas_glyph_draws: 0,
glyph_slots: Vec::new(),
glyph_quads: Vec::new(),
glyph_run_paints: Vec::new(),
resident_glyph_runs: 0,
parallel_draws: 0,
glyph_clears,
retained: Vec::new(),
retained_superseded: Vec::new(),
backdrops: Vec::new(),
compile_spans: CompileSpans::default(),
};
let mut depth = DepthCounter::new();
let helpers = self.parallel_workers.saturating_sub(1);
let plan = if helpers > 0 {
parallel::plan_with(
scene,
root,
Rect::new(0.0, 0.0, f64::from(width), f64::from(height)),
self.retain,
)
} else {
parallel::Plan::default()
};
if helpers > 0 && !plan.is_empty() && plan.cost() >= self.parallel_min_cost {
let mut pool = std::mem::take(&mut self.pool);
let level = self.level;
pool.run(&plan, helpers, level, (width, height), |merge| {
for (index, command) in scene.commands().iter().enumerate() {
self.compile_command(index, command, root, &mut frame, &mut depth, Some(merge));
}
});
self.pool = pool;
} else {
for (index, command) in scene.commands().iter().enumerate() {
self.compile_command(index, command, root, &mut frame, &mut depth, None);
}
}
let walk = clock.lap();
self.close_open_groups(&mut frame);
self.generate_punches(&mut frame);
self.glyph_atlas.end_frame(self.images.allocator_mut());
frame.scissor_clips = self.clips.scissor_clips();
frame.mask_clips = self.clips.mask_clips();
frame.clip_mask_strips = self.clips.mask_strips();
let (evictions, uploads) = self.images.plan();
frame.image_evictions = evictions;
frame.image_uploads = uploads;
frame.atlas_layers = self.images.layers();
frame.compile_spans.validate = validate;
frame.compile_spans.prepare = prepare;
frame.compile_spans.classify = classify;
frame.compile_spans.admit = admit;
frame.compile_spans.walk = walk;
frame.compile_spans.finish = clock.lap();
#[cfg(feature = "perf-trace")]
note_image_pressure(&frame, &self.images);
Ok(frame)
}
fn compile_command(
&mut self,
index: usize,
command: &Command,
root: Affine,
frame: &mut CompiledFrame,
depth: &mut DepthCounter,
merge: Option<&Merge<'_, '_>>,
) {
let combined = root * self.retained_stack.correction();
let claim = merge.and_then(|merge| merge.claim(index, combined));
let route = match command {
Command::GlyphRun(_) => self.take_run_route(),
_ => None,
};
let on_grid = command_on_grid(command, combined);
if !on_grid {
match command {
Command::PushClip { .. }
| Command::PushClipRounded { .. }
| Command::PushClipPath { .. }
| Command::PushLayer { .. } => self.open_blocked_group(),
Command::PushRetained {
key,
rect,
scale,
transform,
..
} => {
let (scale, transform) = retained_entry(on_grid, *scale, *transform);
self.retained_stack.enter(*rect, scale, transform);
self.supersede_retained(frame, *key);
}
_ => {}
}
return;
}
match command {
Command::FillRect {
brush, transform, ..
}
| Command::RoundedRect {
brush, transform, ..
}
| Command::Line {
brush, transform, ..
}
| Command::Path {
brush, transform, ..
} => {
if let Some(geometry) = Geometry::of(command, combined) {
self.draw_shape(
frame,
depth,
PaintSource::Brush(brush),
combined * *transform,
geometry,
claim,
);
}
}
Command::PushClip { rect, transform } => {
let transform = combined * *transform;
self.clips.push_rect(*rect, transform, &mut self.generator);
self.groups.push_clip(transform.transform_rect_bbox(*rect));
}
Command::PushClipRounded {
rect,
radii,
transform,
} => {
let transform = combined * *transform;
self.clips.push_rounded(
*rect,
rounded_rect_radii(*radii),
transform,
&mut self.generator,
);
self.groups.push_clip(transform.transform_rect_bbox(*rect));
}
Command::PushClipPath { path, transform } => {
let transform = combined * *transform;
self.clips.push_path(path, transform, &mut self.generator);
self.groups
.push_clip(transform.transform_rect_bbox(path.bounding_box()));
}
Command::PushLayer {
rect,
alpha,
transform,
} => {
self.open_layer(frame, *rect, *alpha, combined * *transform);
}
Command::PopClip | Command::PopLayer => self.close_group(frame),
Command::ClearRect { rect, transform } => {
self.mark_retained_body_unretainable(frame);
let transform = combined * *transform;
let punch = clear::punch_rect(*rect, transform, self.groups.bounds());
if let Some(device) = punch {
self.punches.push(StagedPunch {
device,
depth: depth.advance(),
});
}
}
Command::PushRetained {
key,
rect,
alpha,
scale,
transform,
} => {
let mut listed = false;
if self.retained_stack.enter(*rect, *scale, *transform) {
let corrected = root * self.retained_stack.correction() * *transform;
if check_finite(corrected).is_ok() {
if self.retain {
self.open_retained(frame, *key, *rect, *alpha, corrected);
listed = true;
} else if *alpha < 1.0 {
self.open_layer(frame, *rect, *alpha, corrected);
self.retained_stack.record_layer(self.groups.depth());
}
}
}
if !listed {
self.supersede_retained(frame, *key);
}
}
Command::PopRetained => {
let closes = self.retained_stack.leave(self.groups.depth());
if closes {
self.close_group(frame);
}
if !self.retained_stack.is_open()
&& let Some(index) = self.retained_open.take()
&& !closes
{
mark_unretainable(frame, index);
}
}
Command::DrawRetained {
key,
rect,
transform,
alpha,
scale,
} => {
if self.retain {
let mapped = combined * *transform * Affine::scale_about(*scale, rect.center());
self.record_draw_retained(frame, depth, *key, *rect, *alpha, mapped);
}
}
Command::Image {
data,
dest,
transform,
} => {
if let Some(geometry) = Geometry::of(command, combined) {
let source = PaintSource::Image { data, dest: *dest };
self.draw_shape(frame, depth, source, combined * *transform, geometry, claim);
}
}
Command::BlurredRoundedRect {
rect,
radii,
std_dev,
color,
transform,
} => {
if let Some(geometry) = Geometry::of(command, combined) {
let source = PaintSource::BlurredRect {
rect: *rect,
radii: *radii,
std_dev: *std_dev,
color: *color,
};
self.draw_shape(frame, depth, source, combined * *transform, geometry, claim);
}
}
Command::GlyphRun(run) => {
let mut clock = PhaseClock::start();
self.compile_glyph_run(run, combined * run.transform, route, frame, depth);
frame.compile_spans.glyphs = frame.compile_spans.glyphs.saturating_add(clock.lap());
}
Command::ShaderQuad {
program,
dest,
transform,
..
} => {
self.mark_retained_body_unretainable(frame);
if config::shader_effects_disabled() {
note_shader_effects_disabled();
return;
}
let id = shader_quad_texture_id(program.id());
if self.externals.get(id).is_none() {
if shader_quad_is_culled(*dest, combined * *transform, self.frame_extent) {
note_shader_quad_culled(program.id());
} else {
note_shader_quad_unrendered(program.id());
}
frame.skipped_externals = frame.skipped_externals.saturating_add(1);
return;
}
self.draw_external_texture(id, *dest, combined * *transform, frame, depth);
}
Command::SceneTexture {
id,
dest,
transform,
} => {
self.mark_retained_body_unretainable(frame);
self.draw_external_texture(*id, *dest, combined * *transform, frame, depth);
}
Command::BackdropBlur {
rect,
radius,
transform,
} => {
self.mark_retained_body_unretainable(frame);
self.record_backdrop(frame, depth, *rect, *radius, combined * *transform);
}
}
}
fn record_backdrop(
&mut self,
frame: &mut CompiledFrame,
depth: &mut DepthCounter,
rect: Rect,
radius: f64,
transform: Affine,
) {
let Some(sigma) = backdrop_sigma(radius, transform) else {
return;
};
let (width, height) = self.frame_extent;
let Some(region) =
backdrop_region(transform.transform_rect_bbox(rect), sigma, (width, height))
else {
return;
};
let layer = u32::try_from(frame.recorder.layers.len()).unwrap_or(u32::MAX);
frame.recorder.push_layer(layers::layer_props(1.0), None);
let transparent = Brush::Solid(Color::TRANSPARENT);
let recorded = self.record(
frame,
depth,
PaintSource::Brush(&transparent),
transform,
Geometry::rect(rect, transform),
None,
);
frame.recorder.pop_layer();
if recorded && let Some(draw) = frame.draws().last() {
frame.backdrops.push(Backdrop {
layer,
depth: draw.depth,
region,
sigma,
});
}
}
fn draw_external_texture(
&mut self,
id: u64,
dest: Rect,
transform: Affine,
frame: &mut CompiledFrame,
depth: &mut DepthCounter,
) {
let source = PaintSource::SceneTexture { id, dest };
let geometry = Geometry::rect(dest, transform);
self.draw_shape(frame, depth, source, transform, geometry, None);
}
fn classify_runs(&mut self, scene: &Scene, root: Affine) {
self.run_routes.clear();
self.next_run = 0;
let mut retained_stack = RetainedStack::new();
for command in scene.commands() {
match command {
Command::PushRetained {
rect,
scale,
transform,
..
} => {
let combined = root * retained_stack.correction();
let on_grid = command_on_grid(command, combined);
let (scale, transform) = retained_entry(on_grid, *scale, *transform);
retained_stack.enter(*rect, scale, transform);
}
Command::PopRetained => {
retained_stack.leave(0);
}
Command::GlyphRun(run) => {
let context_color = match context_paint(&run.brush) {
vello_common::paint::PaintType::Solid(color) => color,
_ => peniko::color::palette::css::BLACK,
};
let key = RunKey::for_run(
run,
root * retained_stack.correction() * run.transform,
self.hint_text,
self.font_facts.of(run.font.font()).color,
context_color,
&mut self.run_glyph_ids,
);
let route = self.glyph_atlas.classify_run(&key);
self.run_routes.push(route);
}
_ => {}
}
}
}
fn take_run_route(&mut self) -> Option<RunRoute> {
let route = self.run_routes.get(self.next_run).copied();
self.next_run = self.next_run.saturating_add(1);
route.map(|route| self.glyph_atlas.admit_run(route))
}
fn compile_glyph_run(
&mut self,
run: &GlyphRun,
transform: Affine,
route: Option<RunRoute>,
frame: &mut CompiledFrame,
depth: &mut DepthCounter,
) {
if run.glyphs.is_empty() || self.clips.blocks_everything() {
return;
}
if !self.font_facts.of(run.font.font()).readable {
note_font_skip();
let glyphs = u32::try_from(run.glyphs.len()).unwrap_or(u32::MAX);
frame.skipped_glyphs = frame.skipped_glyphs.saturating_add(glyphs);
return;
}
let Some(paint) = self.encode_paint(PaintSource::Brush(&run.brush), transform, frame)
else {
return;
};
let hint_text = self.hint_text;
let glyph_run_cache = self.glyph_run_cache;
let Self {
generator,
clips,
groups,
glyphs,
images,
glyph_atlas,
glyph_quads,
glyph_quad_scratch,
..
} = self;
let atlas = match route {
Some(RunRoute::Atlas(_)) => Some((glyph_atlas, images.allocator_mut())),
Some(RunRoute::Outline(_)) | None => None,
};
let quads =
(*glyph_quads && atlas.is_some() && clips.mask().is_none()).then(|| GlyphQuadTargets {
scratch: glyph_quad_scratch,
window: quad_window(generator, groups),
});
let outcome = lower_glyph_run(
run,
transform,
paint,
&run.brush,
hint_text,
atlas,
glyph_run_cache,
GlyphRunTargets {
generator,
clips,
prep: glyphs,
frame,
depth,
quads,
},
);
frame.glyph_draws = frame.glyph_draws.saturating_add(outcome.drawn);
frame.skipped_glyphs = frame.skipped_glyphs.saturating_add(outcome.skipped);
if outcome.resident {
frame.resident_glyph_runs = frame.resident_glyph_runs.saturating_add(1);
}
}
fn open_layer(&mut self, frame: &mut CompiledFrame, rect: Rect, alpha: f32, transform: Affine) {
self.clips.push_rect(rect, transform, &mut self.generator);
let isolated = layers::lower_layer(alpha) == LayerLowering::Isolated;
if isolated {
frame.recorder.push_layer(layers::layer_props(alpha), None);
}
self.groups
.push_layer(transform.transform_rect_bbox(rect), isolated);
}
fn open_retained(
&mut self,
frame: &mut CompiledFrame,
key: u64,
rect: Rect,
alpha: f32,
transform: Affine,
) {
let window = quad_window(&self.generator, &self.groups);
let device = transform.transform_rect_bbox(rect);
let retainable = self.clips.mask().is_none()
&& !self.clips.blocks_everything()
&& rect_within(device, window);
let layer = u32::try_from(frame.recorder.layers.len()).unwrap_or(u32::MAX);
if alpha < 1.0 {
self.open_layer(frame, rect, alpha, transform);
} else {
frame.recorder.push_layer(layers::layer_props(alpha), None);
self.groups.push_retained();
}
self.retained_stack.record_layer(self.groups.depth());
let placement = RetainedPlacement::of(transform);
frame.retained.push(RetainedBracket {
key,
layer,
opacity: alpha,
placement,
extent_px: placement.extent(rect),
kind: RetainedKind::Record {
rect: tile_bounds(device),
retainable,
},
});
self.retained_open = Some(frame.retained.len() - 1);
}
fn record_draw_retained(
&mut self,
frame: &mut CompiledFrame,
depth: &mut DepthCounter,
key: u64,
rect: Rect,
alpha: f32,
mapped: Affine,
) {
if check_finite(mapped).is_err() {
return;
}
let window = quad_window(&self.generator, &self.groups);
let masked = self.clips.mask().is_some();
let layer = u32::try_from(frame.recorder.layers.len()).unwrap_or(u32::MAX);
frame.recorder.push_layer(layers::layer_props(alpha), None);
frame.recorder.pop_layer();
let placement = RetainedPlacement::of(mapped);
frame.retained.push(RetainedBracket {
key,
layer,
opacity: alpha,
placement,
extent_px: placement.extent(rect),
kind: RetainedKind::Draw {
depth: depth.advance(),
window: if self.clips.blocks_everything() {
RectU16::new(0, 0, 0, 0)
} else {
window
},
masked,
},
});
}
fn supersede_retained(&self, frame: &mut CompiledFrame, key: u64) {
if self.retain {
frame.retained_superseded.push(key);
}
}
fn mark_retained_body_unretainable(&mut self, frame: &mut CompiledFrame) {
if let Some(index) = self.retained_open {
mark_unretainable(frame, index);
}
}
fn open_blocked_group(&mut self) {
self.clips
.push_rect(Rect::ZERO, Affine::IDENTITY, &mut self.generator);
self.groups.push_clip(Rect::ZERO);
}
fn close_group(&mut self, frame: &mut CompiledFrame) {
let Some(group) = self.groups.pop() else {
return;
};
if group.closes_clip() {
self.clips.pop();
}
if group.closes_layer() && frame.recorder.has_layers() {
frame.recorder.pop_layer();
}
}
fn close_open_groups(&mut self, frame: &mut CompiledFrame) {
while !self.groups.is_empty() {
self.close_group(frame);
}
self.retained_stack.reset();
if let Some(index) = self.retained_open.take() {
mark_unretainable(frame, index);
}
}
fn generate_punches(&mut self, frame: &mut CompiledFrame) {
for index in 0..self.punches.len() {
let Some(punch) = self.punches.get(index).copied() else {
continue;
};
let start = frame.strips.strips.len();
match fast_rect(punch.device, Affine::IDENTITY) {
Some(device) => {
self.generator
.generate_filled_rect_fast(&device, &mut frame.strips, None);
}
None => {
self.generator.generate_filled_path(
punch.device.path_elements(FLATTEN_TOLERANCE),
Fill::NonZero,
Affine::IDENTITY,
None,
&mut frame.strips,
None,
);
}
}
let strip_range = start..frame.strips.strips.len();
if strip_range.is_empty() {
continue;
}
frame.clears.push(ClearPunch {
strip_range,
bounds: clear::device_bounds(punch.device),
depth: punch.depth,
});
}
}
fn draw_shape(
&mut self,
frame: &mut CompiledFrame,
depth: &mut DepthCounter,
source: PaintSource<'_>,
transform: Affine,
geometry: Geometry<'_>,
claim: Option<Claim<'_>>,
) {
let fast = geometry.is_fast_rect();
if self.record(frame, depth, source, transform, geometry, claim) && fast {
frame.fast_rect_draws = frame.fast_rect_draws.saturating_add(1);
}
}
fn record(
&mut self,
frame: &mut CompiledFrame,
depth: &mut DepthCounter,
source: PaintSource<'_>,
transform: Affine,
geometry: Geometry<'_>,
claim: Option<Claim<'_>>,
) -> bool {
if self.clips.blocks_everything() {
return false;
}
let start = frame.strips.strips.len();
let alpha_start = frame.strips.alphas.len();
let mask = self.clips.mask();
let merged =
mask.is_none() && claim.is_some_and(|claim| claim.append_to(&mut frame.strips));
if !merged {
geometry.generate(&mut self.generator, &mut frame.strips, mask);
}
self.clips.clip_run(&mut frame.strips, start, alpha_start);
let strip_range = start..frame.strips.strips.len();
if strip_range.len() < 2 {
debug_assert!(
strip_range.is_empty() || frame.strips.strips[start].is_sentinel(),
"a one-strip run must be a lone sentinel, not an unterminated content strip"
);
frame.strips.strips.truncate(start);
frame.strips.alphas.truncate(alpha_start);
return false;
}
let Some(paint) = self.encode_paint(source, transform, frame) else {
frame.strips.strips.truncate(start);
frame.strips.alphas.truncate(alpha_start);
return false;
};
let draw = EngineDraw::new(paint, depth.advance(), strip_range.clone());
frame
.recorder
.push_draw(draw, &frame.strips.strips[strip_range]);
if merged {
frame.parallel_draws = frame.parallel_draws.saturating_add(1);
}
true
}
fn encode_paint(
&mut self,
source: PaintSource<'_>,
transform: Affine,
frame: &mut CompiledFrame,
) -> Option<vello_common::paint::Paint> {
let encoded = match source {
PaintSource::Brush(Brush::Image(brush)) => encode_image_brush(
brush,
transform,
&mut frame.encoded_paints,
&mut self.images,
),
PaintSource::Brush(brush) => {
let encoding = encode_brush(brush, transform, &mut frame.encoded_paints);
frame.lut_requests.extend(encoding.lut_request);
return Some(encoding.paint);
}
PaintSource::Image { data, dest } => encode_image_command(
data,
dest,
transform,
&mut frame.encoded_paints,
&mut self.images,
),
PaintSource::SceneTexture { id, dest } => {
return match encode_scene_texture(
id,
dest,
transform,
&mut self.externals,
&mut frame.encoded_paints,
) {
Ok(encoding) => {
frame.external_draws = frame.external_draws.saturating_add(1);
Some(encoding.paint)
}
Err(_) => {
frame.skipped_externals = frame.skipped_externals.saturating_add(1);
None
}
};
}
PaintSource::BlurredRect {
rect,
radii,
std_dev,
color,
} => {
let paint = encode_blurred_rounded_rect(
rect,
radii,
std_dev,
color,
transform,
&mut frame.encoded_paints,
);
return Some(paint);
}
};
match encoded {
Ok(encoding) => {
frame.image_draws = frame.image_draws.saturating_add(1);
Some(encoding.paint)
}
Err(skip) => {
frame.skipped_images = frame.skipped_images.saturating_add(1);
note_image_skip(skip);
None
}
}
}
}
enum PaintSource<'a> {
Brush(&'a Brush),
Image {
data: &'a ImageData,
dest: Rect,
},
SceneTexture {
id: u64,
dest: Rect,
},
BlurredRect {
rect: Rect,
radii: CornerRadii,
std_dev: f64,
color: Color,
},
}
fn note_image_skip(skip: ImageSkip) {
IMAGE_SKIP_WARNING.call_once(|| {
log::warn!("image draw skipped: {skip} (further skips are logged at debug level)");
});
log::debug!("image draw skipped: {skip}");
}
#[cfg(feature = "perf-trace")]
fn note_image_pressure(frame: &CompiledFrame, images: &ImageResidency) {
if !image_pressure_reported(frame, images) {
return;
}
log::info!("{}", image_pressure_line(frame, images));
}
#[cfg(feature = "perf-trace")]
#[must_use]
pub fn image_pressure_reported(frame: &CompiledFrame, images: &ImageResidency) -> bool {
frame.skipped_images != 0 || images.frame_pressure_evictions() != 0
}
#[cfg(feature = "perf-trace")]
#[must_use]
pub fn image_pressure_line(frame: &CompiledFrame, images: &ImageResidency) -> String {
let budget = images.budget();
format!(
"frust-perf img skipped={} evicted={} resident={} budget={}x{}x{}",
frame.skipped_images,
images.frame_pressure_evictions(),
images.entry_count(),
budget.atlas_size.0,
budget.atlas_size.1,
budget.max_atlases,
)
}
fn note_font_skip() {
FONT_SKIP_WARNING.call_once(|| {
log::warn!(
"glyph run skipped: its font blob is not a readable face \
(further skips are logged at debug level)"
);
});
log::debug!("glyph run skipped: its font blob is not a readable face");
}
#[must_use]
pub fn shader_quad_texture_id(program_id: u64) -> u64 {
SceneTextureId::for_shader_program(program_id).get()
}
fn note_shader_quad_unrendered(program_id: u64) {
SHADER_QUAD_SKIP_WARNING.call_once(|| {
log::warn!(
"ShaderQuad draws nothing: fragment program {program_id} has no rendered target \
— either the frame's shader pre-pass did not run before this compile, or the \
program failed to compile (logged once per process)"
);
});
}
fn shader_quad_is_culled(dest: Rect, transform: Affine, frame_extent: (u16, u16)) -> bool {
let frame_rect = Rect::new(
0.0,
0.0,
f64::from(frame_extent.0),
f64::from(frame_extent.1),
);
let bbox = transform.transform_rect_bbox(dest);
bbox.is_finite() && !bbox.overlaps(frame_rect)
}
fn note_shader_quad_culled(program_id: u64) {
log::debug!(
"ShaderQuad draws nothing: fragment program {program_id} was culled by the \
shader-quad pre-pass — its destination does not intersect this frame's target \
(expected; not a missing pre-pass)"
);
}
fn note_shader_effects_disabled() {
SHADER_EFFECTS_DISABLED_WARNING.call_once(|| {
log::warn!(
"ShaderQuad commands draw nothing: FRUST_ENGINE_NO_SHADER_EFFECTS is set \
(logged once per process)"
);
});
}
fn command_transform(command: &Command) -> Option<Affine> {
match command {
Command::FillRect { transform, .. }
| Command::RoundedRect { transform, .. }
| Command::Line { transform, .. }
| Command::PushClip { transform, .. }
| Command::PushClipRounded { transform, .. }
| Command::PushClipPath { transform, .. }
| Command::Image { transform, .. }
| Command::BlurredRoundedRect { transform, .. }
| Command::PushLayer { transform, .. }
| Command::ClearRect { transform, .. }
| Command::Path { transform, .. }
| Command::ShaderQuad { transform, .. }
| Command::SceneTexture { transform, .. }
| Command::PushRetained { transform, .. }
| Command::DrawRetained { transform, .. }
| Command::BackdropBlur { transform, .. } => Some(*transform),
Command::GlyphRun(run) => Some(run.transform),
Command::PopClip | Command::PopLayer | Command::PopRetained => None,
}
}
fn command_on_grid(command: &Command, combined: Affine) -> bool {
command_transform(command).is_none_or(|transform| check_finite(combined * transform).is_ok())
}
fn retained_entry(on_grid: bool, scale: f64, transform: Affine) -> (f64, Affine) {
if on_grid {
(scale, transform)
} else {
(1.0, Affine::IDENTITY)
}
}
fn check_tile_addressable(width: u16, height: u16) -> Result<(), EngineError> {
let addressable = width.checked_next_multiple_of(Tile::WIDTH).is_some()
&& height.checked_next_multiple_of(Tile::HEIGHT).is_some();
if addressable {
Ok(())
} else {
Err(EngineError::TargetTooLarge)
}
}
fn check_finite(transform: Affine) -> Result<(), EngineError> {
if transform.as_coeffs().iter().all(|c| c.is_finite()) {
Ok(())
} else {
Err(EngineError::InvalidTransform)
}
}
fn check_geometry(command: &Command) -> Result<(), EngineError> {
let finite = match command {
Command::FillRect { rect, .. } => rect.is_finite(),
Command::RoundedRect { rect, radii, .. } => rect.is_finite() && radii_are_finite(*radii),
Command::Line { p0, p1, width, .. } => {
p0.is_finite() && p1.is_finite() && width.is_finite()
}
Command::Path { path, style, .. } => path.is_finite() && style_is_finite(style),
Command::PushClip { rect, .. } => rect.is_finite(),
Command::PushClipRounded { rect, radii, .. } => {
rect.is_finite() && radii_are_finite(*radii)
}
Command::PushClipPath { path, .. } => path.is_finite(),
Command::PushLayer { rect, alpha, .. } => rect.is_finite() && alpha.is_finite(),
Command::ClearRect { rect, .. } => rect.is_finite(),
Command::PushRetained {
rect, alpha, scale, ..
}
| Command::DrawRetained {
rect, alpha, scale, ..
} => rect.is_finite() && alpha.is_finite() && scale.is_finite(),
Command::Image { dest, .. } => dest.is_finite(),
Command::SceneTexture { dest, .. } => dest.is_finite(),
Command::ShaderQuad { dest, .. } => dest.is_finite(),
Command::BlurredRoundedRect {
rect,
radii,
std_dev,
..
} => rect.is_finite() && radii_are_finite(*radii) && std_dev.is_finite(),
Command::GlyphRun(run) => glyph_run_is_finite(run),
Command::BackdropBlur { rect, radius, .. } => rect.is_finite() && radius.is_finite(),
Command::PopClip | Command::PopLayer | Command::PopRetained => true,
};
if finite {
Ok(())
} else {
Err(EngineError::InvalidGeometry)
}
}
fn glyph_run_is_finite(run: &GlyphRun) -> bool {
run.font_size.is_finite()
&& run
.glyphs
.iter()
.all(|glyph| glyph.x.is_finite() && glyph.y.is_finite())
}
fn radii_are_finite(radii: CornerRadii) -> bool {
radii.top_left.is_finite()
&& radii.top_right.is_finite()
&& radii.bottom_right.is_finite()
&& radii.bottom_left.is_finite()
}
fn style_is_finite(style: &PathStyle) -> bool {
match style {
PathStyle::Fill => true,
PathStyle::Stroke { width, dash } => {
let dash_finite = match dash {
Some(dash) => {
dash.on.is_finite()
&& dash.off.is_finite()
&& dash.phase.is_finite()
&& dash_cycle_is_normalizable(dash)
}
None => true,
};
width.is_finite() && dash_finite
}
}
}
pub fn dash_cycle_is_normalizable(dash: &DashPattern) -> bool {
if !dash.is_effective() {
return true;
}
let period = dash.on + dash.off;
period.is_finite() && period > 0.0 && dash.phase.rem_euclid(period).is_finite()
}
fn fast_rect(rect: Rect, transform: Affine) -> Option<Rect> {
if !is_axis_aligned(&transform) {
return None;
}
let device = transform.transform_rect_bbox(rect);
is_pixel_aligned(device).then_some(device)
}
fn quad_window(generator: &StripGenerator, groups: &GroupStack) -> RectU16 {
let coordinate = |value: f64| value.clamp(0.0, f64::from(u16::MAX)) as u16;
groups.bounds().fold(
RectU16::new(0, 0, generator.width(), generator.height()),
|window, bounds| {
window.intersect(RectU16::new(
coordinate(bounds.x0),
coordinate(bounds.y0),
coordinate(bounds.x1),
coordinate(bounds.y1),
))
},
)
}
fn mark_unretainable(frame: &mut CompiledFrame, index: usize) {
if let Some(RetainedBracket {
kind: RetainedKind::Record { retainable, .. },
..
}) = frame.retained.get_mut(index)
{
*retainable = false;
}
}
fn rect_within(device: Rect, window: RectU16) -> bool {
device.is_finite()
&& !window.is_empty()
&& device.x0 >= f64::from(window.x0)
&& device.y0 >= f64::from(window.y0)
&& device.x1 <= f64::from(window.x1)
&& device.y1 <= f64::from(window.y1)
}
fn tile_bounds(device: Rect) -> RectU16 {
let snap = |value: f64, step: u16, up: bool| {
let step = f64::from(step);
let snapped = if up {
(value / step).ceil() * step
} else {
(value / step).floor() * step
};
if snapped.is_finite() {
snapped.clamp(0.0, f64::from(u16::MAX)) as u16
} else {
0
}
};
RectU16::new(
snap(device.x0, Tile::WIDTH, false),
snap(device.y0, Tile::HEIGHT, false),
snap(device.x1, Tile::WIDTH, true),
snap(device.y1, Tile::HEIGHT, true),
)
}
fn is_pixel_aligned(rect: Rect) -> bool {
[rect.x0, rect.y0, rect.x1, rect.y1]
.iter()
.all(|v| v.is_finite() && v.fract() == 0.0)
}
fn round_stroke(width: f64) -> Stroke {
Stroke::new(width)
.with_caps(Cap::Round)
.with_join(Join::Round)
}
pub fn dash_path(path: &BezPath, dash: DashPattern) -> BezPath {
let source = well_formed(path.iter());
well_formed(kurbo::dash(source.iter(), dash.phase, &[dash.on, dash.off]))
}
pub fn well_formed(elements: impl Iterator<Item = PathEl>) -> BezPath {
let mut out = BezPath::new();
let mut opened = false;
let mut segments_in_subpath = 0_usize;
for element in elements {
match element {
PathEl::MoveTo(_) => {
opened = true;
segments_in_subpath = 0;
out.push(element);
}
PathEl::ClosePath => {
if segments_in_subpath > 0 {
segments_in_subpath = 0;
out.push(element);
}
}
PathEl::LineTo(_) | PathEl::QuadTo(..) | PathEl::CurveTo(..) => {
if opened {
segments_in_subpath += 1;
out.push(element);
}
}
}
}
out
}
fn rounded_rect_radii(radii: CornerRadii) -> RoundedRectRadii {
RoundedRectRadii::new(
radii.top_left,
radii.top_right,
radii.bottom_right,
radii.bottom_left,
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_viewport_within_three_pixels_of_the_u16_ceiling_is_refused() {
assert!(check_tile_addressable(65532, 65532).is_ok());
assert!(matches!(
check_tile_addressable(65533, 16),
Err(EngineError::TargetTooLarge)
));
assert!(matches!(
check_tile_addressable(16, u16::MAX),
Err(EngineError::TargetTooLarge)
));
}
#[test]
fn an_off_grid_retained_bracket_is_entered_neutrally() {
let combined = Affine::scale(1e200);
let transform = Affine::scale(1e200);
let rect = Rect::new(0.0, 0.0, 10.0, 10.0);
let command = Command::PushRetained {
key: 0,
rect,
alpha: 1.0,
scale: 2.0,
transform,
};
assert!(!command_on_grid(&command, combined));
assert!(command_on_grid(&command, Affine::IDENTITY));
let (scale, entered) = retained_entry(false, 2.0, transform);
assert_eq!(scale, 1.0);
assert_eq!(entered.as_coeffs(), Affine::IDENTITY.as_coeffs());
let (scale, entered) = retained_entry(true, 2.0, transform);
assert_eq!(scale, 2.0);
assert_eq!(entered.as_coeffs(), transform.as_coeffs());
let mut recorded = RetainedStack::new();
recorded.enter(rect, 2.0, transform);
assert_ne!(
recorded.correction().as_coeffs(),
Affine::IDENTITY.as_coeffs()
);
let mut neutral = RetainedStack::new();
let (scale, entered) = retained_entry(false, 2.0, transform);
neutral.enter(rect, scale, entered);
assert_eq!(
neutral.correction().as_coeffs(),
Affine::IDENTITY.as_coeffs()
);
}
#[test]
fn pixel_alignment_rejects_fractional_edges() {
assert!(is_pixel_aligned(Rect::new(0.0, 0.0, 4.0, 4.0)));
assert!(!is_pixel_aligned(Rect::new(0.0, 0.5, 4.0, 4.0)));
assert!(!is_pixel_aligned(Rect::new(0.0, 0.0, 4.0, f64::INFINITY)));
}
#[test]
fn an_unrendered_shader_quad_is_latched_to_once_per_process() {
note_shader_quad_unrendered(1);
assert!(SHADER_QUAD_SKIP_WARNING.is_completed());
note_shader_quad_unrendered(1);
assert!(SHADER_QUAD_SKIP_WARNING.is_completed());
}
#[test]
fn a_disabled_shader_quad_is_latched_to_once_per_process() {
note_shader_effects_disabled();
assert!(SHADER_EFFECTS_DISABLED_WARNING.is_completed());
note_shader_effects_disabled();
assert!(SHADER_EFFECTS_DISABLED_WARNING.is_completed());
}
#[test]
fn a_shader_quad_target_id_is_derived_from_its_program() {
assert_eq!(shader_quad_texture_id(7), shader_quad_texture_id(7));
assert_ne!(shader_quad_texture_id(7), shader_quad_texture_id(8));
assert_ne!(shader_quad_texture_id(7), 7);
assert_eq!(
shader_quad_texture_id(7),
SceneTextureId::for_shader_program(7).get()
);
}
#[test]
fn shader_quad_is_culled_true_when_entirely_outside_the_frame() {
assert!(shader_quad_is_culled(
Rect::new(1000.0, 1000.0, 1008.0, 1008.0),
Affine::IDENTITY,
(64, 64)
));
}
#[test]
fn shader_quad_is_culled_false_when_overlapping_the_frame() {
assert!(!shader_quad_is_culled(
Rect::new(0.0, 0.0, 8.0, 8.0),
Affine::IDENTITY,
(64, 64)
));
}
#[test]
fn shader_quad_is_culled_false_touching_the_frame_edge() {
assert!(!shader_quad_is_culled(
Rect::new(64.0, 0.0, 80.0, 16.0),
Affine::IDENTITY,
(64, 64)
));
}
#[test]
fn shader_quad_is_culled_false_for_a_non_finite_bbox() {
assert!(!shader_quad_is_culled(
Rect::new(0.0, 0.0, f64::NAN, 8.0),
Affine::IDENTITY,
(64, 64)
));
}
#[test]
fn a_culled_shader_quad_is_reported_at_debug_level_not_through_the_unrendered_warning() {
let frame_extent = (64, 64);
let culled_dest = Rect::new(1000.0, 1000.0, 1008.0, 1008.0);
let unrendered_dest = Rect::new(0.0, 0.0, 8.0, 8.0);
assert!(shader_quad_is_culled(
culled_dest,
Affine::IDENTITY,
frame_extent
));
assert!(!shader_quad_is_culled(
unrendered_dest,
Affine::IDENTITY,
frame_extent
));
}
}