mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-26 19:18:14 -06:00
Compute kernels in shaders/fx_distort.wgsl mirroring the CPU effects; per-row/column structure (map coordinates, wave phase, tile bounds, mesh triangle buckets) is computed in f64 on the CPU and uploaded. Displacement Map fetches its map layer through the effect host as the CPU does and samples it on the GPU. CPU oracle tests in tests_fx_distort.rs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
120 lines
5.5 KiB
Rust
120 lines
5.5 KiB
Rust
//! Distort / stylize GPU effects vs the CPU effects (the oracle): see `fx_distort.rs`.
|
||
|
||
use effectcraft_keyframe::Value;
|
||
use effectcraft_project::BitDepth;
|
||
use effectcraft_render::RenderOpts;
|
||
use effectcraft_time::Tick;
|
||
|
||
use crate::tests::{Scene, check, compare_at, effect_case, n, opts, set};
|
||
|
||
fn e(v: u32) -> Value {
|
||
Value::Enum(v)
|
||
}
|
||
|
||
fn on() -> Value {
|
||
Value::Bool(true)
|
||
}
|
||
|
||
#[test]
|
||
fn twirl_bulge_ripple_wave_warp() {
|
||
effect_case("ec.distort.twirl", &[("angle", n(120.0)), ("radius", n(40.0))]);
|
||
effect_case("ec.distort.twirl", &[("angle", n(-400.0)), ("radius", n(70.0)), ("center", Value::Vec2([25.0, 18.0]))]);
|
||
effect_case("ec.distort.bulge", &[("hradius", n(30.0)), ("vradius", n(20.0)), ("height", n(1.8))]);
|
||
effect_case("ec.distort.bulge", &[("height", n(-2.5)), ("taperRadius", n(60.0)), ("pinAllEdges", on()), ("center", Value::Vec2([30.0, 25.0]))]);
|
||
effect_case("ec.distort.ripple", &[("radius", n(80.0)), ("waveWidth", n(9.0)), ("waveHeight", n(4.0))]);
|
||
effect_case("ec.distort.ripple", &[("conversion", e(1)), ("phase", n(70.0)), ("speed", n(3.0)), ("center", Value::Vec2([20.0, 30.0]))]);
|
||
effect_case("ec.distort.wavewarp", &[("height", n(6.0)), ("width", n(25.0)), ("direction", n(30.0))]);
|
||
effect_case("ec.distort.wavewarp", &[("waveType", e(2)), ("pinning", e(1)), ("phase", n(400.0)), ("speed", n(-2.0))]);
|
||
effect_case("ec.distort.wavewarp", &[("waveType", e(8)), ("pinning", e(2)), ("height", n(-5.0)), ("direction", n(200.0))]);
|
||
effect_case("ec.distort.wavewarp", &[("waveType", e(4)), ("pinning", e(7)), ("width", n(33.0))]);
|
||
}
|
||
|
||
#[test]
|
||
fn turbulent_displace_and_cc_lens() {
|
||
effect_case("ec.distort.turbulentdisplace", &[("size", n(30.0)), ("amount", n(20.0))]);
|
||
effect_case(
|
||
"ec.distort.turbulentdisplace",
|
||
&[("displacement", e(1)), ("complexity", n(3.5)), ("evolution", n(90.0)), ("size", n(25.0)), ("pinning", e(2)), ("resizeLayer", on())],
|
||
);
|
||
effect_case(
|
||
"ec.distort.turbulentdisplace",
|
||
&[
|
||
("displacement", e(5)),
|
||
("evolutionOptions/cycleEvolution", on()),
|
||
("evolutionOptions/cycle", n(2.0)),
|
||
("evolution", n(500.0)),
|
||
("evolutionOptions/randomSeed", n(7.0)),
|
||
("pinning", e(0)),
|
||
("amount", n(-30.0)),
|
||
],
|
||
);
|
||
effect_case("ec.distort.turbulentdisplace", &[("displacement", e(8)), ("size", n(15.0)), ("pinning", e(6))]);
|
||
effect_case("ec.distort.cclens", &[("size", n(70.0))]);
|
||
effect_case("ec.distort.cclens", &[("size", n(45.0)), ("convergence", n(60.0)), ("center", Value::Vec2([30.0, 20.0]))]);
|
||
effect_case("ec.distort.cclens", &[("convergence", n(-70.0))]);
|
||
}
|
||
|
||
#[test]
|
||
fn mesh_warp() {
|
||
// 2 rows × 3 columns: 12 vertices.
|
||
let mesh = "0,0 0,0 0,0 0,0 0,0 6,-4 -5,3 0,0 0,0 0,0 0,0 0,0";
|
||
effect_case("ec.distort.meshwarp", &[("rows", n(2.0)), ("columns", n(3.0)), ("quality", n(3.0)), ("mesh", Value::Str(mesh.into()))]);
|
||
let mesh = "-4,-3 2,0 8,5 0,-2 9,9 1,0 -3,4 2,2 4,-6";
|
||
effect_case("ec.distort.meshwarp", &[("rows", n(2.0)), ("columns", n(2.0)), ("quality", n(5.0)), ("mesh", Value::Str(mesh.into()))]);
|
||
}
|
||
|
||
#[test]
|
||
fn mosaic_find_edges_emboss() {
|
||
effect_case("ec.stylize.mosaic", &[]);
|
||
effect_case("ec.stylize.mosaic", &[("horizontal", n(7.0)), ("vertical", n(3.0))]);
|
||
effect_case("ec.stylize.mosaic", &[("horizontal", n(13.0)), ("vertical", n(9.0)), ("sharpColors", on())]);
|
||
effect_case("ec.stylize.findedges", &[]);
|
||
effect_case("ec.stylize.findedges", &[("invert", on()), ("blend", n(30.0))]);
|
||
effect_case("ec.stylize.emboss", &[]);
|
||
effect_case("ec.stylize.emboss", &[("direction", n(160.0)), ("relief", n(4.0)), ("contrast", n(250.0)), ("blend", n(20.0))]);
|
||
}
|
||
|
||
#[test]
|
||
fn displacement_map_self() {
|
||
effect_case("ec.distort.displacementmap", &[("maxHorizontal", n(8.0)), ("maxVertical", n(-5.0))]);
|
||
effect_case("ec.distort.displacementmap", &[("useForHorizontal", e(4)), ("useForVertical", e(3)), ("wrapPixelsAround", on())]);
|
||
effect_case(
|
||
"ec.distort.displacementmap",
|
||
&[("useForHorizontal", e(10)), ("useForVertical", e(6)), ("maxVertical", n(12.0)), ("expandOutput", Value::Bool(false))],
|
||
);
|
||
}
|
||
|
||
/// Displacement Map reading another layer (Center / Stretch / Tile), composited.
|
||
#[test]
|
||
fn displacement_map_layer() {
|
||
for depth in [BitDepth::Bpc8, BitDepth::Bpc32] {
|
||
for (behavior, wrap) in [(0, false), (1, false), (2, true), (2, false)] {
|
||
let mut s = Scene::new(depth);
|
||
let bg = s.solid([0.15, 0.1, 0.2], 97, 61);
|
||
s.push(bg);
|
||
let mut map = s.footage(50, 36);
|
||
map.switches.video = false;
|
||
let mid = s.push(map);
|
||
let mut l = s.footage(70, 44);
|
||
s.effect(
|
||
&mut l,
|
||
"ec.distort.displacementmap",
|
||
&[
|
||
("displacementMapLayer", Value::Layer(Some(mid.0))),
|
||
("useForHorizontal", e(0)),
|
||
("useForVertical", e(4)),
|
||
("maxHorizontal", n(9.0)),
|
||
("maxVertical", n(6.0)),
|
||
("displacementMapBehavior", e(behavior)),
|
||
("wrapPixelsAround", Value::Bool(wrap)),
|
||
],
|
||
);
|
||
set(&mut l, "transform/rotation", Value::Scalar(8.0));
|
||
s.push(l);
|
||
let label = format!("displacement map {behavior} {wrap} {depth:?}");
|
||
check(&label, compare_at(&s, opts(), Tick::ZERO), 0.0);
|
||
check(&format!("{label} half"), compare_at(&s, RenderOpts { scale: 0.5, ..opts() }, Tick::ZERO), 0.0);
|
||
}
|
||
}
|
||
}
|