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