From 04c93df2c4fb8a3474c811bf2c7cf3fd5314b9c4 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Wed, 24 Jun 2026 20:14:41 +0300 Subject: [PATCH] refactor: remove the legacy full view_proj (CPU cull now relative-to-eye) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The GPU draws exclusively via the rotation-only `view_rot` + eye double- single; the full view-projection survived only to project AABBs in the CPU frustum-cull / scissor / LOD passes. Migrate those to the same relative-to-eye math (project `point - eye` through `view_rot`) — pixel- equivalent but precise at UTM scale — then drop the dead uniform: - pipeline cull/scissor/LOD helpers take (view_rot, eye) instead of the full view_proj; render.rs reconstructs eye from the uniform's eye_high/eye_low and passes the cropped view_rot. - Remove the `view_proj` field from the shared Uniforms struct and all six shader structs that only declared it for layout (none read it), and delete Camera::view_proj(). The struct shrinks by one mat4 on both sides, keeping the Rust / WGSL layouts in lock-step. Co-Authored-By: Claude Opus 4.8 --- src/scene/pipeline/mod.rs | 78 ++++++++++++++++++---------------- src/scene/pipeline/uniforms.rs | 6 --- src/scene/view/camera.rs | 22 ---------- src/scene/view/render.rs | 25 ++++++----- src/shaders/face3d.wgsl | 1 - src/shaders/hatch.wgsl | 1 - src/shaders/hatch_batched.wgsl | 1 - src/shaders/image.wgsl | 1 - src/shaders/mesh.wgsl | 1 - src/shaders/wire.wgsl | 1 - 10 files changed, 57 insertions(+), 80 deletions(-) diff --git a/src/scene/pipeline/mod.rs b/src/scene/pipeline/mod.rs index 19b4c397c..5d0973827 100644 --- a/src/scene/pipeline/mod.rs +++ b/src/scene/pipeline/mod.rs @@ -1041,11 +1041,11 @@ impl Pipeline { /// Recompute pixel scissor rects for viewport-clipped wires from the current view_proj. /// Called every frame from prepare() because scissor pixels shift with pan/zoom. - pub fn compute_wire_scissors(&mut self, view_proj: glam::Mat4, clip_w: u32, clip_h: u32) { + pub fn compute_wire_scissors(&mut self, view_rot: glam::Mat4, eye: glam::DVec3, clip_w: u32, clip_h: u32) { self.wire_pixel_scissors = self .gpu_wires .iter() - .map(|w| project_scissor(w.vp_scissor, view_proj, clip_w, clip_h)) + .map(|w| project_scissor(w.vp_scissor, view_rot, eye, clip_w, clip_h)) .collect(); } @@ -1058,7 +1058,8 @@ impl Pipeline { pub fn compute_hatch_lod( &mut self, queue: &wgpu::Queue, - view_proj: glam::Mat4, + view_rot: glam::Mat4, + eye: glam::DVec3, clip_w: u32, clip_h: u32, ) { @@ -1066,19 +1067,19 @@ impl Pipeline { return; }; for (i, aabb) in batch.instance_aabbs.iter().enumerate() { - let skip = aabb_below_pixel(*aabb, view_proj, clip_w, clip_h, 2.0) - || aabb_offscreen(*aabb, view_proj, clip_w, clip_h); + let skip = aabb_below_pixel(*aabb, view_rot, eye, clip_w, clip_h, 2.0) + || aabb_offscreen(*aabb, view_rot, eye, clip_w, clip_h); batch.visibility[i] = if skip { 0 } else { 1 }; } batch.upload_visibility(queue); } /// Recompute pixel scissor rects for viewport-clipped wipeouts. - pub fn compute_wipeout_scissors(&mut self, view_proj: glam::Mat4, clip_w: u32, clip_h: u32) { + pub fn compute_wipeout_scissors(&mut self, view_rot: glam::Mat4, eye: glam::DVec3, clip_w: u32, clip_h: u32) { self.wipeout_pixel_scissors = self .gpu_wipeouts .iter() - .map(|h| project_scissor(h.vp_scissor, view_proj, clip_w, clip_h)) + .map(|h| project_scissor(h.vp_scissor, view_rot, eye, clip_w, clip_h)) .collect(); } @@ -1086,20 +1087,20 @@ impl Pipeline { /// `compute_hatch_lod`'s frustum branch. No sub-pixel skip: /// wipeouts mask, so dropping a sub-pixel one wouldn't be wrong /// but also wouldn't pay off — they're usually few. - pub fn compute_wipeout_lod(&mut self, view_proj: glam::Mat4, clip_w: u32, clip_h: u32) { + pub fn compute_wipeout_lod(&mut self, view_rot: glam::Mat4, eye: glam::DVec3, clip_w: u32, clip_h: u32) { self.wipeout_skip_flags = self .gpu_wipeouts .iter() - .map(|h| aabb_offscreen(h.world_aabb, view_proj, clip_w, clip_h)) + .map(|h| aabb_offscreen(h.world_aabb, view_rot, eye, clip_w, clip_h)) .collect(); } /// Recompute pixel scissor rects for viewport-clipped raster images. - pub fn compute_image_scissors(&mut self, view_proj: glam::Mat4, clip_w: u32, clip_h: u32) { + pub fn compute_image_scissors(&mut self, view_rot: glam::Mat4, eye: glam::DVec3, clip_w: u32, clip_h: u32) { self.image_pixel_scissors = self .gpu_images .iter() - .map(|i| project_scissor(i.vp_scissor, view_proj, clip_w, clip_h)) + .map(|i| project_scissor(i.vp_scissor, view_rot, eye, clip_w, clip_h)) .collect(); } @@ -1175,11 +1176,11 @@ impl Pipeline { /// projected pixel diagonal of each mesh's `world_aabb` (Phase 3.4 /// ladder: >200 px → 0, 50–200 → 1, <50 → 2). Falls back to the /// nearest available lower slot when a level wasn't generated. - pub fn compute_mesh_lod(&mut self, view_proj: glam::Mat4, clip_w: u32, clip_h: u32) { + pub fn compute_mesh_lod(&mut self, view_rot: glam::Mat4, eye: glam::DVec3, clip_w: u32, clip_h: u32) { self.mesh_lod_levels = self .gpu_meshes .iter() - .map(|m| pick_mesh_lod(m, view_proj, clip_w, clip_h)) + .map(|m| pick_mesh_lod(m, view_rot, eye, clip_w, clip_h)) .collect(); // Phase 2.2 — frustum-visibility flag per mesh. Cheap: same // 4-corner projection used for LOD selection, just answering a @@ -1187,7 +1188,7 @@ impl Pipeline { self.mesh_visible = self .gpu_meshes .iter() - .map(|m| !aabb_offscreen(m.world_aabb, view_proj, clip_w, clip_h)) + .map(|m| !aabb_offscreen(m.world_aabb, view_rot, eye, clip_w, clip_h)) .collect(); } @@ -1847,11 +1848,12 @@ impl Pipeline { /// to the next available coarser/finer level so the mesh always renders. fn pick_mesh_lod( mesh: &MeshLodGpu, - view_proj: glam::Mat4, + view_rot: glam::Mat4, + eye: glam::DVec3, clip_w: u32, clip_h: u32, ) -> usize { - let diag_px = aabb_diagonal_pixels(mesh.world_aabb, view_proj, clip_w, clip_h); + let diag_px = aabb_diagonal_pixels(mesh.world_aabb, view_rot, eye, clip_w, clip_h); let target = if diag_px > 200.0 { 0 } else if diag_px > 50.0 { @@ -1870,7 +1872,8 @@ fn pick_mesh_lod( fn aabb_diagonal_pixels( aabb: [f32; 4], - view_proj: glam::Mat4, + view_rot: glam::Mat4, + eye: glam::DVec3, clip_w: u32, clip_h: u32, ) -> f32 { @@ -1881,10 +1884,10 @@ fn aabb_diagonal_pixels( let w = clip_w as f32; let h = clip_h as f32; let corners = [ - view_proj.project_point3(glam::Vec3::new(x0, y0, 0.0)), - view_proj.project_point3(glam::Vec3::new(x1, y0, 0.0)), - view_proj.project_point3(glam::Vec3::new(x0, y1, 0.0)), - view_proj.project_point3(glam::Vec3::new(x1, y1, 0.0)), + view_rot.project_point3((glam::DVec3::new(x0 as f64, y0 as f64, 0.0) - eye).as_vec3()), + view_rot.project_point3((glam::DVec3::new(x1 as f64, y0 as f64, 0.0) - eye).as_vec3()), + view_rot.project_point3((glam::DVec3::new(x0 as f64, y1 as f64, 0.0) - eye).as_vec3()), + view_rot.project_point3((glam::DVec3::new(x1 as f64, y1 as f64, 0.0) - eye).as_vec3()), ]; let mut min_px = f32::INFINITY; let mut max_px = f32::NEG_INFINITY; @@ -1916,7 +1919,8 @@ fn aabb_diagonal_pixels( /// extents` for this reason; meshes already store an absolute rect. fn aabb_offscreen( aabb: [f32; 4], - view_proj: glam::Mat4, + view_rot: glam::Mat4, + eye: glam::DVec3, clip_w: u32, clip_h: u32, ) -> bool { @@ -1927,10 +1931,10 @@ fn aabb_offscreen( let w = clip_w as f32; let h = clip_h as f32; let corners = [ - view_proj.project_point3(glam::Vec3::new(x0, y0, 0.0)), - view_proj.project_point3(glam::Vec3::new(x1, y0, 0.0)), - view_proj.project_point3(glam::Vec3::new(x0, y1, 0.0)), - view_proj.project_point3(glam::Vec3::new(x1, y1, 0.0)), + view_rot.project_point3((glam::DVec3::new(x0 as f64, y0 as f64, 0.0) - eye).as_vec3()), + view_rot.project_point3((glam::DVec3::new(x1 as f64, y0 as f64, 0.0) - eye).as_vec3()), + view_rot.project_point3((glam::DVec3::new(x0 as f64, y1 as f64, 0.0) - eye).as_vec3()), + view_rot.project_point3((glam::DVec3::new(x1 as f64, y1 as f64, 0.0) - eye).as_vec3()), ]; let mut min_px = f32::INFINITY; let mut max_px = f32::NEG_INFINITY; @@ -1958,7 +1962,8 @@ fn aabb_offscreen( /// draw calls that wouldn't contribute a visible pixel. fn aabb_below_pixel( aabb: [f32; 4], - view_proj: glam::Mat4, + view_rot: glam::Mat4, + eye: glam::DVec3, clip_w: u32, clip_h: u32, threshold_px: f32, @@ -1970,10 +1975,10 @@ fn aabb_below_pixel( let w = clip_w as f32; let h = clip_h as f32; let corners = [ - view_proj.project_point3(glam::Vec3::new(x0, y0, 0.0)), - view_proj.project_point3(glam::Vec3::new(x1, y0, 0.0)), - view_proj.project_point3(glam::Vec3::new(x0, y1, 0.0)), - view_proj.project_point3(glam::Vec3::new(x1, y1, 0.0)), + view_rot.project_point3((glam::DVec3::new(x0 as f64, y0 as f64, 0.0) - eye).as_vec3()), + view_rot.project_point3((glam::DVec3::new(x1 as f64, y0 as f64, 0.0) - eye).as_vec3()), + view_rot.project_point3((glam::DVec3::new(x0 as f64, y1 as f64, 0.0) - eye).as_vec3()), + view_rot.project_point3((glam::DVec3::new(x1 as f64, y1 as f64, 0.0) - eye).as_vec3()), ]; let mut min_px = f32::INFINITY; let mut max_px = f32::NEG_INFINITY; @@ -1996,7 +2001,8 @@ fn aabb_below_pixel( /// the projection collapses (off-screen, behind camera). fn project_scissor( rect: Option<[f32; 4]>, - view_proj: glam::Mat4, + view_rot: glam::Mat4, + eye: glam::DVec3, clip_w: u32, clip_h: u32, ) -> Option<[u32; 4]> { @@ -2004,10 +2010,10 @@ fn project_scissor( let w = clip_w as f32; let h = clip_h as f32; let corners = [ - view_proj.project_point3(glam::Vec3::new(x0, y0, 0.0)), - view_proj.project_point3(glam::Vec3::new(x1, y0, 0.0)), - view_proj.project_point3(glam::Vec3::new(x0, y1, 0.0)), - view_proj.project_point3(glam::Vec3::new(x1, y1, 0.0)), + view_rot.project_point3((glam::DVec3::new(x0 as f64, y0 as f64, 0.0) - eye).as_vec3()), + view_rot.project_point3((glam::DVec3::new(x1 as f64, y0 as f64, 0.0) - eye).as_vec3()), + view_rot.project_point3((glam::DVec3::new(x0 as f64, y1 as f64, 0.0) - eye).as_vec3()), + view_rot.project_point3((glam::DVec3::new(x1 as f64, y1 as f64, 0.0) - eye).as_vec3()), ]; let px: Vec = corners.iter().map(|c| (c.x + 1.0) * 0.5 * w).collect(); let py: Vec = corners.iter().map(|c| (1.0 - c.y) * 0.5 * h).collect(); diff --git a/src/scene/pipeline/uniforms.rs b/src/scene/pipeline/uniforms.rs index 30a4a20da..72517fa58 100644 --- a/src/scene/pipeline/uniforms.rs +++ b/src/scene/pipeline/uniforms.rs @@ -4,11 +4,6 @@ use iced::Rectangle; #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] #[repr(C)] pub struct Uniforms { - /// Full view-projection (with translation) — GPU rendering no longer uses - /// this; it remains for the CPU-side frustum-cull / scissor / LOD passes - /// that project offset-relative AABBs to screen. The GPU draws via the - /// relative-to-eye `view_rot` + `eye_high`/`eye_low` below. - pub view_proj: glam::Mat4, pub viewport_size: [f32; 2], /// World units per screen pixel at the current zoom. Used by the /// hatch shader to substitute solid fill when pattern line spacing @@ -54,7 +49,6 @@ impl Uniforms { }; let (eye_high, eye_low) = camera.eye_high_low(); Self { - view_proj: camera.view_proj(bounds), viewport_size: [bounds.width, bounds.height], world_per_pixel, lwdisplay_enable: if lwdisplay_enable { 1.0 } else { 0.0 }, diff --git a/src/scene/view/camera.rs b/src/scene/view/camera.rs index 47f61499f..1a79c1f45 100644 --- a/src/scene/view/camera.rs +++ b/src/scene/view/camera.rs @@ -91,28 +91,6 @@ impl Camera { // ── Projection matrices ──────────────────────────────────────────────── - pub fn view_proj(&self, bounds: Rectangle) -> Mat4 { - let aspect = bounds.width / bounds.height; - - // Up vector: use the rotation to find which world direction is "up" - // in the current camera frame. - let up_dir = self.rotation * Vec3::Y; - - let view = Mat4::look_at_rh(self.eye(), self.target.as_vec3(), up_dir); - let proj = match self.projection { - Projection::Perspective => { - Mat4::perspective_rh(self.fov_y, aspect, self.distance * 0.001, self.distance * 1000.0) - } - Projection::Orthographic => { - let h = self.ortho_size(); - let w = h * aspect; - let (near, far) = self.ortho_depth_range(); - Mat4::orthographic_rh(-w, w, -h, h, near, far) - } - }; - OPENGL_TO_WGPU * proj * view - } - /// Orthographic near/far that CENTRE the target plane at ndc-z ≈ 0.5. /// /// The draw-order depth bias shifts clip-z by ±`DRAW_ORDER_BIAS` (0.001). diff --git a/src/scene/view/render.rs b/src/scene/view/render.rs index 82abcc51e..dc6b74c4e 100644 --- a/src/scene/view/render.rs +++ b/src/scene/view/render.rs @@ -305,13 +305,20 @@ impl shader::Primitive for Primitive { // refreshed every frame it's present, so a drag never re-uploads // the resident base wire buffer. inner.upload_preview_wires(device, &vp.preview_wires[..], &vp.draw_depths); - let vproj = vp.uniforms.view_proj; - inner.compute_wire_scissors(vproj, clip_size.width, clip_size.height); - inner.compute_wipeout_scissors(vproj, clip_size.width, clip_size.height); - inner.compute_image_scissors(vproj, clip_size.width, clip_size.height); - inner.compute_hatch_lod(queue, vproj, clip_size.width, clip_size.height); - inner.compute_wipeout_lod(vproj, clip_size.width, clip_size.height); - inner.compute_mesh_lod(vproj, clip_size.width, clip_size.height); + // Cull / scissor / LOD project AABBs relative-to-eye (matching the + // GPU's RTE path) so the math stays precise at UTM-scale coords. + let view_rot = vp.uniforms.view_rot; + let eye = glam::DVec3::new( + vp.uniforms.eye_high[0] as f64 + vp.uniforms.eye_low[0] as f64, + vp.uniforms.eye_high[1] as f64 + vp.uniforms.eye_low[1] as f64, + vp.uniforms.eye_high[2] as f64 + vp.uniforms.eye_low[2] as f64, + ); + inner.compute_wire_scissors(view_rot, eye, clip_size.width, clip_size.height); + inner.compute_wipeout_scissors(view_rot, eye, clip_size.width, clip_size.height); + inner.compute_image_scissors(view_rot, eye, clip_size.width, clip_size.height); + inner.compute_hatch_lod(queue, view_rot, eye, clip_size.width, clip_size.height); + inner.compute_wipeout_lod(view_rot, eye, clip_size.width, clip_size.height); + inner.compute_mesh_lod(view_rot, eye, clip_size.width, clip_size.height); if vp.show_viewcube { inner.viewcube.upload( queue, @@ -743,9 +750,7 @@ impl Scene { }; let mut uniforms = Uniforms::new(&inst.camera, full_bounds, self.document.header.lineweight_display); - uniforms.view_proj = crop_view_proj(uniforms.view_proj, uo, vo, us, vs); - // The RTE matrix is a view-projection too, so the same clip-space crop - // applies — keep it in lock-step with view_proj for tiled / sub-rect panes. + // Crop the rotation-only RTE view-projection to the visible sub-rect. uniforms.view_rot = crop_view_proj(uniforms.view_rot, uo, vo, us, vs); uniforms.viewport_size = [visible_w, visible_h]; uniforms.flat_shade = if flags.flat_shade { 1.0 } else { 0.0 }; diff --git a/src/shaders/face3d.wgsl b/src/shaders/face3d.wgsl index bb5028aa0..2d52869ff 100644 --- a/src/shaders/face3d.wgsl +++ b/src/shaders/face3d.wgsl @@ -5,7 +5,6 @@ // color [f32; 4] offset 12 16 B struct Uniforms { - view_proj: mat4x4, viewport_size: vec2, world_per_pixel: f32, lwdisplay_enable: f32, diff --git a/src/shaders/hatch.wgsl b/src/shaders/hatch.wgsl index 7db44d6dd..73c2a27db 100644 --- a/src/shaders/hatch.wgsl +++ b/src/shaders/hatch.wgsl @@ -13,7 +13,6 @@ // ── Group 0: frame uniforms (shared) ────────────────────────────────────── struct Uniforms { - view_proj: mat4x4, viewport_size: vec2, world_per_pixel: f32, lwdisplay_enable: f32, diff --git a/src/shaders/hatch_batched.wgsl b/src/shaders/hatch_batched.wgsl index c4841e386..80f15274c 100644 --- a/src/shaders/hatch_batched.wgsl +++ b/src/shaders/hatch_batched.wgsl @@ -18,7 +18,6 @@ // ── Group 0: shared frame uniforms (matches hatch.wgsl) ────────────────── struct Uniforms { - view_proj: mat4x4, viewport_size: vec2, world_per_pixel: f32, lwdisplay_enable: f32, diff --git a/src/shaders/image.wgsl b/src/shaders/image.wgsl index 79c77e9bb..76f4dbd07 100644 --- a/src/shaders/image.wgsl +++ b/src/shaders/image.wgsl @@ -4,7 +4,6 @@ // ── Bind group 0: shared projection uniforms ───────────────────────────────── // Must match the shared `Uniforms` struct (scene::pipeline::uniforms, 112 B). struct Uniforms { - view_proj: mat4x4, viewport_size: vec2, world_per_pixel: f32, lwdisplay_enable: f32, diff --git a/src/shaders/mesh.wgsl b/src/shaders/mesh.wgsl index c94cf7ab5..267779754 100644 --- a/src/shaders/mesh.wgsl +++ b/src/shaders/mesh.wgsl @@ -10,7 +10,6 @@ // flat shade (FlatShaded). struct Uniforms { - view_proj: mat4x4, viewport_size: vec2, world_per_pixel: f32, lwdisplay_enable: f32, diff --git a/src/shaders/wire.wgsl b/src/shaders/wire.wgsl index 0dc07e32c..a2bb2cd4a 100644 --- a/src/shaders/wire.wgsl +++ b/src/shaders/wire.wgsl @@ -14,7 +14,6 @@ // • pat0/pat1 encode up to 8 elements: positive=dash, negative=gap. struct Uniforms { - view_proj: mat4x4, viewport_size: vec2, world_per_pixel: f32, // LWDISPLAY toggle: 0.0 = force 1 px (half_width 0.5), 1.0 = use the