pub mod blur_rrect;
pub mod clear;
pub mod clip;
pub mod layers;
pub mod paint;
pub mod draw;
pub mod external;
pub use clear::ClearPunch;
pub use clip::ClipStack;
pub use draw::{DepthCounter, EngineDraw};
pub use external::{ExternalExtents, ExternalSkip};
pub use layers::{GroupStack, LayerLowering, SnapshotStack};
use std::collections::HashSet;
use std::sync::Once;
use std::time::Duration;
use kurbo::{
Affine, BezPath, Cap, Join, Line, PathEl, Rect, RoundedRect, 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::{AtlasCacher, GlyphAtlas, GlyphPrepCache, PendingClearRect};
use vello_common::clip::PathDataRef;
use vello_common::encode::EncodedPaint;
use vello_common::fearless_simd::Level;
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, inflated_bounds};
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::config;
use crate::error::EngineError;
use crate::text::{
AtlasPolicy, GlyphRunTargets, RunKey, RunRoute, context_paint, font_has_color_glyphs,
font_is_readable, 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)]
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_clears: Vec<PendingClearRect>,
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,
clips: ClipStack,
groups: GroupStack,
snapshots: SnapshotStack,
punches: Vec<StagedPunch>,
images: ImageResidency,
glyphs: GlyphPrepCache,
glyph_atlas: AtlasPolicy,
run_routes: Vec<RunRoute>,
next_run: usize,
run_glyph_ids: HashSet<u32>,
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 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),
clips: ClipStack::new(),
groups: GroupStack::new(),
snapshots: SnapshotStack::new(),
punches: Vec::new(),
glyph_atlas: glyph_atlas_policy(&images),
images,
glyphs: GlyphPrepCache::default(),
run_routes: Vec::new(),
next_run: 0,
run_glyph_ids: HashSet::new(),
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()
}
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)?;
for command in scene.commands() {
if let Some(transform) = command_transform(command) {
check_finite(root * transform)?;
}
check_geometry(command)?;
}
let validate = clock.lap();
self.generator.reset(width, height);
self.frame_extent = (width, height);
self.clips.reset();
self.groups.reset();
self.snapshots.reset();
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_clears,
compile_spans: CompileSpans::default(),
};
let mut depth = DepthCounter::new();
for command in scene.commands() {
self.compile_command(command, root, &mut frame, &mut depth);
}
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,
command: &Command,
root: Affine,
frame: &mut CompiledFrame,
depth: &mut DepthCounter,
) {
let combined = root * self.snapshots.correction();
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::PushLayer { .. } => self.open_blocked_group(),
Command::PushSnapshot {
rect,
scale,
transform,
..
} => {
let (scale, transform) = snapshot_entry(on_grid, *scale, *transform);
self.snapshots.enter(*rect, scale, transform);
}
_ => {}
}
return;
}
match command {
Command::FillRect {
rect,
brush,
transform,
} => {
let transform = combined * *transform;
if let Some(device_rect) = fast_rect(*rect, transform) {
let recorded = self.record(
frame,
depth,
PaintSource::Brush(brush),
transform,
|generator, storage, clip| {
generator.generate_filled_rect_fast(&device_rect, storage, clip);
},
);
if recorded {
frame.fast_rect_draws = frame.fast_rect_draws.saturating_add(1);
}
} else {
self.record(
frame,
depth,
PaintSource::Brush(brush),
transform,
|generator, storage, clip| {
generator.generate_filled_path(
rect.path_elements(FLATTEN_TOLERANCE),
Fill::NonZero,
transform,
None,
storage,
clip,
);
},
);
}
}
Command::RoundedRect {
rect,
radii,
brush,
transform,
} => {
let transform = combined * *transform;
let shape = RoundedRect::from_rect(*rect, rounded_rect_radii(*radii));
self.record(
frame,
depth,
PaintSource::Brush(brush),
transform,
|generator, storage, clip| {
generator.generate_filled_path(
shape.path_elements(FLATTEN_TOLERANCE),
Fill::NonZero,
transform,
None,
storage,
clip,
);
},
);
}
Command::Line {
p0,
p1,
width,
brush,
transform,
} => {
let transform = combined * *transform;
let line = Line::new(*p0, *p1);
let stroke = round_stroke(*width);
self.record(
frame,
depth,
PaintSource::Brush(brush),
transform,
|generator, storage, clip| {
generator.generate_stroked_path(
line.path_elements(FLATTEN_TOLERANCE),
&stroke,
transform,
None,
storage,
clip,
);
},
);
}
Command::Path {
path,
style,
brush,
transform,
} => {
let transform = combined * *transform;
match style {
PathStyle::Fill => {
self.record(
frame,
depth,
PaintSource::Brush(brush),
transform,
|generator, storage, clip| {
generator.generate_filled_path(
path.iter(),
Fill::NonZero,
transform,
None,
storage,
clip,
);
},
);
}
PathStyle::Stroke { width, dash } => {
let stroke = round_stroke(*width);
let dashed = match dash {
Some(dash) if dash.is_effective() => Some(dash_path(path, *dash)),
_ => None,
};
match &dashed {
Some(dashed) => {
self.record(
frame,
depth,
PaintSource::Brush(brush),
transform,
|generator, storage, clip| {
generator.generate_stroked_path(
dashed.iter(),
&stroke,
transform,
None,
storage,
clip,
);
},
);
}
None => {
self.record(
frame,
depth,
PaintSource::Brush(brush),
transform,
|generator, storage, clip| {
generator.generate_stroked_path(
path.iter(),
&stroke,
transform,
None,
storage,
clip,
);
},
);
}
}
}
}
}
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::PushLayer {
rect,
alpha,
transform,
} => {
self.open_layer(frame, *rect, *alpha, combined * *transform);
}
Command::PopClip | Command::PopLayer => self.close_group(frame),
Command::ClearRect { rect, transform } => {
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::PushSnapshot {
rect,
alpha,
scale,
transform,
..
} => {
if self.snapshots.enter(*rect, *scale, *transform) {
let corrected = root * self.snapshots.correction() * *transform;
if *alpha < 1.0 && check_finite(corrected).is_ok() {
self.open_layer(frame, *rect, *alpha, corrected);
self.snapshots.record_layer(self.groups.depth());
}
}
}
Command::PopSnapshot => {
if self.snapshots.leave(self.groups.depth()) {
self.close_group(frame);
}
}
Command::Image {
data,
dest,
transform,
} => {
let transform = combined * *transform;
let source = PaintSource::Image { data, dest: *dest };
if let Some(device_rect) = fast_rect(*dest, transform) {
let recorded = self.record(
frame,
depth,
source,
transform,
|generator, storage, clip| {
generator.generate_filled_rect_fast(&device_rect, storage, clip);
},
);
if recorded {
frame.fast_rect_draws = frame.fast_rect_draws.saturating_add(1);
}
} else {
self.record(
frame,
depth,
source,
transform,
|generator, storage, clip| {
generator.generate_filled_path(
dest.path_elements(FLATTEN_TOLERANCE),
Fill::NonZero,
transform,
None,
storage,
clip,
);
},
);
}
}
Command::BlurredRoundedRect {
rect,
radii,
std_dev,
color,
transform,
} => {
let transform = combined * *transform;
let source = PaintSource::BlurredRect {
rect: *rect,
radii: *radii,
std_dev: *std_dev,
color: *color,
};
let bounds = inflated_bounds(*rect, *std_dev);
if let Some(device_rect) = fast_rect(bounds, transform) {
let recorded = self.record(
frame,
depth,
source,
transform,
|generator, storage, clip| {
generator.generate_filled_rect_fast(&device_rect, storage, clip);
},
);
if recorded {
frame.fast_rect_draws = frame.fast_rect_draws.saturating_add(1);
}
} else {
self.record(
frame,
depth,
source,
transform,
|generator, storage, clip| {
generator.generate_filled_path(
bounds.path_elements(FLATTEN_TOLERANCE),
Fill::NonZero,
transform,
None,
storage,
clip,
);
},
);
}
}
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,
..
} => {
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.draw_external_texture(*id, *dest, combined * *transform, frame, depth);
}
}
}
fn draw_external_texture(
&mut self,
id: u64,
dest: Rect,
transform: Affine,
frame: &mut CompiledFrame,
depth: &mut DepthCounter,
) {
let source = PaintSource::SceneTexture { id, dest };
if let Some(device_rect) = fast_rect(dest, transform) {
let recorded = self.record(
frame,
depth,
source,
transform,
|generator, storage, clip| {
generator.generate_filled_rect_fast(&device_rect, storage, clip);
},
);
if recorded {
frame.fast_rect_draws = frame.fast_rect_draws.saturating_add(1);
}
} else {
self.record(
frame,
depth,
source,
transform,
|generator, storage, clip| {
generator.generate_filled_path(
dest.path_elements(FLATTEN_TOLERANCE),
Fill::NonZero,
transform,
None,
storage,
clip,
);
},
);
}
}
fn classify_runs(&mut self, scene: &Scene, root: Affine) {
self.run_routes.clear();
self.next_run = 0;
let mut snapshots = SnapshotStack::new();
for command in scene.commands() {
match command {
Command::PushSnapshot {
rect,
scale,
transform,
..
} => {
let combined = root * snapshots.correction();
let on_grid = command_on_grid(command, combined);
let (scale, transform) = snapshot_entry(on_grid, *scale, *transform);
snapshots.enter(*rect, scale, transform);
}
Command::PopSnapshot => {
snapshots.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 * snapshots.correction() * run.transform,
self.hint_text,
font_has_color_glyphs(run.font.font()),
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 !font_is_readable(run.font.font()) {
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 Self {
generator,
clips,
glyphs,
images,
glyph_atlas,
..
} = self;
let cacher = match route {
Some(RunRoute::Atlas(_)) => {
AtlasCacher::Enabled(glyph_atlas.atlas_mut(), images.allocator_mut())
}
Some(RunRoute::Outline(_)) | None => AtlasCacher::Disabled,
};
let outcome = lower_glyph_run(
run,
transform,
paint,
&run.brush,
hint_text,
cacher,
GlyphRunTargets {
generator,
clips,
prep: glyphs,
frame,
depth,
},
);
frame.glyph_draws = frame.glyph_draws.saturating_add(outcome.drawn);
frame.skipped_glyphs = frame.skipped_glyphs.saturating_add(outcome.skipped);
}
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_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.snapshots.reset();
}
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 record<F>(
&mut self,
frame: &mut CompiledFrame,
depth: &mut DepthCounter,
source: PaintSource<'_>,
transform: Affine,
generate: F,
) -> bool
where
F: FnOnce(&mut StripGenerator, &mut StripStorage, Option<PathDataRef<'_>>),
{
if self.clips.blocks_everything() {
return false;
}
let start = frame.strips.strips.len();
let alpha_start = frame.strips.alphas.len();
generate(&mut self.generator, &mut frame.strips, self.clips.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]);
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::Image { transform, .. }
| Command::BlurredRoundedRect { transform, .. }
| Command::PushLayer { transform, .. }
| Command::ClearRect { transform, .. }
| Command::Path { transform, .. }
| Command::ShaderQuad { transform, .. }
| Command::SceneTexture { transform, .. }
| Command::PushSnapshot { transform, .. } => Some(*transform),
Command::GlyphRun(run) => Some(run.transform),
Command::PopClip | Command::PopLayer | Command::PopSnapshot => None,
}
}
fn command_on_grid(command: &Command, combined: Affine) -> bool {
command_transform(command).is_none_or(|transform| check_finite(combined * transform).is_ok())
}
fn snapshot_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::PushLayer { rect, alpha, .. } => rect.is_finite() && alpha.is_finite(),
Command::ClearRect { rect, .. } => rect.is_finite(),
Command::PushSnapshot {
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::PopClip | Command::PopLayer | Command::PopSnapshot => 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 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_snapshot_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::PushSnapshot {
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) = snapshot_entry(false, 2.0, transform);
assert_eq!(scale, 1.0);
assert_eq!(entered.as_coeffs(), Affine::IDENTITY.as_coeffs());
let (scale, entered) = snapshot_entry(true, 2.0, transform);
assert_eq!(scale, 2.0);
assert_eq!(entered.as_coeffs(), transform.as_coeffs());
let mut recorded = SnapshotStack::new();
recorded.enter(rect, 2.0, transform);
assert_ne!(
recorded.correction().as_coeffs(),
Affine::IDENTITY.as_coeffs()
);
let mut neutral = SnapshotStack::new();
let (scale, entered) = snapshot_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
));
}
}