add tests
diff --git a/sparse_strips/vello_dev_macros/src/test.rs b/sparse_strips/vello_dev_macros/src/test.rs index 497b708..44d08b9 100644 --- a/sparse_strips/vello_dev_macros/src/test.rs +++ b/sparse_strips/vello_dev_macros/src/test.rs
@@ -225,7 +225,7 @@ let mut ctx = get_ctx::<Scene>(#width, #height, #transparent); #input_fn_name(&mut ctx); if !#no_ref { - check_ref(&ctx, #input_fn_name_str, #webgl_fn_name_str, #hybrid_tolerance, false, RenderMode::OptimizeSpeed, #reference_image_name); + check_ref(&ctx, "", #webgl_fn_name_str, #hybrid_tolerance, false, RenderMode::OptimizeSpeed, #reference_image_name); } } };
diff --git a/sparse_strips/vello_sparse_tests/snapshots/scene_larger_than_surface.png b/sparse_strips/vello_sparse_tests/snapshots/scene_larger_than_surface.png new file mode 100644 index 0000000..183612e --- /dev/null +++ b/sparse_strips/vello_sparse_tests/snapshots/scene_larger_than_surface.png
@@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0c84c102a586cdde6dd01c0ab2ed5ac98e75d6fc69b7b2ffad6860944818c4c0 +size 93
diff --git a/sparse_strips/vello_sparse_tests/snapshots/scene_smaller_than_surface.png b/sparse_strips/vello_sparse_tests/snapshots/scene_smaller_than_surface.png new file mode 100644 index 0000000..c210428 --- /dev/null +++ b/sparse_strips/vello_sparse_tests/snapshots/scene_smaller_than_surface.png
@@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:57ce9fcc9ac98052af7274e3dfd3ac4a93a33594cd962803d80e886ec3a94804 +size 127
diff --git a/sparse_strips/vello_sparse_tests/tests/mod.rs b/sparse_strips/vello_sparse_tests/tests/mod.rs index 4d9f627..1bd174a 100644 --- a/sparse_strips/vello_sparse_tests/tests/mod.rs +++ b/sparse_strips/vello_sparse_tests/tests/mod.rs
@@ -36,5 +36,6 @@ mod mask; mod mix; mod opacity; +mod render_size; mod renderer; mod util;
diff --git a/sparse_strips/vello_sparse_tests/tests/render_size.rs b/sparse_strips/vello_sparse_tests/tests/render_size.rs new file mode 100644 index 0000000..cf5b4e4 --- /dev/null +++ b/sparse_strips/vello_sparse_tests/tests/render_size.rs
@@ -0,0 +1,209 @@ +// Copyright 2025 the Vello Authors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! Tests where the surface dimensions and scene dimensions do not match. +//! +//! Note: These tests do not use vello_test proc macro as they test an edge case where the render +//! surface is a different size than the scene dimensions. + +use crate::renderer::Renderer; +use crate::util::{check_ref_encoded, pixmap_to_png}; +use vello_common::color::palette::css::{LIME, REBECCA_PURPLE}; +use vello_common::kurbo::Rect; +use vello_common::pixmap::Pixmap; +use vello_hybrid::Scene; + +fn draw_simple_scene(scene: &mut Scene, width: f64, height: f64) { + scene.set_paint(LIME.into()); + // Cover background of scene + scene.fill_rect(&Rect::new(0.0, 0.0, width, height)); + + scene.set_paint(REBECCA_PURPLE.into()); + scene.fill_rect(&Rect::new(10., 20., width - 10., height - 20.)); +} + +#[cfg(not(target_arch = "wasm32"))] +#[test] +fn scene_smaller_than_surface_hybrid() { + let scene_width = 50; + let scene_height = 50; + let surface_width = 80; + let surface_height = 80; + + let mut scene = Scene::new(scene_width, scene_height); + draw_simple_scene(&mut scene, scene_width as f64, scene_height as f64); + + let mut pixmap = Pixmap::new(surface_width, surface_height); + + scene.render_to_pixmap(&mut pixmap, vello_cpu::RenderMode::OptimizeSpeed); + let encoded_image = pixmap_to_png(pixmap, surface_width as u32, surface_height as u32); + + check_ref_encoded( + &encoded_image, + "scene_smaller_than_surface", + "scene_smaller_than_surface_hybrid", + 1, + false, + &[], + ); +} + +#[cfg(all(target_arch = "wasm32", feature = "webgl"))] +#[wasm_bindgen_test::wasm_bindgen_test] +async fn scene_smaller_than_surface_hybrid_webgl() { + use wasm_bindgen::JsCast; + use web_sys::{HtmlCanvasElement, WebGl2RenderingContext}; + + let scene_width = 50; + let scene_height = 50; + let surface_width = 80; + let surface_height = 80; + + let mut scene = Scene::new(scene_width, scene_height); + draw_simple_scene(&mut scene, scene_width as f64, scene_height as f64); + + let document = web_sys::window().unwrap().document().unwrap(); + let canvas = document + .create_element("canvas") + .unwrap() + .dyn_into::<HtmlCanvasElement>() + .unwrap(); + + canvas.set_width(surface_width as u32); + canvas.set_height(surface_height as u32); + + // Render the smaller scene to the larger canvas + let mut renderer = vello_hybrid::WebGlRenderer::new(&canvas); + let render_size = vello_hybrid::RenderSize { + width: scene_width as u32, + height: scene_height as u32, + }; + + renderer.render(&scene, &render_size).unwrap(); + + let gl = canvas + .get_context("webgl2") + .unwrap() + .unwrap() + .dyn_into::<WebGl2RenderingContext>() + .unwrap(); + + let mut pixels = vec![0_u8; (surface_width as usize) * (surface_height as usize) * 4]; + gl.read_pixels_with_opt_u8_array( + 0, + 0, + surface_width.into(), + surface_height.into(), + WebGl2RenderingContext::RGBA, + WebGl2RenderingContext::UNSIGNED_BYTE, + Some(&mut pixels), + ) + .unwrap(); + + let mut pixmap = Pixmap::new(surface_width, surface_height); + pixmap.data_as_u8_slice_mut().copy_from_slice(&pixels); + + let encoded_image = pixmap_to_png(pixmap, surface_width as u32, surface_height as u32); + + check_ref_encoded( + &encoded_image, + "", + "scene_smaller_than_surface_hybrid_webgl", + 1, + false, + include_bytes!("../snapshots/scene_smaller_than_surface.png"), + ); +} + +#[cfg(not(target_arch = "wasm32"))] +#[test] +fn scene_larger_than_surface_hybrid() { + let scene_width = 80; + let scene_height = 80; + let surface_width = 50; + let surface_height = 50; + + let mut scene = Scene::new(scene_width, scene_height); + draw_simple_scene(&mut scene, scene_width as f64, scene_height as f64); + + let mut pixmap = Pixmap::new(surface_width, surface_height); + + scene.render_to_pixmap(&mut pixmap, vello_cpu::RenderMode::OptimizeSpeed); + let encoded_image = pixmap_to_png(pixmap, surface_width as u32, surface_height as u32); + + check_ref_encoded( + &encoded_image, + "scene_larger_than_surface", + "scene_larger_than_surface_hybrid", + 1, + false, + &[], + ); +} + +#[cfg(all(target_arch = "wasm32", feature = "webgl"))] +#[wasm_bindgen_test::wasm_bindgen_test] +async fn scene_larger_than_surface_hybrid_webgl() { + use wasm_bindgen::JsCast; + use web_sys::{HtmlCanvasElement, WebGl2RenderingContext}; + + let scene_width = 80; + let scene_height = 80; + let surface_width = 50; + let surface_height = 50; + + let mut scene = Scene::new(scene_width, scene_height); + draw_simple_scene(&mut scene, scene_width as f64, scene_height as f64); + + let document = web_sys::window().unwrap().document().unwrap(); + let canvas = document + .create_element("canvas") + .unwrap() + .dyn_into::<HtmlCanvasElement>() + .unwrap(); + + canvas.set_width(surface_width as u32); + canvas.set_height(surface_height as u32); + + // Render the larger scene to the smaller canvas + let mut renderer = vello_hybrid::WebGlRenderer::new(&canvas); + let render_size = vello_hybrid::RenderSize { + width: scene_width as u32, + height: scene_height as u32, + }; + + renderer.render(&scene, &render_size).unwrap(); + + let gl = canvas + .get_context("webgl2") + .unwrap() + .unwrap() + .dyn_into::<WebGl2RenderingContext>() + .unwrap(); + + let mut pixels = vec![0_u8; (surface_width as usize) * (surface_height as usize) * 4]; + gl.read_pixels_with_opt_u8_array( + 0, + 0, + surface_width.into(), + surface_height.into(), + WebGl2RenderingContext::RGBA, + WebGl2RenderingContext::UNSIGNED_BYTE, + Some(&mut pixels), + ) + .unwrap(); + + let mut pixmap = Pixmap::new(surface_width, surface_height); + pixmap.data_as_u8_slice_mut().copy_from_slice(&pixels); + + let encoded_image = pixmap_to_png(pixmap, surface_width as u32, surface_height as u32); + + check_ref_encoded( + &encoded_image, + "", + "scene_larger_than_surface_hybrid_webgl", + 1, + false, + include_bytes!("../snapshots/scene_larger_than_surface.png"), + ); +}
diff --git a/sparse_strips/vello_sparse_tests/tests/renderer.rs b/sparse_strips/vello_sparse_tests/tests/renderer.rs index 64b0072..c0f6644 100644 --- a/sparse_strips/vello_sparse_tests/tests/renderer.rs +++ b/sparse_strips/vello_sparse_tests/tests/renderer.rs
@@ -236,8 +236,8 @@ M.lock().unwrap() }; - let width = self.width(); - let height = self.height(); + let width = pixmap.width(); + let height = pixmap.height(); // Copied from vello_hybrid/examples/`render_to_file.rs`. @@ -369,8 +369,8 @@ use wasm_bindgen::JsCast; use web_sys::{HtmlCanvasElement, WebGl2RenderingContext}; - let width = self.width(); - let height = self.height(); + let width = pixmap.width(); + let height = pixmap.height(); // Create an offscreen HTMLCanvasElement, render the test image to it, and finally read off // the pixmap for diff checking.
diff --git a/sparse_strips/vello_sparse_tests/tests/util.rs b/sparse_strips/vello_sparse_tests/tests/util.rs index 26d4573..be7374a 100644 --- a/sparse_strips/vello_sparse_tests/tests/util.rs +++ b/sparse_strips/vello_sparse_tests/tests/util.rs
@@ -230,7 +230,6 @@ png_data } -#[cfg(not(target_arch = "wasm32"))] pub(crate) fn check_ref( ctx: &impl Renderer, // The name of the test. @@ -242,83 +241,101 @@ threshold: u8, // Whether the test instance is the "gold standard" and should be used // for creating reference images. + // N.B. Must be `false` on `wasm32` as reference image cannot be written to filesystem. is_reference: bool, render_mode: RenderMode, - _: &[u8], + // `ref_data` is the reference image data, passed directly for wasm32. + ref_data: &[u8], ) { let pixmap = render_pixmap(ctx, render_mode); - let encoded_image = pixmap_to_png(pixmap, ctx.width() as u32, ctx.height() as u32); - let ref_path = REFS_PATH.join(format!("{}.png", test_name)); - let write_ref_image = || { - let optimized = - oxipng::optimize_from_memory(&encoded_image, &oxipng::Options::max_compression()) - .unwrap(); - std::fs::write(&ref_path, optimized).unwrap(); - }; - - if !ref_path.exists() { - if is_reference { - write_ref_image(); - panic!("new reference image was created"); - } else { - panic!("no reference image exists"); - } - } - - let ref_image = load_from_memory(&std::fs::read(&ref_path).unwrap()) - .unwrap() - .into_rgba8(); - let actual = load_from_memory(&encoded_image).unwrap().into_rgba8(); - - let diff_image = get_diff(&ref_image, &actual, threshold); - - if let Some(diff_image) = diff_image { - if std::env::var("REPLACE").is_ok() && is_reference { - write_ref_image(); - panic!("test was replaced"); - } - - if !DIFFS_PATH.exists() { - let _ = std::fs::create_dir_all(DIFFS_PATH.as_path()); - } - - let diff_path = DIFFS_PATH.join(format!("{}.png", specific_name)); - diff_image - .save_with_format(&diff_path, image::ImageFormat::Png) - .unwrap(); - - panic!("test didnt match reference image"); - } + check_ref_encoded( + &encoded_image, + test_name, + specific_name, + threshold, + is_reference, + ref_data, + ); } -#[cfg(target_arch = "wasm32")] -pub(crate) fn check_ref( - ctx: &impl Renderer, - _test_name: &str, +pub(crate) fn check_ref_encoded( + // The encoded image under test. + encoded_image: &[u8], + // The name of the test. + test_name: &str, // The name of the specific instance of the test that is being run // (e.g. test_gpu, test_cpu_u8, etc.) specific_name: &str, // Tolerance for pixel differences. threshold: u8, - // Must be `false` on `wasm32` as reference image cannot be written to filesystem. + // Whether the test instance is the "gold standard" and should be used + // for creating reference images. + // N.B. Must be `false` on `wasm32` as reference image cannot be written to filesystem. is_reference: bool, - render_mode: RenderMode, + // `ref_data` is the reference image data, passed directly for wasm32. ref_data: &[u8], ) { - assert!(!is_reference, "WASM cannot create new reference images"); + #[cfg(not(target_arch = "wasm32"))] + { + assert_eq!(ref_data.len(), 0, "ref_data is only used for wasm32 tests"); + let ref_path = REFS_PATH.join(format!("{}.png", test_name)); - let pixmap = render_pixmap(ctx, render_mode); - let encoded_image = pixmap_to_png(pixmap, ctx.width() as u32, ctx.height() as u32); - let actual = load_from_memory(&encoded_image).unwrap().into_rgba8(); + let write_ref_image = || { + let optimized = + oxipng::optimize_from_memory(&encoded_image, &oxipng::Options::max_compression()) + .unwrap(); + std::fs::write(&ref_path, optimized).unwrap(); + }; - let ref_image = load_from_memory(ref_data).unwrap().into_rgba8(); + if !ref_path.exists() { + if is_reference { + write_ref_image(); + panic!("new reference image was created"); + } else { + panic!("no reference image exists"); + } + } - let diff_image = get_diff(&ref_image, &actual, threshold); - if let Some(ref img) = diff_image { - append_diff_image_to_browser_document(specific_name, img); - panic!("test didn't match reference image. Scroll to bottom of browser to view diff."); + let ref_image = load_from_memory(&std::fs::read(&ref_path).unwrap()) + .unwrap() + .into_rgba8(); + let actual = load_from_memory(&encoded_image).unwrap().into_rgba8(); + + let diff_image = get_diff(&ref_image, &actual, threshold); + + if let Some(diff_image) = diff_image { + if std::env::var("REPLACE").is_ok() && is_reference { + write_ref_image(); + panic!("test was replaced"); + } + + if !DIFFS_PATH.exists() { + let _ = std::fs::create_dir_all(DIFFS_PATH.as_path()); + } + + let diff_path = DIFFS_PATH.join(format!("{}.png", specific_name)); + diff_image + .save_with_format(&diff_path, image::ImageFormat::Png) + .unwrap(); + + panic!("test didnt match reference image"); + } + } + #[cfg(target_arch = "wasm32")] + { + assert!(!is_reference, "WASM cannot create new reference images"); + assert_eq!(test_name.len(), 0, "test_name is not used in wasm32 tests"); + let actual = load_from_memory(&encoded_image).unwrap().into_rgba8(); + + let ref_image = load_from_memory(ref_data).unwrap().into_rgba8(); + + let diff_image = get_diff(&ref_image, &actual, threshold); + if let Some(ref img) = diff_image { + append_diff_image_to_browser_document(specific_name, img); + panic!("test didn't match reference image. Scroll to bottom of browser to view diff."); + } } }