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