Skip to main content

vello_cpu/
text.rs

1// Copyright 2025 the Vello Authors and the Parley Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Vello CPU glyph rendering backend.
5//!
6//! Provides [`GlyphAtlas`] (atlas backed by per-page [`Pixmap`]s) and the
7//! [`GlyphRenderer`](glifo::GlyphRenderer) implementation for [`RenderContext`] that
8//! rasterises glyphs into CPU-accessible pixel buffers.
9//!
10//! The key difference from the hybrid backend is that atlas pages are owned as
11//! [`Arc<Pixmap>`]s here, so the CPU renderer can read pixels directly without
12//! any GPU upload step.
13
14use crate::render::{ATLAS_IMAGE_ID_BASE, DEFAULT_GLYPH_ATLAS_SIZE};
15use crate::{
16    CompositeMode, Image, ImageSource, PaintType, Pixmap, RasterizerSettings, RenderContext,
17    RenderMode, RenderSettings, Resources, color, kurbo, peniko,
18};
19use alloc::boxed::Box;
20use alloc::sync::Arc;
21use alloc::vec::Vec;
22use color::palette::css::BLACK;
23use core::fmt::Debug;
24use core::ops::RangeInclusive;
25use glifo::atlas::{
26    AtlasConfig, AtlasSlot, GlyphAtlas, GlyphCacheConfig, ImageCache, PendingClearRect,
27};
28use glifo::{AtlasCacher, DrawSink, GlyphRunBackend};
29use glifo::{Glyph, renderer};
30use kurbo::{Affine, BezPath, Rect};
31use peniko::BlendMode;
32use peniko::color::{AlphaColor, Srgb};
33use vello_common::fearless_simd::Level;
34use vello_common::paint::ImageId;
35
36fn atlas_page_image_id(page_index: u32) -> ImageId {
37    ImageId::new(ATLAS_IMAGE_ID_BASE + page_index)
38}
39
40#[derive(Debug)]
41pub(crate) struct GlyphAtlasResources {
42    pub(crate) glyph_atlas: GlyphAtlas,
43    pub(crate) image_cache: ImageCache,
44    pub(crate) glyph_renderer: Box<RenderContext>,
45    /// One `Pixmap` per atlas page, grown on demand.
46    // It's a bit annoying to have this in an `Arc`, but it needs to be this way. During fine
47    // rasterization, we need to be able to easily clone the atlas page so that it can be shared
48    // across multiple threads. However, we also need to be able to mutate the pixmap to
49    // sync new glyphs. The way this is achieved is by calling `Arc::get_mut` when syncing
50    // (at this point, the pixmap isn't shared anywhere else). Before fine rasterization, we
51    // then share it with the image registry such that it can easily be fetched and cloned during
52    // fine rasterization. After that, we remove it from the image registry, such that it's uniquely
53    // owned again and can be mutated in the next frame.
54    pub(crate) pixmaps: Vec<Arc<Pixmap>>,
55    /// Width of each atlas page in pixels.
56    page_width: u16,
57    /// Height of each atlas page in pixels.
58    page_height: u16,
59}
60
61impl GlyphAtlasResources {
62    pub(crate) fn with_config(
63        page_width: u16,
64        page_height: u16,
65        level: Level,
66        eviction_config: GlyphCacheConfig,
67    ) -> Self {
68        Self {
69            glyph_atlas: GlyphAtlas::with_config(eviction_config),
70            image_cache: ImageCache::new_with_config(AtlasConfig::default()),
71            glyph_renderer: Box::new(RenderContext::new_with(
72                page_width,
73                page_height,
74                RenderSettings {
75                    level,
76                    num_threads: 0,
77                },
78            )),
79            pixmaps: Vec::new(),
80            page_width,
81            page_height,
82        }
83    }
84
85    pub(crate) fn maintain(&mut self) {
86        self.glyph_atlas.maintain(&mut self.image_cache);
87    }
88}
89
90/// Ensure a pixmap exists for the given page, creating it if needed.
91fn ensure_page(
92    pixmaps: &mut Vec<Arc<Pixmap>>,
93    page_width: u16,
94    page_height: u16,
95    page_index: usize,
96) {
97    while pixmaps.len() <= page_index {
98        pixmaps.push(Arc::new(Pixmap::new(page_width, page_height)));
99    }
100}
101
102impl Resources {
103    pub(crate) fn prepare_glyph_cache(&mut self, render_mode: RenderMode) {
104        if self.glyph_resources.is_some() {
105            self.sync_glyph_cache(render_mode);
106        }
107    }
108
109    pub(crate) fn maintain_glyph_cache(&mut self) {
110        self.glyph_prep_cache.maintain();
111
112        if let Some(glyph_resources) = self.glyph_resources.as_mut() {
113            glyph_resources.maintain();
114            let page_count = glyph_resources.pixmaps.len();
115            for page_index in 0..page_count {
116                self.image_registry.destroy_atlas_page(page_index as u32);
117            }
118            self.clear_evicted_glyph_atlas_regions();
119        }
120    }
121
122    fn ensure_glyph_resources(&mut self, level: Level) {
123        if self.glyph_resources.is_none() {
124            self.glyph_resources = Some(GlyphAtlasResources::with_config(
125                DEFAULT_GLYPH_ATLAS_SIZE,
126                DEFAULT_GLYPH_ATLAS_SIZE,
127                level,
128                GlyphCacheConfig::default(),
129            ));
130        }
131    }
132
133    /// Upload all pending bitmaps, rasterize pending outline/COLR glyphs, etc.
134    fn sync_glyph_cache(&mut self, render_mode: RenderMode) {
135        let glyph_resources = self
136            .glyph_resources
137            .as_mut()
138            .expect("glyph atlas resources must exist before syncing");
139
140        // Upload all pending bitmap glyphs to the image atlas.
141        for upload in glyph_resources.glyph_atlas.drain_pending_uploads() {
142            let page_index = upload.atlas_slot.page_index as usize;
143            ensure_page(
144                &mut glyph_resources.pixmaps,
145                glyph_resources.page_width,
146                glyph_resources.page_height,
147                page_index,
148            );
149            let pixmap = Arc::get_mut(&mut glyph_resources.pixmaps[page_index])
150                .expect("atlas pixmap should be uniquely owned during bitmap upload");
151            copy_pixmap_to_atlas(
152                &upload.pixmap,
153                pixmap,
154                upload.atlas_slot.x,
155                upload.atlas_slot.y,
156                upload.atlas_slot.width,
157                upload.atlas_slot.height,
158            );
159        }
160
161        // Draw all new COLR/outline glyphs into the render context, and then composite them into the
162        // existing atlas page.
163        let glyph_renderer = glyph_resources.glyph_renderer.as_mut();
164        glyph_resources
165            .glyph_atlas
166            .replay_pending_atlas_commands(|recorder| {
167                let page_index = recorder.page_index as usize;
168                ensure_page(
169                    &mut glyph_resources.pixmaps,
170                    glyph_resources.page_width,
171                    glyph_resources.page_height,
172                    page_index,
173                );
174                let page = Arc::get_mut(&mut glyph_resources.pixmaps[page_index])
175                    .expect("atlas page pixmap must be uniquely owned during replay");
176
177                glyph_renderer.reset();
178                renderer::replay_atlas_commands(&mut recorder.commands, glyph_renderer);
179                glyph_renderer.flush();
180                glyph_renderer.render_with(
181                    page,
182                    &mut Self::default(),
183                    RasterizerSettings {
184                        render_mode,
185                        composite_mode: CompositeMode::SrcOver,
186                        ..Default::default()
187                    },
188                );
189            });
190
191        for (page_index, pixmap) in glyph_resources.pixmaps.iter().enumerate() {
192            self.image_registry
193                .register_atlas_page(page_index as u32, Arc::clone(pixmap));
194        }
195    }
196
197    fn clear_evicted_glyph_atlas_regions(&mut self) {
198        let glyph_resources = self
199            .glyph_resources
200            .as_mut()
201            .expect("glyph atlas resources must exist before clearing");
202        for clear in glyph_resources.glyph_atlas.drain_pending_clear_rects() {
203            let pixmap = Arc::get_mut(&mut glyph_resources.pixmaps[clear.page_index as usize])
204                .expect("atlas pixmap should be uniquely owned during region clearing");
205            clear_pixmap_region(pixmap, clear);
206        }
207    }
208}
209
210#[doc(hidden)]
211#[derive(Debug)]
212pub struct CpuGlyphRunBackend<'a> {
213    pub ctx: &'a mut RenderContext,
214    pub resources: &'a mut Resources,
215    pub atlas_cache_enabled: bool,
216}
217
218impl<'a> CpuGlyphRunBackend<'a> {
219    fn render_glyphs<Glyphs>(
220        self,
221        run: glifo::GlyphRun<'a>,
222        glyphs: Glyphs,
223        render: impl FnOnce(&mut glifo::GlyphRunRenderer<'a, 'a, Glyphs>, &mut RenderContext),
224    ) where
225        Glyphs: Iterator<Item = Glyph> + Clone,
226    {
227        let atlas_cacher = if self.atlas_cache_enabled {
228            self.resources
229                .ensure_glyph_resources(self.ctx.render_settings.level);
230            let glyph_resources = self
231                .resources
232                .glyph_resources
233                .as_mut()
234                .expect("glyph atlas resources must exist after initialization");
235            AtlasCacher::Enabled(
236                &mut glyph_resources.glyph_atlas,
237                &mut glyph_resources.image_cache,
238            )
239        } else {
240            AtlasCacher::Disabled
241        };
242
243        let mut glyph_run = run.build(
244            glyphs,
245            self.resources.glyph_prep_cache.as_mut(),
246            atlas_cacher,
247        );
248        render(&mut glyph_run, self.ctx);
249    }
250}
251
252impl<'a> GlyphRunBackend<'a> for CpuGlyphRunBackend<'a> {
253    fn atlas_cache(mut self, enabled: bool) -> Self {
254        self.atlas_cache_enabled = enabled;
255        self
256    }
257
258    fn fill_glyphs<Glyphs>(self, run: glifo::GlyphRun<'a>, glyphs: Glyphs)
259    where
260        Glyphs: Iterator<Item = Glyph> + Clone,
261    {
262        self.render_glyphs(run, glyphs, |glyph_run, ctx| glyph_run.fill_glyphs(ctx));
263    }
264
265    fn stroke_glyphs<Glyphs>(self, run: glifo::GlyphRun<'a>, glyphs: Glyphs)
266    where
267        Glyphs: Iterator<Item = Glyph> + Clone,
268    {
269        self.render_glyphs(run, glyphs, |glyph_run, ctx| {
270            let stroke_adjustment = glyph_run.stroke_adjustment();
271            let original_width = ctx.stroke().width;
272            ctx.stroke_mut().width *= stroke_adjustment;
273            glyph_run.stroke_glyphs(ctx);
274            ctx.stroke_mut().width = original_width;
275        });
276    }
277
278    fn render_decoration<Glyphs>(
279        self,
280        run: glifo::GlyphRun<'a>,
281        glyphs: Glyphs,
282        x_range: RangeInclusive<f32>,
283        baseline_y: f32,
284        offset: f32,
285        size: f32,
286        buffer: f32,
287    ) where
288        Glyphs: Iterator<Item = Glyph> + Clone,
289    {
290        self.render_glyphs(run, glyphs, |glyph_run, ctx| {
291            glyph_run.render_decoration(x_range, baseline_y, offset, size, buffer, ctx);
292        });
293    }
294}
295
296/// A glyph run builder.
297pub type GlyphRunBuilder<'a> = glifo::GlyphRunBuilder<'a, CpuGlyphRunBackend<'a>>;
298
299/// Zero out a rectangular region in the atlas pixmap.
300///
301/// Necessary because atlas rendering uses `SrcOver` blending,
302/// so stale pixels from evicted glyphs would bleed through if not cleared.
303fn clear_pixmap_region(dst: &mut Pixmap, rect: PendingClearRect) {
304    let dst_stride = dst.width() as usize;
305    let dst_data = dst.data_as_u8_slice_mut();
306    let clear_width = rect.width as usize;
307    let clear_height = rect.height as usize;
308
309    for y in 0..clear_height {
310        let row_start = ((rect.y as usize + y) * dst_stride + rect.x as usize) * 4;
311        let row_end = row_start + clear_width * 4;
312        dst_data[row_start..row_end].fill(0);
313    }
314}
315
316/// Copy bitmap glyph pixels into a rectangular region of an atlas page.
317fn copy_pixmap_to_atlas(
318    src: &Pixmap,
319    dst: &mut Pixmap,
320    dst_x: u16,
321    dst_y: u16,
322    width: u16,
323    height: u16,
324) {
325    let copy_width = width as usize;
326    let copy_height = height as usize;
327    let src_stride = src.width() as usize;
328    let dst_stride = dst.width() as usize;
329
330    let src_data = src.data_as_u8_slice();
331    let dst_data = dst.data_as_u8_slice_mut();
332
333    for y in 0..copy_height {
334        let src_row_start = y * src_stride * 4;
335        let src_row_end = src_row_start + copy_width * 4;
336        let dst_row_start = ((dst_y as usize + y) * dst_stride + dst_x as usize) * 4;
337        let dst_row_end = dst_row_start + copy_width * 4;
338
339        dst_data[dst_row_start..dst_row_end].copy_from_slice(&src_data[src_row_start..src_row_end]);
340    }
341}
342
343impl DrawSink for RenderContext {
344    #[inline]
345    fn set_transform(&mut self, t: Affine) {
346        Self::set_transform(self, t);
347    }
348
349    #[inline]
350    fn set_paint(&mut self, paint: glifo::AtlasPaint) {
351        Self::set_paint(self, paint);
352    }
353
354    #[inline]
355    fn set_paint_transform(&mut self, t: Affine) {
356        Self::set_paint_transform(self, t);
357    }
358
359    #[inline]
360    fn fill_path(&mut self, path: &BezPath) {
361        Self::fill_path(self, path);
362    }
363
364    #[inline]
365    fn fill_rect(&mut self, rect: &Rect) {
366        Self::fill_rect(self, rect);
367    }
368
369    #[inline]
370    fn push_clip_layer(&mut self, clip: &BezPath) {
371        Self::push_clip_layer(self, clip);
372    }
373
374    #[inline]
375    fn push_clip_path(&mut self, clip: &BezPath) {
376        Self::push_clip_path(self, clip);
377    }
378
379    #[inline]
380    fn push_blend_layer(&mut self, blend_mode: BlendMode) {
381        Self::push_blend_layer(self, blend_mode);
382    }
383
384    #[inline]
385    fn pop_layer(&mut self) {
386        Self::pop_layer(self);
387    }
388
389    #[inline]
390    fn pop_clip_path(&mut self) {
391        Self::pop_clip_path(self);
392    }
393
394    #[inline]
395    fn width(&self) -> u16 {
396        Self::width(self)
397    }
398
399    #[inline]
400    fn height(&self) -> u16 {
401        Self::height(self)
402    }
403}
404
405impl glifo::GlyphRenderer for RenderContext {
406    type SavedState = vello_common::render_state::RenderState;
407
408    #[inline]
409    fn save_state(&mut self) -> Self::SavedState {
410        self.save_current_state()
411    }
412
413    #[inline]
414    fn restore_state(&mut self, state: Self::SavedState) {
415        Self::restore_state(self, state);
416    }
417
418    #[inline]
419    fn stroke_path(&mut self, path: &BezPath) {
420        Self::stroke_path(self, path);
421    }
422
423    #[inline]
424    fn set_paint_image(&mut self, image: Image) {
425        self.set_paint(image);
426    }
427
428    #[inline]
429    fn set_tint(&mut self, tint: Option<vello_common::paint::Tint>) {
430        Self::set_tint(self, tint);
431    }
432
433    #[inline]
434    fn get_context_color(&self) -> AlphaColor<Srgb> {
435        let paint = self.paint().clone();
436        match paint {
437            PaintType::Solid(s) => s,
438            _ => BLACK,
439        }
440    }
441
442    #[inline]
443    fn current_paint(&self) -> &PaintType {
444        self.paint()
445    }
446
447    #[inline]
448    fn atlas_image_source(&self, atlas_slot: &AtlasSlot) -> ImageSource {
449        ImageSource::opaque_id(atlas_page_image_id(atlas_slot.page_index))
450    }
451
452    #[inline]
453    fn atlas_paint_transform(&self, atlas_slot: &AtlasSlot) -> Affine {
454        Affine::translate((-(atlas_slot.x as f64), -(atlas_slot.y as f64)))
455    }
456}
457
458/// Debug utilities for visualizing glyph bounds during rasterization.
459#[cfg(debug_assertions)]
460#[allow(
461    dead_code,
462    unreachable_pub,
463    clippy::trivially_copy_pass_by_ref,
464    reason = "debug-only utilities called manually during development"
465)]
466mod debug {
467    use core::sync::atomic::{AtomicUsize, Ordering};
468
469    use crate::RenderContext;
470    use crate::kurbo::{Affine, Rect};
471    use crate::peniko;
472    use glifo::atlas::RasterMetrics;
473
474    static COLOR_INDEX: AtomicUsize = AtomicUsize::new(0);
475
476    /// Twelve semi-transparent rotating colours for distinguishing adjacent
477    /// glyph bounds. Each call to [`fill_glyph_bounds`] advances the index.
478    const COLORS: [peniko::Color; 12] = [
479        peniko::Color::new([1.0, 0.0, 0.0, 0.5]), // Red
480        peniko::Color::new([0.0, 1.0, 0.0, 0.5]), // Green
481        peniko::Color::new([0.0, 0.0, 1.0, 0.5]), // Blue
482        peniko::Color::new([1.0, 1.0, 0.0, 0.5]), // Yellow
483        peniko::Color::new([1.0, 0.0, 1.0, 0.5]), // Magenta
484        peniko::Color::new([0.0, 1.0, 1.0, 0.5]), // Cyan
485        peniko::Color::new([1.0, 0.5, 0.0, 0.5]), // Orange
486        peniko::Color::new([0.5, 0.0, 1.0, 0.5]), // Purple
487        peniko::Color::new([0.0, 1.0, 0.5, 0.5]), // Mint
488        peniko::Color::new([1.0, 0.5, 0.5, 0.5]), // Pink
489        peniko::Color::new([0.5, 1.0, 0.5, 0.5]), // Light green
490        peniko::Color::new([0.5, 0.5, 1.0, 0.5]), // Light blue
491    ];
492
493    /// Fill the glyph's pixel bounds with the next rotating debug colour.
494    /// Call before rendering the actual glyph content to visualise the extent.
495    pub fn fill_glyph_bounds(renderer: &mut RenderContext, raster_metrics: &RasterMetrics) {
496        let idx = COLOR_INDEX.fetch_add(1, Ordering::Relaxed) % COLORS.len();
497        renderer.set_transform(Affine::IDENTITY);
498        renderer.set_paint(COLORS[idx]);
499        renderer.fill_rect(&Rect::new(
500            0.0,
501            0.0,
502            raster_metrics.width as f64,
503            raster_metrics.height as f64,
504        ));
505    }
506}