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 }