diff --git a/sparse_strips/vello_bench/src/glyph.rs b/sparse_strips/vello_bench/src/glyph.rs index e43622370b..f24b0f004c 100644 --- a/sparse_strips/vello_bench/src/glyph.rs +++ b/sparse_strips/vello_bench/src/glyph.rs @@ -9,7 +9,7 @@ use parley::{ PositionedLayoutItem, }; use vello_common::pixmap::Pixmap; -use vello_cpu::{Glyph, RenderContext, RenderMode, RenderSettings, Resources}; +use vello_cpu::{Glyph, RenderContext, RenderSettings, Resources}; pub fn glyph(c: &mut Criterion) { let mut g = c.benchmark_group("glyph"); @@ -30,10 +30,7 @@ pub fn glyph(c: &mut Criterion) { layout }; - let settings = RenderSettings { - render_mode: RenderMode::OptimizeSpeed, - ..Default::default() - }; + let settings = RenderSettings::default(); let layout = layout_for(TEXT, 1.0); for (hint_name, hint) in [("hinted", true), ("unhinted", false)] { @@ -116,7 +113,7 @@ fn render_layout( renderer .ctx - .render_to_pixmap(&mut renderer.resources, &mut renderer.pixmap); + .render(&mut renderer.pixmap, &mut renderer.resources); } fn render_glyph_run( diff --git a/sparse_strips/vello_bench/src/integration.rs b/sparse_strips/vello_bench/src/integration.rs index c220a881d3..681a43337c 100644 --- a/sparse_strips/vello_bench/src/integration.rs +++ b/sparse_strips/vello_bench/src/integration.rs @@ -57,7 +57,7 @@ pub fn images(c: &mut Criterion) { } renderer.flush(); - renderer.render_to_pixmap(&mut resources, &mut pixmap); + renderer.render(&mut pixmap, &mut resources); std::hint::black_box(&pixmap); }); }); diff --git a/sparse_strips/vello_common/src/pixmap.rs b/sparse_strips/vello_common/src/pixmap.rs index 83055dd514..70d1401d1a 100644 --- a/sparse_strips/vello_common/src/pixmap.rs +++ b/sparse_strips/vello_common/src/pixmap.rs @@ -29,6 +29,51 @@ pub struct Pixmap { may_have_transparency: bool, } +/// A mutable view into premultiplied RGBA8 pixmap data. +#[derive(Debug)] +pub struct PixmapMut<'a> { + /// Width of the pixmap in pixels. + width: u16, + /// Height of the pixmap in pixels. + height: u16, + /// Buffer of the pixmap in RGBA8 format. + buf: &'a mut [u8], +} + +impl<'a> PixmapMut<'a> { + /// Create a new mutable pixmap view. + /// + /// Returns `None` if `buf` is not exactly `width * height * 4` bytes long. + pub fn new(width: u16, height: u16, buf: &'a mut [u8]) -> Option { + if buf.len() == usize::from(width) * usize::from(height) * 4 { + Some(Self { width, height, buf }) + } else { + None + } + } + + /// Return the width of the pixmap. + pub fn width(&self) -> u16 { + self.width + } + + /// Return the height of the pixmap. + pub fn height(&self) -> u16 { + self.height + } + + /// Returns a mutable reference to the underlying data as premultiplied RGBA8 bytes. + pub fn data_mut(&mut self) -> &mut [u8] { + self.buf + } +} + +impl<'a> From<&'a mut Pixmap> for PixmapMut<'a> { + fn from(pixmap: &'a mut Pixmap) -> Self { + pixmap.as_mut() + } +} + impl Pixmap { /// Create a new pixmap with the given width and height in pixels. /// @@ -293,6 +338,10 @@ impl Pixmap { &self.buf } + // TODO: Now that we have `as_mut`, maybe we don't need the + // mutable methods. If we add a `PixmapRef` we can also remove the + // non-mutable ones. + /// Returns a mutable reference to the underlying data as premultiplied RGBA8. /// /// The pixels are in row-major order. @@ -316,6 +365,15 @@ impl Pixmap { bytemuck::cast_slice_mut(&mut self.buf) } + /// Return a mutable view into this pixmap's pixel data. + pub fn as_mut(&mut self) -> PixmapMut<'_> { + PixmapMut { + width: self.width, + height: self.height, + buf: bytemuck::cast_slice_mut(&mut self.buf), + } + } + /// Sample a pixel from the pixmap. /// /// The pixel data is [premultiplied RGBA8][PremulRgba8]. diff --git a/sparse_strips/vello_cpu/CHANGELOG.md b/sparse_strips/vello_cpu/CHANGELOG.md index 9d6a4be049..22956d372d 100644 --- a/sparse_strips/vello_cpu/CHANGELOG.md +++ b/sparse_strips/vello_cpu/CHANGELOG.md @@ -10,8 +10,17 @@ Subheadings to categorize changes are `added, changed, deprecated, removed, fixe ## [Unreleased] +TODO: Before making a 0.0.10 (or 0.1.0) release, resolve the following issue: +https://github.com/linebender/vello/pull/1665#issuecomment-4667033939! + This release has an [MSRV][] of 1.88. +### Changed +- The API for rendering into a pixmap. The methods `render_to_pixmap` and + `composite_to_pixmap_at_offset` have been replaced with a single unified + `render` (and `render_with`) method that takes additional parameters for tweaking the behavior. + ([#1665][] by [@LaurenzV][]) + ## [0.0.9][] - 2026-05-30 This release has an [MSRV][] of 1.88. @@ -206,6 +215,7 @@ See also the [vello_common 0.0.1](../vello_common/CHANGELOG.md#001---2025-05-10) [#1616]: https://github.com/linebender/vello/pull/1616 [#1628]: https://github.com/linebender/vello/pull/1628 [#1653]: https://github.com/linebender/vello/pull/1653 +[#1665]: https://github.com/linebender/vello/pull/1665 [#1668]: https://github.com/linebender/vello/pull/1668 [#1673]: https://github.com/linebender/vello/pull/1673 diff --git a/sparse_strips/vello_cpu/README.md b/sparse_strips/vello_cpu/README.md index 11b3207744..1b522e196c 100644 --- a/sparse_strips/vello_cpu/README.md +++ b/sparse_strips/vello_cpu/README.md @@ -29,7 +29,7 @@ See https://linebender.org/blog/doc-include/ for related discussion. --> [RenderContext::glyph_run]: https://docs.rs/vello_cpu/latest/vello_cpu/struct.RenderContext.html#method.glyph_run [RenderMode::OptimizeSpeed]: https://docs.rs/vello_cpu/latest/vello_cpu/enum.RenderMode.html#variant.OptimizeSpeed [RenderMode::OptimizeQuality]: https://docs.rs/vello_cpu/latest/vello_cpu/enum.RenderMode.html#variant.OptimizeQuality -[`RenderContext::render_to_pixmap`]: https://docs.rs/vello_cpu/latest/vello_cpu/struct.RenderContext.html#method.render_to_pixmap +[`RenderContext::render`]: https://docs.rs/vello_cpu/latest/vello_cpu/struct.RenderContext.html#method.render [`Pixmap`]: https://docs.rs/vello_cpu/latest/vello_cpu/struct.Pixmap.html @@ -43,15 +43,15 @@ Vello CPU is being developed as part of work to address shortcomings in Vello. To use Vello CPU, you need to: -- Create a [`RenderContext`][], a 2D drawing context for a fixed-size target area. +- Create a [`RenderContext`][], a 2D drawing context for a fixed-size scene area. - For each object in your scene: - Set how the object will be painted, using [`set_paint`][RenderContext::set_paint]. - Set the shape to be drawn for that object, using methods like [`fill_path`][RenderContext::fill_path], [`stroke_path`][RenderContext::stroke_path], or [`glyph_run`][RenderContext::glyph_run]. -- Render it to an image using [`RenderContext::render_to_pixmap`][]. +- Render it to an image using [`RenderContext::render`][]. ```rust -use vello_cpu::{RenderContext, Resources, Pixmap, RenderMode}; +use vello_cpu::{RenderContext, Resources, Pixmap}; use vello_cpu::{color::{palette::css, PremulRgba8}, kurbo::Rect}; let width = 10; let height = 5; @@ -64,7 +64,7 @@ let mut target = Pixmap::new(width, height); // While calling `flush` is only strictly necessary if you are rendering using // multiple threads, it is recommended to always do this. context.flush(); -context.render_to_pixmap(&mut resources, &mut target); +context.render(&mut target, &mut resources); let expected_render = b"\ 0000000000\ diff --git a/sparse_strips/vello_cpu/examples/basic.rs b/sparse_strips/vello_cpu/examples/basic.rs index 54dc7f368c..00474fa9fc 100644 --- a/sparse_strips/vello_cpu/examples/basic.rs +++ b/sparse_strips/vello_cpu/examples/basic.rs @@ -8,7 +8,7 @@ use vello_cpu::color::palette::css::YELLOW; use vello_cpu::kurbo::Affine; use vello_cpu::{ - Level, Pixmap, RenderContext, RenderMode, RenderSettings, Resources, + Level, Pixmap, RasterizerSettings, RenderContext, RenderMode, RenderSettings, Resources, color::palette::css::{BLUE, GREEN, RED}, kurbo::{Circle, Rect, Shape}, }; @@ -52,6 +52,8 @@ fn main() { // using 4+ threads might result in diminishing results, depending on // the workload. num_threads: 0, + }; + let rasterizer_settings = RasterizerSettings { // Define whether the renderer should prioritize speed or quality // during rendering. Currently, the only difference is that // `OptimizeSpeed` will use u8/u16 for the rasterization @@ -60,6 +62,7 @@ fn main() { // worse due to quantization. Unless you really care about that, it is // highly recommended to use the `OptimizeSpeed` rendering mode. render_mode: RenderMode::OptimizeSpeed, + ..Default::default() }; // Vello CPU embraces a slightly different paradigm than a lot of other 2D @@ -126,12 +129,12 @@ fn main() { // Now the second step is to copy the results of the render context into the // pixmap. We do this by creating a new pixmap (or reusing an existing one). - // Please note that the pixmap and the render context need to have the same - // dimensions! Otherwise, the renderer will panic. + // The pixmap and render context can have different dimensions. See the documentation + // of the `render_with` method for more information. let mut pixmap_1 = Pixmap::new(100, 100); // Now, simply extract the results from the render context into the // pixmap. - ctx.render_to_pixmap(&mut resources, &mut pixmap_1); + ctx.render_with(&mut pixmap_1, &mut resources, rasterizer_settings); // Now you can do whatever you want with the pixmap, which provides raw // access to the premultiplied RGBA pixels of the image. If you have enabled @@ -155,11 +158,11 @@ fn main() { // Once again, we render the results into a pixmap again. If you can, // you can just reuse existing pixmaps (assuming they have the correct - // dimension), since all previous pixels in the pixmap will be - // discarded. In our case, we need to create a new one since our call - // to `into_png` consumed the pixmap. + // dimension), since all previous pixels in the pixmap will be cleared by + // the default replace mode. In our case, we need to create a new one since + // our call to `into_png` consumed the pixmap. let mut pixmap_2 = Pixmap::new(100, 100); - ctx.render_to_pixmap(&mut resources, &mut pixmap_2); + ctx.render_with(&mut pixmap_2, &mut resources, rasterizer_settings); let png_2 = pixmap_2.into_png().unwrap(); std::fs::write("example_basic2.png", png_2).unwrap(); } diff --git a/sparse_strips/vello_cpu/examples/clipping.rs b/sparse_strips/vello_cpu/examples/clipping.rs index a9b4badc85..51076f7a59 100644 --- a/sparse_strips/vello_cpu/examples/clipping.rs +++ b/sparse_strips/vello_cpu/examples/clipping.rs @@ -128,7 +128,7 @@ fn main() { fn save_pixmap(ctx: &RenderContext, filename: &str) { let mut resources = Resources::new(); let mut pixmap = Pixmap::new(ctx.width(), ctx.height()); - ctx.render_to_pixmap(&mut resources, &mut pixmap); + ctx.render(&mut pixmap, &mut resources); let png = pixmap.into_png().unwrap(); std::fs::write(format!("{filename}.png"), png).unwrap(); } diff --git a/sparse_strips/vello_cpu/examples/masking.rs b/sparse_strips/vello_cpu/examples/masking.rs index 9f7b310138..b219b1da76 100644 --- a/sparse_strips/vello_cpu/examples/masking.rs +++ b/sparse_strips/vello_cpu/examples/masking.rs @@ -25,7 +25,7 @@ fn main() { mask_ctx.set_paint(RED); mask_ctx.fill_rect(&Rect::new(30.0, 30.0, 170.0, 170.0)); mask_ctx.flush(); - mask_ctx.render_to_pixmap(&mut mask_resources, &mut pixmap); + mask_ctx.render(&mut pixmap, &mut mask_resources); Mask::new_luminance(&pixmap) }; @@ -94,7 +94,7 @@ fn main() { fn save_pixmap(ctx: &RenderContext, filename: &str) { let mut resources = Resources::new(); let mut pixmap = Pixmap::new(ctx.width(), ctx.height()); - ctx.render_to_pixmap(&mut resources, &mut pixmap); + ctx.render(&mut pixmap, &mut resources); let png = pixmap.into_png().unwrap(); std::fs::write(format!("{filename}.png"), png).unwrap(); } diff --git a/sparse_strips/vello_cpu/examples/paints.rs b/sparse_strips/vello_cpu/examples/paints.rs index 3a764cd7e6..b0337b4a49 100644 --- a/sparse_strips/vello_cpu/examples/paints.rs +++ b/sparse_strips/vello_cpu/examples/paints.rs @@ -135,7 +135,7 @@ fn pattern() -> Image { fn save_pixmap(ctx: &RenderContext, filename: &str) { let mut resources = Resources::new(); let mut pixmap = Pixmap::new(ctx.width(), ctx.height()); - ctx.render_to_pixmap(&mut resources, &mut pixmap); + ctx.render(&mut pixmap, &mut resources); let png = pixmap.into_png().unwrap(); std::fs::write(format!("{filename}.png"), png).unwrap(); } diff --git a/sparse_strips/vello_cpu/examples/wasm_cpu/src/lib.rs b/sparse_strips/vello_cpu/examples/wasm_cpu/src/lib.rs index 3dfdb70dfd..5a38f8f0b8 100644 --- a/sparse_strips/vello_cpu/examples/wasm_cpu/src/lib.rs +++ b/sparse_strips/vello_cpu/examples/wasm_cpu/src/lib.rs @@ -72,9 +72,9 @@ impl AppState { self.scenes[self.current_scene].render(&mut self.renderer, self.transform); // Render the current scene with transform - self.renderer.render_to_pixmap( - self.scenes[self.current_scene].resources_mut(), + self.renderer.render( &mut self.pixmap, + self.scenes[self.current_scene].resources_mut(), ); let rgba_bytes = self.pixmap.data_as_u8_slice(); let image_data = web_sys::ImageData::new_with_u8_clamped_array_and_sh( diff --git a/sparse_strips/vello_cpu/examples/winit/src/main.rs b/sparse_strips/vello_cpu/examples/winit/src/main.rs index 69f35d2cf5..b53f6091bb 100644 --- a/sparse_strips/vello_cpu/examples/winit/src/main.rs +++ b/sparse_strips/vello_cpu/examples/winit/src/main.rs @@ -457,9 +457,9 @@ impl ApplicationHandler for App { self.scenes[self.current_scene].render(&mut self.renderer, self.transform); self.renderer.flush(); - self.renderer.render_to_pixmap( - self.scenes[self.current_scene].resources_mut(), + self.renderer.render( &mut self.pixmap, + self.scenes[self.current_scene].resources_mut(), ); // Copy pixmap to window surface diff --git a/sparse_strips/vello_cpu/src/dispatch/mod.rs b/sparse_strips/vello_cpu/src/dispatch/mod.rs index f9830f3621..798bcf9b43 100644 --- a/sparse_strips/vello_cpu/src/dispatch/mod.rs +++ b/sparse_strips/vello_cpu/src/dispatch/mod.rs @@ -5,7 +5,7 @@ pub(crate) mod multi_threaded; pub(crate) mod single_threaded; -use crate::RenderMode; +use crate::RasterizerSettings; use crate::kurbo::{Affine, BezPath, Rect, Stroke}; use crate::peniko::{BlendMode, Fill}; use core::fmt::Debug; @@ -14,6 +14,7 @@ use vello_common::encode::EncodedPaint; use vello_common::filter_effects::Filter; use vello_common::mask::Mask; use vello_common::paint::{ImageResolver, Paint}; +use vello_common::pixmap::PixmapMut; pub(crate) trait Dispatcher: Debug + Send + Sync { fn wide(&self) -> &Wide; @@ -72,23 +73,10 @@ pub(crate) trait Dispatcher: Debug + Send + Sync { fn flush(&mut self, encoded_paints: &[EncodedPaint]); fn rasterize( &self, - buffer: &mut [u8], - render_mode: RenderMode, - width: u16, - height: u16, - encoded_paints: &[EncodedPaint], - image_resolver: &dyn ImageResolver, - ); - fn composite_at_offset( - &self, - buffer: &mut [u8], - width: u16, - height: u16, - dst_x: u16, - dst_y: u16, - dst_buffer_width: u16, - dst_buffer_height: u16, - render_mode: RenderMode, + target: PixmapMut<'_>, + scene_width: u16, + scene_height: u16, + settings: RasterizerSettings, encoded_paints: &[EncodedPaint], image_resolver: &dyn ImageResolver, ); diff --git a/sparse_strips/vello_cpu/src/dispatch/multi_threaded.rs b/sparse_strips/vello_cpu/src/dispatch/multi_threaded.rs index a587225e05..541e30db25 100644 --- a/sparse_strips/vello_cpu/src/dispatch/multi_threaded.rs +++ b/sparse_strips/vello_cpu/src/dispatch/multi_threaded.rs @@ -1,7 +1,6 @@ // Copyright 2025 the Vello Authors // SPDX-License-Identifier: Apache-2.0 OR MIT -use crate::RenderMode; use crate::dispatch::Dispatcher; use crate::dispatch::multi_threaded::cost::{COST_THRESHOLD, estimate_render_task_cost}; use crate::dispatch::multi_threaded::worker::Worker; @@ -9,6 +8,7 @@ use crate::fine::{Fine, FineKernel}; use crate::kurbo::{Affine, BezPath, PathEl, Point, Rect, Stroke}; use crate::peniko::{BlendMode, Fill}; use crate::region::Regions; +use crate::{CompositeMode, RasterizerSettings}; use alloc::boxed::Box; use alloc::sync::Arc; use alloc::vec; @@ -29,6 +29,7 @@ use vello_common::filter_effects::Filter; use vello_common::geometry::RectU16; use vello_common::mask::Mask; use vello_common::paint::{ImageResolver, Paint}; +use vello_common::pixmap::PixmapMut; use vello_common::render_graph::RenderGraph; use vello_common::strip::Strip; use vello_common::strip_generator::StripGenerator; @@ -163,27 +164,29 @@ impl MultiThreadedDispatcher { #[cfg(feature = "f32_pipeline")] fn rasterize_f32( &self, - buffer: &mut [u8], - width: u16, - height: u16, + target: PixmapMut<'_>, + scene_width: u16, + scene_height: u16, + settings: RasterizerSettings, encoded_paints: &[EncodedPaint], image_resolver: &dyn ImageResolver, ) { use crate::fine::F32Kernel; - dispatch!(self.level, simd => self.rasterize_with::<_, F32Kernel>(simd, buffer, width, height, encoded_paints, image_resolver)); + dispatch!(self.level, simd => self.rasterize_with::<_, F32Kernel>(simd, target, scene_width, scene_height, settings, encoded_paints, image_resolver)); } #[cfg(feature = "u8_pipeline")] fn rasterize_u8( &self, - buffer: &mut [u8], - width: u16, - height: u16, + target: PixmapMut<'_>, + scene_width: u16, + scene_height: u16, + settings: RasterizerSettings, encoded_paints: &[EncodedPaint], image_resolver: &dyn ImageResolver, ) { use crate::fine::U8Kernel; - dispatch!(self.level, simd => self.rasterize_with::<_, U8Kernel>(simd, buffer, width, height, encoded_paints, image_resolver)); + dispatch!(self.level, simd => self.rasterize_with::<_, U8Kernel>(simd, target, scene_width, scene_height, settings, encoded_paints, image_resolver)); } fn init(&mut self) { @@ -363,13 +366,21 @@ impl MultiThreadedDispatcher { fn rasterize_with>( &self, simd: S, - buffer: &mut [u8], - width: u16, - height: u16, + mut target: PixmapMut<'_>, + scene_width: u16, + scene_height: u16, + settings: RasterizerSettings, encoded_paints: &[EncodedPaint], image_resolver: &dyn ImageResolver, ) { - let mut buffer = Regions::new(width, height, buffer); + let target_width = target.width(); + let target_height = target.height(); + let mut buffer = Regions::new_at_offset( + (scene_width, scene_height), + settings.offset, + (target_width, target_height), + target.data_mut(), + ); let fines = ThreadLocal::new(); let wide = &self.wide; let alpha_slots = self.alpha_storage.take(); @@ -386,7 +397,12 @@ impl MultiThreadedDispatcher { let wtile = wide.get(x, y); fine.set_coords(x, y); - fine.clear(wtile.bg); + if settings.composite_mode == CompositeMode::Replace || wtile.bg.is_opaque() { + fine.clear(wtile.bg); + } else { + // See the comment in the single-threaded dispatcher. + fine.unpack(region); + } for cmd in &wtile.cmds { let thread_idx = match cmd { Cmd::AlphaFill(a) => Some(wide.attrs.fill[a.attrs_idx as usize].thread_idx), @@ -584,10 +600,10 @@ impl Dispatcher for MultiThreadedDispatcher { fn rasterize( &self, - buffer: &mut [u8], - render_mode: RenderMode, - width: u16, - height: u16, + target: PixmapMut<'_>, + scene_width: u16, + scene_height: u16, + settings: RasterizerSettings, encoded_paints: &[EncodedPaint], image_resolver: &dyn ImageResolver, ) { @@ -596,45 +612,54 @@ impl Dispatcher for MultiThreadedDispatcher { // Only u8 pipeline enabled #[cfg(all(feature = "u8_pipeline", not(feature = "f32_pipeline")))] { - let _ = render_mode; - self.rasterize_u8(buffer, width, height, encoded_paints, image_resolver); + self.rasterize_u8( + target, + scene_width, + scene_height, + settings, + encoded_paints, + image_resolver, + ); } // Only f32 pipeline enabled #[cfg(all(feature = "f32_pipeline", not(feature = "u8_pipeline")))] { - let _ = render_mode; - self.rasterize_f32(buffer, width, height, encoded_paints, image_resolver); + self.rasterize_f32( + target, + scene_width, + scene_height, + settings, + encoded_paints, + image_resolver, + ); } // Both pipelines enabled #[cfg(all(feature = "f32_pipeline", feature = "u8_pipeline"))] - match render_mode { - RenderMode::OptimizeSpeed => { - self.rasterize_u8(buffer, width, height, encoded_paints, image_resolver); + match settings.render_mode { + crate::RenderMode::OptimizeSpeed => { + self.rasterize_u8( + target, + scene_width, + scene_height, + settings, + encoded_paints, + image_resolver, + ); } - RenderMode::OptimizeQuality => { - self.rasterize_f32(buffer, width, height, encoded_paints, image_resolver); + crate::RenderMode::OptimizeQuality => { + self.rasterize_f32( + target, + scene_width, + scene_height, + settings, + encoded_paints, + image_resolver, + ); } } } - fn composite_at_offset( - &self, - _buffer: &mut [u8], - _width: u16, - _height: u16, - _dst_x: u16, - _dst_y: u16, - _dst_buffer_width: u16, - _dst_buffer_height: u16, - _render_mode: RenderMode, - _encoded_paints: &[EncodedPaint], - _image_resolver: &dyn ImageResolver, - ) { - // TODO: Implement composite_at_offset for multi-threaded dispatcher. - unimplemented!("composite_at_offset is not implemented for multi-threaded dispatcher"); - } - fn push_clip_path( &mut self, path: &BezPath, diff --git a/sparse_strips/vello_cpu/src/dispatch/single_threaded.rs b/sparse_strips/vello_cpu/src/dispatch/single_threaded.rs index b0053cead1..ef176bd943 100644 --- a/sparse_strips/vello_cpu/src/dispatch/single_threaded.rs +++ b/sparse_strips/vello_cpu/src/dispatch/single_threaded.rs @@ -1,13 +1,13 @@ // Copyright 2025 the Vello Authors // SPDX-License-Identifier: Apache-2.0 OR MIT -use crate::RenderMode; use crate::dispatch::Dispatcher; use crate::fine::{Fine, FineKernel}; use crate::kurbo::{Affine, BezPath, Rect, Stroke}; use crate::layer_manager::LayerManager; use crate::peniko::{BlendMode, Fill}; use crate::region::Regions; +use crate::{CompositeMode, RasterizerSettings}; use vello_common::clip::ClipContext; use vello_common::coarse::{Cmd, LayerKind, MODE_CPU, Wide, WideTilesBbox}; use vello_common::color::palette::css::TRANSPARENT; @@ -16,7 +16,7 @@ use vello_common::fearless_simd::{Level, Simd}; use vello_common::filter_effects::Filter; use vello_common::mask::Mask; use vello_common::paint::{ImageResolver, Paint, PremulColor}; -use vello_common::pixmap::Pixmap; +use vello_common::pixmap::{Pixmap, PixmapMut}; use vello_common::render_graph::{RenderGraph, RenderNodeKind}; use vello_common::strip_generator::{StripGenerator, StripStorage}; @@ -89,15 +89,16 @@ impl SingleThreadedDispatcher { #[cfg(feature = "f32_pipeline")] fn rasterize_f32( &self, - buffer: &mut [u8], - width: u16, - height: u16, + target: PixmapMut<'_>, + scene_width: u16, + scene_height: u16, + settings: RasterizerSettings, encoded_paints: &[EncodedPaint], image_resolver: &dyn ImageResolver, ) { use crate::fine::F32Kernel; use vello_common::fearless_simd::dispatch; - dispatch!(self.level, simd => self.rasterize_with::<_, F32Kernel>(simd, buffer, width, height, encoded_paints, image_resolver)); + dispatch!(self.level, simd => self.rasterize_with::<_, F32Kernel>(simd, target, scene_width, scene_height, settings, encoded_paints, image_resolver)); } /// Rasterizes the scene using u8 precision (fast). @@ -107,15 +108,16 @@ impl SingleThreadedDispatcher { #[cfg(feature = "u8_pipeline")] fn rasterize_u8( &self, - buffer: &mut [u8], - width: u16, - height: u16, + target: PixmapMut<'_>, + scene_width: u16, + scene_height: u16, + settings: RasterizerSettings, encoded_paints: &[EncodedPaint], image_resolver: &dyn ImageResolver, ) { use crate::fine::U8Kernel; use vello_common::fearless_simd::dispatch; - dispatch!(self.level, simd => self.rasterize_with::<_, U8Kernel>(simd, buffer, width, height, encoded_paints, image_resolver)); + dispatch!(self.level, simd => self.rasterize_with::<_, U8Kernel>(simd, target, scene_width, scene_height, settings, encoded_paints, image_resolver)); } // Note: We purposefully don't add `vectorize` to each of the functions @@ -133,9 +135,10 @@ impl SingleThreadedDispatcher { fn rasterize_with>( &self, simd: S, - buffer: &mut [u8], - width: u16, - height: u16, + target: PixmapMut<'_>, + scene_width: u16, + scene_height: u16, + settings: RasterizerSettings, encoded_paints: &[EncodedPaint], image_resolver: &dyn ImageResolver, ) { @@ -145,9 +148,10 @@ impl SingleThreadedDispatcher { // Use filter-aware path that maintains layer buffers for filter effects. self.rasterize_with_filters::( simd, - buffer, - width, - height, + target, + scene_width, + scene_height, + settings, encoded_paints, image_resolver, &mut layer_manager, @@ -156,9 +160,10 @@ impl SingleThreadedDispatcher { // Use simple direct rasterization for scenes without filters. self.rasterize_simple::( simd, - buffer, - width, - height, + target, + scene_width, + scene_height, + settings, encoded_paints, image_resolver, ); @@ -178,9 +183,10 @@ impl SingleThreadedDispatcher { fn rasterize_with_filters>( &self, simd: S, - buffer: &mut [u8], - width: u16, - height: u16, + mut target: PixmapMut<'_>, + scene_width: u16, + scene_height: u16, + settings: RasterizerSettings, encoded_paints: &[EncodedPaint], image_resolver: &dyn ImageResolver, layer_manager: &mut LayerManager, @@ -218,7 +224,7 @@ impl SingleThreadedDispatcher { x, y, *layer_id, - PremulColor::from_alpha_color(TRANSPARENT), + Some(PremulColor::from_alpha_color(TRANSPARENT)), layer_manager, encoded_paints, image_resolver, @@ -248,16 +254,34 @@ impl SingleThreadedDispatcher { wtile_bbox: _, } => { // Final composition directly to output buffer. - let mut regions = Regions::new(width, height, buffer); + let target_width = target.width(); + let target_height = target.height(); + let mut regions = Regions::new_at_offset( + (scene_width, scene_height), + settings.offset, + (target_width, target_height), + target.data_mut(), + ); regions.update_regions(|region| { // Use the background color from the wide tile. let bg = self.wide.get(region.x, region.y).bg; + let clear_color = if settings.composite_mode == CompositeMode::Replace + || bg.is_opaque() + { + Some(bg) + } else { + // See the comment in `rasterize_simple`. + fine.set_coords(region.x, region.y); + fine.unpack(region); + + None + }; self.process_layer_tile( &mut fine, region.x, region.y, *layer_id, - bg, + clear_color, layer_manager, encoded_paints, image_resolver, @@ -298,14 +322,16 @@ impl SingleThreadedDispatcher { x: u16, y: u16, layer_id: u32, - clear_color: PremulColor, + clear_color: Option, layer_manager: &mut LayerManager, encoded_paints: &[EncodedPaint], image_resolver: &dyn ImageResolver, ) { let wtile = &self.wide.get(x, y); fine.set_coords(x, y); - fine.clear(clear_color); + if let Some(clear_color) = clear_color { + fine.clear(clear_color); + } // Process all commands in this layer's render range. // It can happen that the layer has no associated ranges in this wide tile in @@ -396,13 +422,21 @@ impl SingleThreadedDispatcher { fn rasterize_simple>( &self, simd: S, - buffer: &mut [u8], - width: u16, - height: u16, + mut target: PixmapMut<'_>, + scene_width: u16, + scene_height: u16, + settings: RasterizerSettings, encoded_paints: &[EncodedPaint], image_resolver: &dyn ImageResolver, ) { - let mut regions = Regions::new(width, height, buffer); + let target_width = target.width(); + let target_height = target.height(); + let mut regions = Regions::new_at_offset( + (scene_width, scene_height), + settings.offset, + (target_width, target_height), + target.data_mut(), + ); let mut fine = Fine::::new(simd); regions.update_regions(|region| { @@ -412,8 +446,15 @@ impl SingleThreadedDispatcher { let wtile = self.wide.get(x, y); fine.set_coords(x, y); - // Clear to background and process all commands in order. - fine.clear(wtile.bg); + if settings.composite_mode == CompositeMode::Replace || wtile.bg.is_opaque() { + fine.clear(wtile.bg); + } else { + // If the background is not opaque, it means that it is _fully_ transparent + // (the background mechanism doesn't trigger for partially-transparent paints). + // Therefore, we never need any background color and just need to unpack + // to implement the src-over semantics. + fine.unpack(region); + } for cmd in &wtile.cmds { fine.run_cmd( cmd, @@ -432,99 +473,6 @@ impl SingleThreadedDispatcher { fn has_filters(&self) -> bool { self.render_graph.has_filters() } - - /// Composites at an offset using f32 precision (high quality). - #[cfg(feature = "f32_pipeline")] - fn composite_at_offset_f32( - &self, - buffer: &mut [u8], - width: u16, - height: u16, - dst_x: u16, - dst_y: u16, - dst_buffer_width: u16, - dst_buffer_height: u16, - encoded_paints: &[EncodedPaint], - image_resolver: &dyn ImageResolver, - ) { - use crate::fine::F32Kernel; - use vello_common::fearless_simd::dispatch; - dispatch!(self.level, simd => self.composite_at_offset_with::<_, F32Kernel>( - simd, buffer, width, height, dst_x, dst_y, dst_buffer_width, dst_buffer_height, encoded_paints, image_resolver - )); - } - - /// Composites at an offset using u8 precision (fast). - #[cfg(feature = "u8_pipeline")] - fn composite_at_offset_u8( - &self, - buffer: &mut [u8], - width: u16, - height: u16, - dst_x: u16, - dst_y: u16, - dst_buffer_width: u16, - dst_buffer_height: u16, - encoded_paints: &[EncodedPaint], - image_resolver: &dyn ImageResolver, - ) { - use crate::fine::U8Kernel; - use vello_common::fearless_simd::dispatch; - dispatch!(self.level, simd => self.composite_at_offset_with::<_, U8Kernel>( - simd, buffer, width, height, dst_x, dst_y, dst_buffer_width, dst_buffer_height, encoded_paints, image_resolver - )); - } - - /// Core implementation for compositing at an offset. - /// - /// Composites tiles sequentially, writing directly to the destination buffer - /// at the specified offset. - fn composite_at_offset_with>( - &self, - simd: S, - buffer: &mut [u8], - width: u16, - height: u16, - dst_x: u16, - dst_y: u16, - dst_buffer_width: u16, - dst_buffer_height: u16, - encoded_paints: &[EncodedPaint], - image_resolver: &dyn ImageResolver, - ) { - let mut regions = Regions::new_at_offset( - width, - height, - dst_x, - dst_y, - dst_buffer_width, - dst_buffer_height, - buffer, - ); - let mut fine = Fine::::new(simd); - - regions.update_regions(|region| { - let x = region.x; - let y = region.y; - - let wtile = self.wide.get(x, y); - fine.set_coords(x, y); - - // Unpack existing pixel data from the region instead of clearing, - // so that rendering composites onto the existing pixmap contents. - fine.unpack(region); - for cmd in &wtile.cmds { - fine.run_cmd( - cmd, - &self.strip_storage.alphas, - encoded_paints, - image_resolver, - &self.wide.attrs, - ); - } - fine.pack(region); - }); - } } impl Dispatcher for SingleThreadedDispatcher { @@ -703,124 +651,60 @@ impl Dispatcher for SingleThreadedDispatcher { fn rasterize( &self, - buffer: &mut [u8], - render_mode: RenderMode, - width: u16, - height: u16, + target: PixmapMut<'_>, + scene_width: u16, + scene_height: u16, + settings: RasterizerSettings, encoded_paints: &[EncodedPaint], image_resolver: &dyn ImageResolver, ) { - // If only the u8 pipeline is enabled, then use it + // If only the u8 pipeline is enabled, then use it. #[cfg(all(feature = "u8_pipeline", not(feature = "f32_pipeline")))] { - let _ = render_mode; - self.rasterize_u8(buffer, width, height, encoded_paints, image_resolver); - } - - // If only the f32 pipeline is enabled, then use it - #[cfg(all(feature = "f32_pipeline", not(feature = "u8_pipeline")))] - { - let _ = render_mode; - self.rasterize_f32(buffer, width, height, encoded_paints, image_resolver); - } - - // If both pipelines are enabled, select precision based on render mode parameter. - #[cfg(all(feature = "u8_pipeline", feature = "f32_pipeline"))] - match render_mode { - RenderMode::OptimizeSpeed => { - // Use u8 precision for faster rendering. - self.rasterize_u8(buffer, width, height, encoded_paints, image_resolver); - } - RenderMode::OptimizeQuality => { - // Use f32 precision for higher quality. - self.rasterize_f32(buffer, width, height, encoded_paints, image_resolver); - } - } - - #[cfg(all(not(feature = "u8_pipeline"), not(feature = "f32_pipeline")))] - { - // This case never gets hit because there is a compile_error in the root. - // But have this code disables some warnings and makes the compile error easier to read - let _ = ( - buffer, - render_mode, - width, - height, - encoded_paints, - image_resolver, - ); - } - } - - fn composite_at_offset( - &self, - buffer: &mut [u8], - width: u16, - height: u16, - dst_x: u16, - dst_y: u16, - dst_buffer_width: u16, - dst_buffer_height: u16, - render_mode: RenderMode, - encoded_paints: &[EncodedPaint], - image_resolver: &dyn ImageResolver, - ) { - #[cfg(all(feature = "u8_pipeline", not(feature = "f32_pipeline")))] - { - let _ = render_mode; - self.composite_at_offset_u8( - buffer, - width, - height, - dst_x, - dst_y, - dst_buffer_width, - dst_buffer_height, + self.rasterize_u8( + target, + scene_width, + scene_height, + settings, encoded_paints, image_resolver, ); } + // If only the f32 pipeline is enabled, then use it. #[cfg(all(feature = "f32_pipeline", not(feature = "u8_pipeline")))] { - let _ = render_mode; - self.composite_at_offset_f32( - buffer, - width, - height, - dst_x, - dst_y, - dst_buffer_width, - dst_buffer_height, + self.rasterize_f32( + target, + scene_width, + scene_height, + settings, encoded_paints, image_resolver, ); } + // If both pipelines are enabled, select precision based on render mode parameter. #[cfg(all(feature = "u8_pipeline", feature = "f32_pipeline"))] - match render_mode { - RenderMode::OptimizeSpeed => { - self.composite_at_offset_u8( - buffer, - width, - height, - dst_x, - dst_y, - dst_buffer_width, - dst_buffer_height, + match settings.render_mode { + crate::RenderMode::OptimizeSpeed => { + // Use u8 precision for faster rendering. + self.rasterize_u8( + target, + scene_width, + scene_height, + settings, encoded_paints, image_resolver, ); } - RenderMode::OptimizeQuality => { - self.composite_at_offset_f32( - buffer, - width, - height, - dst_x, - dst_y, - dst_buffer_width, - dst_buffer_height, + crate::RenderMode::OptimizeQuality => { + // Use f32 precision for higher quality. + self.rasterize_f32( + target, + scene_width, + scene_height, + settings, encoded_paints, image_resolver, ); @@ -829,15 +713,13 @@ impl Dispatcher for SingleThreadedDispatcher { #[cfg(all(not(feature = "u8_pipeline"), not(feature = "f32_pipeline")))] { + // This case never gets hit because there is a compile_error in the root. + // But have this code disables some warnings and makes the compile error easier to read let _ = ( - buffer, - width, - height, - dst_x, - dst_y, - dst_buffer_width, - dst_buffer_height, - render_mode, + target, + scene_width, + scene_height, + settings, encoded_paints, image_resolver, ); diff --git a/sparse_strips/vello_cpu/src/lib.rs b/sparse_strips/vello_cpu/src/lib.rs index 11299b2912..6cf3641b84 100644 --- a/sparse_strips/vello_cpu/src/lib.rs +++ b/sparse_strips/vello_cpu/src/lib.rs @@ -13,15 +13,15 @@ //! //! To use Vello CPU, you need to: //! -//! - Create a [`RenderContext`][], a 2D drawing context for a fixed-size target area. +//! - Create a [`RenderContext`][], a 2D drawing context for a fixed-size scene area. //! - For each object in your scene: //! - Set how the object will be painted, using [`set_paint`][RenderContext::set_paint]. //! - Set the shape to be drawn for that object, using methods like [`fill_path`][RenderContext::fill_path], //! [`stroke_path`][RenderContext::stroke_path], or [`glyph_run`][RenderContext::glyph_run]. -//! - Render it to an image using [`RenderContext::render_to_pixmap`][]. +//! - Render it to an image using [`RenderContext::render`][]. //! //! ```rust -//! use vello_cpu::{RenderContext, Resources, Pixmap, RenderMode}; +//! use vello_cpu::{RenderContext, Resources, Pixmap}; //! use vello_cpu::{color::{palette::css, PremulRgba8}, kurbo::Rect}; //! let width = 10; //! let height = 5; @@ -34,7 +34,7 @@ //! // While calling `flush` is only strictly necessary if you are rendering using //! // multiple threads, it is recommended to always do this. //! context.flush(); -//! context.render_to_pixmap(&mut resources, &mut target); +//! context.render(&mut target, &mut resources); //! //! let expected_render = b"\ //! 0000000000\ @@ -161,7 +161,9 @@ pub mod layer_manager; #[doc(hidden)] pub mod region; -pub use render::{RenderContext, RenderSettings, Resources}; +pub use render::{ + CompositeMode, PixelFormat, RasterizerSettings, RenderContext, RenderSettings, Resources, +}; // Note: The first one is not something that should be // exposed, but is currently needed by vello_sparse_tests. #[cfg(feature = "text")] @@ -171,12 +173,12 @@ pub use text::{CpuGlyphRunBackend, GlyphRunBuilder}; pub use vello_common::fearless_simd::Level; pub use vello_common::mask::Mask; pub use vello_common::paint::{Image, ImageSource, Paint, PaintType}; -pub use vello_common::pixmap::Pixmap; +pub use vello_common::pixmap::{Pixmap, PixmapMut}; pub use vello_common::{color, kurbo, peniko}; /// The selected rendering mode. /// For using [`RenderMode::OptimizeQuality`] you also need to enable `f32_pipeline` feature. -#[derive(Copy, Clone, Debug, Default)] +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] pub enum RenderMode { /// Optimize speed (by performing calculations with u8/16). #[default] diff --git a/sparse_strips/vello_cpu/src/region.rs b/sparse_strips/vello_cpu/src/region.rs index 00e78062ad..89e5215bfc 100644 --- a/sparse_strips/vello_cpu/src/region.rs +++ b/sparse_strips/vello_cpu/src/region.rs @@ -17,32 +17,29 @@ pub struct Regions<'a> { impl<'a> Regions<'a> { /// Creates regions from a buffer where the buffer dimensions match the render dimensions. pub fn new(width: u16, height: u16, buffer: &'a mut [u8]) -> Self { - Self::new_at_offset(width, height, 0, 0, width, height, buffer) + Self::new_at_offset((width, height), (0, 0), (width, height), buffer) } /// Creates regions from a buffer at a specific offset. /// /// This is used for rendering to a sub-region of a larger buffer. The regions - /// cover the area of size (`width` × `height`) placed at pixel offset - /// (`dst_x`, `dst_y`) in the destination buffer. + /// cover the scene area placed at the given pixel offset in the destination buffer. /// /// # Arguments - /// * `width` - Width of the content being rendered - /// * `height` - Height of the content being rendered - /// * `dst_x` - X offset in the destination buffer - /// * `dst_y` - Y offset in the destination buffer - /// * `dst_buffer_width` - Total width of the destination buffer - /// * `dst_buffer_height` - Total height of the destination buffer + /// * `scene_size` - Size of the content being rendered + /// * `dst_offset` - Offset in the destination buffer + /// * `dst_size` - Total size of the destination buffer /// * `buffer` - The destination buffer (RGBA, 4 bytes per pixel) pub fn new_at_offset( - width: u16, - height: u16, - dst_x: u16, - dst_y: u16, - dst_buffer_width: u16, - dst_buffer_height: u16, + scene_size: (u16, u16), + dst_offset: (u16, u16), + dst_size: (u16, u16), mut buffer: &'a mut [u8], ) -> Self { + let (width, height) = scene_size; + let (dst_x, dst_y) = dst_offset; + let (dst_buffer_width, dst_buffer_height) = dst_size; + // Calculate effective render area (clamped to destination bounds) let effective_width = width.min(dst_buffer_width.saturating_sub(dst_x)) as usize; let effective_height = height.min(dst_buffer_height.saturating_sub(dst_y)) as usize; diff --git a/sparse_strips/vello_cpu/src/render.rs b/sparse_strips/vello_cpu/src/render.rs index 2ab2e9f77a..32aa32d6d8 100644 --- a/sparse_strips/vello_cpu/src/render.rs +++ b/sparse_strips/vello_cpu/src/render.rs @@ -28,7 +28,7 @@ use vello_common::mask::Mask; use vello_common::paint::{ImageId, ImageResolver, Paint, PaintType, Tint}; use vello_common::peniko::color::palette::css::BLACK; use vello_common::peniko::{BlendMode, Fill}; -use vello_common::pixmap::Pixmap; +use vello_common::pixmap::{Pixmap, PixmapMut}; use vello_common::render_state::RenderState; use vello_common::util::is_axis_aligned; @@ -71,9 +71,12 @@ impl Resources { Self::default() } - pub(crate) fn before_render(&mut self) { + pub(crate) fn before_render(&mut self, render_mode: RenderMode) { #[cfg(feature = "text")] - self.prepare_glyph_cache(); + self.prepare_glyph_cache(render_mode); + + #[cfg(not(feature = "text"))] + let _ = render_mode; } pub(crate) fn after_render(&mut self) { @@ -82,6 +85,62 @@ impl Resources { } } +/// The composition mode that should be used when rendering into a pixmap. +/// +/// For performance reason it is _highly_ recommended that you use `CompositeMode::Replace`, even +/// if you know that the pixmap is already cleared. Only use `SrcOver` if you really have to +/// preserve existing contents. +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +pub enum CompositeMode { + /// Clear the destination pixmap and render the scene into it. + #[default] + Replace, + /// Render the scene into the pixmap using src-over compositing. + SrcOver, +} + +/// The pixel format to assume for the destination pixmap. +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +pub enum PixelFormat { + /// Premultiplied RGBA8. + #[default] + Rgba8, +} + +/// Settings used when rasterizing a scene into a pixmap. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct RasterizerSettings { + /// Whether to prioritize speed or quality when rendering. + /// + /// For most cases (especially for real-time rendering), it is highly recommended to set + /// this to [`RenderMode::OptimizeSpeed`]. If color accuracy is a more significant concern, + /// then you can set this to [`RenderMode::OptimizeQuality`]. + /// + /// Currently, the only difference this makes is that when choosing [`RenderMode::OptimizeSpeed`], + /// rasterization will happen using u8/u16, + /// while [`RenderMode::OptimizeQuality`] will use a f32-based pipeline. + pub render_mode: RenderMode, + /// How rendered content is composited into the destination. + pub composite_mode: CompositeMode, + /// Pixel format of the destination. + pub pixel_format: PixelFormat, + /// Offset in destination pixels where the render context origin is placed. + /// + /// See [`RenderContext::render_with`] for more information. + pub offset: (u16, u16), +} + +impl Default for RasterizerSettings { + fn default() -> Self { + Self { + render_mode: RenderMode::OptimizeSpeed, + composite_mode: CompositeMode::Replace, + pixel_format: PixelFormat::Rgba8, + offset: (0, 0), + } + } +} + /// A render context for CPU-based 2D graphics rendering. /// /// This is the main entry point for drawing operations. It maintains the current @@ -119,15 +178,6 @@ pub struct RenderSettings { /// The number of worker threads that should be used for rendering. Only has an effect /// if the `multithreading` feature is active. pub num_threads: u16, - /// Whether to prioritize speed or quality when rendering. - /// - /// For most cases (especially for real-time rendering), it is highly recommended to set - /// this to `OptimizeSpeed`. If accuracy is a more significant concern (for example for visual - /// regression testing), then you can set this to `OptimizeQuality`. - /// - /// Currently, the only difference this makes is that when choosing `OptimizeSpeed`, rasterization - /// will happen using u8/u16, while `OptimizeQuality` will use a f32-based pipeline. - pub render_mode: RenderMode, } impl Default for RenderSettings { @@ -142,7 +192,6 @@ impl Default for RenderSettings { .min(8), #[cfg(not(feature = "multithreading"))] num_threads: 0, - render_mode: RenderMode::OptimizeSpeed, } } } @@ -622,35 +671,73 @@ impl RenderContext { self.dispatcher.flush(&self.encoded_paints); } - /// Render the current context into a buffer. - /// The buffer is expected to be in premultiplied RGBA8 format with length `width * height * 4` - pub fn render_to_buffer( + /// Render the current context into a target using default rasterizer settings. + /// + /// See the documentation of [`RenderContext::render_with`] for more information. + pub fn render<'a>(&self, target: impl Into>, resources: &mut Resources) { + self.render_with(target, resources, RasterizerSettings::default()); + } + + /// Render the current context into a target using custom rasterizer settings. + /// + /// See the documentation of [`RasterizerSettings`] to understand the tunable parameters for + /// rasterization. + /// + /// There is an important note to make about render sizes. [`RenderContext`] can be configured with + /// a specific width/height, but so can [`Pixmap`]. In the vast majority of cases, you will simply + /// want to configure them both to have the same size. However, it _is_ very much possible for them + /// to have different sizes, which can be useful in certain situations. In principle, the size + /// that you specify when creating a [`RenderContext`] defines the bound of the scene itself. Any + /// content that is to the top/left of (0, 0) and to the right/bottom of (width/height) will be + /// removed. However, the offset in [`RasterizerSettings`] as well as the width/height of + /// the [`PixmapMut`] define at which location the scene will be rasterized into, and allows + /// for further clipping certain parts of the scene away. The semantics are defined as follows: + /// + /// 1. [`RasterizerSettings::offset`] defines the where the top-left corner will be positioned + /// on the pixmap, assuming a y-down coordinate system. In most cases (0, 0) will be the + /// appropriate choice, but other values are certainly sensible. For example, if you want to + /// implement a custom glyph-atlas, you can construct the scene assuming (0, 0) as the origin + /// and then position the glyphs at rasterization time using this feature. + /// + /// 2. In case the pixmap width/height is larger than the offset plus the width/height of the + /// [`RenderContext`], any remaining rows/columns are simply treated as padding (**however**, + /// when using [`CompositeMode::Replace`], then the _whole_ destination pixmap will + /// be cleared, not just the area covered by the scene). One potential reason for doing this + /// is that certain platforms, for example macOS, require a specific byte stride for buffers. + /// For example, let's say that a byte stride of 128 is imposed by the platform, but the actual + /// size of the scene you are drawing is only 20x20. In this case, you can create a pixmap + /// of size 32x20, and the last 12 columns are essentially treated as padding. + /// + /// 3. In case the width/height of the pixmap is _smaller_ than the offset + width/height of the + /// scene, then anything that exceeds the pixmap boundaries is simply cut off. This can be useful + /// if for some reason you only want to rasterize a small cut-out of the original scene. + pub fn render_with<'a>( &self, + target: impl Into>, resources: &mut Resources, - buffer: &mut [u8], - width: u16, - height: u16, - render_mode: RenderMode, + settings: RasterizerSettings, ) { // TODO: Maybe we should move those checks into the dispatcher. let wide = self.dispatcher.wide(); assert!(!wide.has_layers(), "some layers haven't been popped yet"); - assert_eq!( - buffer.len(), - (width as usize) * (height as usize) * 4, - "provided width ({}) and height ({}) do not match buffer size ({})", - width, - height, - buffer.len(), - ); - resources.before_render(); + resources.before_render(settings.render_mode); + let mut target = target.into(); + let target_fully_covered = settings.offset == (0, 0) + && self.width >= target.width() + && self.height >= target.height(); + // If the scene covers the whole pixmap than packing will take care + // of clearing everything anyway, so no reason to clear it explicitly + // here. + if settings.composite_mode == CompositeMode::Replace && !target_fully_covered { + target.data_mut().fill(0); + } self.dispatcher.rasterize( - buffer, - render_mode, - width, - height, + target, + self.width, + self.height, + settings, &self.encoded_paints, &resources.image_registry, ); @@ -662,65 +749,12 @@ impl RenderContext { resources.after_render(); } - /// Render the current context into a pixmap. - pub fn render_to_pixmap(&self, resources: &mut Resources, pixmap: &mut Pixmap) { - let width = pixmap.width(); - let height = pixmap.height(); - self.render_to_buffer( - resources, - pixmap.data_as_u8_slice_mut(), - width, - height, - self.render_settings.render_mode, - ); - } - - /// Composite the current context into a region of a pixmap. - /// - /// The context's content (sized `self.width × self.height`) is composited - /// directly to the destination pixmap starting at `(dst_x, dst_y)`. - /// If the region extends beyond the pixmap bounds, it is clipped. - /// - /// Unlike [`render_to_pixmap`](Self::render_to_pixmap), this method composites on top of - /// existing pixmap content rather than clearing it first, allowing multiple - /// renders to accumulate. - /// - /// This is useful for rendering individual elements (like glyphs) into - /// a spritesheet at specific coordinates. - /// - /// # Panics - /// - /// This method is only supported with the single-threaded dispatcher and will - /// **panic** if called on a `RenderContext` using the multi-threaded dispatcher. - pub fn composite_to_pixmap_at_offset( - &self, - resources: &Resources, - pixmap: &mut Pixmap, - dst_x: u16, - dst_y: u16, - ) { - let dst_buffer_width = pixmap.width(); - let dst_buffer_height = pixmap.height(); - self.dispatcher.composite_at_offset( - pixmap.data_as_u8_slice_mut(), - self.width, - self.height, - dst_x, - dst_y, - dst_buffer_width, - dst_buffer_height, - self.render_settings.render_mode, - &self.encoded_paints, - &resources.image_registry, - ); - } - - /// Return the width of the pixmap. + /// Return the width of the scene. pub fn width(&self) -> u16 { self.width } - /// Return the height of the pixmap. + /// Return the height of the scene. pub fn height(&self) -> u16 { self.height } @@ -840,16 +874,55 @@ impl ImageResolver for ImageRegistry { #[cfg(test)] mod tests { - use crate::RenderContext; #[cfg(feature = "text")] use crate::peniko::{Blob, FontData}; + use crate::{CompositeMode, RasterizerSettings, RenderContext, Resources}; #[cfg(feature = "text")] use alloc::sync::Arc; + use alloc::vec; #[cfg(feature = "text")] use glifo::Glyph; + use vello_common::color::PremulRgba8; + use vello_common::color::palette::css::{BLUE, RED}; use vello_common::kurbo::{Rect, Shape}; + use vello_common::pixmap::{Pixmap, PixmapMut}; use vello_common::tile::Tile; + const GRAY: PremulRgba8 = PremulRgba8 { + r: 9, + g: 10, + b: 11, + a: 255, + }; + + fn red_pixel() -> PremulRgba8 { + RED.premultiply().to_rgba8() + } + + fn blue_pixel() -> PremulRgba8 { + BLUE.premultiply().to_rgba8() + } + + fn transparent_pixel() -> PremulRgba8 { + PremulRgba8::from_u32(0) + } + + fn solid_pixmap(width: u16, height: u16, color: PremulRgba8) -> Pixmap { + Pixmap::from_parts( + vec![color; usize::from(width) * usize::from(height)], + width, + height, + ) + } + + fn red_rect_context(width: u16, height: u16, rect: Rect) -> RenderContext { + let mut ctx = RenderContext::new(width, height); + ctx.set_paint(RED); + ctx.fill_rect(&rect); + ctx.flush(); + ctx + } + #[test] fn clip_overflow() { let mut ctx = RenderContext::new(100, 100); @@ -863,27 +936,196 @@ mod tests { ctx.flush(); } + #[test] + fn render_with_offset_clears_pixels_outside_scene() { + let ctx = red_rect_context(2, 2, Rect::new(0.0, 0.0, 2.0, 2.0)); + let mut resources = Resources::new(); + let mut pixmap = solid_pixmap(4, 3, GRAY); + + ctx.render_with( + &mut pixmap, + &mut resources, + RasterizerSettings { + offset: (1, 1), + ..Default::default() + }, + ); + + for y in 0..3 { + for x in 0..4 { + let expected = if (1..=2).contains(&x) && (1..=2).contains(&y) { + red_pixel() + } else { + transparent_pixel() + }; + + assert_eq!(pixmap.sample(x, y), expected, "pixel at ({x}, {y})"); + } + } + } + + #[test] + fn render_clips_scene_to_target_bounds() { + let ctx = red_rect_context(3, 3, Rect::new(0.0, 0.0, 3.0, 3.0)); + let mut resources = Resources::new(); + let mut pixmap = solid_pixmap(4, 4, GRAY); + + ctx.render_with( + &mut pixmap, + &mut resources, + RasterizerSettings { + offset: (2, 1), + ..Default::default() + }, + ); + + for y in 0..4 { + for x in 0..4 { + let expected = if (2..=3).contains(&x) && (1..=3).contains(&y) { + red_pixel() + } else { + transparent_pixel() + }; + assert_eq!(pixmap.sample(x, y), expected, "pixel at ({x}, {y})"); + } + } + } + + #[test] + fn render_into_padded_pixmap() { + let ctx = red_rect_context(2, 2, Rect::new(0.0, 0.0, 2.0, 2.0)); + let mut resources = Resources::new(); + let mut pixmap = solid_pixmap(4, 2, GRAY); + + ctx.render(&mut pixmap, &mut resources); + + for y in 0..2 { + for x in 0..4 { + let expected = if x < 2 { + red_pixel() + } else { + transparent_pixel() + }; + assert_eq!(pixmap.sample(x, y), expected, "pixel at ({x}, {y})"); + } + } + } + + #[test] + fn render_into_raw_buffer() { + let ctx = red_rect_context(2, 1, Rect::new(0.0, 0.0, 2.0, 1.0)); + let mut resources = Resources::new(); + let mut buffer = vec![0; 3 * 2 * 4]; + for pixel in buffer.chunks_exact_mut(4) { + pixel.copy_from_slice(&[GRAY.r, GRAY.g, GRAY.b, GRAY.a]); + } + + { + let pixmap = PixmapMut::new(3, 2, &mut buffer).unwrap(); + ctx.render_with( + pixmap, + &mut resources, + RasterizerSettings { + offset: (1, 1), + ..Default::default() + }, + ); + } + + let expected = [ + transparent_pixel(), + transparent_pixel(), + transparent_pixel(), + transparent_pixel(), + red_pixel(), + red_pixel(), + ]; + for (pixel, expected) in buffer.chunks_exact(4).zip(expected) { + assert_eq!(pixel, [expected.r, expected.g, expected.b, expected.a]); + } + } + + #[test] + fn pixmap_mut_validates_buffer_length() { + let mut short_buffer = vec![0; 3 * 2 * 4 - 1]; + assert!(PixmapMut::new(3, 2, &mut short_buffer).is_none()); + + let mut exact_buffer = vec![0; 3 * 2 * 4]; + assert!(PixmapMut::new(3, 2, &mut exact_buffer).is_some()); + } + + #[test] + fn render_src_over_opaque() { + let ctx = red_rect_context(2, 1, Rect::new(0.0, 0.0, 1.0, 1.0)); + let mut resources = Resources::new(); + let mut pixmap = solid_pixmap(2, 1, blue_pixel()); + + ctx.render_with( + &mut pixmap, + &mut resources, + RasterizerSettings { + composite_mode: CompositeMode::SrcOver, + ..Default::default() + }, + ); + + assert_eq!(pixmap.sample(0, 0), red_pixel()); + assert_eq!(pixmap.sample(1, 0), blue_pixel()); + } + + #[test] + fn render_src_over_transparent() { + let mut ctx = RenderContext::new(1, 1); + ctx.set_paint(RED.with_alpha(0.5)); + ctx.fill_rect(&Rect::new(0.0, 0.0, 1.0, 1.0)); + ctx.flush(); + + let mut resources = Resources::new(); + let mut pixmap = solid_pixmap(1, 1, blue_pixel()); + + ctx.render_with( + &mut pixmap, + &mut resources, + RasterizerSettings { + composite_mode: CompositeMode::SrcOver, + ..Default::default() + }, + ); + + assert_eq!( + pixmap.sample(0, 0), + PremulRgba8 { + r: 128, + g: 0, + b: 127, + a: 255, + } + ); + } + #[cfg(feature = "multithreading")] #[test] fn multithreaded_crash_after_reset() { - use crate::{Level, RenderMode, RenderSettings}; - use vello_common::pixmap::Pixmap; + use crate::{Level, RasterizerSettings, RenderMode, RenderSettings}; let mut pixmap = Pixmap::new(200, 200); let settings = RenderSettings { level: Level::try_detect().unwrap_or(Level::baseline()), num_threads: 1, + }; + let rasterizer_settings = RasterizerSettings { render_mode: RenderMode::OptimizeQuality, + ..Default::default() }; - let mut resources = crate::Resources::new(); + let mut resources = Resources::new(); let mut ctx = RenderContext::new_with(200, 200, settings); ctx.reset(); ctx.fill_path(&Rect::new(0.0, 0.0, 100.0, 100.0).to_path(0.1)); ctx.flush(); - ctx.render_to_pixmap(&mut resources, &mut pixmap); + ctx.render_with(&mut pixmap, &mut resources, rasterizer_settings); ctx.flush(); - ctx.render_to_pixmap(&mut resources, &mut pixmap); + ctx.render_with(&mut pixmap, &mut resources, rasterizer_settings); } #[cfg(feature = "text")] @@ -899,7 +1141,7 @@ mod tests { y: 0.0, }]; - let mut resources = crate::Resources::new(); + let mut resources = Resources::new(); let mut ctx = RenderContext::new(100, 100); ctx.fill_rect(&Rect::new(0.0, 0.0, 10.0, 10.0)); diff --git a/sparse_strips/vello_cpu/src/text.rs b/sparse_strips/vello_cpu/src/text.rs index 604080e1a2..50394ff7f6 100644 --- a/sparse_strips/vello_cpu/src/text.rs +++ b/sparse_strips/vello_cpu/src/text.rs @@ -13,8 +13,8 @@ use crate::render::{ATLAS_IMAGE_ID_BASE, DEFAULT_GLYPH_ATLAS_SIZE}; use crate::{ - Image, ImageSource, PaintType, Pixmap, RenderContext, RenderMode, RenderSettings, Resources, - color, kurbo, peniko, + CompositeMode, Image, ImageSource, PaintType, Pixmap, RasterizerSettings, RenderContext, + RenderMode, RenderSettings, Resources, color, kurbo, peniko, }; use alloc::boxed::Box; use alloc::sync::Arc; @@ -63,7 +63,6 @@ impl GlyphAtlasResources { page_width: u16, page_height: u16, level: Level, - render_mode: RenderMode, eviction_config: GlyphCacheConfig, ) -> Self { Self { @@ -75,7 +74,6 @@ impl GlyphAtlasResources { RenderSettings { level, num_threads: 0, - render_mode, }, )), pixmaps: Vec::new(), @@ -102,9 +100,9 @@ fn ensure_page( } impl Resources { - pub(crate) fn prepare_glyph_cache(&mut self) { + pub(crate) fn prepare_glyph_cache(&mut self, render_mode: RenderMode) { if self.glyph_resources.is_some() { - self.sync_glyph_cache(); + self.sync_glyph_cache(render_mode); } } @@ -121,20 +119,19 @@ impl Resources { } } - fn ensure_glyph_resources(&mut self, level: Level, render_mode: RenderMode) { + fn ensure_glyph_resources(&mut self, level: Level) { if self.glyph_resources.is_none() { self.glyph_resources = Some(GlyphAtlasResources::with_config( DEFAULT_GLYPH_ATLAS_SIZE, DEFAULT_GLYPH_ATLAS_SIZE, level, - render_mode, GlyphCacheConfig::default(), )); } } /// Upload all pending bitmaps, rasterize pending outline/COLR glyphs, etc. - fn sync_glyph_cache(&mut self) { + fn sync_glyph_cache(&mut self, render_mode: RenderMode) { let glyph_resources = self .glyph_resources .as_mut() @@ -180,7 +177,15 @@ impl Resources { glyph_renderer.reset(); renderer::replay_atlas_commands(&mut recorder.commands, glyph_renderer); glyph_renderer.flush(); - glyph_renderer.composite_to_pixmap_at_offset(&Self::default(), page, 0, 0); + glyph_renderer.render_with( + page, + &mut Self::default(), + RasterizerSettings { + render_mode, + composite_mode: CompositeMode::SrcOver, + ..Default::default() + }, + ); }); for (page_index, pixmap) in glyph_resources.pixmaps.iter().enumerate() { @@ -220,10 +225,8 @@ impl<'a> CpuGlyphRunBackend<'a> { Glyphs: Iterator + Clone, { let atlas_cacher = if self.atlas_cache_enabled { - self.resources.ensure_glyph_resources( - self.ctx.render_settings.level, - self.ctx.render_settings.render_mode, - ); + self.resources + .ensure_glyph_resources(self.ctx.render_settings.level); let glyph_resources = self .resources .glyph_resources @@ -295,7 +298,7 @@ pub type GlyphRunBuilder<'a> = glifo::GlyphRunBuilder<'a, CpuGlyphRunBackend<'a> /// Zero out a rectangular region in the atlas pixmap. /// -/// Necessary because `composite_to_pixmap_at_offset` uses `SrcOver` blending, +/// Necessary because atlas rendering uses `SrcOver` blending, /// so stale pixels from evicted glyphs would bleed through if not cleared. fn clear_pixmap_region(dst: &mut Pixmap, rect: PendingClearRect) { let dst_stride = dst.width() as usize; diff --git a/sparse_strips/vello_sparse_tests/src/regenerate_probe_reference.rs b/sparse_strips/vello_sparse_tests/src/regenerate_probe_reference.rs index 51c44952c5..3b2c40d481 100644 --- a/sparse_strips/vello_sparse_tests/src/regenerate_probe_reference.rs +++ b/sparse_strips/vello_sparse_tests/src/regenerate_probe_reference.rs @@ -19,7 +19,7 @@ use vello_common::{ pixmap::Pixmap, probe::{self, ProbeRenderer}, }; -use vello_cpu::{Level, RenderContext, RenderMode, RenderSettings, Resources}; +use vello_cpu::{Level, RasterizerSettings, RenderContext, RenderMode, RenderSettings, Resources}; static PROBE_PNG_PATH: LazyLock = LazyLock::new(|| { PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../vello_common/assets/probe.png") @@ -78,7 +78,6 @@ fn render_probe_pixmap() -> Pixmap { let settings = RenderSettings { level: Level::fallback(), num_threads: 0, - render_mode: RenderMode::OptimizeQuality, }; let mut ctx = RenderContext::new_with(width, height, settings); @@ -90,7 +89,14 @@ fn render_probe_pixmap() -> Pixmap { let mut resources = Resources::new(); let mut pixmap = Pixmap::new(width, height); - ctx.render_to_pixmap(&mut resources, &mut pixmap); + ctx.render_with( + &mut pixmap, + &mut resources, + RasterizerSettings { + render_mode: RenderMode::OptimizeQuality, + ..Default::default() + }, + ); pixmap } diff --git a/sparse_strips/vello_sparse_tests/tests/basic.rs b/sparse_strips/vello_sparse_tests/tests/basic.rs index 4d9838363b..baf105dd78 100644 --- a/sparse_strips/vello_sparse_tests/tests/basic.rs +++ b/sparse_strips/vello_sparse_tests/tests/basic.rs @@ -16,7 +16,10 @@ use vello_common::kurbo::{Affine, BezPath, Circle, Join, Point, Rect, Shape, Str use vello_common::peniko::{Fill, Gradient}; use vello_cpu::color::palette::css::BLACK; use vello_cpu::peniko::LinearGradientPosition; -use vello_cpu::{Glyph, Level, Pixmap, RenderContext, RenderMode, RenderSettings}; +use vello_cpu::{ + CompositeMode, Glyph, Level, Pixmap, RasterizerSettings, RenderContext, RenderMode, + RenderSettings, +}; use vello_dev_macros::vello_test; #[vello_test(width = 8, height = 8)] @@ -536,13 +539,16 @@ fn test_cmd_size(_: &mut impl Renderer) { /// This demonstrates the glyph caching workflow: /// 1. Create a small `RenderContext` sized for a single glyph /// 2. Render the glyph into that context -/// 3. Use `composite_to_pixmap_at_offset` to blit it to a specific (x, y) position in a larger spritesheet +/// 3. Render it with `CompositeMode::SrcOver` and an offset into a larger spritesheet #[test] -fn composite_to_pixmap_at_offset() { +fn render_src_over_with_offset() { let settings = RenderSettings { level: Level::try_detect().unwrap_or(Level::baseline()), num_threads: 0, + }; + let rasterizer_settings = RasterizerSettings { render_mode: RenderMode::OptimizeQuality, + ..Default::default() }; let spritesheet_width: u16 = 100; let spritesheet_height: u16 = 100; @@ -576,11 +582,15 @@ fn composite_to_pixmap_at_offset() { let positions: [(u16, u16); 3] = [(15, 15), (30, 30), (0, 0)]; for (dst_x, dst_y) in positions { - glyph_renderer.composite_to_pixmap_at_offset( - &glyph_resources, + glyph_renderer.render_with( &mut spritesheet, - dst_x, - dst_y, + &mut glyph_resources, + RasterizerSettings { + render_mode: RenderMode::OptimizeQuality, + composite_mode: CompositeMode::SrcOver, + offset: (dst_x, dst_y), + ..Default::default() + }, ); } @@ -612,20 +622,24 @@ fn composite_to_pixmap_at_offset() { reference_renderer.flush(); let mut reference_pixmap = Pixmap::new(spritesheet_width, spritesheet_height); - reference_renderer.render_to_pixmap(&mut reference_resources, &mut reference_pixmap); + reference_renderer.render_with( + &mut reference_pixmap, + &mut reference_resources, + rasterizer_settings, + ); // Uncomment to save the spritesheet as PNG for visual inspection // let diffs_path = // std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../vello_sparse_tests/diffs"); // let _ = std::fs::create_dir_all(&diffs_path); // let png_data = spritesheet.clone().into_png().unwrap(); - // std::fs::write(diffs_path.join("composite_to_pixmap_at_offset.png"), png_data).unwrap(); + // std::fs::write(diffs_path.join("render_src_over_with_offset.png"), png_data).unwrap(); // Compare the two pixmaps assert_eq!( spritesheet.data_as_u8_slice(), reference_pixmap.data_as_u8_slice(), - "composite_to_pixmap_at_offset result should match direct rendering" + "render result should match direct rendering" ); } diff --git a/sparse_strips/vello_sparse_tests/tests/issues.rs b/sparse_strips/vello_sparse_tests/tests/issues.rs index 9ac0bcd0b3..978e02d50d 100644 --- a/sparse_strips/vello_sparse_tests/tests/issues.rs +++ b/sparse_strips/vello_sparse_tests/tests/issues.rs @@ -23,7 +23,7 @@ use vello_common::peniko::{ColorStops, RadialGradientPosition}; use vello_common::pixmap::Pixmap; use vello_cpu::color::palette::css::{BLACK, RED}; use vello_cpu::peniko::{Compose, Extend}; -use vello_cpu::{Level, RenderContext, RenderMode, RenderSettings}; +use vello_cpu::{Level, RasterizerSettings, RenderContext, RenderMode, RenderSettings}; use vello_dev_macros::vello_test; #[vello_test(width = 8, height = 8)] @@ -434,7 +434,6 @@ fn multi_threading_oob_access() { let settings = RenderSettings { level: Level::try_detect().unwrap_or(Level::baseline()), num_threads: 4, - render_mode: RenderMode::OptimizeQuality, }; let mut ctx = RenderContext::new_with(100, 100, settings); let mut resources = vello_cpu::Resources::new(); @@ -442,10 +441,24 @@ fn multi_threading_oob_access() { ctx.fill_path(&Rect::new(0.0, 0.0, 50.0, 50.0).to_path(0.1)); ctx.flush(); - ctx.render_to_pixmap(&mut resources, &mut pixmap); + ctx.render_with( + &mut pixmap, + &mut resources, + RasterizerSettings { + render_mode: RenderMode::OptimizeQuality, + ..Default::default() + }, + ); ctx.fill_path(&Rect::new(50.0, 50.0, 100.0, 100.0).to_path(0.1)); ctx.flush(); - ctx.render_to_pixmap(&mut resources, &mut pixmap); + ctx.render_with( + &mut pixmap, + &mut resources, + RasterizerSettings { + render_mode: RenderMode::OptimizeQuality, + ..Default::default() + }, + ); } /// See . diff --git a/sparse_strips/vello_sparse_tests/tests/mask.rs b/sparse_strips/vello_sparse_tests/tests/mask.rs index cb081194c8..4d1d7cd065 100644 --- a/sparse_strips/vello_sparse_tests/tests/mask.rs +++ b/sparse_strips/vello_sparse_tests/tests/mask.rs @@ -9,7 +9,7 @@ use vello_common::kurbo::{Point, Rect}; use vello_common::mask::Mask; use vello_common::peniko::{ColorStop, ColorStops, Gradient}; use vello_cpu::peniko::LinearGradientPosition; -use vello_cpu::{Level, RenderMode, RenderSettings}; +use vello_cpu::{Level, RenderSettings}; use vello_cpu::{Pixmap, RenderContext}; use vello_dev_macros::vello_test; @@ -19,7 +19,6 @@ pub(crate) fn example_mask(alpha_mask: bool) -> Mask { let settings = RenderSettings { level: Level::baseline(), num_threads: 0, - render_mode: RenderMode::OptimizeSpeed, }; let mut mask_ctx = RenderContext::new_with(100, 100, settings); let mut resources = vello_cpu::Resources::new(); @@ -49,7 +48,7 @@ pub(crate) fn example_mask(alpha_mask: bool) -> Mask { mask_ctx.set_paint(grad); mask_ctx.fill_rect(&Rect::new(10.0, 10.0, 90.0, 90.0)); - mask_ctx.render_to_pixmap(&mut resources, &mut mask_pix); + mask_ctx.render(&mut mask_pix, &mut resources); if alpha_mask { Mask::new_alpha(&mask_pix) diff --git a/sparse_strips/vello_sparse_tests/tests/renderer.rs b/sparse_strips/vello_sparse_tests/tests/renderer.rs index a2bea3d437..799b19aeec 100644 --- a/sparse_strips/vello_sparse_tests/tests/renderer.rs +++ b/sparse_strips/vello_sparse_tests/tests/renderer.rs @@ -12,7 +12,7 @@ use vello_common::mask::Mask; use vello_common::paint::{ImageId, ImageSource, PaintType, Tint}; use vello_common::peniko::{BlendMode, Fill, FontData, ImageQuality}; use vello_common::pixmap::Pixmap; -use vello_cpu::{Level, RenderContext, RenderMode, RenderSettings, Resources}; +use vello_cpu::{Level, RasterizerSettings, RenderContext, RenderMode, RenderSettings, Resources}; use vello_hybrid::{ RenderSettings as HybridRenderSettings, Resources as HybridResources, SampleRect, Scene, SceneConstraints, TextureId, @@ -102,6 +102,7 @@ pub(crate) trait Renderer: Sized { pub(crate) struct CpuRenderer { ctx: RenderContext, resources: Resources, + render_mode: RenderMode, } impl Renderer for CpuRenderer { @@ -115,14 +116,11 @@ impl Renderer for CpuRenderer { render_mode: RenderMode, _default_blending_only: bool, ) -> Self { - let settings = RenderSettings { - level, - num_threads, - render_mode, - }; + let settings = RenderSettings { level, num_threads }; Self { ctx: RenderContext::new_with(width, height, settings), resources: Resources::new(), + render_mode, } } @@ -250,7 +248,14 @@ impl Renderer for CpuRenderer { } fn render_to_pixmap(&mut self, pixmap: &mut Pixmap) { - self.ctx.render_to_pixmap(&mut self.resources, pixmap); + self.ctx.render_with( + pixmap, + &mut self.resources, + RasterizerSettings { + render_mode: self.render_mode, + ..Default::default() + }, + ); } fn width(&self) -> u16 { diff --git a/sparse_strips/vello_toy/src/svg.rs b/sparse_strips/vello_toy/src/svg.rs index eb512c435b..f3fb9a33eb 100644 --- a/sparse_strips/vello_toy/src/svg.rs +++ b/sparse_strips/vello_toy/src/svg.rs @@ -20,7 +20,7 @@ use usvg::{Node, Paint, PaintOrder}; use vello_cpu::color::AlphaColor; use vello_cpu::kurbo::{Affine, BezPath, Stroke}; use vello_cpu::peniko::Fill; -use vello_cpu::{Level, Pixmap, RenderContext, RenderMode, RenderSettings, Resources}; +use vello_cpu::{Level, Pixmap, RenderContext, RenderSettings, Resources}; fn main() { let args = Args::parse(); @@ -36,7 +36,6 @@ fn main() { let settings = RenderSettings { level: Level::new(), num_threads: args.num_threads as u16, - render_mode: RenderMode::OptimizeSpeed, }; let mut ctx = RenderContext::new_with(width, height, settings); let mut resources = Resources::new(); @@ -51,7 +50,7 @@ fn main() { render_tree(&mut ctx, &mut sctx, &tree); ctx.flush(); - ctx.render_to_pixmap(&mut resources, &mut pixmap); + ctx.render(&mut pixmap, &mut resources); runtime += start.elapsed(); num_iters += 1;