Vello test infrastructure (vello_tests, vello_sparse_tests) and the vello_cpu renderer as a deterministic reference path
Document status:
reviewed. Canonical source.
Summary
The Vello repository contains two test systems. vello_tests targets the compute-shader renderer (vello): property tests, snapshot tests that treat GPU shaders as the source of truth while also executing the CPU shader fallbacks, and GPU-versus-CPU comparison tests; all image comparisons pool a FLIP error map and assert on its mean. vello_sparse_tests targets the sparse-strips renderers (vello_cpu, vello_hybrid, WebGL): a proc macro expands each test into per-backend and per-SIMD-level variants compared against one reference PNG with an integer per-component tolerance (0 for the f32 CPU pipeline, 2 for the u8 pipeline, 1 for SIMD and hybrid) and an optional count of pixels allowed to deviate fully. As of August 2026, vello_cpu 0.2.0 is described by its README as a CPU-only renderer with broad feature support, SIMD paths for all major architectures, an optional f32 pipeline intended for test snapshots, and remaining gaps (complex filter graphs panic, experimental glyph caching); the enclosing sparse_strips README still marks the directory as not production-ready.
Evidence
- Test kinds: property tests run on GPU and CPU; snapshot tests “use the GPU shaders as a source of truth, but the CPU shaders are also ran”; they have “a non-exact comparison metric, because of small differences between rendering on different platforms”, including “fast math” on Apple platforms; comparison tests check that “the GPU renderer matches the reference CPU renderer” and are expected to be phased out (
vello_tests/README.md). - Storage: smoke snapshots live in-repository under
smoke_snapshotsand “are always required to pass”; other snapshots use git LFS as “an experiment”, and tests pass on CI if LFS files fail to download because of bandwidth or storage limits (vello_tests/README.md, “LFS”). - Metric:
nv_flip::flip(expected, rendered, nv_flip::DEFAULT_PIXELS_PER_DEGREE)builds aFlipPool; images are converted to RGB8 (alpha dropped); a size mismatch is a failure (vello_tests/src/snapshot.rs, lines 300–345).DEFAULT_PIXELS_PER_DEGREE = 67.0(nv-flip/src/lib.rs, line 15; crate 0.1.2). - Thresholds:
assert_mean_less_than(0.01)in all four smoke snapshots (vello_tests/tests/smoke_snapshots.rs, lines 29, 47, 75, 119) and0.001in a known-issue reproduction (vello_tests/tests/known_issues.rs, line 55); the helper assertsvalue < 0.1as a sanity bound and documents that a non-zero mean may arise “due to fast math on the GPU or different precisions” (vello_tests/src/compare.rs, lines 37–48). - Controls:
VELLO_TEST_UPDATE,VELLO_TEST_CREATE,VELLO_TEST_GENERATE_ALL,VELLO_SKIP_LFS_SNAPSHOTS, each acceptingall,cpu,gpuor a test name (vello_tests/src/snapshot.rs, lines 90–290;src/lib.rs,env_var_relates_to);VELLO_CI_GPU_SUPPORT=nosetscfg(skip_gpu_tests)(vello_tests/build.rs);VELLO_DEBUG_TESTdumps intermediate images (src/lib.rs, line 88). Default anti-aliasing for tests isAaConfig::Area(src/lib.rs, line 71). - Sparse-strips tolerance semantics: a tolerance of 0 “means that it must be an exact match”; 1 means each component may differ by at most 1;
DEFAULT_CPU_U8_TOLERANCE = 2,DEFAULT_SIMD_TOLERANCE = 1,DEFAULT_CPU_F32_TOLERANCE = 0,DEFAULT_HYBRID_TOLERANCE = 1; the u8 value of 2 avoids per-test overrides for bilinear image cases (sparse_strips/vello_dev_macros/src/lib.rs, lines 12–23). - Macro attributes:
cpu_u8_tolerance,hybrid_tolerance(added to the defaults),diff_pixels(“maximum number of pixels that are allowed to completely deviate”, motivated by gradient colour-stop boundaries under floating-point inaccuracy),transparent,skip_cpu,skip_multithreaded,skip_hybrid,hybrid_only,hybrid_no_depth,no_ref,glyph,ignore_reason; generated variants are_cpu_u8_scalar,_cpu_u8_neon,_cpu_u8_sse42,_cpu_u8_avx2,_cpu_u8_wasm, f32 counterparts,_hybrid,_hybrid_webgl,_hybrid_no_depth; one instance is flaggedis_referenceand writes the reference PNG (sparse_strips/vello_dev_macros/src/test.rs, lines 13–70, 80–140, 218–225, 486–580). - Sparse-strips comparison:
check_refrenders, encodes PNG, loadssnapshots/<test>.png, computesget_diff(ref, actual, threshold, diff_pixels);is_pix_diffcompares R, G, B only (alpha ignored) withabs_diff > threshold, treats two alpha-0 pixels as equal; a test fails when the count of differing pixels exceedsdiff_pixels;REPLACE=1rewrites the reference from the reference instance; references are oxipng-optimised; on wasm the snapshot bytes are inlined withinclude_bytes!(sparse_strips/vello_sparse_tests/tests/util.rs, lines 360–460, 549–666;vello_dev_macros/src/test.rs, lines 197–215). - Sparse-strips targets: the crate tests “CPU, WGPU, WASM32 WebGL”; WebGL runs via
wasm-pack test --headless --chrome --features webgl --release(sparse_strips/vello_sparse_tests/README.md). - Architecture: sparse strips aim to run “on GPUs without compute shader support, using only fragment and vertex shaders”, mitigate performance cliffs and handle low-memory conditions; crates
vello_common,vello_cpu(“CPU-based renderer optimized for multithreading and SIMD”),vello_hybrid,vello_sparse_shaders(WGSL→GLSL for WebGL); the directory is “not yet suitable for production use” (sparse_strips/README.md). - vello_cpu status: “a solid CPU-only 2D renderer with broad, reliable feature support” with “optimized SIMD implementations for all major architectures”; limitations: complex filter graphs panic, multi-threaded filters unsupported, glyph caching experimental, API lifecycle rough; features
u8_pipeline(OptimizeSpeed) andf32_pipeline(OptimizeQuality, “espectially useful for rendering test snapshots”),std/libm,multithreading,text; MSRV 1.88 (sparse_strips/vello_cpu/README.md). Design is documented in a 2025 ETH master’s thesis linked from the README. - Versions: vello 0.10.0 (2026-08-14), vello_cpu/vello_hybrid/vello_common 0.2.0 (2026-08-07) (crates.io;
CHANGELOG.md; GitHub releasessparse-strips-v0.2.0). - Text:
vello_tests/tests/hinting.rsandemoji.rsexist as snapshot groups (directory listing);vello_sparse_tests/tests/glyph.rsgenerates cached and uncached glyph variants (vello_dev_macros/src/test.rs,glyph).
Mechanism
vello_tests snapshot:
img = render(scene, params{use_cpu, aa=Area}) # GPU via wgpu or CPU shader fallback
ref = decode(smoke_snapshots/<name>.png | lfs snapshots)
require size(img) == size(ref)
map = FLIP(rgb(ref), rgb(img), ppd = 67)
pass iff mean(map) < threshold # 0.01 typical, 0.001 strict
vello_tests compare_gpu_cpu:
pass iff mean(FLIP(cpu_render, gpu_render)) < threshold # threshold < 0.1 enforced
vello_sparse_tests (#[vello_test(width, height, ...)]):
for variant in {cpu_u8_{scalar,neon,sse42,avx2,wasm}, cpu_f32_..., hybrid, hybrid_webgl, hybrid_no_depth}:
tol = base_tol(variant) + user_tol
n = count(pixels p: not both alpha 0 and any c in RGB |ref_c - img_c| > tol)
pass iff n <= diff_pixels # diff_pixels default 0
reference PNG written once by the designated reference variant; REPLACE=1 regenerates
The CPU f32 pipeline with tolerance 0 is the only configuration in either harness that asserts bit-exact equality against a stored image; this observation is NUIF’s, derived from the constants above.
NUIF relevance
Borrow
- Use
vello_cpuwithRenderMode::OptimizeQuality(f32 pipeline, scalar or a pinned SIMD level) behind the NUIF renderer trait as the deterministic conformance path, because the crate’s own harness already holds that configuration to tolerance 0. - Reuse the tiered tolerance model (exact for CPU f32, ±1 for SIMD/hybrid, ±2 for u8, FLIP mean for wgpu) as the template for NUIF’s determinism tiers, because it is derived from measured behaviour of a Rust renderer rather than assumed.
Adapt
- Replace
diff_pixelsescape hatches with fixture-level tier assignment and recorded reasons, because gradient boundary flips are a property of the fixture class and should be visible in the conformance report. - Record the vello_cpu version, pipeline, SIMD level and thread count in every
renderresult, because the harness shows that each of these changes tolerance.
Reject
- Do not treat wgpu output as a source of truth for conformance, as
vello_testsdoes for its snapshots, because NUIF’s ADR 0003 requires the CPU path to be normative and the GPU path to be an experiment. - Do not depend on git LFS for reference rasters; NUIF fixtures must be small, in-repository and always required to pass, because Vello’s harness deliberately passes when LFS is unavailable.
Open questions
- Whether
vello_cpumultithreaded rendering is bit-identical to single-threaded output in the f32 pipeline; the harness has askip_multithreadedattribute but the tolerance tables do not distinguish thread counts. - Whether the
vello_hybrid±1 tolerance is stable across wgpu backends (Vulkan, Metal, D3D12, WebGL), since the variants share one reference image. - Filter support gaps in
vello_cpu(complex filter graphs) versus the effects vocabulary NUIF intends to specify in spec/05.