GPU tests: pass on Mesa lavapipe (boundary flips, skip the Reshape case that crashes it); cargo fmt (#2239)

* cargo fmt

* GPU tests: llvmpipe on Vulkan gets the boundary-flip allowance; skip the Reshape boundary cases that crash lavapipe (#2238)

* GPU tests: the llvmpipe boundary-flip allowance is an opt-in of the two tests that need it
This commit is contained in:
aiongg
2026-10-23 14:16:37 -04:00
committed by GitHub
parent 89b8db7718
commit 7e49932caf
5 changed files with 45 additions and 8 deletions
+1 -3
View File
@@ -50,9 +50,7 @@ pub fn resolve(stored: &str, project: Option<&str>) -> Result<PathBuf, NotFollow
// Resolved, but still relative: it would be taken from the working directory, which the
// project does not choose. A Windows drive or share path (`C:\x`) stays invalid outside
// Windows even with a project.
Refused::Invalid
if project.is_none() && craft_docpath::resolve(stored, None).is_ok_and(|p| cfg!(windows) || !is_windows_rooted(&p)) =>
{
Refused::Invalid if project.is_none() && craft_docpath::resolve(stored, None).is_ok_and(|p| cfg!(windows) || !is_windows_rooted(&p)) => {
NotFollowed::NoProjectFolder
}
why => NotFollowed::Refused(why),
+31 -2
View File
@@ -79,6 +79,35 @@ pub(crate) fn on_gl() -> bool {
gpu().is_some_and(|g| g.ctx.name.ends_with("(Gl)"))
}
/// The test device is Mesa's software rasteriser, llvmpipe: on OpenGL, or as lavapipe on Vulkan
/// (the Linux CI gate).
pub(crate) fn on_llvmpipe() -> bool {
gpu().is_some_and(|g| g.ctx.name.starts_with("llvmpipe"))
}
thread_local! {
static LLVMPIPE_FLIPS: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
}
/// Until the guard drops, comparisons on lavapipe get [`GL_BOUNDARY_FLIPS`] too. Only the tests
/// that measured such flips there opt in (CC HexTile cell edges, CC Threshold RGB thresholds);
/// every other comparison on the Linux gate stays exact.
pub(crate) fn allow_llvmpipe_boundary_flips() -> impl Drop {
struct Reset;
impl Drop for Reset {
fn drop(&mut self) {
LLVMPIPE_FLIPS.with(|f| f.set(false));
}
}
LLVMPIPE_FLIPS.with(|f| f.set(true));
Reset
}
/// The boundary-flip allowance applies: OpenGL, or llvmpipe in a test that opted in.
fn boundary_flips_allowed() -> bool {
on_gl() || (LLVMPIPE_FLIPS.with(std::cell::Cell::get) && on_llvmpipe())
}
/// On GL, the fraction of pixels allowed past the tolerance: pixels on a decision boundary (a
/// hex cell's edge, a threshold, a corner pin's coverage edge, a median's rank tie) may land on
/// the other side, since GL drivers may divide through a reciprocal and GLSL's `fma` need not be
@@ -255,7 +284,7 @@ pub(crate) fn check(label: &str, d: Option<Diff>, allow: f64) {
let frac = d.over as f64 / d.total as f64;
// Quantised depths: rounding-boundary flips (see the module docs).
let (allow, cap) = if d.quantized && allow == 0.0 { (0.003, 4.0 / 255.0 + 1e-6) } else { (allow, f32::INFINITY) };
let (allow, cap) = if on_gl() { (allow.max(GL_BOUNDARY_FLIPS), f32::INFINITY) } else { (allow, cap) };
let (allow, cap) = if boundary_flips_allowed() { (allow.max(GL_BOUNDARY_FLIPS), f32::INFINITY) } else { (allow, cap) };
assert!(frac <= allow && d.max <= cap, "{label}: {} of {} pixels over tolerance (max {:.6}); worst {:?}", d.over, d.total, d.max, d.worst);
if d.over > 0 {
eprintln!("{label}: {} of {} pixels over tolerance (max {:.6})", d.over, d.total, d.max);
@@ -454,7 +483,7 @@ pub(crate) fn effect_direct(id: &str, vals: &[(&str, Value)], adjustment: bool)
let out = effectcraft_render::Accelerator::effects(g, &[effectcraft_render::FxStep { spec, ctx: ctx() }], &buf, None).expect("the GPU runs the effect");
assert_eq!((out.offset, out.scale), (cpu.offset, cpu.scale), "{id}: geometry");
let d = diff(&cpu.img, &out.img, 1e-3);
let allow = if on_gl() { (d.total as f64 * GL_BOUNDARY_FLIPS) as usize } else { 0 };
let allow = if boundary_flips_allowed() { (d.total as f64 * GL_BOUNDARY_FLIPS) as usize } else { 0 };
assert!(
d.over <= allow,
"{id} {vals:?} adj {adjustment} scale {scale}: {} of {} pixels over 1e-3 (max {}); worst {:?}",
+2 -1
View File
@@ -12,7 +12,7 @@ use effectcraft_project::{BitDepth, MaskMode};
use effectcraft_render::RenderOpts;
use effectcraft_time::Tick;
use crate::tests::{Scene, c, check, compare_at, diff, effect_case, gpu, n, opts, pattern, set};
use crate::tests::{Scene, allow_llvmpipe_boundary_flips, c, check, compare_at, diff, effect_case, gpu, n, opts, pattern, set};
pub(crate) fn e(v: u32) -> Value {
Value::Enum(v)
@@ -193,6 +193,7 @@ fn composites_and_colour() {
#[test]
fn stylize() {
let _flips = allow_llvmpipe_boundary_flips();
for (done, flags) in
[(30.0, [true, true, true, false]), (55.0, [false, true, false, true]), (70.0, [true, false, false, false]), (10.0, [false, false, true, true])]
{
+2 -1
View File
@@ -6,7 +6,7 @@ use effectcraft_project::BitDepth;
use effectcraft_render::RenderOpts;
use effectcraft_time::Tick;
use crate::tests::{Scene, c, check, compare_at, effect_case, n, opts, set};
use crate::tests::{Scene, allow_llvmpipe_boundary_flips, c, check, compare_at, effect_case, n, opts, set};
fn e(v: u32) -> Value {
Value::Enum(v)
@@ -22,6 +22,7 @@ fn pt(x: f64, y: f64) -> Value {
#[test]
fn posterize_thresholds_strobe_vignette() {
let _flips = allow_llvmpipe_boundary_flips();
effect_case("ec.stylize.posterize", &[("level", n(4.0))]);
effect_case("ec.stylize.posterize", &[("level", n(9.0))]);
effect_case("ec.stylize.threshold", &[("level", n(100.0))]);
+9 -1
View File
@@ -2,6 +2,8 @@
//! Emboss, Cartoon) vs the CPU effects (the oracle): direct on a buffer at full and half
//! resolution, as adjustment, and composited at 8 and 32 bpc.
use std::io::Write;
use effectcraft_effects::MaskShape;
use effectcraft_keyframe::{ShapePath, Value};
use effectcraft_project::build::{self, Ids};
@@ -9,7 +11,7 @@ use effectcraft_project::{BitDepth, MaskMode};
use effectcraft_render::RenderOpts;
use effectcraft_time::Tick;
use crate::tests::{Scene, c, check, compare_at, diff, effect_case, gpu, n, opts, pattern};
use crate::tests::{Scene, c, check, compare_at, diff, effect_case, gpu, n, on_llvmpipe, opts, pattern};
fn e(v: u32) -> Value {
Value::Enum(v)
@@ -182,6 +184,12 @@ fn smear_and_reshape() {
],
];
for vals in reshape {
if on_llvmpipe() && vals.iter().any(|(k, _)| *k == "boundaryMask") {
// Straight to stderr: libtest captures `eprintln!` of passing tests, and the skip
// should show in the log.
let _ = writeln!(std::io::stderr(), "tests_fx_warp::smear_and_reshape: skipping ec.distort.reshape {vals:?} on llvmpipe (crashes lavapipe, #2238)");
continue;
}
direct_with_masks("ec.distort.reshape", vals);
composite_with_masks("ec.distort.reshape", vals);
}