From 23a0de90006bd24d26a94df328dc9721e03d7606 Mon Sep 17 00:00:00 2001 From: Laurenz Stampfl Date: Thu, 12 Mar 2026 15:46:00 +0100 Subject: [PATCH 1/6] vello_sparse_tests: Add support for multi-frame tests --- sparse_strips/vello_dev_macros/src/test.rs | 77 ++++++++++++++++--- .../snapshots/multi_frame_basic.png | 3 + .../snapshots/multi_frame_with_opacity.png | 3 + .../vello_sparse_tests/tests/basic.rs | 31 ++++++++ .../vello_sparse_tests/tests/renderer.rs | 13 ++++ .../vello_sparse_tests/tests/util.rs | 14 +--- 6 files changed, 118 insertions(+), 23 deletions(-) create mode 100644 sparse_strips/vello_sparse_tests/snapshots/multi_frame_basic.png create mode 100644 sparse_strips/vello_sparse_tests/snapshots/multi_frame_with_opacity.png diff --git a/sparse_strips/vello_dev_macros/src/test.rs b/sparse_strips/vello_dev_macros/src/test.rs index 2af9df720c..5508bf2f4a 100644 --- a/sparse_strips/vello_dev_macros/src/test.rs +++ b/sparse_strips/vello_dev_macros/src/test.rs @@ -44,6 +44,11 @@ struct Arguments { no_ref: bool, /// A reason for ignoring a test. ignore_reason: Option, + /// The number of frames to render. When greater than 1, the test function receives an + /// additional `u32` argument indicating the current frame index. The generated test will + /// loop `frame_count` times, resetting the context before each frame. Only the final + /// frame is checked against the reference image. + frame_count: u32, } impl Default for Arguments { @@ -61,6 +66,7 @@ impl Default for Arguments { no_ref: false, diff_pixels: 0, ignore_reason: None, + frame_count: 1, } } } @@ -158,8 +164,28 @@ pub(crate) fn vello_test_inner(attr: TokenStream, item: TokenStream) -> TokenStr ignore_reason, no_ref, diff_pixels, + frame_count, } = parse_args(&attrs); + let has_frame_count = frame_count > 1; + + let test_fn_call = if has_frame_count { + quote! { #input_fn_name(&mut ctx, frame); } + } else { + quote! { #input_fn_name(&mut ctx); } + }; + + let width_f64 = width as f64; + let height_f64 = height as f64; + let draw_bg_snippet = if !transparent { + quote! { + ctx.set_paint(vello_common::color::palette::css::WHITE); + ctx.fill_rect(&vello_common::kurbo::Rect::new(0.0, 0.0, #width_f64, #height_f64)); + } + } else { + quote! {} + }; + // Wasm doesn't have access to the filesystem. For wasm, inline the snapshot bytes into the // binary. let reference_image_name = Ident::new( @@ -276,9 +302,15 @@ pub(crate) fn vello_test_inner(attr: TokenStream, item: TokenStream) -> TokenStr }; use vello_cpu::{RenderContext, RenderMode}; - let mut ctx = get_ctx::(#width, #height, #transparent, #num_threads, #level, #render_mode, false); - #input_fn_name(&mut ctx); - ctx.flush(); + let mut ctx = get_ctx::(#width, #height, #num_threads, #level, #render_mode, false); + for frame in 0..#frame_count { + if frame > 0 { + ctx.reset() + } + #draw_bg_snippet + #test_fn_call + ctx.flush(); + } if !#no_ref { check_ref(&ctx, #input_fn_name_str, #fn_name_str, #tolerance, #diff_pixels, #is_reference, #reference_image_name); } @@ -462,9 +494,15 @@ pub(crate) fn vello_test_inner(attr: TokenStream, item: TokenStream) -> TokenStr use crate::renderer::HybridRenderer; use vello_cpu::RenderMode; - let mut ctx = get_ctx::(#width, #height, #transparent, 0, "fallback", RenderMode::OptimizeSpeed, false); - #input_fn_name(&mut ctx); - ctx.flush(); + let mut ctx = get_ctx::(#width, #height, 0, "fallback", RenderMode::OptimizeSpeed, false); + for frame in 0..#frame_count { + if frame > 0 { + ctx.reset() + } + #draw_bg_snippet + #test_fn_call + ctx.flush(); + } if !#no_ref { check_ref(&ctx, #input_fn_name_str, #hybrid_fn_name_str, #hybrid_tolerance, #diff_pixels, false, #reference_image_name); } @@ -480,9 +518,15 @@ pub(crate) fn vello_test_inner(attr: TokenStream, item: TokenStream) -> TokenStr use crate::renderer::HybridRenderer; use vello_cpu::RenderMode; - let mut ctx = get_ctx::(#width, #height, #transparent, 0, "fallback", RenderMode::OptimizeSpeed, true); - #input_fn_name(&mut ctx); - ctx.flush(); + let mut ctx = get_ctx::(#width, #height, 0, "fallback", RenderMode::OptimizeSpeed, true); + for frame in 0..#frame_count { + if frame > 0 { + ctx.reset() + } + #draw_bg_snippet + #test_fn_call + ctx.flush(); + } if !#no_ref { check_ref(&ctx, #input_fn_name_str, #hybrid_constrained_fn_name_str, #hybrid_tolerance, #diff_pixels, false, #reference_image_name); } @@ -498,9 +542,15 @@ pub(crate) fn vello_test_inner(attr: TokenStream, item: TokenStream) -> TokenStr use crate::renderer::HybridRenderer; use vello_cpu::RenderMode; - let mut ctx = get_ctx::(#width, #height, #transparent, 0, "fallback", RenderMode::OptimizeSpeed, false); - #input_fn_name(&mut ctx); - ctx.flush(); + let mut ctx = get_ctx::(#width, #height, 0, "fallback", RenderMode::OptimizeSpeed, false); + for frame in 0..#frame_count { + if frame > 0 { + ctx.reset() + } + #draw_bg_snippet + #test_fn_call + ctx.flush(); + } if !#no_ref { check_ref(&ctx, #input_fn_name_str, #webgl_fn_name_str, #hybrid_tolerance, #diff_pixels, false, #reference_image_name); } @@ -533,6 +583,9 @@ fn parse_args(attribute_input: &AttributeInput) -> Arguments { "hybrid_tolerance" => { args.hybrid_tolerance = parse_int_lit::(expr, "hybrid_tolerance"); } + "frame_count" => { + args.frame_count = parse_int_lit::(expr, "frame_count"); + } _ => panic!("unknown pair attribute {key_str}"), } } diff --git a/sparse_strips/vello_sparse_tests/snapshots/multi_frame_basic.png b/sparse_strips/vello_sparse_tests/snapshots/multi_frame_basic.png new file mode 100644 index 0000000000..3a20dc49ec --- /dev/null +++ b/sparse_strips/vello_sparse_tests/snapshots/multi_frame_basic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:958d9764756cdb8efd8238b05ce2003976fb808a4927ed0208635240b024b273 +size 116 diff --git a/sparse_strips/vello_sparse_tests/snapshots/multi_frame_with_opacity.png b/sparse_strips/vello_sparse_tests/snapshots/multi_frame_with_opacity.png new file mode 100644 index 0000000000..3a20dc49ec --- /dev/null +++ b/sparse_strips/vello_sparse_tests/snapshots/multi_frame_with_opacity.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:958d9764756cdb8efd8238b05ce2003976fb808a4927ed0208635240b024b273 +size 116 diff --git a/sparse_strips/vello_sparse_tests/tests/basic.rs b/sparse_strips/vello_sparse_tests/tests/basic.rs index 93ee03136f..e41823d055 100644 --- a/sparse_strips/vello_sparse_tests/tests/basic.rs +++ b/sparse_strips/vello_sparse_tests/tests/basic.rs @@ -547,3 +547,34 @@ fn composite_to_pixmap_at_offset() { "composite_to_pixmap_at_offset result should match direct rendering" ); } + +/// Ensure that fast path strips from the last frame don't leak into the next one. +#[vello_test(transparent, frame_count = 2)] +fn multi_frame_basic(ctx: &mut impl Renderer, frame: u32) { + let rect = Rect::new(15.0, 15.0, 85.0, 85.0); + + if frame == 0 { + ctx.set_paint(RED.with_alpha(0.5)); + } else { + ctx.set_paint(BLUE.with_alpha(0.5)); + } + + ctx.fill_rect(&rect); +} + +/// Ensure that wide tiles are properly reset between two frames. +#[vello_test(transparent, frame_count = 2)] +fn multi_frame_with_clip_layer(ctx: &mut impl Renderer, frame: u32) { + let rect = Rect::new(15.0, 15.0, 85.0, 85.0); + + ctx.push_clip_layer(&rect.to_path(0.1)); + + if frame == 0 { + ctx.set_paint(RED.with_alpha(0.5)); + } else { + ctx.set_paint(BLUE.with_alpha(0.5)); + } + + ctx.fill_rect(&rect); + ctx.pop_layer(); +} diff --git a/sparse_strips/vello_sparse_tests/tests/renderer.rs b/sparse_strips/vello_sparse_tests/tests/renderer.rs index e8f9476e98..0f4761ac7f 100644 --- a/sparse_strips/vello_sparse_tests/tests/renderer.rs +++ b/sparse_strips/vello_sparse_tests/tests/renderer.rs @@ -62,6 +62,7 @@ pub(crate) trait Renderer: Sized { fn set_blend_mode(&mut self, blend_mode: BlendMode); fn set_filter_effect(&mut self, filter: Filter); fn reset_filter_effect(&mut self); + fn reset(&mut self); fn render_to_pixmap(&self, pixmap: &mut Pixmap); fn width(&self) -> u16; fn height(&self) -> u16; @@ -207,6 +208,10 @@ impl Renderer for RenderContext { Self::reset_filter_effect(self); } + fn reset(&mut self) { + Self::reset(self); + } + fn render_to_pixmap(&self, pixmap: &mut Pixmap) { Self::render_to_pixmap(self, pixmap); } @@ -448,6 +453,10 @@ impl Renderer for HybridRenderer { self.scene.reset_filter_effect(); } + fn reset(&mut self) { + self.scene.reset(); + } + // This method creates device resources every time it is called. This does not matter much for // testing, but should not be used as a basis for implementing something real. This would be a // very bad example for that. @@ -793,6 +802,10 @@ impl Renderer for HybridRenderer { self.scene.reset_filter_effect(); } + fn reset(&mut self) { + self.scene.reset(); + } + // vello_hybrid WebGL renderer backend. fn render_to_pixmap(&self, pixmap: &mut Pixmap) { use web_sys::WebGl2RenderingContext; diff --git a/sparse_strips/vello_sparse_tests/tests/util.rs b/sparse_strips/vello_sparse_tests/tests/util.rs index 6fa5182399..34ef464a7a 100644 --- a/sparse_strips/vello_sparse_tests/tests/util.rs +++ b/sparse_strips/vello_sparse_tests/tests/util.rs @@ -12,9 +12,9 @@ use smallvec::smallvec; use std::cmp::max; use std::sync::Arc; use vello_common::color::DynamicColor; -use vello_common::color::palette::css::{BLUE, GREEN, RED, WHITE, YELLOW}; +use vello_common::color::palette::css::{BLUE, GREEN, RED, YELLOW}; use vello_common::glyph::Glyph; -use vello_common::kurbo::{BezPath, Join, Point, Rect, Shape, Stroke, Vec2}; +use vello_common::kurbo::{BezPath, Join, Point, Stroke, Vec2}; use vello_common::peniko::{Blob, ColorStop, ColorStops, FontData}; use vello_common::pixmap::Pixmap; use vello_cpu::{Level, RenderMode}; @@ -100,7 +100,6 @@ macro_rules! load_image { pub(crate) fn get_ctx( width: u16, height: u16, - transparent: bool, num_threads: u16, level: &str, render_mode: RenderMode, @@ -143,7 +142,7 @@ pub(crate) fn get_ctx( _ => panic!("unknown level: {level}"), }; - let mut ctx = T::new( + let ctx = T::new( width, height, num_threads, @@ -152,13 +151,6 @@ pub(crate) fn get_ctx( default_blending_only, ); - if !transparent { - let path = Rect::new(0.0, 0.0, width as f64, height as f64).to_path(0.1); - - ctx.set_paint(WHITE); - ctx.fill_path(&path); - } - ctx } From c4798c26d8b03949b8ec137af1cf7ac93cd2a6a2 Mon Sep 17 00:00:00 2001 From: Laurenz Stampfl Date: Thu, 12 Mar 2026 16:15:28 +0100 Subject: [PATCH 2/6] fix --- ...ame_with_opacity.png => multi_frame_with_clip_layer.png} | 0 sparse_strips/vello_sparse_tests/tests/util.rs | 6 ++---- 2 files changed, 2 insertions(+), 4 deletions(-) rename sparse_strips/vello_sparse_tests/snapshots/{multi_frame_with_opacity.png => multi_frame_with_clip_layer.png} (100%) diff --git a/sparse_strips/vello_sparse_tests/snapshots/multi_frame_with_opacity.png b/sparse_strips/vello_sparse_tests/snapshots/multi_frame_with_clip_layer.png similarity index 100% rename from sparse_strips/vello_sparse_tests/snapshots/multi_frame_with_opacity.png rename to sparse_strips/vello_sparse_tests/snapshots/multi_frame_with_clip_layer.png diff --git a/sparse_strips/vello_sparse_tests/tests/util.rs b/sparse_strips/vello_sparse_tests/tests/util.rs index 34ef464a7a..ff69e4a23d 100644 --- a/sparse_strips/vello_sparse_tests/tests/util.rs +++ b/sparse_strips/vello_sparse_tests/tests/util.rs @@ -142,16 +142,14 @@ pub(crate) fn get_ctx( _ => panic!("unknown level: {level}"), }; - let ctx = T::new( + T::new( width, height, num_threads, level, render_mode, default_blending_only, - ); - - ctx + ) } pub(crate) fn render_pixmap(ctx: &impl Renderer) -> Pixmap { From 26323531c7abaf667ac8f9c11c75aa31785d460c Mon Sep 17 00:00:00 2001 From: Laurenz Stampfl <47084093+LaurenzV@users.noreply.github.com> Date: Mon, 16 Mar 2026 15:36:14 +0100 Subject: [PATCH 3/6] Update sparse_strips/vello_dev_macros/src/test.rs Co-authored-by: Alex Gemberg --- sparse_strips/vello_dev_macros/src/test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sparse_strips/vello_dev_macros/src/test.rs b/sparse_strips/vello_dev_macros/src/test.rs index 5508bf2f4a..17fe250b7e 100644 --- a/sparse_strips/vello_dev_macros/src/test.rs +++ b/sparse_strips/vello_dev_macros/src/test.rs @@ -305,7 +305,7 @@ pub(crate) fn vello_test_inner(attr: TokenStream, item: TokenStream) -> TokenStr let mut ctx = get_ctx::(#width, #height, #num_threads, #level, #render_mode, false); for frame in 0..#frame_count { if frame > 0 { - ctx.reset() +ctx.reset(); } #draw_bg_snippet #test_fn_call From 90b6a44fe68e00a3e3b523177931b7a9617529be Mon Sep 17 00:00:00 2001 From: Laurenz Stampfl Date: Mon, 16 Mar 2026 15:39:16 +0100 Subject: [PATCH 4/6] Reformat --- sparse_strips/vello_dev_macros/src/test.rs | 42 +++++++++++----------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/sparse_strips/vello_dev_macros/src/test.rs b/sparse_strips/vello_dev_macros/src/test.rs index 17fe250b7e..3215effb4d 100644 --- a/sparse_strips/vello_dev_macros/src/test.rs +++ b/sparse_strips/vello_dev_macros/src/test.rs @@ -293,29 +293,29 @@ pub(crate) fn vello_test_inner(attr: TokenStream, item: TokenStream) -> TokenStr }; quote! { - #cfg_attr - #ignore_snippet - #test_attr - fn #fn_name() { - use crate::util::{ - check_ref, get_ctx - }; - use vello_cpu::{RenderContext, RenderMode}; - - let mut ctx = get_ctx::(#width, #height, #num_threads, #level, #render_mode, false); - for frame in 0..#frame_count { - if frame > 0 { -ctx.reset(); + #cfg_attr + #ignore_snippet + #test_attr + fn #fn_name() { + use crate::util::{ + check_ref, get_ctx + }; + use vello_cpu::{RenderContext, RenderMode}; + + let mut ctx = get_ctx::(#width, #height, #num_threads, #level, #render_mode, false); + for frame in 0..#frame_count { + if frame > 0 { + ctx.reset(); + } + #draw_bg_snippet + #test_fn_call + ctx.flush(); + } + if !#no_ref { + check_ref(&ctx, #input_fn_name_str, #fn_name_str, #tolerance, #diff_pixels, #is_reference, #reference_image_name); + } } - #draw_bg_snippet - #test_fn_call - ctx.flush(); } - if !#no_ref { - check_ref(&ctx, #input_fn_name_str, #fn_name_str, #tolerance, #diff_pixels, #is_reference, #reference_image_name); - } - } - } }; #[cfg(target_arch = "aarch64")] From eb301c1d0309056dc98d2a758e508535550e2d55 Mon Sep 17 00:00:00 2001 From: Laurenz Stampfl Date: Mon, 16 Mar 2026 15:40:25 +0100 Subject: [PATCH 5/6] Undo format change --- sparse_strips/vello_dev_macros/src/test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sparse_strips/vello_dev_macros/src/test.rs b/sparse_strips/vello_dev_macros/src/test.rs index 3215effb4d..eac34357c3 100644 --- a/sparse_strips/vello_dev_macros/src/test.rs +++ b/sparse_strips/vello_dev_macros/src/test.rs @@ -305,7 +305,7 @@ pub(crate) fn vello_test_inner(attr: TokenStream, item: TokenStream) -> TokenStr let mut ctx = get_ctx::(#width, #height, #num_threads, #level, #render_mode, false); for frame in 0..#frame_count { if frame > 0 { - ctx.reset(); + ctx.reset(); } #draw_bg_snippet #test_fn_call From 5aca476dd6380f9bf823d6cf8d84fd7ac1586a3b Mon Sep 17 00:00:00 2001 From: Laurenz Stampfl Date: Mon, 16 Mar 2026 16:08:50 +0100 Subject: [PATCH 6/6] Reformat --- sparse_strips/vello_dev_macros/src/test.rs | 42 +++++++++++----------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/sparse_strips/vello_dev_macros/src/test.rs b/sparse_strips/vello_dev_macros/src/test.rs index eac34357c3..83ef772eeb 100644 --- a/sparse_strips/vello_dev_macros/src/test.rs +++ b/sparse_strips/vello_dev_macros/src/test.rs @@ -293,29 +293,29 @@ pub(crate) fn vello_test_inner(attr: TokenStream, item: TokenStream) -> TokenStr }; quote! { - #cfg_attr - #ignore_snippet - #test_attr - fn #fn_name() { - use crate::util::{ - check_ref, get_ctx - }; - use vello_cpu::{RenderContext, RenderMode}; - - let mut ctx = get_ctx::(#width, #height, #num_threads, #level, #render_mode, false); - for frame in 0..#frame_count { - if frame > 0 { - ctx.reset(); - } - #draw_bg_snippet - #test_fn_call - ctx.flush(); - } - if !#no_ref { - check_ref(&ctx, #input_fn_name_str, #fn_name_str, #tolerance, #diff_pixels, #is_reference, #reference_image_name); - } + #cfg_attr + #ignore_snippet + #test_attr + fn #fn_name() { + use crate::util::{ + check_ref, get_ctx + }; + use vello_cpu::{RenderContext, RenderMode}; + + let mut ctx = get_ctx::(#width, #height, #num_threads, #level, #render_mode, false); + for frame in 0..#frame_count { + if frame > 0 { + ctx.reset(); } + #draw_bg_snippet + #test_fn_call + ctx.flush(); } + if !#no_ref { + check_ref(&ctx, #input_fn_name_str, #fn_name_str, #tolerance, #diff_pixels, #is_reference, #reference_image_name); + } + } + } }; #[cfg(target_arch = "aarch64")]