| // Copyright 2025 the Vello Authors | |
| // SPDX-License-Identifier: Apache-2.0 OR MIT | |
| use std::time::{Duration, Instant}; | |
| use criterion::Criterion; | |
| use parley::{ | |
| Alignment, AlignmentOptions, FontContext, FontFamily, GlyphRun, Layout, LayoutContext, | |
| PositionedLayoutItem, | |
| }; | |
| use vello_common::pixmap::Pixmap; | |
| use vello_cpu::{Glyph, RenderContext, RenderSettings, Resources}; | |
| pub fn glyph(c: &mut Criterion) { | |
| if !crate::EXTENDED { | |
| return; | |
| } | |
| let mut g = c.benchmark_group("glyph"); | |
| const WIDTH: u16 = 256; | |
| const HEIGHT: u16 = 256; | |
| const TEXT: &str = "The quick brown fox jumps over the lazy dog 0123456789"; | |
| let layout_for = |text: &str, scale: f32| { | |
| let mut layout_cx = LayoutContext::new(); | |
| let mut font_cx = FontContext::new(); | |
| let mut builder = layout_cx.ranged_builder(&mut font_cx, text, scale, true); | |
| builder.push_default(FontFamily::named("Roboto")); | |
| let mut layout: Layout<Brush> = builder.build(text); | |
| let max_advance = Some(WIDTH as f32); | |
| layout.break_all_lines(max_advance); | |
| layout.align(Alignment::Start, AlignmentOptions::default()); | |
| layout | |
| }; | |
| let settings = RenderSettings::default(); | |
| let layout = layout_for(TEXT, 1.0); | |
| for (hint_name, hint) in [("hinted", true), ("unhinted", false)] { | |
| g.bench_function(format!("cached_{hint_name}"), |b| { | |
| let mut renderer = GlyphBenchRenderer::new(WIDTH, HEIGHT, settings); | |
| render_layout(&mut renderer, &layout, hint, true); | |
| b.iter_custom(|iters| { | |
| let mut total_time = Duration::from_nanos(0); | |
| for _ in 0..iters { | |
| // Don't include `reset` time in the benchmark. | |
| renderer.reset(); | |
| let start = Instant::now(); | |
| render_layout(&mut renderer, &layout, hint, true); | |
| total_time += start.elapsed(); | |
| } | |
| total_time | |
| }); | |
| }); | |
| // Note that even for `uncached`, the outline and hint cache will still be used. This benchmark | |
| // is only for testing the difference between having atlas caching enabled and disabled. | |
| g.bench_function(format!("uncached_{hint_name}"), |b| { | |
| let mut renderer = GlyphBenchRenderer::new(WIDTH, HEIGHT, settings); | |
| render_layout(&mut renderer, &layout, hint, false); | |
| b.iter_custom(|iters| { | |
| let mut total_time = Duration::from_nanos(0); | |
| for _ in 0..iters { | |
| renderer.reset(); | |
| let start = Instant::now(); | |
| render_layout(&mut renderer, &layout, hint, false); | |
| total_time += start.elapsed(); | |
| } | |
| total_time | |
| }); | |
| }); | |
| } | |
| } | |
| #[derive(Clone, Copy, Default, Debug, PartialEq)] | |
| struct Brush; | |
| struct GlyphBenchRenderer { | |
| ctx: RenderContext, | |
| resources: Resources, | |
| pixmap: Pixmap, | |
| } | |
| impl GlyphBenchRenderer { | |
| fn new(width: u16, height: u16, settings: RenderSettings) -> Self { | |
| Self { | |
| ctx: RenderContext::new_with(width, height, settings), | |
| resources: Resources::new(), | |
| pixmap: Pixmap::new(width, height), | |
| } | |
| } | |
| fn reset(&mut self) { | |
| self.ctx.reset(); | |
| } | |
| } | |
| fn render_layout( | |
| renderer: &mut GlyphBenchRenderer, | |
| layout: &Layout<Brush>, | |
| hint: bool, | |
| atlas_cache: bool, | |
| ) { | |
| for line in layout.lines() { | |
| for item in line.items() { | |
| if let PositionedLayoutItem::GlyphRun(glyph_run) = item { | |
| render_glyph_run(renderer, &glyph_run, hint, atlas_cache); | |
| } | |
| } | |
| } | |
| renderer | |
| .ctx | |
| .render(&mut renderer.pixmap, &mut renderer.resources); | |
| } | |
| fn render_glyph_run( | |
| renderer: &mut GlyphBenchRenderer, | |
| glyph_run: &GlyphRun<'_, Brush>, | |
| hint: bool, | |
| atlas_cache: bool, | |
| ) { | |
| let mut run_x = glyph_run.offset(); | |
| let run_y = glyph_run.baseline(); | |
| let glyphs = glyph_run.glyphs().map(move |glyph| { | |
| let glyph_x = run_x + glyph.x; | |
| let glyph_y = run_y - glyph.y; | |
| run_x += glyph.advance; | |
| Glyph { | |
| id: glyph.id, | |
| x: glyph_x, | |
| y: glyph_y, | |
| } | |
| }); | |
| let run = glyph_run.run(); | |
| renderer | |
| .ctx | |
| .glyph_run(&mut renderer.resources, run.font()) | |
| .font_size(run.font_size()) | |
| .hint(hint) | |
| .atlas_cache(atlas_cache) | |
| .fill_glyphs(glyphs) | |
| .unwrap(); | |
| } |