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