Skip to content

linux: render at the fractional output scale instead of the integer scale factor - #359

Open
Reliacrt wants to merge 1 commit into
vercel-labs:mainfrom
Reliacrt:fix/linux-fractional-scale
Open

linux: render at the fractional output scale instead of the integer scale factor#359
Reliacrt wants to merge 1 commit into
vercel-labs:mainfrom
Reliacrt:fix/linux-fractional-scale

Conversation

@Reliacrt

Copy link
Copy Markdown

Summary

The Linux GTK host derives the canvas render scale from gtk_widget_get_scale_factor, which rounds to the integer wl_output scale. On a fractionally-scaled Wayland output the compositor's real scale differs from that integer:

  • niri at scale 1.5 (and KWin fractional scaling) report wl_output::scale = 2 (the protocol only carries integers) but deliver the true 1.5 via wp_fractional_scale_manager_v1.
  • The canvas renders at 2x (640x1040 for a 320x520 logical surface) and the compositor resamples the buffer down to 1.5x on presentation.

That buffer/compositor mismatch softens and jags every curve — glyph arcs, rounded button corners, chart lines — regardless of the renderer's own AA quality.

Fix

Use GDK's fractional-scale API (gdk_surface_get_scale, GDK >= 4.12) wherever the host previously read the integer gtk_widget_get_scale_factor / gdk_surface_get_scale_factor:

  • new native_sdk_gtk_fractional_scale() helper (falls back to the integer factor on pre-4.12 GTK)
  • scale-change callback, frame emission, and the drawing-area resize path
  • the draw-time device-scale comparison, so the buffer stays 1:1 with the widget and the NEAREST fast path still holds
  • window frame/resize event payloads
  • a one-shot notify::scale connection on the surface so fractional scale changes (window moved to an output with a different 150%-style scale) re-sync geometry

Verification

Environment: Linux + niri, output scale 1.5, 2560x1600.

  • Present-path instrumentation showed the fix in action: buf=480x780 logical=320x520 widget_scale=1.5 dev_scale=1.5 filter=NEAREST (previously 640x1040 / 2.0 / resampled).
  • Automation snapshot reports gpu_scale=1.5 after the fix (was 2).
  • Rounded corners and glyph arcs render smooth on-screen after the change; before it they showed visible stair-stepping on the same hardware and compositor.
  • zig build validate passes; zig build test passes in the local gate run (a known flaky --listen-runner failure on this machine also reproduces standalone-clean and does not compile gtk_host.c; desktop-platform-tests, which does, passes standalone with this change).

Notes

  • The examples/calculator window-height issue seen alongside this (hardcoded window_height = 490 in src/main.zig clips the last keypad row under a Linux titlebar) is a separate example bug; happy to file it separately if wanted.

@vercel

vercel Bot commented Aug 15, 2026

Copy link
Copy Markdown

@Reliacrt is attempting to deploy a commit to the Vercel Labs Team on Vercel.

A member of the Team first needs to authorize it.

Comment thread src/platform/linux/gtk_host.c Outdated
Comment thread src/platform/linux/gtk_host.c
@Reliacrt
Reliacrt force-pushed the fix/linux-fractional-scale branch from 725b534 to d5a56ca Compare August 15, 2026 09:14
@Reliacrt

Copy link
Copy Markdown
Author

Fixed both review findings in d5a56ca:

  1. Use-after-free (notify::scale handler): the handler id is now stored in view->gpu_scale_notify_id and explicitly disconnected in native_sdk_teardown_gpu_surface_view before the view memory is freed. The surface belongs to the window and outlives the view, so this removes the dangling-handler window.

  2. GTK 4.10 compilation: gdk_surface_get_scale() is now gated behind #if GDK_VERSION_MAX_ALLOWED >= GDK_VERSION_4_12 (GDK's version mechanism; GDK_CHECK_VERSION is not defined in the GDK headers), so builds on the supported GTK 4.10 fall back to gtk_widget_get_scale_factor — behavior on 4.10 is unchanged from before this patch. Also fixed GtkWidget *root = gtk_widget_get_root(...) to GTK_WIDGET(...) since the API returns GtkRoot *.

Verified: rebuilt the example against the patched SDK (ReleaseFast) — compiles clean and renders (gpu_scale=1.5 at runtime).

@Reliacrt
Reliacrt force-pushed the fix/linux-fractional-scale branch from d5a56ca to cad2d3b Compare August 15, 2026 09:23
@Reliacrt

Copy link
Copy Markdown
Author

Addressed the remaining GTK 4.10 build concern in cad2d3b: the three emit-path call sites (window frame, resize, and the window-state scale) now route through a new native_sdk_gtk_surface_scale() helper that guards gdk_surface_get_scale() behind #if GDK_VERSION_MAX_ALLOWED >= GDK_VERSION_4_12 and falls back to gdk_surface_get_scale_factor() on older GDK. Rebuilt against the patched SDK (ReleaseFast): compiles clean, renders at gpu_scale=1.5.

…cale factor

The GTK host derives the canvas render scale from
gtk_widget_get_scale_factor, which rounds to the integer wl_output
scale. On a 150%-scaled Wayland output the compositor reports scale 2
(integer protocol) but its real scale is 1.5 (fractional-scale
protocol): the canvas renders at 2x and the compositor resamples the
buffer down to 1.5x, so every curve — glyph arcs, rounded corners,
chart lines — comes out softened and jagged.

Use gdk_surface_get_scale (GDK >= 4.12), which returns the exact
fractional scale, for the render resolution, the resize/frame event
payloads, and the draw-time device-scale comparison (keeps the
buffer at 1:1 with the widget so the NEAREST fast path still holds).
Also connect notify::scale on the surface so fractional scale
changes re-sync geometry when a window moves between outputs.

Verified on niri (output scale 1.5, 2560x1600): gpu_scale goes from 2
to 1.5, the present path reports buf/logical/widget_scale all at 1.5
with NEAREST, and curves render smoothly.
@Reliacrt
Reliacrt force-pushed the fix/linux-fractional-scale branch from cad2d3b to eb34da0 Compare August 15, 2026 09:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant