Skip to content

Commit 86e9573

Browse files
draft of testing in the browser
1 parent 8491dca commit 86e9573

File tree

9 files changed

+313
-5
lines changed

9 files changed

+313
-5
lines changed

.github/workflows/ci.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,9 +306,27 @@ jobs:
306306

307307
test-stable-wasm:
308308
name: cargo test (wasm32)
309+
needs: prime-lfs-cache
309310
runs-on: ubuntu-latest
310311
steps:
311312
- uses: actions/checkout@v4
313+
# We intentionally do not use lfs: true here, instead using the caching method to save LFS bandwidth.
314+
315+
- name: Restore lfs cache
316+
id: lfs-cache
317+
uses: actions/cache/restore@v4
318+
with:
319+
path: .git/lfs
320+
# The files targeted with git lfs
321+
key: vello-lfs-${{ needs.prime-lfs-cache.outputs.lfs-hash }}
322+
enableCrossOsArchive: true
323+
324+
- name: Checkout LFS files
325+
# `git lfs checkout` requires that each individual glob is a separate command line argument.
326+
# The string `''' '''` is how you write `' '` in GitHub's expression context (i.e. two quotes separated by a space)
327+
# The quotes are to avoid the shell from evaluating the globs itself.
328+
run: git lfs checkout '${{ join(fromJson(env.LFS_FILES), ''' ''') }}'
329+
continue-on-error: true
312330

313331
- name: install stable toolchain
314332
uses: dtolnay/rust-toolchain@master
@@ -337,6 +355,10 @@ jobs:
337355
run: wasm-pack test --headless --chrome
338356
working-directory: sparse_strips/vello_hybrid/examples/native_webgl
339357

358+
- name: check vello_hybrid webgl backend passes vello_sparse_tests suite
359+
run: wasm-pack test --headless --chrome --features webgl
360+
working-directory: sparse_strips/vello_sparse_tests
361+
340362

341363
check-stable-android:
342364
name: cargo check (aarch64-android)

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sparse_strips/vello_dev_macros/src/test.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ pub(crate) fn vello_test_inner(attr: TokenStream, item: TokenStream) -> TokenStr
6161
let u8_fn_name = Ident::new(&format!("{}_cpu_u8", input_fn_name), input_fn_name.span());
6262
let f32_fn_name = Ident::new(&format!("{}_cpu_f32", input_fn_name), input_fn_name.span());
6363
let hybrid_fn_name = Ident::new(&format!("{}_hybrid", input_fn_name), input_fn_name.span());
64+
let webgl_fn_name = Ident::new(
65+
&format!("{}_hybrid_webgl", input_fn_name),
66+
input_fn_name.span(),
67+
);
6468

6569
// TODO: Tests with the same names in different modules can clash, see
6670
// https://github.com/linebender/vello/pull/925#discussion_r2070710362.
@@ -70,6 +74,7 @@ pub(crate) fn vello_test_inner(attr: TokenStream, item: TokenStream) -> TokenStr
7074
let u8_fn_name_str = u8_fn_name.to_string();
7175
let f32_fn_name_str = f32_fn_name.to_string();
7276
let hybrid_fn_name_str = hybrid_fn_name.to_string();
77+
let webgl_fn_name_str = webgl_fn_name.to_string();
7378

7479
let Arguments {
7580
width,
@@ -127,6 +132,7 @@ pub(crate) fn vello_test_inner(attr: TokenStream, item: TokenStream) -> TokenStr
127132
render_mode: proc_macro2::TokenStream| {
128133
quote! {
129134
#ignore_cpu
135+
#[cfg(not(all(target_arch = "wasm32", feature = "webgl")))]
130136
#[test]
131137
fn #fn_name() {
132138
use crate::util::{
@@ -166,6 +172,7 @@ pub(crate) fn vello_test_inner(attr: TokenStream, item: TokenStream) -> TokenStr
166172
#f32_snippet
167173

168174
#ignore_hybrid
175+
#[cfg(not(all(target_arch = "wasm32", feature = "webgl")))]
169176
#[test]
170177
fn #hybrid_fn_name() {
171178
use crate::util::{
@@ -180,6 +187,23 @@ pub(crate) fn vello_test_inner(attr: TokenStream, item: TokenStream) -> TokenStr
180187
check_ref(&ctx, #input_fn_name_str, #hybrid_fn_name_str, #hybrid_tolerance, false, RenderMode::OptimizeSpeed);
181188
}
182189
}
190+
191+
#ignore_hybrid
192+
#[cfg(all(target_arch = "wasm32", feature = "webgl"))]
193+
#[wasm_bindgen_test::wasm_bindgen_test]
194+
async fn #webgl_fn_name() {
195+
use crate::util::{
196+
check_ref, get_ctx
197+
};
198+
use vello_hybrid::Scene;
199+
use vello_cpu::RenderMode;
200+
201+
let mut ctx = get_ctx::<Scene>(#width, #height, #transparent);
202+
#input_fn_name(&mut ctx);
203+
if !#no_ref {
204+
check_ref(&ctx, #input_fn_name_str, #webgl_fn_name_str, #hybrid_tolerance, false, RenderMode::OptimizeSpeed);
205+
}
206+
}
183207
};
184208

185209
expanded.into()

sparse_strips/vello_sparse_tests/Cargo.toml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,24 @@ image = { workspace = true, features = ["png"] }
2828
skrifa = { workspace = true }
2929
smallvec = { workspace = true }
3030

31+
[target.'cfg(target_arch = "wasm32")'.dependencies]
32+
wasm-bindgen-test = "0.3.50"
33+
web-sys = { version = "0.3.77", features = [
34+
"HtmlCanvasElement",
35+
"WebGl2RenderingContext",
36+
"Document",
37+
"Window",
38+
"Element",
39+
"HtmlElement",
40+
"HtmlImageElement",
41+
"Blob",
42+
"BlobPropertyBag",
43+
"Url",
44+
] }
45+
wasm-bindgen = "0.2.100"
46+
47+
[features]
48+
webgl = ["vello_hybrid/webgl"]
49+
3150
[lints]
3251
workspace = true
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<div align="center">
2+
3+
# Vello Sparse Tests
4+
5+
[![Apache 2.0 or MIT license.](https://img.shields.io/badge/license-Apache--2.0_OR_MIT-blue.svg)](#license)
6+
\
7+
[![Linebender Zulip chat.](https://img.shields.io/badge/Linebender-%23vello-blue?logo=Zulip)](https://xi.zulipchat.com/#narrow/channel/197075-vello)
8+
[![GitHub Actions CI status.](https://img.shields.io/github/actions/workflow/status/linebender/vello/ci.yml?logo=github&label=CI)](https://github.com/linebender/vello/actions)
9+
10+
</div>
11+
12+
This is a development only crate for testing the sparse_strip renderers across a corpus of reference
13+
images:
14+
- cpu
15+
- wgpu
16+
- wasm32 WebGL
17+
18+
## Testing WebGL on the Browser
19+
20+
To run the `vello_sparse_tests` suite on WebGL headless:
21+
22+
```
23+
wasm-pack test --headless --chrome --features webgl
24+
```
25+
26+
To debug the output images in webgl, run the same command without `--headless`. Any tests that fail
27+
will have their diff image appended to the bottom of the page.
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2025 the Vello Authors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
//! Build step for `vello_sparse_tests`.
5+
//!
6+
//! This build step exists primarily for the wasm32 target testing the browser exclusive "webgl"
7+
//! feature which cannot read snapshot images from the file system. Instead, the reference images
8+
//! are inlined into the binary.
9+
#[cfg(not(feature = "webgl"))]
10+
fn main() {}
11+
#[cfg(feature = "webgl")]
12+
fn main() {
13+
use std::fs;
14+
use std::io::Write;
15+
use std::path::Path;
16+
println!("cargo:rerun-if-changed=snapshots");
17+
18+
let out_dir = std::env::var("OUT_DIR").unwrap();
19+
let dest_path = Path::new(&out_dir).join("reference_images.rs");
20+
let mut f = fs::File::create(&dest_path).unwrap();
21+
22+
writeln!(
23+
f,
24+
"// Generated by build.rs: Inlined reference images for WASM tests"
25+
)
26+
.unwrap();
27+
writeln!(f).unwrap();
28+
writeln!(
29+
f,
30+
"fn get_reference_image(name: &str) -> Option<&'static [u8]> {{"
31+
)
32+
.unwrap();
33+
writeln!(f, " match name {{").unwrap();
34+
35+
let snapshot_dir = Path::new("../vello_sparse_tests/snapshots");
36+
if snapshot_dir.exists() {
37+
for entry in fs::read_dir(snapshot_dir).unwrap() {
38+
let entry = entry.unwrap();
39+
let path = entry.path();
40+
if path.extension().and_then(|s| s.to_str()) == Some("png") {
41+
let name = path.file_stem().unwrap().to_str().unwrap();
42+
writeln!(
43+
f,
44+
r#" "{}" => Some(include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/../vello_sparse_tests/snapshots/{}.png"))),"#,
45+
name, name
46+
).unwrap();
47+
}
48+
}
49+
}
50+
51+
writeln!(f, " _ => None,").unwrap();
52+
writeln!(f, " }}").unwrap();
53+
writeln!(f, "}}").unwrap();
54+
}

sparse_strips/vello_sparse_tests/tests/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
2121
#![allow(missing_docs, reason = "we don't need docs for testing")]
2222
#![allow(clippy::cast_possible_truncation, reason = "not critical for testing")]
23+
#![cfg(all(target_arch = "wasm32", feature = "webgl"))]
24+
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
2325

2426
mod basic;
2527
mod blurred_rounded_rect;

sparse_strips/vello_sparse_tests/tests/renderer.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ impl Renderer for Scene {
219219
// This method creates device resources every time it is called. This does not matter much for
220220
// testing, but should not be used as a basis for implementing something real. This would be a
221221
// very bad example for that.
222+
#[cfg(not(all(target_arch = "wasm32", feature = "webgl")))]
222223
fn render_to_pixmap(&self, pixmap: &mut Pixmap, _: RenderMode) {
223224
// On some platforms using `cargo test` triggers segmentation faults in wgpu when the GPU
224225
// tests are run in parallel (likely related to the number of device resources being
@@ -362,6 +363,57 @@ impl Renderer for Scene {
362363
texture_copy_buffer.unmap();
363364
}
364365

366+
#[cfg(all(target_arch = "wasm32", feature = "webgl"))]
367+
fn render_to_pixmap(&self, pixmap: &mut Pixmap, _: RenderMode) {
368+
use wasm_bindgen::JsCast;
369+
use web_sys::{HtmlCanvasElement, WebGl2RenderingContext};
370+
371+
let width = self.width();
372+
let height = self.height();
373+
374+
// Create an offscreen HTMLCanvasElement, render the test image to it, and finally read off
375+
// the pixmap for diff checking.
376+
let document = web_sys::window().unwrap().document().unwrap();
377+
378+
let canvas = document
379+
.create_element("canvas")
380+
.unwrap()
381+
.dyn_into::<HtmlCanvasElement>()
382+
.unwrap();
383+
384+
canvas.set_width(width.into());
385+
canvas.set_height(height.into());
386+
387+
let mut renderer = vello_hybrid::WebGlRenderer::new(&canvas);
388+
let render_size = vello_hybrid::RenderSize {
389+
width: width.into(),
390+
height: height.into(),
391+
};
392+
393+
renderer.render(self, &render_size).unwrap();
394+
395+
let gl = canvas
396+
.get_context("webgl2")
397+
.unwrap()
398+
.unwrap()
399+
.dyn_into::<WebGl2RenderingContext>()
400+
.unwrap();
401+
let mut pixels = vec![0_u8; (width as usize) * (height as usize) * 4];
402+
gl.read_pixels_with_opt_u8_array(
403+
0,
404+
0,
405+
width.into(),
406+
height.into(),
407+
WebGl2RenderingContext::RGBA,
408+
WebGl2RenderingContext::UNSIGNED_BYTE,
409+
Some(&mut pixels),
410+
)
411+
.unwrap();
412+
413+
let pixmap_data = pixmap.data_as_u8_slice_mut();
414+
pixmap_data.copy_from_slice(&pixels);
415+
}
416+
365417
fn width(&self) -> u16 {
366418
Self::width(self)
367419
}

0 commit comments

Comments
 (0)