git.lucas.co / cce-designer
graphic design tool
git clone https://git.lucas.co/cce-designer.git

commitabd065b8bec8a62c33f4117e1e5cf7538a3209a8
parent6b38981524
authorLucas Galante <lsgalante12@gmail.com>
date2026-10-07 10:23
fix(viewport): the background quad is a screen_space draw

cce-ui@38bcad1 replaced the scene shader's z = 9.99 sentinel with a
per-draw flag, SceneDraw::screen_space. The viewport background (and the
vk smoke test's) set it and put their NDC corners at z 0; every other
draw says false.

Under the sentinel, user geometry with a vertex within 0.01 of z = 9.99
(a Transform at translate z 9.99 over a sphere or plane) was drawn as
background corners: spikes across the viewport. That is gone with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

 src/app.rs      | 40 +++++++++++++++++++++-------------------
 src/vk_smoke.rs | 13 +++++++------
 2 files changed, 28 insertions(+), 25 deletions(-)

diff --git a/src/app.rs b/src/app.rs
index fc11461..cc21426 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -3822,15 +3822,17 @@ impl State {
         self.viewport_dirty = true;
     }
 
+    /// The viewport background: NDC corners, drawn `screen_space` (z is
+    /// ignored), so it fills the pane's scissor at the far plane.
     pub(crate) fn viewport_bg_vertices(bg_color: [f32; 3]) -> Vec<Vertex3D> {
         vec![
-            Vertex3D { position: [-1.0, -1.0, 9.99], color: bg_color }, // Bottom-left
-            Vertex3D { position: [ 1.0, -1.0, 9.99], color: bg_color }, // Bottom-right
-            Vertex3D { position: [-1.0,  1.0, 9.99], color: bg_color }, // Top-left
+            Vertex3D { position: [-1.0, -1.0, 0.0], color: bg_color }, // Bottom-left
+            Vertex3D { position: [ 1.0, -1.0, 0.0], color: bg_color }, // Bottom-right
+            Vertex3D { position: [-1.0,  1.0, 0.0], color: bg_color }, // Top-left
 
-            Vertex3D { position: [ 1.0, -1.0, 9.99], color: bg_color }, // Bottom-right
-            Vertex3D { position: [ 1.0,  1.0, 9.99], color: bg_color }, // Top-right
-            Vertex3D { position: [-1.0,  1.0, 9.99], color: bg_color }, // Top-left
+            Vertex3D { position: [ 1.0, -1.0, 0.0], color: bg_color }, // Bottom-right
+            Vertex3D { position: [ 1.0,  1.0, 0.0], color: bg_color }, // Top-right
+            Vertex3D { position: [-1.0,  1.0, 0.0], color: bg_color }, // Top-left
         ]
     }
 
@@ -12683,37 +12685,37 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
                         // nothing re-uploads; the next entry just sorts anew.
                         self.sorted_fill_key = None;
                     }
-                    let mut draws = vec![SceneDraw { mesh: meshes.viewport_bg, mvp, wireframe: false, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false, instances: None }];
+                    let mut draws = vec![SceneDraw { mesh: meshes.viewport_bg, mvp, wireframe: false, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false, instances: None, screen_space: true }];
                     if self.viewport().show_grid {
-                        draws.push(SceneDraw { mesh: meshes.grid, mvp, wireframe: false, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false, instances: None });
+                        draws.push(SceneDraw { mesh: meshes.grid, mvp, wireframe: false, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false, instances: None, screen_space: false });
                     }
                     if self.viewport().show_origin {
-                        draws.push(SceneDraw { mesh: meshes.origin, mvp, wireframe: false, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false, instances: None });
+                        draws.push(SceneDraw { mesh: meshes.origin, mvp, wireframe: false, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false, instances: None, screen_space: false });
                     }
                     if self.viewport().show_camera_pivot {
-                        draws.push(SceneDraw { mesh: meshes.pivot, mvp: mvp_pivot, wireframe: false, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false, instances: None });
+                        draws.push(SceneDraw { mesh: meshes.pivot, mvp: mvp_pivot, wireframe: false, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false, instances: None, screen_space: false });
                     }
                     // Selected-Group markers: full-opacity selection feedback,
                     // deliberately outside the Render node's Opacity.
                     if self.group_point_count > 0 {
-                        draws.push(SceneDraw { mesh: meshes.group_sphere, mvp, wireframe: false, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false, instances: Some(meshes.group_points) });
+                        draws.push(SceneDraw { mesh: meshes.group_sphere, mvp, wireframe: false, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false, instances: Some(meshes.group_points), screen_space: false });
                     }
                     // The marked groups: the same amber as a selected
                     // group's markers, and the same tier.
                     if self.marked_group_count > 0 {
-                        draws.push(SceneDraw { mesh: meshes.group_sphere, mvp, wireframe: false, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false, instances: Some(meshes.marked_points) });
+                        draws.push(SceneDraw { mesh: meshes.group_sphere, mvp, wireframe: false, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false, instances: Some(meshes.marked_points), screen_space: false });
                     }
                     // The spreadsheet's selected rows, while it is shown.
                     if self.show_spreadsheet && self.row_marker_count > 0 {
-                        draws.push(SceneDraw { mesh: meshes.group_sphere, mvp, wireframe: false, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false, instances: Some(meshes.row_points) });
+                        draws.push(SceneDraw { mesh: meshes.group_sphere, mvp, wireframe: false, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false, instances: Some(meshes.row_points), screen_space: false });
                     }
                     // Show Point Markers and Show Vertex Markers, the same
                     // full-opacity tier, each its sphere instanced.
                     if self.overlay_point_count > 0 {
-                        draws.push(SceneDraw { mesh: meshes.point_sphere, mvp, wireframe: false, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false, instances: Some(meshes.overlay_points) });
+                        draws.push(SceneDraw { mesh: meshes.point_sphere, mvp, wireframe: false, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false, instances: Some(meshes.overlay_points), screen_space: false });
                     }
                     if self.vertex_marker_count > 0 {
-                        draws.push(SceneDraw { mesh: meshes.vertex_sphere, mvp, wireframe: false, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false, instances: Some(meshes.vertex_points) });
+                        draws.push(SceneDraw { mesh: meshes.vertex_sphere, mvp, wireframe: false, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false, instances: Some(meshes.vertex_points), screen_space: false });
                     }
                     // The page the level shows stands in the scene as an
                     // image: after the furniture and the markers, which are
@@ -12736,18 +12738,18 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
                         // width deliberately fixed (a chunky Wire Width is a
                         // wireframe styling choice, not a normals one).
                         if self.overlay_normal_count > 0 {
-                            draws.push(SceneDraw { mesh: meshes.overlay_normals, mvp, wireframe: true, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: true, instances: None });
+                            draws.push(SceneDraw { mesh: meshes.overlay_normals, mvp, wireframe: true, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: true, instances: None, screen_space: false });
                         }
                         // Pull arrows: selection feedback, like the group
                         // markers — full opacity, a little heavier than the
                         // whiskers.
                         if self.pull_arrow_count > 0 {
-                            draws.push(SceneDraw { mesh: meshes.pull_arrows, mvp, wireframe: true, wire_tint: NO_TINT, opacity: 1.0, line_width: 2.0, wire_base_width: 0.0, prelit: false, see_through: true, instances: None });
+                            draws.push(SceneDraw { mesh: meshes.pull_arrows, mvp, wireframe: true, wire_tint: NO_TINT, opacity: 1.0, line_width: 2.0, wire_base_width: 0.0, prelit: false, see_through: true, instances: None, screen_space: false });
                         }
                         // With wires coming, the fill is pushed back by its
                         // slope-scaled offset so the lattice reads solid.
                         let base = if self.wireframe { self.wire_width } else { 0.0 };
-                        let mut fill = Some(SceneDraw { mesh: meshes.spheres, mvp, wireframe: false, wire_tint: NO_TINT, opacity: geo_opacity, line_width: 1.0, wire_base_width: base, prelit: self.smooth_shading, see_through, instances: None });
+                        let mut fill = Some(SceneDraw { mesh: meshes.spheres, mvp, wireframe: false, wire_tint: NO_TINT, opacity: geo_opacity, line_width: 1.0, wire_base_width: base, prelit: self.smooth_shading, see_through, instances: None, screen_space: false });
                         // A see-through fill writes no depth, so wires drawn
                         // AFTER it pass everywhere and the far side's paint
                         // over the near faces. Seen through, the wires go
@@ -12777,7 +12779,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
                                 [0.0, 0.0, 0.0, 0.0]
                             };
                             let wire_alpha = self.wire_opacity.clamp(0.0, 1.0);
-                            draws.push(SceneDraw { mesh: meshes.sphere_edges, mvp, wireframe: true, wire_tint: tint, opacity: wire_alpha, line_width: self.wire_width, wire_base_width: 0.0, prelit: false, see_through, instances: None });
+                            draws.push(SceneDraw { mesh: meshes.sphere_edges, mvp, wireframe: true, wire_tint: tint, opacity: wire_alpha, line_width: self.wire_width, wire_base_width: 0.0, prelit: false, see_through, instances: None, screen_space: false });
                         }
                         draws.extend(fill);
                     }
diff --git a/src/vk_smoke.rs b/src/vk_smoke.rs
index cea73d2..cfa1460 100644
--- a/src/vk_smoke.rs
+++ b/src/vk_smoke.rs
@@ -201,12 +201,13 @@ fn grid_verts() -> Vec<Vertex3D> {
     v
 }
 
-/// The designer's viewport-background quad: z=9.99 triggers the shader's
-/// far-plane special case, filling the scissor with the (linear) bg color.
+/// The designer's viewport-background quad: NDC corners, drawn
+/// `screen_space`, filling the scissor with the (linear) bg color at the far
+/// plane.
 fn viewport_bg_verts() -> Vec<Vertex3D> {
     let color = cce_ui::colors::to_linear_rgb([0.10, 0.10, 0.13]);
     let mut v = Vec::new();
-    quad3(&mut v, [-1.0, -1.0, 9.99], [1.0, -1.0, 9.99], [1.0, 1.0, 9.99], [-1.0, 1.0, 9.99], color);
+    quad3(&mut v, [-1.0, -1.0, 0.0], [1.0, -1.0, 0.0], [1.0, 1.0, 0.0], [-1.0, 1.0, 0.0], color);
     v
 }
 
@@ -481,9 +482,9 @@ fn main() {
                 renderer.stage_scene(
                     pane,
                     vec![
-                        SceneDraw { mesh: bg_mesh, mvp: Mat4::IDENTITY.to_cols_array_2d(), wireframe: false, wire_tint: [0.0; 4], opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false, instances: None },
-                        SceneDraw { mesh: grid_mesh, mvp, wireframe: false, wire_tint: [0.0; 4], opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false, instances: None },
-                        SceneDraw { mesh: cube_mesh, mvp, wireframe: false, wire_tint: [0.0; 4], opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false, instances: None },
+                        SceneDraw { mesh: bg_mesh, mvp: Mat4::IDENTITY.to_cols_array_2d(), wireframe: false, wire_tint: [0.0; 4], opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false, instances: None, screen_space: true },
+                        SceneDraw { mesh: grid_mesh, mvp, wireframe: false, wire_tint: [0.0; 4], opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false, instances: None, screen_space: false },
+                        SceneDraw { mesh: cube_mesh, mvp, wireframe: false, wire_tint: [0.0; 4], opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false, instances: None, screen_space: false },
                     ],
                 );
             }