git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

tests/plate_golden.rs (9.1K)

  1 //! The plate-path golden: one scene through every surface that carries a
  2 //! material — root and nested plates in both frost regimes, the roll overlay,
  3 //! bevels, every `ControlPlate` stance with and without a face and a focus
  4 //! tint, inset plates, wells, the sphere and the droplet — tessellated at two
  5 //! scales on both the SDF and the legacy edge path, and dumped as text.
  6 //!
  7 //! This is the RFC-material migration's exit test (`docs/rfc-material.md`
  8 //! § 7): a step that must not move a pixel proves it by tessellating the SAME
  9 //! SCENE to the SAME BYTES as the commit before it. The scene is built through
 10 //! the public paint API, so the builder is rewritten as the API changes while
 11 //! the dump it must reproduce is not.
 12 //!
 13 //! Not a fixture check — the dump depends on the machine's live style config
 14 //! (roll width, radii, frost recipe), so it is generated and compared on the
 15 //! same machine across commits:
 16 //!
 17 //! ```sh
 18 //! CCE_PLATE_GOLDEN_WRITE=/tmp/golden.txt cargo test -p cce-ui --test plate_golden
 19 //! # ...migrate...
 20 //! CCE_PLATE_GOLDEN=/tmp/golden.txt       cargo test -p cce-ui --test plate_golden
 21 //! ```
 22 //!
 23 //! With neither variable set the test passes trivially (it only asserts that
 24 //! the scene tessellates).
 25 
 26 use cce_ui::scene::layout::Rect;
 27 use cce_ui::scene::paint::{ControlPlate, DropletSpec, PaintCtx, PlateSpec, PlateStance};
 28 use cce_ui::scene::Material;
 29 use std::fmt::Write as _;
 30 
 31 fn r(x: f32, y: f32, w: f32, h: f32) -> Rect {
 32     Rect { x, y, width: w, height: h }
 33 }
 34 
 35 /// The scene. Every branch of the tessellator that reads a colour or the
 36 /// material push constants is reached at least once.
 37 fn scene(sw: f32, sh: f32) -> cce_ui::scene::paint::DisplayList {
 38     let mut pc = PaintCtx::new();
 39     let depth = cce_ui::layout::bevel_width();
 40     let rr = (8.0, 8.0, 8.0, 8.0);
 41 
 42     // Root plate: opaque, and "frosted" (alpha must stay positive — the
 43     // compositor's regime).
 44     pc.plate_spec(&PlateSpec {
 45         rect: r(0.0, 0.0, sw, sh),
 46         material: cce_ui::scene::Material::opaque([0.10, 0.10, 0.14, 0.9]),
 47         window_corners: (true, true, true, true),
 48         depth,
 49     });
 50     pc.plate_spec(&PlateSpec {
 51         rect: r(0.0, 0.0, sw, sh),
 52         material: cce_ui::scene::Material::opaque([0.10, 0.10, 0.14, 0.9]).with_frost(cce_ui::scene::Frost::from_style()),
 53         window_corners: (true, true, true, true),
 54         depth,
 55     });
 56     // A carve into the root plate: exercises the CSG feature grouping.
 57     pc.recess(r(20.0, 20.0, 100.0, 30.0), rr, depth.min(6.0));
 58 
 59     // Nested pane plates: opaque, frosted (the in-app sentinel), on a right
 60     // edge (mixed corners).
 61     pc.plate_spec(&PlateSpec {
 62         rect: r(20.0, 60.0, 160.0, 100.0),
 63         material: cce_ui::scene::Material::opaque([0.2, 0.2, 0.25, 0.8]),
 64         window_corners: (false, false, false, false),
 65         depth: depth.min(6.0),
 66     });
 67     pc.plate_spec(&PlateSpec {
 68         rect: r(200.0, 60.0, 160.0, 100.0),
 69         material: cce_ui::scene::Material::opaque([0.02, 0.02, 0.03, 0.25]).with_frost(cce_ui::scene::Frost::from_style()),
 70         window_corners: (false, false, false, false),
 71         depth: depth.min(6.0),
 72     });
 73     pc.plate_spec(&PlateSpec {
 74         rect: r(sw - 120.0, 0.0, 120.0, sh),
 75         material: cce_ui::scene::Material::opaque([0.2, 0.2, 0.25, 0.8]).with_frost(cce_ui::scene::Frost::from_style()),
 76         window_corners: (false, true, true, false),
 77         depth,
 78     });
 79     // The fill-less roll overlay (negative depth) and an explicit shape.
 80     pc.plate_spec(&PlateSpec {
 81         rect: r(0.0, 0.0, sw, sh),
 82         material: cce_ui::scene::Material::opaque([0.0; 4]),
 83         window_corners: (true, true, true, true),
 84         depth: -depth,
 85     });
 86     pc.plate_shaped(r(30.0, 170.0, 40.0, 40.0), (20.0, 20.0, 20.0, 20.0), &cce_ui::scene::Material::from_fill([0.3, 0.3, 0.35, 1.0]), 4.0, Some(2.0));
 87 
 88     // Bare plates the legacy callers emit: an opaque face, a frosted one
 89     // (negative alpha handed straight in, the menu idiom).
 90     pc.plate(r(80.0, 170.0, 60.0, 30.0), rr, &cce_ui::scene::Material::from_fill([0.13, 0.14, 0.16, 1.0]), 4.0);
 91     pc.plate(r(150.0, 170.0, 60.0, 30.0), rr, &cce_ui::scene::Material::from_fill([0.13, 0.14, 0.16, -0.8]), 4.0);
 92 
 93     // Bevels, plain and tinted (the focused-pane ring).
 94     pc.bevel(r(220.0, 170.0, 60.0, 30.0), rr, &cce_ui::scene::Material::from_fill([0.25, 0.25, 0.3, 1.0]), 4.0);
 95     pc.bevel_tinted(r(290.0, 170.0, 60.0, 30.0), rr, &cce_ui::scene::Material::from_fill([0.25, 0.25, 0.3, 1.0]), 4.0, [0.4, 0.6, 1.0]);
 96     pc.bevel_tinted(r(290.0, 205.0, 60.0, 30.0), rr, &cce_ui::scene::Material::from_fill([0.0; 4]), 4.0, [0.4, 0.6, 1.0]);
 97 
 98     // Every control stance × face × tint.
 99     let faces: [[f32; 4]; 3] = [[0.3, 0.3, 0.36, 1.0], [0.0; 4], [0.3, 0.3, 0.36, -0.6]];
100     let mut y = 210.0;
101     for stance in [PlateStance::Raised, PlateStance::Flush, PlateStance::Flat] {
102         let mut x = 20.0;
103         for face in faces {
104             for tint in [None, Some(ControlPlate::focus_tint())] {
105                 let plate = ControlPlate::control(r(x, y, 36.0, 20.0), 6.0, stance, Material::face(face)).with_tint(tint);
106                 pc.control_plate(&plate);
107                 x += 42.0;
108             }
109         }
110         y += 26.0;
111     }
112 
113     // Inset plates and wells.
114     pc.inset_plate(r(20.0, 290.0, 60.0, 24.0), rr, Material::face([0.2, 0.2, 0.24, 1.0]).as_ref(), 4.0);
115     pc.inset_plate(r(90.0, 290.0, 60.0, 24.0), rr, Material::face([0.0; 4]).as_ref(), 4.0);
116     pc.inset_plate_tinted(r(160.0, 290.0, 60.0, 24.0), rr, Material::face([0.2, 0.2, 0.24, -0.5]).as_ref(), 4.0, [1.0, 0.5, 0.2]);
117     let pane = Material::pane();
118     let glass = Material::opaque([0.02, 0.02, 0.03, 0.25]).with_frost(cce_ui::scene::Frost::from_style());
119     pc.well_floor(r(230.0, 290.0, 40.0, 24.0), 6.0, &pane, false);
120     pc.well_floor(r(280.0, 290.0, 40.0, 24.0), 6.0, &pane, true);
121     pc.canvas_well(r(330.0, 290.0, 40.0, 24.0), 6.0, &pane, true, false);
122     pc.canvas_well(r(330.0, 320.0, 40.0, 24.0), 6.0, &glass, false, true);
123 
124     // The lit ball and the water: default spec, and one with its own finish
125     // and depth terms.
126     pc.sphere(40.0, 340.0, 10.0, &cce_ui::scene::Material::from_fill([0.4, 0.4, 0.5, 1.0]));
127     pc.droplet(r(80.0, 330.0, 120.0, 30.0), &cce_ui::scene::Material::from_fill([0.1, 0.1, 0.12, -0.7]).with_finish(DropletSpec::default().finish()), DropletSpec::default());
128     let wet = DropletSpec::parse("gleam=1.0 shine=16 rim=0.3 clarity=0.5 core=0.4 shadow=0.3 refr=2");
129     pc.droplet(r(220.0, 330.0, 120.0, 30.0), &cce_ui::scene::Material::from_fill([0.1, 0.1, 0.12, 0.9]).with_finish(wet.finish()), wet);
130     pc.droplet_scrim(r(220.0, 365.0, 120.0, 30.0), &cce_ui::scene::Material::from_fill([0.1, 0.1, 0.12, 0.9]), wet, 3.0);
131 
132     pc.finish()
133 }
134 
135 fn dump(out: &mut String, label: &str, dl: &cce_ui::scene::paint::DisplayList, sw: f32, sh: f32, scale: f32) {
136     let (verts, batches, images, features) =
137         cce_ui::backend::window_runner::tessellate_display_list(dl, sw, sh, scale);
138     writeln!(out, "== {label} scale={scale} verts={} batches={} images={} features={}",
139         verts.len(), batches.len(), images.len(), features.len()).unwrap();
140     for (i, v) in verts.iter().enumerate() {
141         writeln!(out, "v{i} {:?} {:?} {:?}", v.position, v.color, v.clip_circle).unwrap();
142     }
143     for (i, b) in batches.iter().enumerate() {
144         writeln!(out, "b{i} scissor={:?} clip={:?} {}..{} blur={} plate={:?}",
145             b.scissor, b.clip_rrect, b.start, b.end, b.blur_behind, b.plate).unwrap();
146     }
147     for (i, f) in features.iter().enumerate() {
148         writeln!(out, "f{i} {f:?}").unwrap();
149     }
150 }
151 
152 #[test]
153 fn plate_paths_tessellate_to_the_golden() {
154     let (sw, sh) = (400.0, 400.0);
155     let mut out = String::new();
156     for shader in [true, false] {
157         cce_ui::layout::get_style_registry().write().unwrap().set_float("bevel_shader", if shader { 1.0 } else { 0.0 });
158         let dl = scene(sw, sh);
159         for scale in [1.0, 2.0] {
160             dump(&mut out, if shader { "sdf" } else { "legacy" }, &dl, sw, sh, scale);
161         }
162     }
163     cce_ui::layout::get_style_registry().write().unwrap().set_float("bevel_shader", 1.0);
164     assert!(out.lines().count() > 100, "the scene tessellated to almost nothing");
165 
166     if let Ok(path) = std::env::var("CCE_PLATE_GOLDEN_WRITE") {
167         std::fs::write(&path, &out).expect("write golden");
168         eprintln!("wrote {} lines to {path}", out.lines().count());
169         return;
170     }
171     if let Ok(path) = std::env::var("CCE_PLATE_GOLDEN") {
172         let want = std::fs::read_to_string(&path).expect("read golden");
173         if want != out {
174             let mut shown = 0;
175             for (n, (a, b)) in want.lines().zip(out.lines()).enumerate() {
176                 if a != b {
177                     eprintln!("line {}:\n  golden: {a}\n  now:    {b}", n + 1);
178                     shown += 1;
179                     if shown >= 12 {
180                         break;
181                     }
182                 }
183             }
184             panic!(
185                 "tessellation drifted from {path}: {} vs {} lines, first differences above",
186                 want.lines().count(),
187                 out.lines().count()
188             );
189         }
190     }
191 }