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

commit0cc89694a329b59acf2eab4d235749a4b353b11a
parent949e35e80a
authorLucas Galante <[email protected]>
date2026-08-15 09:34
feat: CCE_PLATE_DEBUG also reports who closed the grouping window

The fallback reason "no enclosing plate (0 open)" is ambiguous in the way that
matters: it cannot distinguish "this app emits no filled plates" from "it does,
and something closed the window before the carve arrived". I read it as the
first and drew the wrong conclusion from it — that the DE builds surfaces as
flat quad plus edges-only relief, so there is nothing to carve into.

That is false. The oracle now counts grouping windows opened and names the prim
kind that closed each one early, and the answer is the second reading
everywhere: cce-designer opens ELEVEN windows a frame — its panes are already
Bevel plates via append_widget_plate_tinted — and loses all eleven to Quadx9,
RoundedRectx1, Borderx1. cce-files opens one and loses it to a Quad; the cce-ui
demo opens one, groups 2 carves, then loses it to a RoundedRect.

So the lever is emission ORDER inside a pane, not the pane's prim type.

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

 src/backend/window_runner.rs | 38 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 38 insertions(+)

diff --git a/src/backend/window_runner.rs b/src/backend/window_runner.rs
index 521b4a7..9ca4734 100644
--- a/src/backend/window_runner.rs
+++ b/src/backend/window_runner.rs
@@ -1514,6 +1514,21 @@ pub struct DlImage {
 /// which is why `plate_stack` is a stack (see its comment below).
 ///
 /// Off by default and read once; the classification below runs only when set.
+/// Prim discriminant name, for `CCE_PLATE_DEBUG` reporting only.
+fn prim_kind(p: &crate::scene::paint::Prim) -> &'static str {
+    use crate::scene::paint::Prim as P;
+    match p {
+        P::Quad { .. } => "Quad", P::RoundedRect { .. } => "RoundedRect",
+        P::Border { .. } => "Border", P::Bevel { .. } => "Bevel",
+        P::Recess { .. } => "Recess", P::Boss { .. } => "Boss",
+        P::Ridge { .. } => "Ridge", P::Plate { .. } => "Plate",
+        P::Arc { .. } => "Arc", P::ArcShaded { .. } => "ArcShaded",
+        P::Vector { .. } => "Vector", P::Circle { .. } => "Circle",
+        P::Sphere { .. } => "Sphere", P::ConcaveFillet { .. } => "ConcaveFillet",
+        P::Groove { .. } => "Groove", P::Text { .. } => "Text", P::Image { .. } => "Image",
+    }
+}
+
 fn plate_debug() -> bool {
     static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
     *ON.get_or_init(|| std::env::var("CCE_PLATE_DEBUG").is_ok_and(|v| v != "0"))
@@ -1550,6 +1565,11 @@ pub fn tessellate_display_list(
     let dbg_plates = plate_debug();
     let mut dbg_grouped = 0usize;
     let mut dbg_fell_back: Vec<String> = Vec::new();
+    let mut dbg_opened = 0usize;
+    // Which prim kind closed a still-open grouping window, and how many plates
+    // it closed — the answer to "why was there no enclosing plate?".
+    let mut dbg_closed_by: std::collections::BTreeMap<&'static str, usize> =
+        std::collections::BTreeMap::new();
 
     // SDF-lit plate path (shader2d's plate branch) vs the legacy banded vertex
     // shading, plus the frame-constant lighting inputs it pushes per plate.
@@ -2098,6 +2118,9 @@ pub fn tessellate_display_list(
             // Ordinary geometry painted after a plate ends its carve-grouping
             // window: a recess emitted later must overlay this geometry (the
             // fallback path), not shade beneath it inside the plate's draw.
+            if dbg_plates && !plate_stack.is_empty() {
+                *dbg_closed_by.entry(prim_kind(&item.prim)).or_insert(0) += plate_stack.len();
+            }
             plate_stack.clear();
             if let Some(last) = batches.last_mut() {
                 if last.plate.is_none()
@@ -2113,6 +2136,9 @@ pub fn tessellate_display_list(
         batches.push(DlBatch { scissor: item.clip, clip_rrect: item.clip_rrect, start, end, plate, blur_behind });
         if let Some(prect) = made_plate {
             plate_stack.push((batches.len() - 1, prect));
+            if dbg_plates {
+                dbg_opened += 1;
+            }
         }
     }
 
@@ -2122,6 +2148,18 @@ pub fn tessellate_display_list(
             dbg_grouped + dbg_fell_back.len(),
             dbg_fell_back.len(),
         );
+        eprintln!(
+            "plate-dbg:   {dbg_opened} grouping window(s) opened by a filled plate; closed early by {}",
+            if dbg_closed_by.is_empty() {
+                "nothing".to_string()
+            } else {
+                dbg_closed_by
+                    .iter()
+                    .map(|(k, n)| format!("{k}x{n}"))
+                    .collect::<Vec<_>>()
+                    .join(", ")
+            }
+        );
         for line in &dbg_fell_back {
             eprintln!("plate-dbg: {line}");
         }