Skip to main content

vello_cpu/
render.rs

1// Copyright 2025 the Vello Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Basic render operations.
5
6use crate::RenderMode;
7use crate::dispatch::Dispatcher;
8
9#[cfg(feature = "multithreading")]
10use crate::dispatch::multi_threaded::MultiThreadedDispatcher;
11use crate::dispatch::single_threaded::SingleThreadedDispatcher;
12use crate::kurbo::{PathEl, Point};
13use alloc::boxed::Box;
14#[cfg(feature = "text")]
15use alloc::sync::Arc;
16use alloc::vec;
17use alloc::vec::Vec;
18use vello_common::blurred_rounded_rect::BlurredRoundedRectangle;
19use vello_common::encode::{EncodeExt, EncodedPaint};
20use vello_common::fearless_simd::Level;
21use vello_common::filter_effects::Filter;
22use vello_common::kurbo::{Affine, BezPath, Cap, Join, Rect, Stroke};
23use vello_common::mask::Mask;
24#[cfg(feature = "text")]
25use vello_common::paint::ImageSource;
26use vello_common::paint::{Paint, PaintType};
27use vello_common::peniko::color::palette::css::BLACK;
28use vello_common::peniko::{BlendMode, Fill};
29use vello_common::pixmap::Pixmap;
30use vello_common::recording::{PushLayerCommand, Recordable, Recorder, Recording, RenderCommand};
31use vello_common::strip::Strip;
32use vello_common::strip_generator::{GenerationMode, StripGenerator, StripStorage};
33#[cfg(feature = "text")]
34use vello_common::{
35    color::{AlphaColor, Srgb},
36    colr::{ColrPainter, ColrRenderer},
37    glyph::{GlyphRenderer, GlyphRunBuilder, GlyphType, PreparedGlyph},
38};
39
40/// A render context for CPU-based 2D graphics rendering.
41///
42/// This is the main entry point for drawing operations. It maintains the current
43/// rendering state (transforms, paint, stroke, etc.) and dispatches drawing commands
44/// to the underlying rasterization engine.
45#[derive(Debug)]
46pub struct RenderContext {
47    /// Width of the render target in pixels.
48    pub(crate) width: u16,
49    /// Height of the render target in pixels.
50    pub(crate) height: u16,
51    /// Current paint (color, gradient, or image) to be applied to drawn shapes.
52    pub(crate) paint: PaintType,
53    /// Transform applied to the paint, independent of geometry transform.
54    pub(crate) paint_transform: Affine,
55    /// The current mask in place.
56    pub(crate) mask: Option<Mask>,
57    /// Current stroke settings (width, join, cap style, etc.).
58    pub(crate) stroke: Stroke,
59    /// Current transformation matrix applied to all geometry.
60    pub(crate) transform: Affine,
61    /// Current fill rule (`NonZero` or `EvenOdd`) for filling paths.
62    pub(crate) fill_rule: Fill,
63    /// Current blend mode for drawing operations.
64    pub(crate) blend_mode: BlendMode,
65    /// Temporary path buffer to avoid repeated allocations.
66    pub(crate) temp_path: BezPath,
67    /// Optional threshold for aliasing.
68    pub(crate) aliasing_threshold: Option<u8>,
69    pub(crate) encoded_paints: Vec<EncodedPaint>,
70    pub(crate) filter: Option<Filter>,
71    #[cfg_attr(
72        not(feature = "text"),
73        allow(dead_code, reason = "used when the `text` feature is enabled")
74    )]
75    pub(crate) render_settings: RenderSettings,
76    dispatcher: Box<dyn Dispatcher>,
77    #[cfg(feature = "text")]
78    pub(crate) glyph_caches: Option<vello_common::glyph::GlyphCaches>,
79}
80
81/// Settings to apply to the render context.
82#[derive(Copy, Clone, Debug)]
83pub struct RenderSettings {
84    /// The SIMD level that should be used for rendering operations.
85    pub level: Level,
86    /// The number of worker threads that should be used for rendering. Only has an effect
87    /// if the `multithreading` feature is active.
88    pub num_threads: u16,
89    /// Whether to prioritize speed or quality when rendering.
90    ///
91    /// For most cases (especially for real-time rendering), it is highly recommended to set
92    /// this to `OptimizeSpeed`. If accuracy is a more significant concern (for example for visual
93    /// regression testing), then you can set this to `OptimizeQuality`.
94    ///
95    /// Currently, the only difference this makes is that when choosing `OptimizeSpeed`, rasterization
96    /// will happen using u8/u16, while `OptimizeQuality` will use a f32-based pipeline.
97    pub render_mode: RenderMode,
98}
99
100impl Default for RenderSettings {
101    fn default() -> Self {
102        Self {
103            level: Level::try_detect().unwrap_or(Level::fallback()),
104            #[cfg(feature = "multithreading")]
105            num_threads: (std::thread::available_parallelism()
106                .unwrap()
107                .get()
108                .saturating_sub(1) as u16)
109                .min(8),
110            #[cfg(not(feature = "multithreading"))]
111            num_threads: 0,
112            render_mode: RenderMode::OptimizeSpeed,
113        }
114    }
115}
116
117impl RenderContext {
118    /// Create a new render context with the given width and height in pixels.
119    pub fn new(width: u16, height: u16) -> Self {
120        Self::new_with(width, height, RenderSettings::default())
121    }
122
123    /// Create a new render context with specific settings.
124    pub fn new_with(width: u16, height: u16, settings: RenderSettings) -> Self {
125        #[cfg(feature = "multithreading")]
126        let dispatcher: Box<dyn Dispatcher> = if settings.num_threads == 0 {
127            Box::new(SingleThreadedDispatcher::new(width, height, settings.level))
128        } else {
129            Box::new(MultiThreadedDispatcher::new(
130                width,
131                height,
132                settings.num_threads,
133                settings.level,
134            ))
135        };
136
137        #[cfg(not(feature = "multithreading"))]
138        let dispatcher: Box<dyn Dispatcher> =
139            { Box::new(SingleThreadedDispatcher::new(width, height, settings.level)) };
140
141        let transform = Affine::IDENTITY;
142        let fill_rule = Fill::NonZero;
143        let paint = BLACK.into();
144        let paint_transform = Affine::IDENTITY;
145        let stroke = Stroke {
146            width: 1.0,
147            join: Join::Bevel,
148            start_cap: Cap::Butt,
149            end_cap: Cap::Butt,
150            ..Default::default()
151        };
152        let encoded_paints = vec![];
153        let temp_path = BezPath::new();
154        let aliasing_threshold = None;
155
156        Self {
157            width,
158            height,
159            dispatcher,
160            transform,
161            aliasing_threshold,
162            blend_mode: BlendMode::default(),
163            paint,
164            render_settings: settings,
165            mask: None,
166            paint_transform,
167            fill_rule,
168            stroke,
169            temp_path,
170            encoded_paints,
171            filter: None,
172            #[cfg(feature = "text")]
173            glyph_caches: Some(Default::default()),
174        }
175    }
176
177    fn encode_current_paint(&mut self) -> Paint {
178        match self.paint.clone() {
179            PaintType::Solid(s) => s.into(),
180            PaintType::Gradient(g) => {
181                // TODO: Add caching?
182                g.encode_into(
183                    &mut self.encoded_paints,
184                    self.transform * self.paint_transform,
185                )
186            }
187            PaintType::Image(i) => i.encode_into(
188                &mut self.encoded_paints,
189                self.transform * self.paint_transform,
190            ),
191        }
192    }
193
194    /// Fill a path.
195    pub fn fill_path(&mut self, path: &BezPath) {
196        self.with_optional_filter(|ctx| {
197            let paint = ctx.encode_current_paint();
198            ctx.dispatcher.fill_path(
199                path,
200                ctx.fill_rule,
201                ctx.transform,
202                paint,
203                ctx.blend_mode,
204                ctx.aliasing_threshold,
205                ctx.mask.clone(),
206                &ctx.encoded_paints,
207            );
208        });
209    }
210
211    /// Stroke a path.
212    pub fn stroke_path(&mut self, path: &BezPath) {
213        self.with_optional_filter(|ctx| {
214            let paint = ctx.encode_current_paint();
215            ctx.dispatcher.stroke_path(
216                path,
217                &ctx.stroke,
218                ctx.transform,
219                paint,
220                ctx.blend_mode,
221                ctx.aliasing_threshold,
222                ctx.mask.clone(),
223                &ctx.encoded_paints,
224            );
225        });
226    }
227
228    /// Fill a rectangle.
229    pub fn fill_rect(&mut self, rect: &Rect) {
230        self.with_optional_filter(|ctx| {
231            ctx.rect_to_temp_path(rect);
232            let paint = ctx.encode_current_paint();
233            ctx.dispatcher.fill_path(
234                &ctx.temp_path,
235                ctx.fill_rule,
236                ctx.transform,
237                paint,
238                ctx.blend_mode,
239                ctx.aliasing_threshold,
240                ctx.mask.clone(),
241                &ctx.encoded_paints,
242            );
243        });
244    }
245
246    /// Stroke a rectangle.
247    pub fn stroke_rect(&mut self, rect: &Rect) {
248        self.with_optional_filter(|ctx| {
249            ctx.rect_to_temp_path(rect);
250            let paint = ctx.encode_current_paint();
251            ctx.dispatcher.stroke_path(
252                &ctx.temp_path,
253                &ctx.stroke,
254                ctx.transform,
255                paint,
256                ctx.blend_mode,
257                ctx.aliasing_threshold,
258                ctx.mask.clone(),
259                &ctx.encoded_paints,
260            );
261        });
262    }
263
264    fn rect_to_temp_path(&mut self, rect: &Rect) {
265        self.temp_path.truncate(0);
266        self.temp_path
267            .push(PathEl::MoveTo(Point::new(rect.x0, rect.y0)));
268        self.temp_path
269            .push(PathEl::LineTo(Point::new(rect.x1, rect.y0)));
270        self.temp_path
271            .push(PathEl::LineTo(Point::new(rect.x1, rect.y1)));
272        self.temp_path
273            .push(PathEl::LineTo(Point::new(rect.x0, rect.y1)));
274        self.temp_path.push(PathEl::ClosePath);
275    }
276
277    /// Fill a blurred rectangle with the given radius and standard deviation.
278    ///
279    /// Note that this only works properly if the current paint is set to a solid color.
280    /// If not, it will fall back to using black as the fill color.
281    pub fn fill_blurred_rounded_rect(&mut self, rect: &Rect, radius: f32, std_dev: f32) {
282        let color = match self.paint {
283            PaintType::Solid(s) => s,
284            // Fallback to black when attempting to blur a rectangle with an image/gradient paint
285            _ => BLACK,
286        };
287
288        let blurred_rect = BlurredRoundedRectangle {
289            rect: *rect,
290            color,
291            radius,
292            std_dev,
293        };
294
295        // The actual rectangle we paint needs to be larger so that the blurring effect
296        // is not cut off.
297        // The impulse response of a gaussian filter is infinite.
298        // For performance reason we cut off the filter at some extent where the response is close to zero.
299        let kernel_size = 2.5 * std_dev;
300        let inflated_rect = rect.inflate(f64::from(kernel_size), f64::from(kernel_size));
301        let transform = self.transform * self.paint_transform;
302
303        self.rect_to_temp_path(&inflated_rect);
304
305        let paint = blurred_rect.encode_into(&mut self.encoded_paints, transform);
306        self.dispatcher.fill_path(
307            &self.temp_path,
308            Fill::NonZero,
309            self.transform,
310            paint,
311            self.blend_mode,
312            self.aliasing_threshold,
313            self.mask.clone(),
314            &self.encoded_paints,
315        );
316    }
317
318    /// Creates a builder for drawing a run of glyphs that have the same attributes.
319    #[cfg(feature = "text")]
320    pub fn glyph_run(&mut self, font: &crate::peniko::FontData) -> GlyphRunBuilder<'_, Self> {
321        GlyphRunBuilder::new(font.clone(), self.transform, self)
322    }
323
324    /// Push a new layer with the given properties.
325    ///
326    /// Note that the mask, if provided, needs to have the same size as the render context. Otherwise,
327    /// it will be ignored. In addition to that, the mask will not be affected by the current
328    /// transformation matrix in place.
329    pub fn push_layer(
330        &mut self,
331        clip_path: Option<&BezPath>,
332        blend_mode: Option<BlendMode>,
333        opacity: Option<f32>,
334        mask: Option<Mask>,
335        filter: Option<Filter>,
336    ) {
337        let mask = mask.and_then(|m| {
338            if m.width() != self.width || m.height() != self.height {
339                None
340            } else {
341                Some(m)
342            }
343        });
344
345        let blend_mode = blend_mode.unwrap_or_default();
346        let opacity = opacity.unwrap_or(1.0);
347
348        self.dispatcher.push_layer(
349            clip_path,
350            self.fill_rule,
351            self.transform,
352            blend_mode,
353            opacity,
354            self.aliasing_threshold,
355            mask,
356            filter,
357        );
358    }
359
360    /// Push a new clip layer.
361    ///
362    /// See the explanation in the [clipping](https://github.com/linebender/vello/tree/main/sparse_strips/vello_cpu/examples)
363    /// example for how this method differs from `push_clip_path`.
364    pub fn push_clip_layer(&mut self, path: &BezPath) {
365        self.push_layer(Some(path), None, None, None, None);
366    }
367
368    /// Push a new blend layer.
369    pub fn push_blend_layer(&mut self, blend_mode: BlendMode) {
370        self.push_layer(None, Some(blend_mode), None, None, None);
371    }
372
373    /// Push a new opacity layer.
374    pub fn push_opacity_layer(&mut self, opacity: f32) {
375        self.push_layer(None, None, Some(opacity), None, None);
376    }
377
378    /// Push a new mask layer. The mask needs to have the same dimensions as the
379    /// render context. The mask will not be affected by the current transform
380    /// in place.
381    ///
382    /// See the explanation in the [masking](https://github.com/linebender/vello/tree/main/sparse_strips/masking/examples)
383    /// example for how this method differs from `set_mask`.
384    pub fn push_mask_layer(&mut self, mask: Mask) {
385        self.push_layer(None, None, None, Some(mask), None);
386    }
387
388    /// Push a filter layer that affects all subsequent drawing operations.
389    ///
390    /// WARNING: Note that filters are currently incomplete and experimental. In
391    /// particular, they will lead to a panic when used in combination with
392    /// multi-threaded rendering.
393    pub fn push_filter_layer(&mut self, filter: Filter) {
394        self.push_layer(None, None, None, None, Some(filter));
395    }
396
397    /// Set the aliasing threshold.
398    ///
399    /// If set to `None` (which is the recommended option in nearly all cases),
400    /// anti-aliasing will be applied.
401    ///
402    /// If instead set to some value, then a pixel will be fully painted if
403    /// the coverage is bigger than the threshold (between 0 and 255), otherwise
404    /// it will not be painted at all.
405    ///
406    /// Note that there is no performance benefit to disabling anti-aliasing and
407    /// this functionality is simply provided for compatibility.
408    pub fn set_aliasing_threshold(&mut self, aliasing_threshold: Option<u8>) {
409        self.aliasing_threshold = aliasing_threshold;
410    }
411
412    /// Pop the last-pushed layer.
413    pub fn pop_layer(&mut self) {
414        self.dispatcher.pop_layer();
415    }
416
417    /// Set the current stroke.
418    pub fn set_stroke(&mut self, stroke: Stroke) {
419        self.stroke = stroke;
420    }
421
422    /// Get the current stroke
423    pub fn stroke(&self) -> &Stroke {
424        &self.stroke
425    }
426
427    /// Set the current paint.
428    pub fn set_paint(&mut self, paint: impl Into<PaintType>) {
429        self.paint = paint.into();
430    }
431
432    /// Get the current paint.
433    pub fn paint(&self) -> &PaintType {
434        &self.paint
435    }
436
437    /// Set the blend mode that should be used when drawing objects.
438    pub fn set_blend_mode(&mut self, blend_mode: BlendMode) {
439        self.blend_mode = blend_mode;
440    }
441
442    /// Get the currently active blend mode.
443    pub fn blend_mode(&self) -> BlendMode {
444        self.blend_mode
445    }
446
447    /// Set the current paint transform.
448    ///
449    /// The paint transform is applied to the paint after the transform of the geometry the paint
450    /// is drawn in, i.e., the paint transform is applied after the global transform. This allows
451    /// transforming the paint independently from the drawn geometry.
452    pub fn set_paint_transform(&mut self, paint_transform: Affine) {
453        self.paint_transform = paint_transform;
454    }
455
456    /// Get the current paint transform.
457    pub fn paint_transform(&self) -> &Affine {
458        &self.paint_transform
459    }
460
461    /// Reset the current paint transform.
462    pub fn reset_paint_transform(&mut self) {
463        self.paint_transform = Affine::IDENTITY;
464    }
465
466    /// Set the current fill rule.
467    pub fn set_fill_rule(&mut self, fill_rule: Fill) {
468        self.fill_rule = fill_rule;
469    }
470
471    /// Set the mask to use for path-painting operations. The mask needs to
472    /// have the same dimensions as the render context. The mask will not be
473    /// affected by the current transform in place.
474    ///
475    /// See the explanation in the [masking](https://github.com/linebender/vello/tree/main/sparse_strips/masking/examples)
476    /// example for how this method differs from `push_mask_layer`.
477    pub fn set_mask(&mut self, mask: Mask) {
478        self.mask = Some(mask);
479    }
480
481    /// Reset the mask that is used for path-painting operations.
482    pub fn reset_mask(&mut self) {
483        self.mask = None;
484    }
485
486    /// Get the current fill rule.
487    pub fn fill_rule(&self) -> &Fill {
488        &self.fill_rule
489    }
490
491    /// Set the current transform.
492    pub fn set_transform(&mut self, transform: Affine) {
493        self.transform = transform;
494    }
495
496    /// Get the current transform.
497    pub fn transform(&self) -> &Affine {
498        &self.transform
499    }
500
501    /// Reset the current transform.
502    pub fn reset_transform(&mut self) {
503        self.transform = Affine::IDENTITY;
504    }
505
506    /// Apply filter to the current paint (affects next drawn elements).
507    ///
508    /// This sets a filter that will be applied to the next drawn element.
509    /// To apply a filter to multiple elements, use `push_filter_layer` instead.
510    pub fn set_filter_effect(&mut self, filter: Filter) {
511        self.filter = Some(filter);
512    }
513
514    /// Reset the current filter effect.
515    pub fn reset_filter_effect(&mut self) {
516        self.filter = None;
517    }
518
519    /// Reset the render context.
520    pub fn reset(&mut self) {
521        self.dispatcher.reset();
522        self.encoded_paints.clear();
523        self.mask = None;
524        self.reset_transform();
525        self.reset_paint_transform();
526        #[cfg(feature = "text")]
527        self.glyph_caches.as_mut().unwrap().maintain();
528        self.blend_mode = BlendMode::default();
529    }
530
531    /// Push a new clip path to the clip stack.
532    ///
533    /// See the explanation in the [clipping](https://github.com/linebender/vello/tree/main/sparse_strips/vello_cpu/examples)
534    /// example for how this method differs from `push_clip_layer`.
535    pub fn push_clip_path(&mut self, path: &BezPath) {
536        self.dispatcher.push_clip_path(
537            path,
538            self.fill_rule,
539            self.transform,
540            self.aliasing_threshold,
541        );
542    }
543
544    /// Pop a clip path from the clip stack.
545    ///
546    /// Note that unlike `push_clip_layer`, it is permissible to have pending
547    /// pushed clip paths before finishing the rendering operation.
548    pub fn pop_clip_path(&mut self) {
549        self.dispatcher.pop_clip_path();
550    }
551
552    /// Flush any pending operations.
553    ///
554    /// This is a no-op when using the single-threaded render mode, and can be ignored.
555    /// For multi-threaded rendering, you _have_ to call this before rasterizing, otherwise
556    /// the program will panic.
557    pub fn flush(&mut self) {
558        self.dispatcher.flush(&self.encoded_paints);
559    }
560
561    /// Render the current context into a buffer.
562    /// The buffer is expected to be in premultiplied RGBA8 format with length `width * height * 4`
563    pub fn render_to_buffer(
564        &self,
565        buffer: &mut [u8],
566        width: u16,
567        height: u16,
568        render_mode: RenderMode,
569    ) {
570        // TODO: Maybe we should move those checks into the dispatcher.
571        let wide = self.dispatcher.wide();
572        assert!(!wide.has_layers(), "some layers haven't been popped yet");
573        assert_eq!(
574            buffer.len(),
575            (width as usize) * (height as usize) * 4,
576            "provided width ({}) and height ({}) do not match buffer size ({})",
577            width,
578            height,
579            buffer.len(),
580        );
581
582        self.dispatcher
583            .rasterize(buffer, render_mode, width, height, &self.encoded_paints);
584    }
585
586    /// Render the current context into a pixmap.
587    pub fn render_to_pixmap(&self, pixmap: &mut Pixmap) {
588        let width = pixmap.width();
589        let height = pixmap.height();
590        self.render_to_buffer(
591            pixmap.data_as_u8_slice_mut(),
592            width,
593            height,
594            self.render_settings.render_mode,
595        );
596    }
597
598    /// Return the width of the pixmap.
599    pub fn width(&self) -> u16 {
600        self.width
601    }
602
603    /// Return the height of the pixmap.
604    pub fn height(&self) -> u16 {
605        self.height
606    }
607
608    /// Return the render settings used by the `RenderContext`.
609    pub fn render_settings(&self) -> &RenderSettings {
610        &self.render_settings
611    }
612
613    /// Execute a drawing operation, optionally wrapping it in a filter layer.
614    fn with_optional_filter<F>(&mut self, mut f: F)
615    where
616        F: FnMut(&mut Self),
617    {
618        if let Some(filter) = self.filter.clone() {
619            self.push_filter_layer(filter);
620            f(self);
621            self.pop_layer();
622        } else {
623            f(self);
624        }
625    }
626}
627
628#[cfg(feature = "text")]
629impl GlyphRenderer for RenderContext {
630    fn fill_glyph(&mut self, prepared_glyph: PreparedGlyph<'_>) {
631        match prepared_glyph.glyph_type {
632            GlyphType::Outline(glyph) => {
633                let paint = self.encode_current_paint();
634                self.dispatcher.fill_path(
635                    glyph.path,
636                    Fill::NonZero,
637                    prepared_glyph.transform,
638                    paint,
639                    self.blend_mode,
640                    self.aliasing_threshold,
641                    self.mask.clone(),
642                    &self.encoded_paints,
643                );
644            }
645            GlyphType::Bitmap(glyph) => {
646                // We need to change the state of the render context
647                // to render the bitmap, but don't want to pollute the context,
648                // so simulate a `save` and `restore` operation.
649
650                use vello_common::peniko::ImageSampler;
651                let old_transform = self.transform;
652                let old_paint = self.paint.clone();
653
654                // If we scale down by a large factor, fall back to cubic scaling.
655                let quality = if prepared_glyph.transform.as_coeffs()[0] < 0.5
656                    || prepared_glyph.transform.as_coeffs()[3] < 0.5
657                {
658                    crate::peniko::ImageQuality::High
659                } else {
660                    crate::peniko::ImageQuality::Medium
661                };
662
663                let image = vello_common::paint::Image {
664                    image: ImageSource::Pixmap(Arc::new(glyph.pixmap)),
665                    sampler: ImageSampler {
666                        x_extend: crate::peniko::Extend::Pad,
667                        y_extend: crate::peniko::Extend::Pad,
668                        quality,
669                        alpha: 1.0,
670                    },
671                };
672
673                self.set_paint(image);
674                self.set_transform(prepared_glyph.transform);
675                self.fill_rect(&glyph.area);
676
677                // Restore the state.
678                self.set_paint(old_paint);
679                self.transform = old_transform;
680            }
681            GlyphType::Colr(glyph) => {
682                // Same as for bitmap glyphs, save the state and restore it later on.
683
684                use vello_common::peniko::ImageSampler;
685                let old_transform = self.transform;
686                let old_paint = self.paint.clone();
687                let context_color = match old_paint {
688                    PaintType::Solid(s) => s,
689                    _ => BLACK,
690                };
691
692                let area = glyph.area;
693
694                let glyph_pixmap = {
695                    let settings = RenderSettings {
696                        level: self.render_settings.level,
697                        render_mode: self.render_settings.render_mode,
698                        num_threads: 0,
699                    };
700
701                    let mut ctx = Self::new_with(glyph.pix_width, glyph.pix_height, settings);
702                    let mut pix = Pixmap::new(glyph.pix_width, glyph.pix_height);
703
704                    let mut colr_painter = ColrPainter::new(glyph, context_color, &mut ctx);
705                    colr_painter.paint();
706
707                    // Technically not necessary since we always render single-threaded, but just
708                    // to be safe.
709                    ctx.flush();
710                    ctx.render_to_pixmap(&mut pix);
711
712                    pix
713                };
714
715                let image = vello_common::paint::Image {
716                    image: ImageSource::Pixmap(Arc::new(glyph_pixmap)),
717                    sampler: ImageSampler {
718                        x_extend: crate::peniko::Extend::Pad,
719                        y_extend: crate::peniko::Extend::Pad,
720                        // Since the pixmap will already have the correct size, no need to
721                        // use a different image quality here.
722                        quality: crate::peniko::ImageQuality::Low,
723                        alpha: 1.0,
724                    },
725                };
726
727                self.set_paint(image);
728                self.set_transform(prepared_glyph.transform);
729                self.fill_rect(&area);
730
731                // Restore the state.
732                self.set_paint(old_paint);
733                self.transform = old_transform;
734            }
735        }
736    }
737
738    fn stroke_glyph(&mut self, prepared_glyph: PreparedGlyph<'_>) {
739        match prepared_glyph.glyph_type {
740            GlyphType::Outline(glyph) => {
741                let paint = self.encode_current_paint();
742                self.dispatcher.stroke_path(
743                    glyph.path,
744                    &self.stroke,
745                    prepared_glyph.transform,
746                    paint,
747                    self.blend_mode,
748                    self.aliasing_threshold,
749                    self.mask.clone(),
750                    &self.encoded_paints,
751                );
752            }
753            GlyphType::Bitmap(_) | GlyphType::Colr(_) => {
754                // The definitions of COLR and bitmap glyphs can't meaningfully support being stroked.
755                // (COLR's imaging model only has fills)
756                self.fill_glyph(prepared_glyph);
757            }
758        }
759    }
760
761    fn take_glyph_caches(&mut self) -> vello_common::glyph::GlyphCaches {
762        self.glyph_caches.take().unwrap()
763    }
764
765    fn restore_glyph_caches(&mut self, cache: vello_common::glyph::GlyphCaches) {
766        self.glyph_caches = Some(cache);
767    }
768}
769
770#[cfg(feature = "text")]
771impl ColrRenderer for RenderContext {
772    fn push_clip_layer(&mut self, clip: &BezPath) {
773        Self::push_clip_layer(self, clip);
774    }
775
776    fn push_blend_layer(&mut self, blend_mode: BlendMode) {
777        Self::push_blend_layer(self, blend_mode);
778    }
779
780    fn fill_solid(&mut self, color: AlphaColor<Srgb>) {
781        self.set_paint(color);
782        self.fill_rect(&Rect::new(
783            0.0,
784            0.0,
785            f64::from(self.width),
786            f64::from(self.height),
787        ));
788    }
789
790    fn fill_gradient(&mut self, gradient: crate::peniko::Gradient) {
791        self.set_paint(gradient);
792        self.fill_rect(&Rect::new(
793            0.0,
794            0.0,
795            f64::from(self.width),
796            f64::from(self.height),
797        ));
798    }
799
800    fn set_paint_transform(&mut self, affine: Affine) {
801        Self::set_paint_transform(self, affine);
802    }
803
804    fn pop_layer(&mut self) {
805        Self::pop_layer(self);
806    }
807}
808
809impl Recordable for RenderContext {
810    fn record<F>(&mut self, recording: &mut Recording, f: F)
811    where
812        F: FnOnce(&mut Recorder<'_>),
813    {
814        let mut recorder = Recorder::new(
815            recording,
816            self.transform,
817            #[cfg(feature = "text")]
818            self.take_glyph_caches(),
819        );
820        f(&mut recorder);
821        #[cfg(feature = "text")]
822        {
823            self.glyph_caches = Some(recorder.take_glyph_caches());
824        }
825    }
826
827    fn prepare_recording(&mut self, recording: &mut Recording) {
828        let buffers = recording.take_cached_strips();
829        let (strip_storage, strip_start_indices) =
830            self.generate_strips_from_commands(recording.commands(), buffers);
831        recording.set_cached_strips(strip_storage, strip_start_indices);
832    }
833
834    fn execute_recording(&mut self, recording: &Recording) {
835        let (cached_strips, cached_alphas) = recording.get_cached_strips();
836        let adjusted_strips = self.prepare_cached_strips(cached_strips, cached_alphas);
837
838        // Use pre-calculated strip start indices from when we generated the cache.
839        let strip_start_indices = recording.get_strip_start_indices();
840        let mut range_index = 0;
841
842        // Replay commands in order, using cached strips for geometry.
843        for command in recording.commands() {
844            match command {
845                RenderCommand::FillPath(_)
846                | RenderCommand::StrokePath(_)
847                | RenderCommand::FillRect(_)
848                | RenderCommand::StrokeRect(_) => {
849                    self.process_geometry_command(
850                        strip_start_indices,
851                        range_index,
852                        &adjusted_strips,
853                    );
854                    range_index += 1;
855                }
856                #[cfg(feature = "text")]
857                RenderCommand::FillOutlineGlyph(_) | RenderCommand::StrokeOutlineGlyph(_) => {
858                    self.process_geometry_command(
859                        strip_start_indices,
860                        range_index,
861                        &adjusted_strips,
862                    );
863                    range_index += 1;
864                }
865                RenderCommand::SetPaint(paint) => {
866                    self.set_paint(paint.clone());
867                }
868                RenderCommand::SetPaintTransform(transform) => {
869                    self.set_paint_transform(*transform);
870                }
871                RenderCommand::ResetPaintTransform => {
872                    self.reset_paint_transform();
873                }
874                RenderCommand::SetTransform(transform) => {
875                    self.set_transform(*transform);
876                }
877                RenderCommand::SetFillRule(fill_rule) => {
878                    self.set_fill_rule(*fill_rule);
879                }
880                RenderCommand::SetStroke(stroke) => {
881                    self.set_stroke(stroke.clone());
882                }
883                RenderCommand::SetFilterEffect(filter) => {
884                    self.set_filter_effect(filter.clone());
885                }
886                RenderCommand::ResetFilterEffect => {
887                    self.reset_filter_effect();
888                }
889                RenderCommand::PushLayer(PushLayerCommand {
890                    clip_path,
891                    blend_mode,
892                    opacity,
893                    mask,
894                    filter,
895                }) => {
896                    self.push_layer(
897                        clip_path.as_ref(),
898                        *blend_mode,
899                        *opacity,
900                        mask.clone(),
901                        filter.clone(),
902                    );
903                }
904                RenderCommand::PopLayer => {
905                    self.pop_layer();
906                }
907            }
908        }
909    }
910}
911
912/// Saved state for recording operations.
913#[derive(Debug)]
914struct RenderState {
915    transform: Affine,
916    fill_rule: Fill,
917    stroke: Stroke,
918    paint: PaintType,
919    paint_transform: Affine,
920}
921
922/// Recording management implementation.
923impl RenderContext {
924    /// Generate strips from strip commands and capture ranges.
925    ///
926    /// Returns:
927    /// - `collected_strips`: The generated strips.
928    /// - `collected_alphas`: The generated alphas.
929    /// - `strip_start_indices`: The start indices of strips for each geometry command.
930    fn generate_strips_from_commands(
931        &mut self,
932        commands: &[RenderCommand],
933        buffers: (StripStorage, Vec<usize>),
934    ) -> (StripStorage, Vec<usize>) {
935        let (mut strip_storage, mut strip_start_indices) = buffers;
936        strip_storage.clear();
937        strip_storage.set_generation_mode(GenerationMode::Append);
938        strip_start_indices.clear();
939
940        let saved_state = self.take_current_state();
941        let mut strip_generator =
942            StripGenerator::new(self.width, self.height, self.render_settings.level);
943
944        for command in commands {
945            let start_index = strip_storage.strips.len();
946
947            match command {
948                RenderCommand::FillPath(path) => {
949                    strip_generator.generate_filled_path(
950                        path,
951                        self.fill_rule,
952                        self.transform,
953                        self.aliasing_threshold,
954                        &mut strip_storage,
955                        None,
956                    );
957                    strip_start_indices.push(start_index);
958                }
959                RenderCommand::StrokePath(path) => {
960                    strip_generator.generate_stroked_path(
961                        path,
962                        &self.stroke,
963                        self.transform,
964                        self.aliasing_threshold,
965                        &mut strip_storage,
966                        None,
967                    );
968                    strip_start_indices.push(start_index);
969                }
970                RenderCommand::FillRect(rect) => {
971                    self.rect_to_temp_path(rect);
972                    strip_generator.generate_filled_path(
973                        &self.temp_path,
974                        self.fill_rule,
975                        self.transform,
976                        self.aliasing_threshold,
977                        &mut strip_storage,
978                        None,
979                    );
980                    strip_start_indices.push(start_index);
981                }
982                RenderCommand::StrokeRect(rect) => {
983                    self.rect_to_temp_path(rect);
984                    strip_generator.generate_stroked_path(
985                        &self.temp_path,
986                        &self.stroke,
987                        self.transform,
988                        self.aliasing_threshold,
989                        &mut strip_storage,
990                        None,
991                    );
992                    strip_start_indices.push(start_index);
993                }
994                #[cfg(feature = "text")]
995                RenderCommand::FillOutlineGlyph((path, glyph_transform)) => {
996                    strip_generator.generate_filled_path(
997                        path,
998                        self.fill_rule,
999                        *glyph_transform,
1000                        self.aliasing_threshold,
1001                        &mut strip_storage,
1002                        None,
1003                    );
1004                    strip_start_indices.push(start_index);
1005                }
1006                #[cfg(feature = "text")]
1007                RenderCommand::StrokeOutlineGlyph((path, glyph_transform)) => {
1008                    strip_generator.generate_stroked_path(
1009                        path,
1010                        &self.stroke,
1011                        *glyph_transform,
1012                        self.aliasing_threshold,
1013                        &mut strip_storage,
1014                        None,
1015                    );
1016                    strip_start_indices.push(start_index);
1017                }
1018                RenderCommand::SetTransform(transform) => {
1019                    self.transform = *transform;
1020                }
1021                RenderCommand::SetFillRule(fill_rule) => {
1022                    self.fill_rule = *fill_rule;
1023                }
1024                RenderCommand::SetStroke(stroke) => {
1025                    self.stroke = stroke.clone();
1026                }
1027
1028                _ => {}
1029            }
1030        }
1031
1032        self.restore_state(saved_state);
1033
1034        (strip_storage, strip_start_indices)
1035    }
1036}
1037
1038/// Recording management implementation.
1039impl RenderContext {
1040    fn process_geometry_command(
1041        &mut self,
1042        strip_start_indices: &[usize],
1043        range_index: usize,
1044        adjusted_strips: &[Strip],
1045    ) {
1046        assert!(
1047            range_index < strip_start_indices.len(),
1048            "Strip range index out of bounds"
1049        );
1050        let start = strip_start_indices[range_index];
1051        let end = strip_start_indices
1052            .get(range_index + 1)
1053            .copied()
1054            .unwrap_or(adjusted_strips.len());
1055        let count = end - start;
1056        if count == 0 {
1057            // There are no strips to generate.
1058            return;
1059        }
1060        assert!(
1061            start < adjusted_strips.len() && count > 0,
1062            "Invalid strip range"
1063        );
1064        let paint = self.encode_current_paint();
1065        self.dispatcher.generate_wide_cmd(
1066            &adjusted_strips[start..end],
1067            paint,
1068            self.blend_mode,
1069            &self.encoded_paints,
1070        );
1071    }
1072
1073    /// Prepare cached strips for rendering by adjusting indices.
1074    fn prepare_cached_strips(
1075        &mut self,
1076        cached_strips: &[Strip],
1077        cached_alphas: &[u8],
1078    ) -> Vec<Strip> {
1079        // Calculate offset for alpha indices based on current dispatcher's alpha buffer size.
1080        let alpha_offset = {
1081            let storage = self.dispatcher.strip_storage_mut();
1082            let offset = storage.alphas.len() as u32;
1083            // Extend the dispatcher's alpha buffer with cached alphas.
1084            storage.alphas.extend(cached_alphas);
1085
1086            offset
1087        };
1088        // Create adjusted strips with corrected alpha indices.
1089        cached_strips
1090            .iter()
1091            .map(move |strip| {
1092                let mut adjusted_strip = *strip;
1093                adjusted_strip.set_alpha_idx(adjusted_strip.alpha_idx() + alpha_offset);
1094                adjusted_strip
1095            })
1096            .collect()
1097    }
1098
1099    /// Save the current rendering state.
1100    fn take_current_state(&mut self) -> RenderState {
1101        RenderState {
1102            paint: self.paint.clone(),
1103            paint_transform: self.paint_transform,
1104            transform: self.transform,
1105            fill_rule: self.fill_rule,
1106            stroke: core::mem::take(&mut self.stroke),
1107        }
1108    }
1109
1110    /// Restore the saved rendering state.
1111    fn restore_state(&mut self, state: RenderState) {
1112        self.transform = state.transform;
1113        self.fill_rule = state.fill_rule;
1114        self.stroke = state.stroke;
1115        self.paint = state.paint;
1116        self.paint_transform = state.paint_transform;
1117    }
1118}
1119
1120#[cfg(test)]
1121mod tests {
1122    use crate::RenderContext;
1123    use vello_common::kurbo::{Rect, Shape};
1124    use vello_common::tile::Tile;
1125
1126    #[test]
1127    fn clip_overflow() {
1128        let mut ctx = RenderContext::new(100, 100);
1129
1130        for _ in 0..(usize::from(u16::MAX) + 1).div_ceil(usize::from(Tile::HEIGHT * Tile::WIDTH)) {
1131            ctx.fill_rect(&Rect::new(0.0, 0.0, 1.0, 1.0));
1132        }
1133
1134        ctx.push_clip_layer(&Rect::new(20.0, 20.0, 180.0, 180.0).to_path(0.1));
1135        ctx.pop_layer();
1136        ctx.flush();
1137    }
1138
1139    #[cfg(feature = "multithreading")]
1140    #[test]
1141    fn multithreaded_crash_after_reset() {
1142        use crate::{Level, RenderMode, RenderSettings};
1143        use vello_common::pixmap::Pixmap;
1144
1145        let mut pixmap = Pixmap::new(200, 200);
1146        let settings = RenderSettings {
1147            level: Level::try_detect().unwrap_or(Level::fallback()),
1148            num_threads: 1,
1149            render_mode: RenderMode::OptimizeQuality,
1150        };
1151
1152        let mut ctx = RenderContext::new_with(200, 200, settings);
1153        ctx.reset();
1154        ctx.fill_path(&Rect::new(0.0, 0.0, 100.0, 100.0).to_path(0.1));
1155        ctx.flush();
1156        ctx.render_to_pixmap(&mut pixmap);
1157        ctx.flush();
1158        ctx.render_to_pixmap(&mut pixmap);
1159    }
1160}