[vello_sparse_tests] Add vello_cpu scalar test coverage on WASM (#1065)
### Context
This is a test only PR - adding additional test coverage for scalar
vello_cpu tests running on the browser.
**Before this PR:**
```sh
$ wasm-pack test --firefox --headless --features=webgl
test result: ok. 85 passed; 0 failed; 165 ignored; 0 filtered out; finished in 7.49s
```
**After this PR:**
```sh
$ wasm-pack test --firefox --headless --features=webgl
test result: ok. 581 passed; 0 failed; 169 ignored; 0 filtered out; finished in 22.18s
```
This can also be seen [in
CI](https://github.com/linebender/vello/actions/runs/15842375361/job/44657293874?pr=1065#step:11:790)
of this PR.
### Changes
- `load_image` has been moved into `utils` and has become a macro. This
is required such that WASM can embed the image assets into the compiled
WASM binary (as wasm doesn't have access to filesystem).
- Most changes are to the `vello_test` proc macro, which now generates
two additional tests, a u8 and f32 scalar wasm cpu test.
### Test plan
This whole PR is the test.
diff --git a/sparse_strips/vello_dev_macros/src/test.rs b/sparse_strips/vello_dev_macros/src/test.rs
index 79f164a..8f35d9f 100644
--- a/sparse_strips/vello_dev_macros/src/test.rs
+++ b/sparse_strips/vello_dev_macros/src/test.rs
@@ -80,6 +80,14 @@
&format!("{}_cpu_f32_neon", input_fn_name),
input_fn_name.span(),
);
+ let u8_fn_name_scalar_wasm = Ident::new(
+ &format!("{}_cpu_u8_scalar_wasm", input_fn_name),
+ input_fn_name.span(),
+ );
+ let f32_fn_name_scalar_wasm: Ident = Ident::new(
+ &format!("{}_cpu_f32_scalar_wasm", input_fn_name),
+ input_fn_name.span(),
+ );
let multithreaded_fn_name = Ident::new(
&format!("{}_cpu_multithreaded", input_fn_name),
input_fn_name.span(),
@@ -99,6 +107,8 @@
let f32_fn_name_str_scalar = f32_fn_name_scalar.to_string();
let u8_fn_name_str_neon = u8_fn_name_neon.to_string();
let f32_fn_name_str_neon = f32_fn_name_neon.to_string();
+ let u8_fn_name_scalar_wasm_str = u8_fn_name_scalar_wasm.to_string();
+ let f32_fn_name_scalar_wasm_str = f32_fn_name_scalar_wasm.to_string();
let multithreaded_fn_name_str = multithreaded_fn_name.to_string();
let hybrid_fn_name_str = hybrid_fn_name.to_string();
let webgl_fn_name_str = webgl_fn_name.to_string();
@@ -182,15 +192,30 @@
level: &str,
ignore: bool,
render_mode: proc_macro2::TokenStream| {
+ // Use the name to infer if the test is running in the browser.
+ let is_wasm_test = fn_name_str.contains("wasm");
+ // WASM cannot create references, so force `is_reference` to be `false` unconditionally.
+ let is_reference = if is_wasm_test { false } else { is_reference };
let ignore_snippet = if ignore {
ignore_snippet.clone()
} else {
quote! {}
};
+ let (cfg_attr, test_attr) = if is_wasm_test {
+ assert_eq!(num_threads, 0, "wasm is single threaded");
+ (
+ quote! { #[cfg(target_arch = "wasm32")] },
+ quote! { #[wasm_bindgen_test::wasm_bindgen_test] },
+ )
+ } else {
+ (quote! {}, quote! { #[test] })
+ };
+
quote! {
+ #cfg_attr
#ignore_snippet
- #[test]
+ #test_attr
fn #fn_name() {
use crate::util::{
check_ref, get_ctx
@@ -232,6 +257,26 @@
skip_cpu,
quote! { RenderMode::OptimizeQuality },
);
+ let u8_snippet_wasm = cpu_snippet(
+ u8_fn_name_scalar_wasm,
+ u8_fn_name_scalar_wasm_str,
+ cpu_u8_tolerance_scalar,
+ false,
+ 0,
+ "fallback",
+ skip_cpu,
+ quote! { RenderMode::OptimizeSpeed },
+ );
+ let f32_snippet_wasm = cpu_snippet(
+ f32_fn_name_scalar_wasm,
+ f32_fn_name_scalar_wasm_str,
+ cpu_f32_tolerance_scalar,
+ true,
+ 0,
+ "fallback",
+ skip_cpu,
+ quote! { RenderMode::OptimizeQuality },
+ );
let multi_threaded_snippet = cpu_snippet(
multithreaded_fn_name,
multithreaded_fn_name_str,
@@ -274,10 +319,14 @@
#neon_u8_snippet
+ #u8_snippet_wasm
+
#f32_snippet
#neon_f32_snippet
+ #f32_snippet_wasm
+
#multi_threaded_snippet
#ignore_hybrid
diff --git a/sparse_strips/vello_sparse_tests/README.md b/sparse_strips/vello_sparse_tests/README.md
index d8903dd..ca44ee9 100644
--- a/sparse_strips/vello_sparse_tests/README.md
+++ b/sparse_strips/vello_sparse_tests/README.md
@@ -44,7 +44,7 @@
Requirements:
- on MacOS, a minimum Clang major version of 20 is required.
-To run the `vello_sparse_tests` suite on WebGL headless:
+To run the `vello_sparse_tests` suite including the WebGL tests:
```sh
wasm-pack test --headless --chrome --features webgl
diff --git a/sparse_strips/vello_sparse_tests/tests/image.rs b/sparse_strips/vello_sparse_tests/tests/image.rs
index c55ba72..8bab917 100644
--- a/sparse_strips/vello_sparse_tests/tests/image.rs
+++ b/sparse_strips/vello_sparse_tests/tests/image.rs
@@ -2,10 +2,10 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
use crate::gradient::tan_45;
+use crate::load_image;
use crate::renderer::Renderer;
use crate::util::crossed_line_star;
use std::f64::consts::PI;
-use std::path::Path;
use std::sync::Arc;
use vello_common::kurbo::{Affine, Point, Rect};
use vello_common::paint::Image;
@@ -13,33 +13,28 @@
use vello_common::pixmap::Pixmap;
use vello_dev_macros::vello_test;
-pub(crate) fn load_image(name: &str) -> Arc<Pixmap> {
- let path = Path::new(env!("CARGO_MANIFEST_DIR")).join(format!("tests/assets/{name}.png"));
- Arc::new(Pixmap::from_png(std::fs::File::open(path).unwrap()).unwrap())
-}
-
fn rgb_img_10x10() -> Arc<Pixmap> {
- load_image("rgb_image_10x10")
+ load_image!("rgb_image_10x10")
}
fn rgb_img_2x2() -> Arc<Pixmap> {
- load_image("rgb_image_2x2")
+ load_image!("rgb_image_2x2")
}
fn rgb_img_2x3() -> Arc<Pixmap> {
- load_image("rgb_image_2x3")
+ load_image!("rgb_image_2x3")
}
fn rgba_img_10x10() -> Arc<Pixmap> {
- load_image("rgba_image_10x10")
+ load_image!("rgba_image_10x10")
}
fn luma_img_10x10() -> Arc<Pixmap> {
- load_image("luma_image_10x10")
+ load_image!("luma_image_10x10")
}
fn lumaa_img_10x10() -> Arc<Pixmap> {
- load_image("lumaa_image_10x10")
+ load_image!("lumaa_image_10x10")
}
fn repeat(ctx: &mut impl Renderer, x_extend: Extend, y_extend: Extend) {
diff --git a/sparse_strips/vello_sparse_tests/tests/mix.rs b/sparse_strips/vello_sparse_tests/tests/mix.rs
index e141a2b..9b00cc8 100644
--- a/sparse_strips/vello_sparse_tests/tests/mix.rs
+++ b/sparse_strips/vello_sparse_tests/tests/mix.rs
@@ -1,7 +1,7 @@
// Copyright 2025 the Vello Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
-use crate::image::load_image;
+use crate::load_image;
use crate::renderer::Renderer;
use smallvec::smallvec;
use vello_common::color::palette::css::{BLUE, LIME, MAGENTA, RED, YELLOW};
@@ -49,7 +49,7 @@
};
let image = Image {
- pixmap: load_image("cowboy"),
+ pixmap: load_image!("cowboy"),
x_extend: Extend::Pad,
y_extend: Extend::Pad,
quality: ImageQuality::Low,
diff --git a/sparse_strips/vello_sparse_tests/tests/mod.rs b/sparse_strips/vello_sparse_tests/tests/mod.rs
index 4d9f627..6f5658e 100644
--- a/sparse_strips/vello_sparse_tests/tests/mod.rs
+++ b/sparse_strips/vello_sparse_tests/tests/mod.rs
@@ -37,4 +37,5 @@
mod mix;
mod opacity;
mod renderer;
+#[macro_use]
mod util;
diff --git a/sparse_strips/vello_sparse_tests/tests/util.rs b/sparse_strips/vello_sparse_tests/tests/util.rs
index 0d5469e..a382004 100644
--- a/sparse_strips/vello_sparse_tests/tests/util.rs
+++ b/sparse_strips/vello_sparse_tests/tests/util.rs
@@ -32,6 +32,27 @@
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../vello_sparse_tests/diffs")
});
+/// Helper for loading png images contained within "tests/assets/**".
+#[macro_export]
+macro_rules! load_image {
+ ($name:expr) => {{
+ #[cfg(target_arch = "wasm32")]
+ {
+ let bytes = include_bytes!(concat!("../tests/assets/", $name, ".png"));
+ std::sync::Arc::new(vello_common::pixmap::Pixmap::from_png(&bytes[..]).unwrap())
+ }
+
+ #[cfg(not(target_arch = "wasm32"))]
+ {
+ let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
+ .join(format!("tests/assets/{}.png", $name));
+ std::sync::Arc::new(
+ vello_common::pixmap::Pixmap::from_png(std::fs::File::open(path).unwrap()).unwrap(),
+ )
+ }
+ }};
+}
+
pub(crate) fn get_ctx<T: Renderer>(
width: u16,
height: u16,