GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
feat(relief): Prim::Lattice — a periodic well field carved as one surface
A grid drawn as one Recess ring per cell is N free overlays: at every
crossing four rounded rings stack in colour space and read as several
overlapping effects, and the ring walls (straddling a boundary inflated
by the roll) overlap each other down the rail centre once the roll
exceeds a quarter gap. Shader mode 13 folds the pixel into the period
and measures the nearest cell's edge outward — the union distance of
every well — in ONE profile evaluation, so rails and crossings are true
mitres, and the whole lattice costs one draw with no feature budget.
Mirrored in the height-field export, with a test pinning the rail
centre and the crossing at the plateau.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
src/backend/window_runner.rs | 26 +++++++++++++++++++-
src/scene/heightfield.rs | 56 ++++++++++++++++++++++++++++++++++++++++++--
src/scene/paint.rs | 45 ++++++++++++++++++++++++++++++++---
src/vk/shader2d.wgsl | 20 ++++++++++++++++
4 files changed, 141 insertions(+), 6 deletions(-)
diff --git a/src/backend/window_runner.rs b/src/backend/window_runner.rs
index 7797c3a..a1d69f7 100644
--- a/src/backend/window_runner.rs
+++ b/src/backend/window_runner.rs
@@ -1712,7 +1712,7 @@ fn prim_kind(p: &crate::scene::paint::Prim) -> &'static str {
P::Sphere { .. } => "Sphere", P::Droplet { .. } => "Droplet",
P::DropletScrim { .. } => "DropletScrim",
P::ConcaveFillet { .. } => "ConcaveFillet",
- P::Groove { .. } => "Groove", P::Glow { .. } => "Glow",
+ P::Groove { .. } => "Groove", P::Lattice { .. } => "Lattice", P::Glow { .. } => "Glow",
P::Text { .. } => "Text", P::Image { .. } => "Image",
}
}
@@ -2542,6 +2542,30 @@ pub fn tessellate_display_list(
);
}
}
+ Prim::Lattice { rect, period, origin, cell, radius, depth } if shader_plates => {
+ // A periodic well field (shader mode 13): one cover quad over
+ // `rect`; the shader folds each pixel into the period and
+ // measures the nearest cell, so the whole lattice is a single
+ // evaluation. p_rect = one cell's centre + half-extents,
+ // p_radii = the corner radius, p_host.xy = the period; the
+ // host-box fade sides are pushed far out (a lattice never
+ // fades against a host — its own rect bounds it).
+ let (pw, ph) = (period.0.max(1e-3), period.1.max(1e-3));
+ verts.extend(quad_vertices(rect.x, rect.y, rect.width, rect.height, sw, sh, [0.0; 4]));
+ plate = Some(crate::vk::PlatePush {
+ rect: [origin.0 * scale, origin.1 * scale, cell.0 * 0.5 * scale, cell.1 * 0.5 * scale],
+ radii: [*radius * scale; 4],
+ light: [plate_light[0], plate_light[1], plate_light[2], *depth * scale],
+ material: plate_mat,
+ host: [pw * scale, ph * scale, 1e6, 1e6],
+ specular_tint: [1.0, 1.0, 1.0, 0.0],
+ mode: 13.0,
+ shape: crate::layout::corner_shape(),
+ });
+ }
+ // Legacy banded path: no periodic wall — the lattice draws nothing
+ // there, like the fillet (A/B comparison path only).
+ Prim::Lattice { .. } => {}
}
let end = verts.len() as u32;
if end == start {
diff --git a/src/scene/heightfield.rs b/src/scene/heightfield.rs
index 347284b..3fddd99 100644
--- a/src/scene/heightfield.rs
+++ b/src/scene/heightfield.rs
@@ -217,6 +217,7 @@ const MODE_FILLET_UP: i32 = 7;
const MODE_GROOVE: i32 = 8;
const MODE_TROUGH: i32 = 9;
const MODE_ROLL: i32 = 11;
+const MODE_LATTICE: i32 = 13;
impl HeightField {
/// Sample one frame's plate batches, in draw order, over a `width` ×
@@ -237,7 +238,7 @@ impl HeightField {
for b in batches {
let Some(p) = b.plate else { continue };
let mode = p.mode.round() as i32;
- if !matches!(mode, 1..=9 | 11) {
+ if !matches!(mode, 1..=9 | 11 | 13) {
continue;
}
let shape = p.shape.clamp(2.0, 16.0);
@@ -253,7 +254,10 @@ impl HeightField {
let r = p.rect[2] + t;
(p.rect[0] - r, p.rect[1] - r, p.rect[0] + r, p.rect[1] + r)
}
- MODE_GROOVE => (0.0, 0.0, width as f32, height as f32),
+ // A lattice's shading is bounded by its cover quad, which the
+ // batch does not record; the one consumer (cce-grid) covers
+ // its whole surface, so the window is the honest bound.
+ MODE_GROOVE | MODE_LATTICE => (0.0, 0.0, width as f32, height as f32),
_ => (
p.rect[0] - p.rect[2] - t - 2.0,
p.rect[1] - p.rect[3] - t - 2.0,
@@ -330,6 +334,19 @@ impl HeightField {
let u = (fd / t + 0.5).clamp(0.0, 1.0);
-carve_drop * (1.0 - prof.carve_height(u))
}
+ MODE_LATTICE => {
+ // Fold into the period about one cell's centre and
+ // measure that cell — the union distance of every
+ // well (see the shader's MODE_LATTICE). Positive
+ // outside the cell; the wall runs from the edge
+ // outward over t, floor at the edge.
+ let (px, py) = (p.host[0].max(1e-3), p.host[1].max(1e-3));
+ let c = (pt.0 - p.rect[0], pt.1 - p.rect[1]);
+ let c = (c.0 - px * (c.0 / px).round(), c.1 - py * (c.1 / py).round());
+ let d_out = rr_sdf(c, [0.0, 0.0, p.rect[2], p.rect[3]], p.radii, shape, t);
+ let u = (1.0 - d_out / t).clamp(0.0, 1.0);
+ -carve_drop * prof.carve_height(u)
+ }
MODE_FILLET_DOWN | MODE_FILLET_UP => {
let (cx, cy) = (pt.0 - p.rect[0], pt.1 - p.rect[1]);
let dist = (cx * cx + cy * cy).sqrt().max(1e-4);
@@ -509,6 +526,41 @@ mod tests {
assert_eq!(hf.px[5 * 100 + 5], 0.0);
}
+ #[test]
+ fn a_lattice_is_one_surface_with_plateau_rails_and_mitred_crossings() {
+ // Period 50, cells 30 wide (gap 20), wall 10 = the half-gap, one
+ // cell centred at (25, 25). p_rect = cell centre + half-extents,
+ // p_host.xy = period, radii 4 (the fixture's).
+ let mut b = plate([25.0, 25.0, 15.0, 15.0], 10.0, [50.0, 50.0, 1e6, 1e6]);
+ b.plate.as_mut().unwrap().mode = 13.0;
+ let hf = HeightField::from_frame(&[b], &[], 100, 100, 1.0);
+ let at = |x: usize, y: usize| hf.px[y * 100 + x];
+ let drop = RECESS_DEPTH * 10.0;
+ // Every cell floor, not just the one the push names: (25,25) and
+ // its period neighbour (75,75).
+ assert!((at(25, 25) + drop).abs() < 1e-3, "floor {}", at(25, 25));
+ assert!((at(75, 75) + drop).abs() < 1e-3, "neighbour floor {}", at(75, 75));
+ // The rail centre line between two cells is exactly one run from
+ // either edge: plateau, not a doubled wall. Pixel centres straddle
+ // the line by half a px (49.5 and 50.5 are each 9.5 from a cell), so
+ // the two samples sit a hair into opposite walls — equal, and within
+ // the wall's first half-px of drop.
+ assert!((at(49, 25) - at(50, 25)).abs() < 1e-3, "rail centre symmetric {} {}", at(49, 25), at(50, 25));
+ assert!(at(50, 25) > -0.1 && at(50, 25) <= 0.0, "rail centre {}", at(50, 25));
+ assert!(at(50, 25) > at(45, 25), "rail centre above the wall");
+ // The crossing where four cells meet is farther from every cell than
+ // the wall runs: plateau — the mitre the per-cell rings never gave.
+ assert!(at(50, 50).abs() < 1e-3, "crossing {}", at(50, 50));
+ // Halfway out the wall is between floor and plateau, on both sides
+ // of the rail (one wall from each cell, symmetric). Pixel centres:
+ // 45.5 is 5.5 past the first cell's edge at 40, 54.5 is 5.5 before
+ // the neighbour's edge at 60.
+ let w1 = at(45, 25);
+ let w2 = at(54, 25);
+ assert!(w1 < 0.0 && w1 > -drop, "wall {w1}");
+ assert!((w1 - w2).abs() < 1e-3, "walls symmetric {w1} {w2}");
+ }
+
#[test]
fn resample_keeps_the_face_height() {
let b = plate([50.0, 50.0, 40.0, 40.0], 8.0, [0.0; 4]);
diff --git a/src/scene/paint.rs b/src/scene/paint.rs
index 98e6e27..aef49cf 100644
--- a/src/scene/paint.rs
+++ b/src/scene/paint.rs
@@ -124,14 +124,15 @@ impl PlateSpec {
/// The **relief primitives** are the members of this enum that describe a lit
/// surface rather than a flat fill: [`Prim::Bevel`], [`Prim::Plate`],
/// [`Prim::Recess`], [`Prim::Boss`], [`Prim::Ridge`], [`Prim::ConcaveFillet`],
-/// [`Prim::Groove`] and [`Prim::Sphere`]. They share one lighting model — the
+/// [`Prim::Groove`], [`Prim::Lattice`] and [`Prim::Sphere`]. They share one lighting model — the
/// DE's light vector, roll width and profile, per-pixel through shader2d's
/// SDF branch (see `crate::layout::bevel_shader`) — and split in two:
///
/// - **plates** carry their own fill: `Bevel`, `Plate`. Shader mode 1.
/// - **carves** emit shading ONLY, no fill, over whatever is already painted
-/// beneath: `Recess`, `Boss`, `Ridge`, `ConcaveFillet`, `Groove`. Modes 2-4
-/// and 6-8. (`Sphere`, mode 5, is neither — a lit ball under the same model.)
+/// beneath: `Recess`, `Boss`, `Ridge`, `ConcaveFillet`, `Groove`, `Lattice`.
+/// Modes 2-4, 6-8 and 13. (`Sphere`, mode 5, is neither — a lit ball under
+/// the same model.)
///
/// That split is load-bearing for flat-path hosts, which need one list for the
/// faces and another for the edges drawn over them (cce-files' `rects` vs
@@ -579,6 +580,24 @@ pub enum Prim {
/// plate dies into the plate's own rolled edge instead of ending on a hard line.
/// SDF path only — the legacy banded tessellation draws nothing (like `Ridge`).
Groove { a: (f32, f32), b: (f32, f32), width: f32, depth: f32, host: Rect },
+ /// A periodic field of identical rounded-box wells — every cell of a grid
+ /// carved into whatever is painted beneath, as ONE surface. The wells
+ /// repeat every `period` (x, y) with one cell centred at `origin`, each
+ /// `cell` (w, h) big with `radius` corners; the wall runs from the cell
+ /// edge OUTWARD over `depth` px (floor at the edge, plateau one run out),
+ /// so a rail between two cells carries one wall from each side and the
+ /// rail face is whatever the runs leave. Shading lands only inside `rect`.
+ ///
+ /// This exists because a lattice drawn as one [`Prim::Recess`] per cell is
+ /// N independent overlays: where four rounded rings meet at a crossing
+ /// their shadings stack in colour space and read as overlapping effects,
+ /// not a junction. Here the pixel is folded into the period and the
+ /// distance is to the NEAREST cell — the union of every well — evaluated
+ /// once, so the rail centre lines and the diagonals at each crossing are
+ /// true mitres, and the cost is one draw regardless of how many cells the
+ /// surface holds (a free carve per cell also runs into the per-frame
+ /// feature budget long before a zoomed-out grid does). SDF path only.
+ Lattice { rect: Rect, period: (f32, f32), origin: (f32, f32), cell: (f32, f32), radius: f32, depth: f32 },
/// Text in sRGB u8 (the `TextLabel` convention). `font` is a font string for
/// `get_text_buffer` (family, or "family:size"); `bounds` is a logical `[l, t, r, b]` clip
/// for the glyph pass (Phase 6: the backend renders these through the glyph pass when the app
@@ -961,6 +980,9 @@ impl PaintCtx {
self.concave_fillet(cx, cy, radius, depth, start, raised)
}
Prim::Groove { a, b, width, depth, host } => self.groove(a, b, width, depth, host),
+ Prim::Lattice { rect, period, origin, cell, radius, depth } => {
+ self.lattice(rect, period, origin, cell, radius, depth)
+ }
Prim::Image { image, rect, alpha } => self.image(image, rect, alpha),
}
None
@@ -979,6 +1001,23 @@ impl PaintCtx {
});
}
+ /// A periodic field of rounded wells carved as one surface — see
+ /// [`Prim::Lattice`]. `origin` is any one cell's centre; `rect` bounds the
+ /// shading. All logical px, like every other carve.
+ pub fn lattice(
+ &mut self,
+ rect: Rect,
+ period: (f32, f32),
+ origin: (f32, f32),
+ cell: (f32, f32),
+ radius: f32,
+ depth: f32,
+ ) {
+ let (ox, oy) = self.offset;
+ let rect = self.apply_offset(rect);
+ self.push(Prim::Lattice { rect, period, origin: (origin.0 + ox, origin.1 + oy), cell, radius, depth });
+ }
+
pub fn border(&mut self, rect: Rect, radii: Radii, fill: [f32; 4], border: [f32; 4], thickness: f32) {
let rect = self.apply_offset(rect);
self.push(Prim::Border { rect, radii, fill, border, thickness });
diff --git a/src/vk/shader2d.wgsl b/src/vk/shader2d.wgsl
index 7f7e2da..63ac75e 100644
--- a/src/vk/shader2d.wgsl
+++ b/src/vk/shader2d.wgsl
@@ -139,6 +139,7 @@ const MODE_TROUGH: i32 = 9; // sunken valley straddling the boundary
const MODE_DROPLET: i32 = 10; // hanging water droplet clinging to the box top
const MODE_ROLL: i32 = 11; // fill-less rolled perimeter, composited as an overlay
const MODE_DROPLET_SCRIM: i32 = 12; // flat feathered fill of the droplet silhouette
+const MODE_LATTICE: i32 = 13; // periodic well field: nearest-cell carve, one evaluation
// Fillet modes rejoin the shared free-carve path as their flat equivalents.
const FILLET_TO_STEP: i32 = 4; // 6 -> RECESS, 7 -> BOSS
@@ -674,6 +675,25 @@ fn plate_shade(frag: vec2f, vcol: vec4f) -> vec4f {
fd = abs(s) - rrect_clip.p_rect.z;
fgd = nrm * select(-1.0, 1.0, s >= 0.0);
eff = MODE_RECESS;
+ } else if (mode == MODE_LATTICE) {
+ // MODE_LATTICE: a periodic field of identical rounded wells. Fold
+ // the pixel into the period about one cell's centre (p_rect.xy;
+ // period in p_host.xy) and take the box distance to THAT cell —
+ // identical axis-aligned boxes centred in their period cells, so
+ // the folded cell is always the nearest one and this is the exact
+ // union distance of every well. One profile evaluation, so the
+ // rails between cells and the diagonals at each crossing are true
+ // mitres instead of stacked per-cell overlays. The wall runs from
+ // the cell edge OUTWARD: floor at the edge, plateau one run out —
+ // the +t/2 shifts the shared path's boundary-straddling band so it
+ // spans [edge, edge + t]. Rejoins the free-carve path as a recess.
+ let per = max(rrect_clip.p_host.xy, vec2f(1e-3));
+ var c = frag - rrect_clip.p_rect.xy;
+ c = c - per * round(c / per);
+ let lg = rr_sdf_grad(c, vec4f(0.0, 0.0, rrect_clip.p_rect.zw), rrect_clip.p_radii);
+ fd = -lg.z + 0.5 * t;
+ fgd = lg.xy;
+ eff = MODE_RECESS;
} else if (mode == MODE_FILLET_DOWN || mode == MODE_FILLET_UP) {
eff = mode - FILLET_TO_STEP;
let c = frag - rrect_clip.p_rect.xy;