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

commit0681818239b8ea033e6311137e3a0ad4f0076c2a
parent35a91d0d9c
authorLucas Galante <[email protected]>
date2026-08-18 10:57
runner: pin grid surfaces to buffer scale 1

A grid patch's scale is BUFFER px per virtual unit — the compositor's
patch manager folds the output scale into it, so the surface must not
adopt the output scale on top: the client would render a doubled buffer
that the compositor immediately downsamples back (blur). Pin
scale_factor and wl buffer scale to 1 at surface creation and ignore
scale_factor_changed for grid apps.

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

 email2.log                   |  0
 settings2.log                |  0
 src/backend/window_runner.rs | 21 ++++++++++++++++++++-
 3 files changed, 20 insertions(+), 1 deletion(-)

diff --git a/email2.log b/email2.log
new file mode 100644
index 0000000..e69de29
diff --git a/settings2.log b/settings2.log
new file mode 100644
index 0000000..e69de29
diff --git a/src/backend/window_runner.rs b/src/backend/window_runner.rs
index 094d261..96f9dfe 100644
--- a/src/backend/window_runner.rs
+++ b/src/backend/window_runner.rs
@@ -3558,6 +3558,11 @@ impl<A: Application> CompositorHandler for EngineState<A> {
             // not clobber the override.
             return;
         }
+        if self.inner.as_ref().map_or(false, |a| a.grid()) {
+            // Grid surfaces stay at scale 1 — patch.scale is the sole
+            // resolution authority (see the pin at surface creation).
+            return;
+        }
         // Resume bounce: when the surface sits on no LIVE output (the DRM
         // connector was destroyed and not yet re-created), the reported
         // factor is SCTK's no-outputs fallback, not information — hold the
@@ -4732,8 +4737,22 @@ fn run_session<'l, A: Application>(
     engine_state.inner = Some(inner);
 
     let surface = engine_state.compositor_state.create_surface(&qh);
+    // A grid app's surface is pinned to scale 1: the patch's `scale` is
+    // BUFFER px per virtual unit and already carries the output scale (the
+    // patch manager folds it in), so adopting the output scale here would
+    // square it — the client renders a doubled buffer and the compositor
+    // downsamples it straight back into blur.
+    if engine_state.inner.as_ref().unwrap().grid() {
+        engine_state.scale_factor = 1.0;
+    }
     // Forced-scale mode renders scaled-up into a buffer_scale-1 surface.
-    let buffer_scale = if crate::scale::forced_scale().is_some() { 1 } else { scale as i32 };
+    let buffer_scale = if crate::scale::forced_scale().is_some()
+        || engine_state.inner.as_ref().unwrap().grid()
+    {
+        1
+    } else {
+        scale as i32
+    };
     surface.set_buffer_scale(buffer_scale);
     engine_state.committed_buffer_scale = buffer_scale;