Skip to main content

vello_common/
recording.rs

1// Copyright 2025 the Vello Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Recording API for caching sparse strips
5
6use crate::filter_effects::Filter;
7#[cfg(feature = "text")]
8use crate::glyph::{GlyphRenderer, GlyphRunBuilder, GlyphType, PreparedGlyph};
9use crate::kurbo::{Affine, BezPath, Rect, Stroke};
10use crate::mask::Mask;
11use crate::paint::PaintType;
12#[cfg(feature = "text")]
13use crate::peniko::FontData;
14use crate::peniko::{BlendMode, Fill};
15use crate::strip::Strip;
16use crate::strip_generator::StripStorage;
17use alloc::vec::Vec;
18
19/// Cached sparse strip data.
20#[derive(Debug, Default)]
21pub struct CachedStrips {
22    /// The strip storage.
23    strip_storage: StripStorage,
24    /// Strip start indices for each geometry command.
25    strip_start_indices: Vec<usize>,
26}
27
28impl CachedStrips {
29    /// Create a new cached strips instance.
30    pub fn new(strip_storage: StripStorage, strip_start_indices: Vec<usize>) -> Self {
31        Self {
32            strip_storage,
33            strip_start_indices,
34        }
35    }
36
37    /// Clear the contents.
38    pub fn clear(&mut self) {
39        self.strip_storage.clear();
40        self.strip_start_indices.clear();
41    }
42
43    /// Check if this cached strips is empty.
44    pub fn is_empty(&self) -> bool {
45        self.strip_storage.is_empty() && self.strip_start_indices.is_empty()
46    }
47
48    /// Get the number of strips.
49    pub fn strip_count(&self) -> usize {
50        self.strip_storage.strips.len()
51    }
52
53    /// Get the number of alpha bytes.
54    pub fn alpha_count(&self) -> usize {
55        self.strip_storage.alphas.len()
56    }
57
58    /// Get strips as slice.
59    pub fn strips(&self) -> &[Strip] {
60        &self.strip_storage.strips
61    }
62
63    /// Get alphas as slice
64    pub fn alphas(&self) -> &[u8] {
65        &self.strip_storage.alphas
66    }
67
68    /// Get strip start indices.
69    pub fn strip_start_indices(&self) -> &[usize] {
70        &self.strip_start_indices
71    }
72
73    /// Takes ownership of all buffers.
74    pub fn take(&mut self) -> (StripStorage, Vec<usize>) {
75        let strip_storage = core::mem::take(&mut self.strip_storage);
76        let strip_start_indices = core::mem::take(&mut self.strip_start_indices);
77        (strip_storage, strip_start_indices)
78    }
79}
80
81/// A recording of rendering commands that can cache generated strips.
82#[derive(Debug)]
83pub struct Recording {
84    /// Recorded commands.
85    commands: Vec<RenderCommand>,
86    /// Cached sparse strips.
87    cached_strips: CachedStrips,
88    /// Track the transform of the underlying rasterization context.
89    transform: Affine,
90}
91
92/// Command for pushing a new layer.
93#[derive(Debug, Clone)]
94pub struct PushLayerCommand {
95    /// Clip path.
96    pub clip_path: Option<BezPath>,
97    /// Blend mode.
98    pub blend_mode: Option<BlendMode>,
99    /// Opacity.
100    pub opacity: Option<f32>,
101    /// Mask.
102    pub mask: Option<Mask>,
103    /// Filter.
104    pub filter: Option<Filter>,
105}
106
107/// Individual rendering commands that can be recorded.
108#[derive(Debug)]
109pub enum RenderCommand {
110    /// Fill a path.
111    FillPath(BezPath),
112    /// Stroke a path.
113    StrokePath(BezPath),
114    /// Fill a rectangle.
115    FillRect(Rect),
116    /// Stroke a rectangle.
117    StrokeRect(Rect),
118    /// Set the current transform.
119    SetTransform(Affine),
120    /// Set the fill rule.
121    SetFillRule(Fill),
122    /// Set the stroke parameters.
123    SetStroke(Stroke),
124    /// Push a new layer with optional clipping and effects.
125    PushLayer(PushLayerCommand),
126    /// Pop the current layer.
127    PopLayer,
128    /// Set the current paint.
129    SetPaint(PaintType),
130    /// Set the paint transform.
131    SetPaintTransform(Affine),
132    /// Reset the paint transform.
133    ResetPaintTransform,
134    /// Set the current filter effect.
135    SetFilterEffect(Filter),
136    /// Reset the current filter effect.
137    ResetFilterEffect,
138    /// Render a fill outline glyph.
139    #[cfg(feature = "text")]
140    FillOutlineGlyph((BezPath, Affine)),
141    /// Render a stroke outline glyph.
142    #[cfg(feature = "text")]
143    StrokeOutlineGlyph((BezPath, Affine)),
144}
145
146impl Recording {
147    /// Create a new empty recording.
148    pub fn new() -> Self {
149        Self {
150            commands: Vec::new(),
151            cached_strips: CachedStrips::default(),
152            transform: Affine::IDENTITY,
153        }
154    }
155
156    /// Set the transform.
157    pub(crate) fn set_transform(&mut self, transform: Affine) {
158        self.transform = transform;
159    }
160
161    /// Get commands as a slice.
162    pub fn commands(&self) -> &[RenderCommand] {
163        &self.commands
164    }
165
166    /// Get the number of commands.
167    pub fn command_count(&self) -> usize {
168        self.commands.len()
169    }
170
171    /// Check if recording has cached strips.
172    pub fn has_cached_strips(&self) -> bool {
173        !self.cached_strips.is_empty()
174    }
175
176    /// Get the number of cached strips.
177    pub fn strip_count(&self) -> usize {
178        self.cached_strips.strip_count()
179    }
180
181    /// Get the number of cached alpha bytes.
182    pub fn alpha_count(&self) -> usize {
183        self.cached_strips.alpha_count()
184    }
185
186    /// Get cached strips.
187    pub fn get_cached_strips(&self) -> (&[Strip], &[u8]) {
188        (self.cached_strips.strips(), self.cached_strips.alphas())
189    }
190
191    /// Takes cached strip buffers.
192    pub fn take_cached_strips(&mut self) -> (StripStorage, Vec<usize>) {
193        self.cached_strips.take()
194    }
195
196    /// Get strip start indices.
197    pub fn get_strip_start_indices(&self) -> &[usize] {
198        self.cached_strips.strip_start_indices()
199    }
200
201    /// Clear the recording contents.
202    pub fn clear(&mut self) {
203        self.commands.clear();
204        self.cached_strips.clear();
205        self.transform = Affine::IDENTITY;
206    }
207
208    /// Add a command to the recording.
209    pub(crate) fn add_command(&mut self, command: RenderCommand) {
210        self.commands.push(command);
211    }
212
213    /// Set cached strips.
214    pub fn set_cached_strips(
215        &mut self,
216        strip_storage: StripStorage,
217        strip_start_indices: Vec<usize>,
218    ) {
219        self.cached_strips = CachedStrips::new(strip_storage, strip_start_indices);
220    }
221}
222
223impl Default for Recording {
224    fn default() -> Self {
225        Self::new()
226    }
227}
228
229/// Trait for rendering contexts that support recording and replaying operations.
230///
231/// # State Modification During Replay
232///
233/// **Important:** When replaying recordings using methods like `execute_recording()`,
234/// the renderer's state (transform, paint, fill rule, stroke settings, etc.) will be
235/// modified to match the state changes captured in the recording. The renderer will
236/// be left in the final state after all commands have been executed.
237///
238/// # Multithreading Limitation
239///
240/// **Note:** Recording and replay functionality is not currently implemented for
241/// `vello_cpu` when multithreading is enabled. This limitation only affects
242/// `vello_cpu` in multithreaded mode; single-threaded `vello_cpu` and `vello_hybrid`
243/// work correctly with recordings.
244///
245/// # Usage Pattern
246///
247/// A consumer needs to do the following to render:
248/// ```ignore
249/// let mut recording = Recording::new();
250/// scene.record(&mut recording, |ctx| { ... });
251/// scene.prepare_recording(&mut recording);
252/// scene.execute_recording(&recording);
253/// ```
254///
255/// Or to prepare for later rendering:
256/// ```ignore
257/// let mut recording = Recording::new();
258/// scene.record(&mut recording, |ctx| { ... });
259/// scene.prepare_recording(&mut recording);
260///
261/// // sometime later
262/// scene.execute_recording(&recording);
263/// ```
264pub trait Recordable {
265    /// Record rendering commands into a recording.
266    ///
267    /// This method allows you to capture a sequence of rendering operations
268    /// in a `Recording` that can be cached and replayed later.
269    ///
270    /// # Example
271    /// ```ignore
272    /// let mut recording = Recording::new();
273    /// scene.record(&mut recording, |ctx| {
274    ///     ctx.fill_rect(&Rect::new(0.0, 0.0, 100.0, 100.0));
275    ///     ctx.set_paint(Color::RED);
276    ///     ctx.stroke_path(&some_path);
277    /// });
278    /// ```
279    fn record<F>(&mut self, recording: &mut Recording, f: F)
280    where
281        F: FnOnce(&mut Recorder<'_>);
282
283    /// Generate sparse strips for a recording.
284    ///
285    /// This method processes the recorded commands and generates cached sparse strips
286    /// without executing the rendering. This allows you to pre-generate strips for
287    /// better control over when the expensive computation happens.
288    ///
289    /// # Example
290    /// ```ignore
291    /// let mut recording = Recording::new();
292    /// scene.record(&mut recording, |ctx| {
293    ///     ctx.fill_rect(&Rect::new(0.0, 0.0, 100.0, 100.0));
294    /// });
295    ///
296    /// // Generate strips explicitly
297    /// scene.prepare_recording(&mut recording);
298    /// ```
299    fn prepare_recording(&mut self, recording: &mut Recording);
300
301    /// Execute a recording directly without preparation.
302    ///
303    /// This method executes the rendering commands from a recording, using any
304    /// cached sparse strips that have been previously generated. If the recording
305    /// has not been prepared (no cached strips), this will result in empty rendering.
306    ///
307    /// Use this method when you have a recording that has already been prepared
308    /// via `prepare_recording()`, or when you want to execute commands immediately
309    /// without explicit preparation.
310    ///
311    /// # Example
312    /// ```ignore
313    /// let mut recording = Recording::new();
314    /// scene.record(&mut recording, |ctx| {
315    ///     ctx.fill_rect(&Rect::new(0.0, 0.0, 100.0, 100.0));
316    /// });
317    ///
318    /// // Prepare strips first
319    /// scene.prepare_recording(&mut recording);
320    ///
321    /// // Then execute with cached strips
322    /// scene.execute_recording(&recording);
323    /// ```
324    fn execute_recording(&mut self, recording: &Recording);
325}
326
327/// Recorder context that captures commands.
328#[derive(Debug)]
329pub struct Recorder<'a> {
330    /// The recording to capture commands into.
331    recording: &'a mut Recording,
332
333    #[cfg(feature = "text")]
334    glyph_caches: Option<crate::glyph::GlyphCaches>,
335}
336
337impl<'a> Recorder<'a> {
338    /// Create a new recorder for the given recording.
339    pub fn new(
340        recording: &'a mut Recording,
341        transform: Affine,
342        #[cfg(feature = "text")] glyph_caches: crate::glyph::GlyphCaches,
343    ) -> Self {
344        let mut s = Self {
345            recording,
346            #[cfg(feature = "text")]
347            glyph_caches: Some(glyph_caches),
348        };
349        // Ensure that the initial transform is saved on the recording.
350        s.set_transform(transform);
351        s
352    }
353
354    /// Fill a path with current paint and fill rule.
355    pub fn fill_path(&mut self, path: &BezPath) {
356        self.recording
357            .add_command(RenderCommand::FillPath(path.clone()));
358    }
359
360    /// Stroke a path with current paint and stroke settings.
361    pub fn stroke_path(&mut self, path: &BezPath) {
362        self.recording
363            .add_command(RenderCommand::StrokePath(path.clone()));
364    }
365
366    /// Fill a rectangle with current paint and fill rule.
367    pub fn fill_rect(&mut self, rect: &Rect) {
368        self.recording.add_command(RenderCommand::FillRect(*rect));
369    }
370
371    /// Stroke a rectangle with current paint and stroke settings.
372    pub fn stroke_rect(&mut self, rect: &Rect) {
373        self.recording.add_command(RenderCommand::StrokeRect(*rect));
374    }
375
376    /// Set the transform for subsequent operations.
377    pub fn set_transform(&mut self, transform: Affine) {
378        self.recording.set_transform(transform);
379        self.recording
380            .add_command(RenderCommand::SetTransform(transform));
381    }
382
383    /// Set the fill rule for subsequent fill operations.
384    pub fn set_fill_rule(&mut self, fill_rule: Fill) {
385        self.recording
386            .add_command(RenderCommand::SetFillRule(fill_rule));
387    }
388
389    /// Set the stroke settings for subsequent stroke operations.
390    pub fn set_stroke(&mut self, stroke: Stroke) {
391        self.recording.add_command(RenderCommand::SetStroke(stroke));
392    }
393
394    /// Set the paint for subsequent rendering operations.
395    pub fn set_paint(&mut self, paint: impl Into<PaintType>) {
396        self.recording
397            .add_command(RenderCommand::SetPaint(paint.into()));
398    }
399
400    /// Set the current paint transform.
401    pub fn set_paint_transform(&mut self, paint_transform: Affine) {
402        self.recording
403            .add_command(RenderCommand::SetPaintTransform(paint_transform));
404    }
405
406    /// Reset the current paint transform.
407    pub fn reset_paint_transform(&mut self) {
408        self.recording
409            .add_command(RenderCommand::ResetPaintTransform);
410    }
411
412    /// Set the current filter effect.
413    pub fn set_filter_effect(&mut self, filter: Filter) {
414        self.recording
415            .add_command(RenderCommand::SetFilterEffect(filter));
416    }
417
418    /// Reset the current filter effect.
419    pub fn reset_filter_effect(&mut self) {
420        self.recording.add_command(RenderCommand::ResetFilterEffect);
421    }
422
423    /// Push a new layer with the given properties.
424    pub fn push_layer(
425        &mut self,
426        clip_path: Option<&BezPath>,
427        blend_mode: Option<BlendMode>,
428        opacity: Option<f32>,
429        mask: Option<Mask>,
430        filter: Option<Filter>,
431    ) {
432        self.recording
433            .add_command(RenderCommand::PushLayer(PushLayerCommand {
434                clip_path: clip_path.cloned(),
435                blend_mode,
436                opacity,
437                mask,
438                filter,
439            }));
440    }
441
442    /// Push a new clip layer.
443    pub fn push_clip_layer(&mut self, clip_path: &BezPath) {
444        self.push_layer(Some(clip_path), None, None, None, None);
445    }
446
447    /// Push a new filter layer.
448    ///
449    /// WARNING: Note that filters are currently incomplete and experimental.
450    pub fn push_filter_layer(&mut self, filter: Filter) {
451        self.push_layer(None, None, None, None, Some(filter));
452    }
453
454    /// Pop the last pushed layer.
455    pub fn pop_layer(&mut self) {
456        self.recording.add_command(RenderCommand::PopLayer);
457    }
458
459    /// Creates a builder for drawing a run of glyphs that have the same attributes.
460    #[cfg(feature = "text")]
461    pub fn glyph_run(&mut self, font: &FontData) -> GlyphRunBuilder<'_, Self> {
462        GlyphRunBuilder::new(font.clone(), self.recording.transform, self)
463    }
464}
465
466#[cfg(feature = "text")]
467impl GlyphRenderer for Recorder<'_> {
468    fn fill_glyph(&mut self, glyph: PreparedGlyph<'_>) {
469        match glyph.glyph_type {
470            GlyphType::Outline(outline_glyph) => {
471                if !outline_glyph.path.is_empty() {
472                    self.recording.add_command(RenderCommand::FillOutlineGlyph((
473                        outline_glyph.path.clone(),
474                        glyph.transform,
475                    )));
476                }
477            }
478
479            _ => {
480                unimplemented!("Recording glyphs of type {:?}", glyph.glyph_type);
481            }
482        }
483    }
484
485    fn stroke_glyph(&mut self, glyph: PreparedGlyph<'_>) {
486        match glyph.glyph_type {
487            GlyphType::Outline(outline_glyph) => {
488                if !outline_glyph.path.is_empty() {
489                    self.recording
490                        .add_command(RenderCommand::StrokeOutlineGlyph((
491                            outline_glyph.path.clone(),
492                            glyph.transform,
493                        )));
494                }
495            }
496            _ => {
497                unimplemented!("Recording glyphs of type {:?}", glyph.glyph_type);
498            }
499        }
500    }
501
502    fn restore_glyph_caches(&mut self, caches: crate::glyph::GlyphCaches) {
503        self.glyph_caches = Some(caches);
504    }
505    fn take_glyph_caches(&mut self) -> crate::glyph::GlyphCaches {
506        self.glyph_caches.take().unwrap_or_default()
507    }
508}