From 600717c1ec28eb085403cdafd4ee556bb817f479 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Thu, 28 May 2026 10:38:56 +0300 Subject: [PATCH] fix(paper): skip GPU for off-canvas inactive viewports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Zooming the paper view in PSPACE grows each floating viewport's screen rectangle until its edges run past the canvas. The per-viewport blit computes its surface offset via `(screen_rect.x * cw) as u32`, which saturates negative values to 0 — so a viewport hanging off the left edge was being painted at the canvas top-left instead of its actual position, producing a drifting ghost copy on top of the CPU projection that PaperCanvas was already drawing correctly clipped. `active_viewports` for paper now skips the GPU pass for any inactive content viewport whose screen rect would extend beyond the canvas. The CPU projection already handles those, so only one (correct) copy is visible. The active viewport keeps the GPU path so its per-viewport render mode (Hidden Line, Gouraud, …) still applies. Co-Authored-By: Claude Opus 4.7 --- src/scene/mod.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 99019187b..f4665a602 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -4644,12 +4644,30 @@ impl Scene { ) else { continue; }; + let is_active = self.active_viewport == Some(h); + // Skip the GPU pass for inactive viewports whose screen rect + // extends beyond the canvas — the per-viewport pipeline blits + // the full MSAA at a u32-saturated (≥0) surface offset, so a + // negative offset would paint the content at the canvas + // corner instead of where the viewport actually sits. The CPU + // projection in PaperCanvas already handles those correctly + // clipped to the canvas, so the user simply sees that copy. + // The active viewport always renders through the GPU so its + // own render mode (Hidden Line, Gouraud, …) still applies. + if !is_active + && (screen_rect.x < 0.0 + || screen_rect.y < 0.0 + || screen_rect.x + screen_rect.width > canvas_w + || screen_rect.y + screen_rect.height > canvas_h) + { + continue; + } out.push(ViewportInstance { handle: h, screen_rect, camera, render_mode: vp.render_mode, - active: self.active_viewport == Some(h), + active: is_active, }); } out