Skip to main content

glifo/atlas/
commands.rs

1// Copyright 2026 the Vello Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Deferred atlas rendering commands.
5//!
6//! During glyph encoding, outline and COLR glyph draw commands are recorded
7//! into an [`AtlasCommandRecorder`] rather than being executed immediately.
8//! At render time the application drains the pending recorders (grouped by
9//! atlas page) and replays them into a single glyph renderer that is reset
10//! between pages.
11//!
12//! This approach:
13//! - Supports multiple atlas pages (not just page 0)
14//! - Keeps a single glyph renderer (same atlas page size)
15//! - Mirrors the `drain_pending_uploads` pattern used for bitmap glyphs
16
17use alloc::sync::Arc;
18use alloc::vec::Vec;
19
20use crate::DrawSink;
21use crate::color::{AlphaColor, Srgb};
22use crate::kurbo::{Affine, BezPath, Rect};
23use crate::peniko::{BlendMode, Gradient};
24use vello_common::paint::PaintType;
25
26/// Paint type for atlas commands.
27#[derive(Clone, Debug)]
28pub enum AtlasPaint {
29    /// A solid colour (used for outlines and COLR solid fills).
30    Solid(AlphaColor<Srgb>),
31    /// A gradient (used for COLR gradient fills).
32    Gradient(Gradient),
33}
34
35impl From<AlphaColor<Srgb>> for AtlasPaint {
36    fn from(c: AlphaColor<Srgb>) -> Self {
37        Self::Solid(c)
38    }
39}
40
41impl From<Gradient> for AtlasPaint {
42    fn from(g: Gradient) -> Self {
43        Self::Gradient(g)
44    }
45}
46
47impl From<AtlasPaint> for PaintType {
48    fn from(paint: AtlasPaint) -> Self {
49        match paint {
50            AtlasPaint::Solid(color) => Self::Solid(color),
51            AtlasPaint::Gradient(gradient) => Self::Gradient(gradient),
52        }
53    }
54}
55
56/// A single draw command recorded for deferred atlas rendering.
57///
58/// The variants correspond 1:1 to the methods on [`DrawSink`].
59///
60/// [`DrawSink`]: crate::interface::DrawSink
61#[derive(Clone, Debug)]
62pub enum AtlasCommand {
63    /// Set the current transform.
64    SetTransform(Affine),
65    /// Set the current paint (solid colour or gradient).
66    SetPaint(AtlasPaint),
67    /// Set the paint transform.
68    SetPaintTransform(Affine),
69    /// Fill a path with the current paint and transform.
70    FillPath(Arc<BezPath>),
71    /// Fill a rectangle with the current paint and transform.
72    FillRect(Rect),
73    /// Push a clip layer defined by a path.
74    PushClipLayer(Arc<BezPath>),
75    /// Push a clip path.
76    PushClipPath(Arc<BezPath>),
77    /// Push a blend/compositing layer.
78    PushBlendLayer(BlendMode),
79    /// Pop the most recent clip or blend layer.
80    PopLayer,
81    /// Pop the most recent clip path.
82    PopClipPath,
83}
84
85/// Records atlas draw commands for a single atlas page.
86///
87/// The recorder exposes the same method API as the actual renderers
88/// (`RenderContext`, `Scene`). It also implements [`DrawSink`] so
89/// that the COLR glyph painter can write into it directly.
90///
91/// [`DrawSink`]: crate::DrawSink
92pub struct AtlasCommandRecorder {
93    /// Which atlas page these commands target.
94    pub page_index: u32,
95    /// The recorded commands.
96    pub commands: Vec<AtlasCommand>,
97    /// Width of the glyph renderer / atlas page (pixels).
98    pub(crate) width: u16,
99    /// Height of the glyph renderer / atlas page (pixels).
100    pub(crate) height: u16,
101}
102
103impl AtlasCommandRecorder {
104    /// Create a new recorder for the given atlas page.
105    ///
106    /// `width` and `height` must match the glyph renderer dimensions
107    /// (i.e. the atlas page size) so that COLR `fill_solid` / `fill_gradient`
108    /// produce correctly-sized fill rects.
109    pub fn new(page_index: u32, width: u16, height: u16) -> Self {
110        Self {
111            page_index,
112            commands: Vec::new(),
113            width,
114            height,
115        }
116    }
117}
118
119impl DrawSink for AtlasCommandRecorder {
120    #[inline]
121    fn set_transform(&mut self, t: Affine) {
122        self.commands.push(AtlasCommand::SetTransform(t));
123    }
124
125    #[inline]
126    fn set_paint(&mut self, paint: AtlasPaint) {
127        self.commands.push(AtlasCommand::SetPaint(paint));
128    }
129
130    #[inline]
131    fn set_paint_transform(&mut self, t: Affine) {
132        self.commands.push(AtlasCommand::SetPaintTransform(t));
133    }
134
135    #[inline]
136    fn fill_path(&mut self, path: &BezPath) {
137        self.commands
138            .push(AtlasCommand::FillPath(Arc::new(path.clone())));
139    }
140
141    #[inline]
142    fn fill_rect(&mut self, rect: &Rect) {
143        self.commands.push(AtlasCommand::FillRect(*rect));
144    }
145
146    #[inline]
147    fn push_clip_layer(&mut self, clip: &BezPath) {
148        self.commands
149            .push(AtlasCommand::PushClipLayer(Arc::new(clip.clone())));
150    }
151
152    #[inline]
153    fn push_clip_path(&mut self, clip: &BezPath) {
154        self.commands
155            .push(AtlasCommand::PushClipPath(Arc::new(clip.clone())));
156    }
157
158    #[inline]
159    fn push_blend_layer(&mut self, blend_mode: BlendMode) {
160        self.commands.push(AtlasCommand::PushBlendLayer(blend_mode));
161    }
162
163    #[inline]
164    fn pop_layer(&mut self) {
165        self.commands.push(AtlasCommand::PopLayer);
166    }
167
168    #[inline]
169    fn pop_clip_path(&mut self) {
170        self.commands.push(AtlasCommand::PopClipPath);
171    }
172
173    #[inline]
174    fn width(&self) -> u16 {
175        self.width
176    }
177
178    #[inline]
179    fn height(&self) -> u16 {
180        self.height
181    }
182}
183
184impl core::fmt::Debug for AtlasCommandRecorder {
185    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
186        f.debug_struct("AtlasCommandRecorder")
187            .field("page_index", &self.page_index)
188            .field("commands", &self.commands.len())
189            .field("width", &self.width)
190            .field("height", &self.height)
191            .finish()
192    }
193}