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

commita018d486cca03873fa792eb5353a321159817054
parent063177b1e9
authorLucas Galante <[email protected]>
date2026-08-30 20:59
fix(frost): snapshot for every fill prim, and a blur you can see

The frosted menus shipped without frost. Two independent causes, found by
measuring pixels rather than trusting the pipeline:

The frame snapshot only triggered for Plate/Bevel/Droplet prims with a
negative fill alpha. The Dropdown popover's face is a Border prim and the
menubar panels are plain Quads, so their sentinel reached the shader with
the STALE SCENE BACKDROP bound instead of the frame-so-far: resolve_blur
returned a flat tint — translucent-looking, but showing no content and
blurring nothing. (The context menu, a Plate, was the one flavor that
actually worked, and the earlier "20% show-through" measurement was the
List row's own hover tint masquerading as evidence.) The blur_behind
matcher now covers every fill-bearing prim — Quad, RoundedRect, and
Border's fill included.

And the kernel was too tight to read as blur at all: 7x7 taps at 2.5px
stride is ±7.5px of reach, which leaves fine detail legible through the
glass. The stride is now 5.5px (±16.5px, effective sigma ~11px); the
linear sampler still papers over the gaps.

Verified in cce-files: the selected row now diffuses through the open
dropdown as a soft glow (10,753 pixels changed in the popover region vs
the broken build), where both prior builds rendered pixel-identical
"frost".

Co-Authored-By: Claude Fable 5 <[email protected]>

 src/backend/window_runner.rs | 17 ++++++++++++++---
 src/vk/shader2d.wgsl         |  9 ++++++---
 2 files changed, 20 insertions(+), 6 deletions(-)

diff --git a/src/backend/window_runner.rs b/src/backend/window_runner.rs
index b959880..b81104b 100644
--- a/src/backend/window_runner.rs
+++ b/src/backend/window_runner.rs
@@ -1742,13 +1742,24 @@ pub fn tessellate_display_list(
         let start = verts.len() as u32;
         let mut plate: Option<crate::vk::PlatePush> = None;
         let mut made_plate: Option<crate::scene::layout::Rect> = None;
-        // Blur-behind marker: a Plate/Bevel whose fill alpha is negative asks
-        // the renderer to snapshot the frame-so-far before it draws.
+        // Blur-behind marker: a prim whose FILL alpha is negative asks the
+        // renderer to snapshot the frame-so-far before it draws. Every
+        // fill-bearing prim counts — the shader's a<0 branch runs for all of
+        // them, and a variant missing here still frosts, but against the
+        // stale scene backdrop instead of the frame: a flat tint with no
+        // content and no blur, which is how the Dropdown popover (Border)
+        // and the menubar panels (Quad) shipped visibly unfrosted while the
+        // context menu (Plate) worked.
         let blur_behind = matches!(
             &item.prim,
             crate::scene::paint::Prim::Bevel { color, .. }
             | crate::scene::paint::Prim::Plate { color, .. }
-            | crate::scene::paint::Prim::Droplet { color, .. } if color[3] < 0.0
+            | crate::scene::paint::Prim::Droplet { color, .. }
+            | crate::scene::paint::Prim::Quad { color, .. }
+            | crate::scene::paint::Prim::RoundedRect { color, .. } if color[3] < 0.0
+        ) || matches!(
+            &item.prim,
+            crate::scene::paint::Prim::Border { fill, .. } if fill[3] < 0.0
         );
         // Logical [cx, cy, r] → the physical-pixel triple the vertex attribute carries.
         let no = item
diff --git a/src/vk/shader2d.wgsl b/src/vk/shader2d.wgsl
index 3880d47..f4f9e58 100644
--- a/src/vk/shader2d.wgsl
+++ b/src/vk/shader2d.wgsl
@@ -840,11 +840,14 @@ fn resolve_blur(pos: vec2f, color: vec4f) -> vec4f {
     var blurred = vec4f(0.0);
     var total_weight = 0.0;
 
-    // 7x7 Gaussian blur kernel, samples every 2.5 px (~±7.5px reach); the
-    // linear sampler between taps papers over the stride.
+    // 7x7 Gaussian blur kernel, samples every 5.5 px (±16.5px reach,
+    // effective sigma ~11px); the linear sampler between taps papers over
+    // the stride. The old 2.5px stride (±7.5px reach) was technically a
+    // blur but read as plain translucency — fine detail beneath a frosted
+    // menu stayed legible, which is not what frosted glass does.
     for (var x = -3.0; x <= 3.0; x += 1.0) {
         for (var y = -3.0; y <= 3.0; y += 1.0) {
-            let offset = vec2f(x, y) * 2.5;
+            let offset = vec2f(x, y) * 5.5;
             let sample_uv = (pos + offset) / tex_size;
             let weight = exp(-(x*x + y*y) / (2.0 * 2.0 * 2.0));
             blurred += textureSample(t_backdrop, s_backdrop, sample_uv) * weight;