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#[cfg(feature = "multithreading")]
9use crate::dispatch::multi_threaded::MultiThreadedDispatcher;
10#[cfg(feature = "text")]
11use crate::text::{GlyphAtlasResources, GlyphRunBuilder};
12#[cfg(feature = "text")]
13use glifo::GlyphPrepCache;
14
15use crate::dispatch::single_threaded::SingleThreadedDispatcher;
16use crate::kurbo::{PathEl, Point};
17use alloc::boxed::Box;
18use alloc::sync::Arc;
19use alloc::vec;
20use alloc::vec::Vec;
21use hashbrown::HashMap;
22use vello_common::blurred_rounded_rect::BlurredRoundedRectangle;
23use vello_common::encode::{EncodeExt, EncodedPaint};
24use vello_common::fearless_simd::Level;
25use vello_common::filter::FilterData;
26use vello_common::filter_effects::Filter;
27use vello_common::kurbo::{Affine, BezPath, Rect, Stroke};
28use vello_common::mask::Mask;
29use vello_common::paint::{ImageId, ImageResolver, Paint, PaintType, Tint};
30use vello_common::peniko::color::palette::css::BLACK;
31use vello_common::peniko::{BlendMode, Fill};
32use vello_common::pixmap::{Pixmap, PixmapMut};
33use vello_common::render_state::RenderState;
34use vello_common::transforms::{RootTransforms, Transforms};
35use vello_common::util::is_axis_aligned;
36
37#[cfg(feature = "text")]
38pub(crate) const DEFAULT_GLYPH_ATLAS_SIZE: u16 = 4096;
39// Why do we need this? The reason is that the way uploaded images work in Vello Hybrid
40// is different from how they work in Vello CPU.
41//
42// In Vello Hybrid, all images, regardless of whether they are user-uploaded
43// images or cached glyphs, are stored in an image atlas at a certain location. An image ID then
44// uniquely resolves to an atlas page index + a location on that page. Whenever we want to
45// cache a new glyph, we simply allocate a location in the image atlas and then return the image
46// ID associated with that location.
47//
48// On Vello CPU, it works differently: An image ID is associated with a complete pixmap.
49// If a user uploads an image, instead of blitting it into a bigger image atlas, we just
50// store the user-provided pixmap and associate an image ID with the whole pixmap. However,
51// for glyph caching to work we need the same semantics as in Vello Hybrid. Therefore, we
52// use a marker to determine whether an image ID refers to a normal uploaded image or a cached
53// glyph and apply special handling based on that.
54//
55// All IDs < than this value are reserved for normal images, all IDs >= this value are
56// reserved for atlas pages.
57pub(crate) const ATLAS_IMAGE_ID_BASE: u32 = u32::MAX / 2;
58
59/// Persistent resources required by Vello CPU for rendering.
60///
61/// You should create one such instance per renderer.
62#[derive(Debug, Default)]
63pub struct Resources {
64    pub(crate) image_registry: ImageRegistry,
65    #[cfg(feature = "text")]
66    pub(crate) glyph_prep_cache: GlyphPrepCache,
67    // Will be initialized lazily on first use.
68    #[cfg(feature = "text")]
69    pub(crate) glyph_resources: Option<GlyphAtlasResources>,
70}
71
72impl Resources {
73    /// Create a new set of renderer resources.
74    pub fn new() -> Self {
75        Self::default()
76    }
77
78    pub(crate) fn before_render(&mut self, render_mode: RenderMode) {
79        #[cfg(feature = "text")]
80        self.prepare_glyph_cache(render_mode);
81
82        #[cfg(not(feature = "text"))]
83        let _ = render_mode;
84    }
85
86    pub(crate) fn after_render(&mut self) {
87        #[cfg(feature = "text")]
88        self.maintain_glyph_cache();
89    }
90}
91
92// TODO: Consider changing `Replace` to overwrite only the rendered region, leaving pixels outside
93// it unchanged. See https://github.com/linebender/vello/pull/1665#issuecomment-4667033939
94
95/// The composition mode that should be used when rendering into a pixmap.
96///
97/// For performance reason it is _highly_ recommended that you use `CompositeMode::Replace`, even
98/// if you know that the pixmap is already cleared. Only use `SrcOver` if you really have to
99/// preserve existing contents.
100#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
101pub enum CompositeMode {
102    /// Clear the destination pixmap and render the scene into it.
103    #[default]
104    Replace,
105    /// Render the scene into the pixmap using src-over compositing.
106    SrcOver,
107}
108
109/// The pixel format to assume for the destination pixmap.
110#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
111pub enum PixelFormat {
112    /// Premultiplied RGBA8.
113    #[default]
114    Rgba8,
115}
116
117/// Settings used when rasterizing a scene into a pixmap.
118#[derive(Copy, Clone, Debug, PartialEq, Eq)]
119pub struct RasterizerSettings {
120    /// Whether to prioritize speed or quality when rendering.
121    ///
122    /// For most cases (especially for real-time rendering), it is highly recommended to set
123    /// this to [`RenderMode::OptimizeSpeed`]. If color accuracy is a more significant concern,
124    /// then you can set this to [`RenderMode::OptimizeQuality`].
125    ///
126    /// Currently, the only difference this makes is that when choosing [`RenderMode::OptimizeSpeed`],
127    /// rasterization will happen using u8/u16,
128    /// while [`RenderMode::OptimizeQuality`] will use a f32-based pipeline.
129    pub render_mode: RenderMode,
130    /// How rendered content is composited into the destination.
131    pub composite_mode: CompositeMode,
132    /// Pixel format of the destination.
133    pub pixel_format: PixelFormat,
134    /// Offset in destination pixels where the render context origin is placed.
135    ///
136    /// See [`RenderContext::render_with`] for more information.
137    pub offset: (u16, u16),
138}
139
140impl Default for RasterizerSettings {
141    fn default() -> Self {
142        Self {
143            render_mode: RenderMode::OptimizeSpeed,
144            composite_mode: CompositeMode::Replace,
145            pixel_format: PixelFormat::Rgba8,
146            offset: (0, 0),
147        }
148    }
149}
150
151/// A render context for CPU-based 2D graphics rendering.
152///
153/// This is the main entry point for drawing operations. It maintains the current
154/// rendering state (transforms, paint, stroke, etc.) and dispatches drawing commands
155/// to the underlying rasterization engine.
156#[derive(Debug)]
157pub struct RenderContext {
158    /// Width of the render target in pixels.
159    pub(crate) width: u16,
160    /// Height of the render target in pixels.
161    pub(crate) height: u16,
162    /// The current rendering state.
163    pub(crate) state: RenderState,
164    root_transforms: RootTransforms,
165    /// The current mask in place.
166    pub(crate) mask: Option<Mask>,
167    /// Temporary path buffer to avoid repeated allocations.
168    pub(crate) temp_path: BezPath,
169    /// Optional threshold for aliasing.
170    pub(crate) aliasing_threshold: Option<u8>,
171    pub(crate) encoded_paints: Vec<EncodedPaint>,
172    pub(crate) filter: Option<Filter>,
173    #[cfg_attr(
174        not(feature = "text"),
175        allow(dead_code, reason = "used when the `text` feature is enabled")
176    )]
177    pub(crate) render_settings: RenderSettings,
178    dispatcher: Box<dyn Dispatcher>,
179}
180
181/// Settings to apply to the render context.
182#[derive(Copy, Clone, Debug)]
183pub struct RenderSettings {
184    /// The SIMD level that should be used for rendering operations.
185    pub level: Level,
186    /// The number of worker threads that should be used for rendering. Only has an effect
187    /// if the `multithreading` feature is active.
188    pub num_threads: u16,
189}
190
191impl Default for RenderSettings {
192    fn default() -> Self {
193        Self {
194            level: Level::try_detect().unwrap_or(Level::baseline()),
195            #[cfg(feature = "multithreading")]
196            num_threads: (std::thread::available_parallelism()
197                .unwrap()
198                .get()
199                .saturating_sub(1) as u16)
200                .min(8),
201            #[cfg(not(feature = "multithreading"))]
202            num_threads: 0,
203        }
204    }
205}
206
207impl RenderContext {
208    /// Create a new render context with the given width and height in pixels.
209    pub fn new(width: u16, height: u16) -> Self {
210        Self::new_with(width, height, RenderSettings::default())
211    }
212
213    /// Create a new render context with specific settings.
214    pub fn new_with(width: u16, height: u16, settings: RenderSettings) -> Self {
215        #[cfg(feature = "multithreading")]
216        let dispatcher: Box<dyn Dispatcher> = if settings.num_threads == 0 {
217            Box::new(SingleThreadedDispatcher::new(width, height, settings.level))
218        } else {
219            Box::new(MultiThreadedDispatcher::new(
220                width,
221                height,
222                settings.num_threads,
223                settings.level,
224            ))
225        };
226
227        #[cfg(not(feature = "multithreading"))]
228        let dispatcher: Box<dyn Dispatcher> =
229            { Box::new(SingleThreadedDispatcher::new(width, height, settings.level)) };
230
231        let encoded_paints = vec![];
232        let temp_path = BezPath::new();
233        let aliasing_threshold = None;
234
235        Self {
236            width,
237            height,
238            dispatcher,
239            state: RenderState::default(),
240            root_transforms: RootTransforms::default(),
241            aliasing_threshold,
242            render_settings: settings,
243            mask: None,
244            temp_path,
245            encoded_paints,
246            filter: None,
247        }
248    }
249
250    fn transforms(&self) -> &Transforms {
251        &self.state.transforms
252    }
253
254    fn transforms_mut(&mut self) -> &mut Transforms {
255        &mut self.state.transforms
256    }
257
258    fn encode_current_paint(&mut self) -> Paint {
259        match self.state.paint.clone() {
260            PaintType::Solid(s) => s.into(),
261            PaintType::Gradient(g) => {
262                let transform = self
263                    .root_transforms
264                    .effective_paint_transform(self.transforms());
265                // TODO: Add caching?
266                g.encode_into(&mut self.encoded_paints, transform, None)
267            }
268            PaintType::Image(i) => {
269                let transform = self
270                    .root_transforms
271                    .effective_paint_transform(self.transforms());
272                i.encode_into(&mut self.encoded_paints, transform, self.state.tint)
273            }
274        }
275    }
276
277    /// Fill a path.
278    pub fn fill_path(&mut self, path: &BezPath) {
279        // TODO: Similarly to Vello Hybrid, make sure that inline blend + filter are applies
280        // to the same layer.
281        self.with_optional_filter(|ctx| {
282            let paint = ctx.encode_current_paint();
283            let transform = ctx
284                .root_transforms
285                .effective_path_transform(ctx.transforms());
286            ctx.dispatcher.fill_path(
287                path,
288                ctx.state.fill_rule,
289                transform,
290                paint,
291                ctx.state.blend_mode,
292                ctx.aliasing_threshold,
293                ctx.mask.clone(),
294            );
295        });
296    }
297
298    /// Stroke a path.
299    pub fn stroke_path(&mut self, path: &BezPath) {
300        self.with_optional_filter(|ctx| {
301            let paint = ctx.encode_current_paint();
302            let transform = ctx
303                .root_transforms
304                .effective_path_transform(ctx.transforms());
305            ctx.dispatcher.stroke_path(
306                path,
307                &ctx.state.stroke,
308                transform,
309                paint,
310                ctx.state.blend_mode,
311                ctx.aliasing_threshold,
312                ctx.mask.clone(),
313            );
314        });
315    }
316
317    /// Fill a rectangle.
318    pub fn fill_rect(&mut self, rect: &Rect) {
319        self.with_optional_filter(|ctx| {
320            let paint = ctx.encode_current_paint();
321            let transform = ctx
322                .root_transforms
323                .effective_path_transform(ctx.transforms());
324
325            // Fast path: Use optimized rect filling if we have no skew in the path transform
326            // and anti-aliasing is enabled.
327            // TODO: Maybe also support no anti-aliasing in the fast path
328            if is_axis_aligned(&transform) && ctx.aliasing_threshold.is_none() {
329                // Transform the rect to screen coordinates.
330                let transformed_rect = transform.transform_rect_bbox(*rect);
331                ctx.dispatcher.fill_rect_fast(
332                    &transformed_rect,
333                    paint,
334                    ctx.state.blend_mode,
335                    ctx.mask.clone(),
336                );
337            } else {
338                // Fall back to path-based rendering for rotated/skewed transforms.
339                ctx.rect_to_temp_path(rect);
340                ctx.dispatcher.fill_path(
341                    &ctx.temp_path,
342                    ctx.state.fill_rule,
343                    transform,
344                    paint,
345                    ctx.state.blend_mode,
346                    ctx.aliasing_threshold,
347                    ctx.mask.clone(),
348                );
349            }
350        });
351    }
352
353    /// Stroke a rectangle.
354    pub fn stroke_rect(&mut self, rect: &Rect) {
355        self.with_optional_filter(|ctx| {
356            ctx.rect_to_temp_path(rect);
357            let paint = ctx.encode_current_paint();
358            let transform = ctx
359                .root_transforms
360                .effective_path_transform(ctx.transforms());
361            ctx.dispatcher.stroke_path(
362                &ctx.temp_path,
363                &ctx.state.stroke,
364                transform,
365                paint,
366                ctx.state.blend_mode,
367                ctx.aliasing_threshold,
368                ctx.mask.clone(),
369            );
370        });
371    }
372
373    fn rect_to_temp_path(&mut self, rect: &Rect) {
374        self.temp_path.truncate(0);
375        self.temp_path
376            .push(PathEl::MoveTo(Point::new(rect.x0, rect.y0)));
377        self.temp_path
378            .push(PathEl::LineTo(Point::new(rect.x1, rect.y0)));
379        self.temp_path
380            .push(PathEl::LineTo(Point::new(rect.x1, rect.y1)));
381        self.temp_path
382            .push(PathEl::LineTo(Point::new(rect.x0, rect.y1)));
383        self.temp_path.push(PathEl::ClosePath);
384    }
385
386    /// Fill a blurred rectangle with the given corner radius and standard deviation.
387    ///
388    /// When `invert` is `true`, the inverse (`1 - alpha`) of the blur coverage is painted: the
389    /// paint is fully opaque outside the blurred rectangle and fades to transparent inside it. This
390    /// can be used to implement inset box shadows.
391    ///
392    /// Note that this only works properly if the current paint is set to a solid color.
393    /// If not, it will fall back to using black as the fill color.
394    pub fn fill_blurred_rounded_rect(
395        &mut self,
396        rect: &Rect,
397        radius: f32,
398        std_dev: f32,
399        invert: bool,
400    ) {
401        let rect = rect.abs();
402        let color = match self.state.paint {
403            PaintType::Solid(s) => s,
404            // Fallback to black when attempting to blur a rectangle with an image/gradient paint
405            _ => BLACK,
406        };
407
408        let blurred_rect = BlurredRoundedRectangle {
409            rect,
410            color,
411            radius,
412            std_dev,
413            invert,
414        };
415
416        // The actual rectangle we paint needs to be larger so that the blurring effect
417        // is not cut off.
418        // The impulse response of a gaussian filter is infinite.
419        // For performance reason we cut off the filter at some extent where the response is close to zero.
420        let kernel_size = 2.5 * std_dev;
421        let inflated_rect = rect.inflate(f64::from(kernel_size), f64::from(kernel_size));
422        let transform = self
423            .root_transforms
424            .effective_path_transform(self.transforms());
425        let paint_transform = self
426            .root_transforms
427            .effective_paint_transform(self.transforms());
428
429        self.rect_to_temp_path(&inflated_rect);
430
431        let paint = blurred_rect.encode_into(&mut self.encoded_paints, paint_transform, None);
432        self.dispatcher.fill_path(
433            &self.temp_path,
434            Fill::NonZero,
435            transform,
436            paint,
437            self.state.blend_mode,
438            self.aliasing_threshold,
439            self.mask.clone(),
440        );
441    }
442
443    /// Creates a builder for drawing a run of glyphs that have the same attributes.
444    #[cfg(feature = "text")]
445    pub fn glyph_run<'a>(
446        &'a mut self,
447        resources: &'a mut Resources,
448        font: &crate::peniko::FontData,
449    ) -> GlyphRunBuilder<'a> {
450        glifo::GlyphRunBuilder::new(
451            font.clone(),
452            self.transforms().scene_transform(),
453            *self.transforms().paint_transform(),
454            crate::text::CpuGlyphRunBackend {
455                ctx: self,
456                resources,
457                atlas_cache_enabled: false,
458            },
459        )
460    }
461
462    /// Push a new layer with the given properties.
463    ///
464    /// Note that the mask, if provided, needs to have the same size as the render context. Otherwise,
465    /// it will be ignored. In addition to that, the mask will not be affected by the current
466    /// transformation matrix in place.
467    ///
468    /// # Panics
469    ///
470    /// Panics if `filter` is provided when this context uses multi-threaded rendering.
471    pub fn push_layer(
472        &mut self,
473        clip_path: Option<&BezPath>,
474        blend_mode: Option<BlendMode>,
475        opacity: Option<f32>,
476        mask: Option<Mask>,
477        filter: Option<Filter>,
478    ) {
479        let mask = mask.and_then(|m| {
480            if m.width() != self.width || m.height() != self.height {
481                None
482            } else {
483                Some(m)
484            }
485        });
486
487        let blend_mode = blend_mode.unwrap_or_default();
488        let opacity = opacity.unwrap_or(1.0);
489        let layer_transform = self
490            .root_transforms
491            .effective_path_transform(self.transforms());
492        let filter_data = filter.map(|filter| FilterData::new(filter, layer_transform));
493
494        // The important part! Let's say we have an element placed in a way such that
495        // its drop shadow starts at (0, 0). In order for it to render correctly, we would
496        // have to render parts of the shape that at negative viewport coordinates, which is
497        // not supported. Therefore, we instead shift everything down such that we can assume
498        // everything left/above (0, 0) is not needed for correct rendering, and simply
499        // shift everything back when actually compositing the rendered filter layer.
500        let relative_transform = filter_data.as_ref().map_or(Affine::IDENTITY, |data| {
501            let (shift_x, shift_y) = data.source_shift();
502            Affine::translate((f64::from(shift_x), f64::from(shift_y)))
503        });
504        self.root_transforms.push_root(relative_transform);
505
506        self.dispatcher.push_layer(
507            clip_path,
508            self.state.fill_rule,
509            layer_transform,
510            blend_mode,
511            opacity,
512            self.aliasing_threshold,
513            mask,
514            filter_data,
515        );
516    }
517
518    /// Push a new clip layer.
519    ///
520    /// See the explanation in the [clipping](https://github.com/linebender/vello/tree/main/sparse_strips/vello_cpu/examples)
521    /// example for how this method differs from `push_clip_path`.
522    pub fn push_clip_layer(&mut self, path: &BezPath) {
523        self.push_layer(Some(path), None, None, None, None);
524    }
525
526    /// Push a new blend layer.
527    pub fn push_blend_layer(&mut self, blend_mode: BlendMode) {
528        self.push_layer(None, Some(blend_mode), None, None, None);
529    }
530
531    /// Push a new opacity layer.
532    pub fn push_opacity_layer(&mut self, opacity: f32) {
533        self.push_layer(None, None, Some(opacity), None, None);
534    }
535
536    /// Push a new mask layer. The mask needs to have the same dimensions as the
537    /// render context. The mask will not be affected by the current transform
538    /// in place.
539    ///
540    /// See the explanation in the [masking](https://github.com/linebender/vello/tree/main/sparse_strips/masking/examples)
541    /// example for how this method differs from `set_mask`.
542    pub fn push_mask_layer(&mut self, mask: Mask) {
543        self.push_layer(None, None, None, Some(mask), None);
544    }
545
546    /// Push a filter layer that affects all subsequent drawing operations.
547    ///
548    /// WARNING: Note that filters are currently incomplete and experimental. In
549    /// particular, they will lead to a panic when used in combination with
550    /// multi-threaded rendering.
551    ///
552    /// # Panics
553    ///
554    /// Panics when this context uses multi-threaded rendering.
555    pub fn push_filter_layer(&mut self, filter: Filter) {
556        self.push_layer(None, None, None, None, Some(filter));
557    }
558
559    /// Set the aliasing threshold.
560    ///
561    /// If set to `None` (which is the recommended option in nearly all cases),
562    /// anti-aliasing will be applied.
563    ///
564    /// If instead set to some value, then a pixel will be fully painted if
565    /// the coverage is bigger than the threshold (between 0 and 255), otherwise
566    /// it will not be painted at all.
567    ///
568    /// Note that there is no performance benefit to disabling anti-aliasing and
569    /// this functionality is simply provided for compatibility.
570    pub fn set_aliasing_threshold(&mut self, aliasing_threshold: Option<u8>) {
571        self.aliasing_threshold = aliasing_threshold;
572    }
573
574    /// Pop the last-pushed layer.
575    pub fn pop_layer(&mut self) {
576        self.dispatcher.pop_layer();
577        self.root_transforms.pop_root();
578    }
579
580    /// Set the current stroke.
581    pub fn set_stroke(&mut self, stroke: Stroke) {
582        self.state.stroke = stroke;
583    }
584
585    /// Get the current stroke.
586    pub fn stroke(&self) -> &Stroke {
587        &self.state.stroke
588    }
589
590    /// Get a mutable reference to the current stroke.
591    #[cfg(feature = "text")]
592    pub(crate) fn stroke_mut(&mut self) -> &mut Stroke {
593        &mut self.state.stroke
594    }
595
596    /// Set the current paint.
597    ///
598    /// If the paint is an image with `ImageSource::OpaqueId`, it will be
599    /// resolved to the corresponding pixmap at rasterization time.
600    /// Make sure to register images with [`Resources::register_image`] first.
601    pub fn set_paint(&mut self, paint: impl Into<PaintType>) {
602        self.state.paint = paint.into();
603    }
604
605    /// Get the current paint.
606    pub fn paint(&self) -> &PaintType {
607        &self.state.paint
608    }
609
610    /// Set the tint for subsequent image paint operations.
611    pub fn set_tint(&mut self, tint: Option<Tint>) {
612        self.state.tint = tint;
613    }
614
615    /// Clear the tint, so subsequent image paints are drawn without tinting.
616    pub fn reset_tint(&mut self) {
617        self.state.tint = None;
618    }
619
620    /// Set the blend mode that should be used when drawing objects.
621    pub fn set_blend_mode(&mut self, blend_mode: BlendMode) {
622        self.state.blend_mode = blend_mode;
623    }
624
625    /// Get the currently active blend mode.
626    pub fn blend_mode(&self) -> BlendMode {
627        self.state.blend_mode
628    }
629
630    /// Set the current paint transform.
631    ///
632    /// The paint transform is applied to the paint after the transform of the geometry the paint
633    /// is drawn in, i.e., the paint transform is applied after the global transform. This allows
634    /// transforming the paint independently from the drawn geometry.
635    pub fn set_paint_transform(&mut self, paint_transform: Affine) {
636        self.transforms_mut().set_paint_transform(paint_transform);
637    }
638
639    /// Get the current paint transform.
640    pub fn paint_transform(&self) -> &Affine {
641        self.transforms().paint_transform()
642    }
643
644    /// Reset the current paint transform.
645    pub fn reset_paint_transform(&mut self) {
646        self.transforms_mut().reset_paint_transform();
647    }
648
649    /// Set the current fill rule.
650    pub fn set_fill_rule(&mut self, fill_rule: Fill) {
651        self.state.fill_rule = fill_rule;
652    }
653
654    /// Set the mask to use for path-painting operations. The mask needs to
655    /// have the same dimensions as the render context. The mask will not be
656    /// affected by the current transform in place.
657    ///
658    /// See the explanation in the [masking](https://github.com/linebender/vello/tree/main/sparse_strips/masking/examples)
659    /// example for how this method differs from `push_mask_layer`.
660    pub fn set_mask(&mut self, mask: Mask) {
661        self.mask = Some(mask);
662    }
663
664    /// Reset the mask that is used for path-painting operations.
665    pub fn reset_mask(&mut self) {
666        self.mask = None;
667    }
668
669    /// Get the current fill rule.
670    pub fn fill_rule(&self) -> &Fill {
671        &self.state.fill_rule
672    }
673
674    /// Set the current transform.
675    pub fn set_transform(&mut self, transform: Affine) {
676        self.transforms_mut().set_transform(transform);
677    }
678
679    /// Get the current transform.
680    pub fn transform(&self) -> &Affine {
681        self.transforms().transform()
682    }
683
684    /// Reset the current transform.
685    pub fn reset_transform(&mut self) {
686        self.transforms_mut().reset_transform();
687    }
688
689    /// Apply filter to the current paint (affects next drawn elements).
690    ///
691    /// This sets a filter that will be applied to the next drawn element.
692    /// To apply a filter to multiple elements, use `push_filter_layer` instead.
693    /// # Panics
694    ///
695    /// When this context uses multi-threaded rendering.
696    pub fn set_filter_effect(&mut self, filter: Filter) {
697        self.filter = Some(filter);
698    }
699
700    /// Reset the current filter effect.
701    pub fn reset_filter_effect(&mut self) {
702        self.filter = None;
703    }
704
705    /// Reset the render context and update the scene size.
706    pub fn reset_and_resize(&mut self, width: u16, height: u16) {
707        self.width = width;
708        self.height = height;
709
710        self.reset();
711    }
712
713    /// Reset the render context.
714    pub fn reset(&mut self) {
715        self.dispatcher.reset(self.width, self.height);
716        self.encoded_paints.clear();
717        self.mask = None;
718        self.root_transforms.reset();
719        self.state.reset();
720    }
721
722    /// Push a new clip path to the clip stack.
723    ///
724    /// See the explanation in the [clipping](https://github.com/linebender/vello/tree/main/sparse_strips/vello_cpu/examples)
725    /// example for how this method differs from `push_clip_layer`.
726    pub fn push_clip_path(&mut self, path: &BezPath) {
727        let transform = self.transforms().clip_path_transform();
728        self.dispatcher.push_clip_path(
729            path,
730            self.state.fill_rule,
731            transform,
732            self.aliasing_threshold,
733        );
734    }
735
736    /// Pop a clip path from the clip stack.
737    ///
738    /// Note that unlike `push_clip_layer`, it is permissible to have pending
739    /// pushed clip paths before finishing the rendering operation.
740    pub fn pop_clip_path(&mut self) {
741        self.dispatcher.pop_clip_path();
742    }
743
744    /// Flush any pending operations.
745    ///
746    /// This is a no-op when using the single-threaded render mode, and can be ignored.
747    /// For multi-threaded rendering, you _have_ to call this before rasterizing, otherwise
748    /// the program will panic.
749    pub fn flush(&mut self) {
750        self.dispatcher.flush();
751    }
752
753    /// Render the current context into a target using default rasterizer settings.
754    ///
755    /// See the documentation of [`RenderContext::render_with`] for more information.
756    pub fn render<'a>(&self, target: impl Into<PixmapMut<'a>>, resources: &mut Resources) {
757        self.render_with(target, resources, RasterizerSettings::default());
758    }
759
760    /// Render the current context into a target using custom rasterizer settings.
761    ///
762    /// See the documentation of [`RasterizerSettings`] to understand the tunable parameters for
763    /// rasterization.
764    ///
765    /// There is an important note to make about render sizes. [`RenderContext`] can be configured with
766    /// a specific width/height, but so can [`Pixmap`]. In the vast majority of cases, you will simply
767    /// want to configure them both to have the same size. However, it _is_ very much possible for them
768    /// to have different sizes, which can be useful in certain situations. In principle, the size
769    /// that you specify when creating a [`RenderContext`] defines the bound of the scene itself. Any
770    /// content that is to the top/left of (0, 0) and to the right/bottom of (width/height) will be
771    /// removed. However, the offset in [`RasterizerSettings`] as well as the width/height of
772    /// the [`PixmapMut`] define at which location the scene will be rasterized into, and allows
773    /// for further clipping certain parts of the scene away. The semantics are defined as follows:
774    ///
775    /// 1. [`RasterizerSettings::offset`] defines the where the top-left corner will be positioned
776    ///    on the pixmap, assuming a y-down coordinate system. In most cases (0, 0) will be the
777    ///    appropriate choice, but other values are certainly sensible. For example, if you want to
778    ///    implement a custom glyph-atlas, you can construct the scene assuming (0, 0) as the origin
779    ///    and then position the glyphs at rasterization time using this feature.
780    ///
781    /// 2. In case the pixmap width/height is larger than the offset plus the width/height of the
782    ///    [`RenderContext`], any remaining rows/columns are simply treated as padding (**however**,
783    ///    when using [`CompositeMode::Replace`], then the _whole_ destination pixmap will
784    ///    be cleared, not just the area covered by the scene). One potential reason for doing this
785    ///    is that certain platforms, for example macOS, require a specific byte stride for buffers.
786    ///    For example, let's say that a byte stride of 128 is imposed by the platform, but the actual
787    ///    size of the scene you are drawing is only 20x20. In this case, you can create a pixmap
788    ///    of size 32x20, and the last 12 columns are essentially treated as padding.
789    ///
790    /// 3. In case the width/height of the pixmap is _smaller_ than the offset + width/height of the
791    ///    scene, then anything that exceeds the pixmap boundaries is simply cut off. This can be useful
792    ///    if for some reason you only want to rasterize a small cut-out of the original scene.
793    pub fn render_with<'a>(
794        &self,
795        target: impl Into<PixmapMut<'a>>,
796        resources: &mut Resources,
797        settings: RasterizerSettings,
798    ) {
799        // TODO: Maybe we should move those checks into the dispatcher.
800        assert!(
801            !self.dispatcher.has_layers(),
802            "some layers haven't been popped yet"
803        );
804
805        resources.before_render(settings.render_mode);
806        let mut target = target.into();
807        let target_fully_covered = settings.offset == (0, 0)
808            && self.width >= target.width()
809            && self.height >= target.height();
810        // If the scene covers the whole pixmap than packing will take care
811        // of clearing everything anyway, so no reason to clear it explicitly
812        // here.
813        if settings.composite_mode == CompositeMode::Replace && !target_fully_covered {
814            target.data_mut().fill(0);
815        }
816
817        self.dispatcher.rasterize(
818            target,
819            self.width,
820            self.height,
821            settings,
822            &self.encoded_paints,
823            &resources.image_registry,
824        );
825        // TODO: We need to figure something out here API-wise. At the moment, the user can
826        // theoretically rasterize the same `RenderContext` multiple times without resetting in-between.
827        // However, if glyph caching is enabled, this method call could now evict that were previously
828        // assumed to exist in `RenderContext`, meaning that if the user rasterizes the same `RenderContext`
829        // again without resetting it, some of the cached glyphs might be stale and not exist anymore.
830        resources.after_render();
831    }
832
833    /// Return the width of the scene.
834    pub fn width(&self) -> u16 {
835        self.width
836    }
837
838    /// Return the height of the scene.
839    pub fn height(&self) -> u16 {
840        self.height
841    }
842
843    /// Return the render settings used by the `RenderContext`.
844    pub fn render_settings(&self) -> &RenderSettings {
845        &self.render_settings
846    }
847
848    /// Execute a drawing operation, optionally wrapping it in a filter layer.
849    fn with_optional_filter<F>(&mut self, mut f: F)
850    where
851        F: FnMut(&mut Self),
852    {
853        if let Some(filter) = self.filter.clone() {
854            self.push_filter_layer(filter);
855            f(self);
856            self.pop_layer();
857        } else {
858            f(self);
859        }
860    }
861
862    /// Take current rendering state and reset the existing state to its default.
863    pub fn take_current_state(&mut self) -> RenderState {
864        core::mem::take(&mut self.state)
865    }
866
867    /// Save a copy of the current rendering state.
868    pub fn save_current_state(&mut self) -> RenderState {
869        self.state.clone()
870    }
871
872    /// Restore rendering state.
873    pub fn restore_state(&mut self, state: RenderState) {
874        self.state = state;
875    }
876
877    /// Whether rendering is currently configured to run in multi-threaded mode.
878    pub fn is_multi_threaded(&self) -> bool {
879        self.dispatcher.is_multi_threaded()
880    }
881}
882
883/// Image registry implementation.
884impl Resources {
885    /// Register a pixmap in the image registry and return its [`ImageId`].
886    pub fn register_image(&mut self, pixmap: Arc<Pixmap>) -> ImageId {
887        self.image_registry.register(pixmap)
888    }
889
890    /// Remove an image from the registry.
891    pub fn destroy_image(&mut self, id: ImageId) -> bool {
892        self.image_registry.destroy(id)
893    }
894
895    /// Resolve an `ImageId` to its pixmap data.
896    pub fn resolve_image(&self, id: ImageId) -> Option<Arc<Pixmap>> {
897        self.image_registry.resolve(id)
898    }
899
900    /// Clear the image registry.
901    pub fn clear_images(&mut self) {
902        self.image_registry.clear();
903    }
904}
905
906/// Registry that maps opaque [`ImageId`]s to [`Pixmap`] data.
907///
908/// Used by [`RenderContext`] to resolve `ImageSource::OpaqueId` at rasterization time.
909#[derive(Debug, Default)]
910pub(crate) struct ImageRegistry {
911    images: HashMap<u32, Arc<Pixmap>>,
912    next_id: u32,
913}
914
915impl ImageRegistry {
916    fn register(&mut self, pixmap: Arc<Pixmap>) -> ImageId {
917        let id = self.next_id;
918        assert!(
919            id < ATLAS_IMAGE_ID_BASE,
920            "image registry exhausted non-atlas image IDs"
921        );
922
923        self.next_id += 1;
924        self.images.insert(id, pixmap);
925        ImageId::new(id)
926    }
927
928    #[cfg(feature = "text")]
929    pub(crate) fn register_atlas_page(&mut self, page_index: u32, pixmap: Arc<Pixmap>) {
930        self.images.insert(
931            ImageId::new(ATLAS_IMAGE_ID_BASE + page_index).as_u32(),
932            pixmap,
933        );
934    }
935
936    pub(crate) fn destroy(&mut self, id: ImageId) -> bool {
937        self.images.remove(&id.as_u32()).is_some()
938    }
939
940    #[cfg(feature = "text")]
941    pub(crate) fn destroy_atlas_page(&mut self, page_index: u32) -> bool {
942        self.destroy(ImageId::new(ATLAS_IMAGE_ID_BASE + page_index))
943    }
944
945    fn resolve(&self, id: ImageId) -> Option<Arc<Pixmap>> {
946        self.images.get(&id.as_u32()).cloned()
947    }
948
949    fn clear(&mut self) {
950        self.images.clear();
951        self.next_id = 0;
952    }
953}
954
955impl ImageResolver for ImageRegistry {
956    fn resolve(&self, id: ImageId) -> Option<Arc<Pixmap>> {
957        self.images.get(&id.as_u32()).cloned()
958    }
959}
960
961#[cfg(test)]
962mod tests {
963    #[cfg(feature = "text")]
964    use crate::peniko::{Blob, FontData};
965    use crate::{CompositeMode, RasterizerSettings, RenderContext, Resources};
966    #[cfg(feature = "text")]
967    use alloc::sync::Arc;
968    use alloc::vec;
969    #[cfg(feature = "text")]
970    use glifo::Glyph;
971    use vello_common::color::PremulRgba8;
972    use vello_common::color::palette::css::{BLUE, RED};
973    use vello_common::kurbo::{Rect, Shape};
974    use vello_common::pixmap::{Pixmap, PixmapMut};
975    use vello_common::tile::Tile;
976
977    const GRAY: PremulRgba8 = PremulRgba8 {
978        r: 9,
979        g: 10,
980        b: 11,
981        a: 255,
982    };
983
984    fn red_pixel() -> PremulRgba8 {
985        RED.premultiply().to_rgba8()
986    }
987
988    fn blue_pixel() -> PremulRgba8 {
989        BLUE.premultiply().to_rgba8()
990    }
991
992    fn transparent_pixel() -> PremulRgba8 {
993        PremulRgba8::from_u32(0)
994    }
995
996    fn solid_pixmap(width: u16, height: u16, color: PremulRgba8) -> Pixmap {
997        Pixmap::from_parts(
998            vec![color; usize::from(width) * usize::from(height)],
999            width,
1000            height,
1001        )
1002    }
1003
1004    fn red_rect_context(width: u16, height: u16, rect: Rect) -> RenderContext {
1005        let mut ctx = RenderContext::new(width, height);
1006        ctx.set_paint(RED);
1007        ctx.fill_rect(&rect);
1008        ctx.flush();
1009        ctx
1010    }
1011
1012    #[test]
1013    fn clip_overflow() {
1014        let mut ctx = RenderContext::new(100, 100);
1015
1016        for _ in 0..(usize::from(u16::MAX) + 1).div_ceil(usize::from(Tile::HEIGHT * Tile::WIDTH)) {
1017            ctx.fill_rect(&Rect::new(0.0, 0.0, 1.0, 1.0));
1018        }
1019
1020        ctx.push_clip_layer(&Rect::new(20.0, 20.0, 180.0, 180.0).to_path(0.1));
1021        ctx.pop_layer();
1022        ctx.flush();
1023    }
1024
1025    #[test]
1026    fn render_with_offset_clears_pixels_outside_scene() {
1027        let ctx = red_rect_context(2, 2, Rect::new(0.0, 0.0, 2.0, 2.0));
1028        let mut resources = Resources::new();
1029        let mut pixmap = solid_pixmap(4, 3, GRAY);
1030
1031        ctx.render_with(
1032            &mut pixmap,
1033            &mut resources,
1034            RasterizerSettings {
1035                offset: (1, 1),
1036                ..Default::default()
1037            },
1038        );
1039
1040        for y in 0..3 {
1041            for x in 0..4 {
1042                let expected = if (1..=2).contains(&x) && (1..=2).contains(&y) {
1043                    red_pixel()
1044                } else {
1045                    transparent_pixel()
1046                };
1047
1048                assert_eq!(pixmap.sample(x, y), expected, "pixel at ({x}, {y})");
1049            }
1050        }
1051    }
1052
1053    #[test]
1054    fn render_clips_scene_to_target_bounds() {
1055        let ctx = red_rect_context(3, 3, Rect::new(0.0, 0.0, 3.0, 3.0));
1056        let mut resources = Resources::new();
1057        let mut pixmap = solid_pixmap(4, 4, GRAY);
1058
1059        ctx.render_with(
1060            &mut pixmap,
1061            &mut resources,
1062            RasterizerSettings {
1063                offset: (2, 1),
1064                ..Default::default()
1065            },
1066        );
1067
1068        for y in 0..4 {
1069            for x in 0..4 {
1070                let expected = if (2..=3).contains(&x) && (1..=3).contains(&y) {
1071                    red_pixel()
1072                } else {
1073                    transparent_pixel()
1074                };
1075                assert_eq!(pixmap.sample(x, y), expected, "pixel at ({x}, {y})");
1076            }
1077        }
1078    }
1079
1080    #[test]
1081    fn render_into_padded_pixmap() {
1082        let ctx = red_rect_context(2, 2, Rect::new(0.0, 0.0, 2.0, 2.0));
1083        let mut resources = Resources::new();
1084        let mut pixmap = solid_pixmap(4, 2, GRAY);
1085
1086        ctx.render(&mut pixmap, &mut resources);
1087
1088        for y in 0..2 {
1089            for x in 0..4 {
1090                let expected = if x < 2 {
1091                    red_pixel()
1092                } else {
1093                    transparent_pixel()
1094                };
1095                assert_eq!(pixmap.sample(x, y), expected, "pixel at ({x}, {y})");
1096            }
1097        }
1098    }
1099
1100    #[test]
1101    fn reset_and_resize_updates_scene_size() {
1102        let mut ctx = RenderContext::new(8, 4);
1103        let mut resources = Resources::new();
1104        let mut pixmap = Pixmap::new(4, 8);
1105
1106        ctx.reset_and_resize(4, 8);
1107        assert_eq!(ctx.width(), 4);
1108        assert_eq!(ctx.height(), 8);
1109
1110        ctx.set_paint(BLUE);
1111        ctx.fill_rect(&Rect::new(0.0, 0.0, 4.0, 8.0));
1112        ctx.flush();
1113        ctx.render(&mut pixmap, &mut resources);
1114
1115        for y in 0..8 {
1116            for x in 0..4 {
1117                assert_eq!(pixmap.sample(x, y), blue_pixel(), "pixel at ({x}, {y})");
1118            }
1119        }
1120    }
1121
1122    #[test]
1123    fn render_into_raw_buffer() {
1124        let ctx = red_rect_context(2, 1, Rect::new(0.0, 0.0, 2.0, 1.0));
1125        let mut resources = Resources::new();
1126        let mut buffer = vec![0; 3 * 2 * 4];
1127        for pixel in buffer.chunks_exact_mut(4) {
1128            pixel.copy_from_slice(&[GRAY.r, GRAY.g, GRAY.b, GRAY.a]);
1129        }
1130
1131        {
1132            let pixmap = PixmapMut::new(3, 2, &mut buffer).unwrap();
1133            ctx.render_with(
1134                pixmap,
1135                &mut resources,
1136                RasterizerSettings {
1137                    offset: (1, 1),
1138                    ..Default::default()
1139                },
1140            );
1141        }
1142
1143        let expected = [
1144            transparent_pixel(),
1145            transparent_pixel(),
1146            transparent_pixel(),
1147            transparent_pixel(),
1148            red_pixel(),
1149            red_pixel(),
1150        ];
1151        for (pixel, expected) in buffer.chunks_exact(4).zip(expected) {
1152            assert_eq!(pixel, [expected.r, expected.g, expected.b, expected.a]);
1153        }
1154    }
1155
1156    #[test]
1157    fn pixmap_mut_validates_buffer_length() {
1158        let mut short_buffer = vec![0; 3 * 2 * 4 - 1];
1159        assert!(PixmapMut::new(3, 2, &mut short_buffer).is_none());
1160
1161        let mut exact_buffer = vec![0; 3 * 2 * 4];
1162        assert!(PixmapMut::new(3, 2, &mut exact_buffer).is_some());
1163    }
1164
1165    #[test]
1166    fn render_src_over_opaque() {
1167        let ctx = red_rect_context(2, 1, Rect::new(0.0, 0.0, 1.0, 1.0));
1168        let mut resources = Resources::new();
1169        let mut pixmap = solid_pixmap(2, 1, blue_pixel());
1170
1171        ctx.render_with(
1172            &mut pixmap,
1173            &mut resources,
1174            RasterizerSettings {
1175                composite_mode: CompositeMode::SrcOver,
1176                ..Default::default()
1177            },
1178        );
1179
1180        assert_eq!(pixmap.sample(0, 0), red_pixel());
1181        assert_eq!(pixmap.sample(1, 0), blue_pixel());
1182    }
1183
1184    #[test]
1185    fn render_src_over_transparent() {
1186        let mut ctx = RenderContext::new(1, 1);
1187        ctx.set_paint(RED.with_alpha(0.5));
1188        ctx.fill_rect(&Rect::new(0.0, 0.0, 1.0, 1.0));
1189        ctx.flush();
1190
1191        let mut resources = Resources::new();
1192        let mut pixmap = solid_pixmap(1, 1, blue_pixel());
1193
1194        ctx.render_with(
1195            &mut pixmap,
1196            &mut resources,
1197            RasterizerSettings {
1198                composite_mode: CompositeMode::SrcOver,
1199                ..Default::default()
1200            },
1201        );
1202
1203        assert_eq!(
1204            pixmap.sample(0, 0),
1205            PremulRgba8 {
1206                r: 128,
1207                g: 0,
1208                b: 127,
1209                a: 255,
1210            }
1211        );
1212    }
1213
1214    #[cfg(feature = "multithreading")]
1215    #[test]
1216    fn multithreaded_crash_after_reset() {
1217        use crate::{Level, RasterizerSettings, RenderMode, RenderSettings};
1218
1219        let mut pixmap = Pixmap::new(200, 200);
1220        let settings = RenderSettings {
1221            level: Level::try_detect().unwrap_or(Level::baseline()),
1222            num_threads: 1,
1223        };
1224        let rasterizer_settings = RasterizerSettings {
1225            render_mode: RenderMode::OptimizeQuality,
1226            ..Default::default()
1227        };
1228
1229        let mut resources = Resources::new();
1230        let mut ctx = RenderContext::new_with(200, 200, settings);
1231        ctx.reset();
1232        ctx.fill_path(&Rect::new(0.0, 0.0, 100.0, 100.0).to_path(0.1));
1233        ctx.flush();
1234        ctx.render_with(&mut pixmap, &mut resources, rasterizer_settings);
1235        ctx.flush();
1236        ctx.render_with(&mut pixmap, &mut resources, rasterizer_settings);
1237    }
1238
1239    #[cfg(feature = "multithreading")]
1240    #[test]
1241    fn multithreaded_render_empty_frame_after_reset() {
1242        use crate::RenderSettings;
1243
1244        let mut ctx = RenderContext::new_with(
1245            100,
1246            100,
1247            RenderSettings {
1248                num_threads: 4,
1249                ..Default::default()
1250            },
1251        );
1252        let mut resources = Resources::new();
1253        let mut pixmap = Pixmap::new(100, 100);
1254
1255        ctx.fill_rect(&Rect::new(0.0, 0.0, 100.0, 100.0));
1256        ctx.flush();
1257        ctx.render(&mut pixmap, &mut resources);
1258
1259        ctx.reset();
1260        ctx.flush();
1261        ctx.render(&mut pixmap, &mut resources);
1262    }
1263
1264    #[cfg(feature = "multithreading")]
1265    #[test]
1266    fn multithreaded_push_clip_path_before_draw() {
1267        use crate::RenderSettings;
1268
1269        let mut ctx = RenderContext::new_with(
1270            100,
1271            100,
1272            RenderSettings {
1273                num_threads: 1,
1274                ..Default::default()
1275            },
1276        );
1277        let clip = Rect::new(0.0, 0.0, 50.0, 50.0).to_path(0.1);
1278
1279        // Just make sure we don't panic.
1280        ctx.push_clip_path(&clip);
1281        ctx.flush();
1282        ctx.pop_clip_path();
1283        ctx.flush();
1284    }
1285
1286    #[cfg(feature = "multithreading")]
1287    #[test]
1288    fn multithreaded_reset_with_pending_tasks() {
1289        use crate::RenderSettings;
1290
1291        let mut ctx = RenderContext::new_with(
1292            100,
1293            100,
1294            RenderSettings {
1295                num_threads: 4,
1296                ..Default::default()
1297            },
1298        );
1299
1300        // Note: This test only works if we draw enough rectangles
1301        // to trigger a batch send.
1302        for _ in 0..300 {
1303            ctx.fill_rect(&Rect::new(0.0, 0.0, 100., 100.0));
1304        }
1305
1306        ctx.reset();
1307    }
1308
1309    #[cfg(feature = "multithreading")]
1310    #[test]
1311    fn multithreaded_drop_with_pending_tasks() {
1312        use crate::RenderSettings;
1313
1314        for _ in 0..10 {
1315            let mut ctx = RenderContext::new_with(
1316                100,
1317                100,
1318                RenderSettings {
1319                    num_threads: 4,
1320                    ..Default::default()
1321                },
1322            );
1323
1324            // Note: This test only works if we draw enough rectangles
1325            // to trigger a batch send.
1326            for _ in 0..300 {
1327                ctx.fill_rect(&Rect::new(0.0, 0.0, 100., 100.0));
1328            }
1329
1330            drop(ctx);
1331        }
1332    }
1333
1334    #[cfg(feature = "text")]
1335    #[test]
1336    fn glyph_atlas_resources_are_lazy() {
1337        const ROBOTO_FONT: &[u8] =
1338            include_bytes!("../../../examples/assets/roboto/Roboto-Regular.ttf");
1339
1340        let font = FontData::new(Blob::new(Arc::new(ROBOTO_FONT)), 0);
1341        let glyphs = [Glyph {
1342            id: 1,
1343            x: 0.0,
1344            y: 0.0,
1345        }];
1346
1347        let mut resources = Resources::new();
1348        let mut ctx = RenderContext::new(100, 100);
1349
1350        ctx.fill_rect(&Rect::new(0.0, 0.0, 10.0, 10.0));
1351        ctx.fill_path(&Rect::new(10.0, 10.0, 20.0, 20.0).to_path(0.1));
1352        ctx.glyph_run(&mut resources, &font)
1353            .fill_glyphs(glyphs.into_iter());
1354
1355        assert!(resources.glyph_resources.is_none());
1356
1357        ctx.glyph_run(&mut resources, &font)
1358            .atlas_cache(true)
1359            .fill_glyphs(glyphs.into_iter());
1360
1361        assert!(resources.glyph_resources.is_some());
1362    }
1363}