Skip to content

Windows start on a whole pixel at a fractional scale - #680

Open
DevomB wants to merge 1 commit into
lock-follows-scalefrom
windows-on-pixel-grid
Open

DevomB wants to merge 1 commit into
lock-follows-scalefrom
windows-on-pixel-grid

Conversation

@DevomB

@DevomB DevomB commented Oct 29, 2026

Copy link
Copy Markdown
Owner

This is the desktop audit's hidpi-scale-fractional-odd-geometry finding.

wlroots' scene places a surface at round(x × scale) and sizes it as round((x + w) × scale) − round(x × scale) (wlr_scene.c scale_box). When x × scale is not whole, the on-screen size of a buffer drawn at round(w × scale) differs from the buffer by one pixel. That happens for an odd x at 1.5, or an x that is not a multiple of 4 at 1.25. pixman then takes its transform path: soft, and repeated on every redraw, even for Firefox, which draws at the exact scale. dwl's tiles start wherever mfact and the 6 px border put them, so most windows were off the grid.

Stacked: based on #675 (lock-follows-scale). The read is this commit only, 3f849c8. It goes in a batch after #675.

  • tools/desktop/dwl-zone-borders.py: two edits.
    • A snaptopixel(Client *c) placed before resize. At a scale that is a whole number of quarters but not whole, it moves the surface's origin (geom + bw, relative to the monitor) up to the next multiple of the step: 2 at 1.5, 4 at 1.25 and 1.75. Any other scale is left as it is.
    • Its call in resize, after applybounds.
    • A tiled window takes the step from its width and height, so it stays inside its tile. Tiles are recomputed by every arrange, so nothing builds up.
    • A floating window only moves, so dragging it doesn't shrink it. Fullscreen sits at 0 with no border, so it doesn't change.
    • The (int)c->bw keeps the arithmetic signed for a floating window left of its monitor.
  • tools/tests/dwl-headless.sh:
    • At scale 1 the one window must be configured at 1280×720 minus the border. This is the control.
    • kryptik-display, built there against wlroots' output-management XML, then applies * 1.25. The window must now be configured smaller by the step from the border to the next multiple of 4: 1010×562 with borderpx 6, where unsnapped would be 1012×564.
  • docs/design/laptop.md: the grid, and the check.

Evidence: the headless check runs in CI's Compositor build job, and it tests the snapping arithmetic through dwl itself. That the pixman scaling then goes away follows from scale_box. No screenshot checks it.

wlroots puts a surface at its position times the output's scale, rounded,
and sizes it by rounding its far edge. Where the position times the scale
is not whole, as for an odd x at 1.5 or one not a multiple of 4 at 1.25, a
buffer drawn at the window's size times the scale is shown a pixel off its
own size, and pixman scales it, soft and on every redraw, even for a
client such as Firefox that draws at the exact scale. dwl's tiles start
wherever mfact and the border put them, so most windows were off the grid.

resize now moves a window's surface onto its screen's grid, in steps of 2
at 1.5 and 4 at 1.25 or 1.75, and leaves other scales as they are. A tiled
window gives up the pixels from its width and height, so it stays inside
its tile; a floating one moves, so a drag does not shrink it.

dwl-headless.sh checks it: at scale 1 the one window fills the output
inside its border, and once kryptik-display has put the output at 1.25 it
is configured smaller by the step from its border to the next multiple of 4.
@DevomB
DevomB marked this pull request as ready for review October 29, 2026 15:08
DevomB added a commit that referenced this pull request Oct 29, 2026
…each window on a whole pixel of its screen, so a buffer drawn at the exact scale is shown at its own size

This branch has not been deployed

No deployments
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