From 2b39361959c205c55f8297dbd0a364398088a7fc Mon Sep 17 00:00:00 2001 From: valadaptive Date: Tue, 3 Feb 2026 02:48:58 -0500 Subject: [PATCH 1/3] Add parley_draw benchmarks --- Cargo.lock | 2 + parley_bench/Cargo.toml | 2 + parley_bench/benches/main.rs | 15 ++- parley_bench/src/draw.rs | 204 +++++++++++++++++++++++++++++++++++ parley_bench/src/lib.rs | 1 + 5 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 parley_bench/src/draw.rs diff --git a/Cargo.lock b/Cargo.lock index ea7c42f6..f3ec262b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3324,8 +3324,10 @@ version = "0.0.0" dependencies = [ "parley", "parley_dev", + "parley_draw", "skrifa 0.40.0", "tango-bench", + "vello_cpu", ] [[package]] diff --git a/parley_bench/Cargo.toml b/parley_bench/Cargo.toml index e0aa9186..dbc8f6d3 100644 --- a/parley_bench/Cargo.toml +++ b/parley_bench/Cargo.toml @@ -12,8 +12,10 @@ publish = false [dependencies] parley = { workspace = true, default-features = true } parley_dev = { workspace = true } +parley_draw = { workspace = true, default-features = true, features = ["vello_cpu", "png"] } skrifa = { workspace = true } tango-bench = "0.6" +vello_cpu = { workspace = true, default-features = true } [[bench]] name = "main" diff --git a/parley_bench/benches/main.rs b/parley_bench/benches/main.rs index 586ede27..b2921bcc 100644 --- a/parley_bench/benches/main.rs +++ b/parley_bench/benches/main.rs @@ -8,5 +8,18 @@ use tango_bench::{tango_benchmarks, tango_main}; use parley_bench::benches::{defaults, styled}; use parley_bench::fontique_benches::system_fonts_init; -tango_benchmarks!(defaults(), styled(), system_fonts_init()); +use parley_bench::draw::{ + draw_no_underline_cold_cache, draw_no_underline_warm_cache, draw_with_underline_cold_cache, + draw_with_underline_warm_cache, +}; + +tango_benchmarks!( + defaults(), + styled(), + draw_no_underline_cold_cache(), + draw_no_underline_warm_cache(), + draw_with_underline_cold_cache(), + draw_with_underline_warm_cache(), + system_fonts_init() +); tango_main!(); diff --git a/parley_bench/src/draw.rs b/parley_bench/src/draw.rs new file mode 100644 index 00000000..55d307b7 --- /dev/null +++ b/parley_bench/src/draw.rs @@ -0,0 +1,204 @@ +// Copyright 2025 the Parley Authors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! # Draw Benchmarks +//! +//! Benchmarks for text rendering using parley_draw with vello_cpu. + +use crate::{ColorBrush, FONT_FAMILY_LIST, with_contexts}; +use parley::{ + Alignment, AlignmentOptions, FontFamily, Layout, PositionedLayoutItem, StyleProperty, +}; +use parley_draw::{Glyph, GlyphCaches, GlyphRunBuilder}; +use std::hint::black_box; +use tango_bench::{Benchmark, benchmark_fn}; +use vello_cpu::{RenderContext, kurbo}; + +const DISPLAY_SCALE: f32 = 1.0; +const QUANTIZE: bool = true; +const MAX_ADVANCE: f32 = 400.0 * DISPLAY_SCALE; +const PADDING: u16 = 20; + +/// Long sample text for benchmarking. +const SAMPLE_TEXT: &str = "Call me Ishmael. Some years ago—never mind how long precisely—having\ +little or no money in my purse, and nothing particular to interest me\ +on shore, I thought I would sail about a little and see the watery part\ +of the world. It is a way I have of driving off the spleen and\ +regulating the circulation. Whenever I find myself growing grim about\ +the mouth; whenever it is a damp, drizzly November in my soul; whenever\ +I find myself involuntarily pausing before coffin warehouses, and\ +bringing up the rear of every funeral I meet; and especially whenever\ +my hypos get such an upper hand of me, that it requires a strong moral\ +principle to prevent me from deliberately stepping into the street, and\ +methodically knocking people’s hats off—then, I account it high time to\ +get to sea as soon as I can. This is my substitute for pistol and ball.\ +With a philosophical flourish Cato throws himself upon his sword; I\ +quietly take to the ship. There is nothing surprising in this. If they\ +but knew it, almost all men in their degree, some time or other,\ +cherish very nearly the same feelings towards the ocean with me. + +There now is your insular city of the Manhattoes, belted round by\ +wharves as Indian isles by coral reefs—commerce surrounds it with her\ +surf. Right and left, the streets take you waterward. Its extreme\ +downtown is the battery, where that noble mole is washed by waves, and\ +cooled by breezes, which a few hours previous were out of sight of\ +land. Look at the crowds of water-gazers there."; + +/// Builds a layout with or without underlines. +fn build_layout(text: &str, underline: bool) -> Layout { + with_contexts(|font_cx, layout_cx| { + let mut builder = layout_cx.ranged_builder(font_cx, text, DISPLAY_SCALE, QUANTIZE); + builder.push_default(FontFamily::from(FONT_FAMILY_LIST)); + builder.push_default(StyleProperty::FontSize(16.0)); + + if underline { + builder.push(StyleProperty::Underline(true), 0..text.len()); + } + + let mut layout: Layout = builder.build(text); + layout.break_all_lines(Some(MAX_ADVANCE)); + layout.align( + Some(MAX_ADVANCE), + Alignment::Start, + AlignmentOptions::default(), + ); + layout + }) +} + +/// Renders a layout to a renderer, optionally with underlines. +fn render_layout( + layout: &Layout, + renderer: &mut RenderContext, + glyph_caches: &mut GlyphCaches, + with_underline: bool, +) { + for line in layout.lines() { + for item in line.items() { + match item { + PositionedLayoutItem::GlyphRun(glyph_run) => { + let run = glyph_run.run(); + GlyphRunBuilder::new(run.font().clone(), *renderer.transform(), renderer) + .font_size(run.font_size()) + .hint(true) + .normalized_coords(run.normalized_coords()) + .fill_glyphs( + glyph_run.positioned_glyphs().map(|glyph| Glyph { + id: glyph.id, + x: glyph.x, + y: glyph.y, + }), + glyph_caches, + ); + + if with_underline { + if let Some(decoration) = &glyph_run.style().underline { + let offset = + decoration.offset.unwrap_or(run.metrics().underline_offset); + let size = decoration.size.unwrap_or(run.metrics().underline_size); + + let x = glyph_run.offset(); + let x1 = x + glyph_run.advance(); + let baseline = glyph_run.baseline(); + + GlyphRunBuilder::new( + run.font().clone(), + *renderer.transform(), + renderer, + ) + .font_size(run.font_size()) + .normalized_coords(run.normalized_coords()) + .render_decoration( + glyph_run.positioned_glyphs().map(|glyph| Glyph { + id: glyph.id, + x: glyph.x, + y: glyph.y, + }), + x..=x1, + baseline, + offset, + size, + 1.0, // buffer around exclusions + glyph_caches, + ); + } + } + } + PositionedLayoutItem::InlineBox(_) => {} + } + } + } +} + +/// Creates the render context for drawing. +fn create_renderer(layout: &Layout) -> RenderContext { + let width = layout.width().ceil() as u16 + PADDING * 2; + let height = layout.height().ceil() as u16 + PADDING * 2; + + let mut renderer = RenderContext::new(width, height); + renderer.set_transform(kurbo::Affine::translate(kurbo::Vec2::new( + PADDING as f64, + PADDING as f64, + ))); + renderer +} + +/// Benchmark for drawing text without underlines, with a fresh cache each iteration. +pub fn draw_no_underline_cold_cache() -> Vec { + vec![benchmark_fn("Draw - No underline (cold cache)", |b| { + let layout = build_layout(SAMPLE_TEXT, false); + + b.iter(move || { + let layout = layout.clone(); + let mut renderer = create_renderer(&layout); + let mut glyph_caches = GlyphCaches::new(); + render_layout(&layout, &mut renderer, &mut glyph_caches, false); + black_box(&renderer); + }) + })] +} + +/// Benchmark for drawing text without underlines, reusing the cache. +pub fn draw_no_underline_warm_cache() -> Vec { + vec![benchmark_fn("Draw - No underline (warm cache)", |b| { + let layout = build_layout(SAMPLE_TEXT, false); + let mut glyph_caches = GlyphCaches::new(); + + b.iter(move || { + let mut renderer = create_renderer(&layout); + render_layout(&layout, &mut renderer, &mut glyph_caches, false); + glyph_caches.maintain(); + black_box(&renderer); + }) + })] +} + +/// Benchmark for drawing text with underlines, with a fresh cache each iteration. +pub fn draw_with_underline_cold_cache() -> Vec { + vec![benchmark_fn("Draw - With underline (cold cache)", |b| { + let layout = build_layout(SAMPLE_TEXT, true); + + b.iter(move || { + let layout = layout.clone(); + let mut renderer = create_renderer(&layout); + let mut glyph_caches = GlyphCaches::new(); + render_layout(&layout, &mut renderer, &mut glyph_caches, true); + black_box(&renderer); + }) + })] +} + +/// Benchmark for drawing text with underlines, reusing the cache. +pub fn draw_with_underline_warm_cache() -> Vec { + vec![benchmark_fn("Draw - With underline (warm cache)", |b| { + let layout = build_layout(SAMPLE_TEXT, true); + let mut glyph_caches = GlyphCaches::new(); + + b.iter(move || { + let mut renderer = create_renderer(&layout); + render_layout(&layout, &mut renderer, &mut glyph_caches, true); + glyph_caches.maintain(); + black_box(&renderer); + }) + })] +} diff --git a/parley_bench/src/lib.rs b/parley_bench/src/lib.rs index 405b9ba1..30236720 100644 --- a/parley_bench/src/lib.rs +++ b/parley_bench/src/lib.rs @@ -18,6 +18,7 @@ use parley::{ }; pub mod benches; +pub mod draw; pub mod fontique_benches; /// A color brush. From d998844ae2f51eabd42d35da7d10890feaf58816 Mon Sep 17 00:00:00 2001 From: valadaptive Date: Thu, 5 Feb 2026 09:02:55 -0500 Subject: [PATCH 2/3] Add -p (parallel) to bench comparison --- parley_bench/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parley_bench/README.md b/parley_bench/README.md index 29595900..69e00668 100644 --- a/parley_bench/README.md +++ b/parley_bench/README.md @@ -23,5 +23,5 @@ cargo export target/benchmarks -- bench --bench=main # Apply changes to Parley # Compare changes with baseline -cargo bench -q --bench=main -- compare target/benchmarks/main +cargo bench -q --bench=main -- compare target/benchmarks/main -p ``` From ea7974804f96078204299d4be7e5d772058b40a2 Mon Sep 17 00:00:00 2001 From: valadaptive Date: Thu, 5 Feb 2026 10:05:11 -0500 Subject: [PATCH 3/3] appease the petulant paperclip --- parley_bench/src/draw.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/parley_bench/src/draw.rs b/parley_bench/src/draw.rs index 55d307b7..df4107e8 100644 --- a/parley_bench/src/draw.rs +++ b/parley_bench/src/draw.rs @@ -3,7 +3,7 @@ //! # Draw Benchmarks //! -//! Benchmarks for text rendering using parley_draw with vello_cpu. +//! Benchmarks for text rendering using `parley_draw` with `vello_cpu`. use crate::{ColorBrush, FONT_FAMILY_LIST, with_contexts}; use parley::{ @@ -132,7 +132,15 @@ fn render_layout( /// Creates the render context for drawing. fn create_renderer(layout: &Layout) -> RenderContext { + #[expect( + clippy::cast_possible_truncation, + reason = "the layout's not *that* big" + )] let width = layout.width().ceil() as u16 + PADDING * 2; + #[expect( + clippy::cast_possible_truncation, + reason = "the layout's not *that* big" + )] let height = layout.height().ceil() as u16 + PADDING * 2; let mut renderer = RenderContext::new(width, height);