Coordinate Spaces
There are two main coordinate spaces in niri:Physical space
Pixels of every individual output
Logical space
Shared among all outputs, takes into account the scale of every output
Wayland clients mostly work in the logical space, and it’s the most convenient space to do all the layout in, since it bakes in the output scaling factor.
The Physical Pixel Problem
However, many things need to be sized or positioned at integer physical coordinates.- Requirements
- The challenge
Things that must align to integer physical pixels:
- Wayland toplevel buffers (assumed to be placed at an integer physical pixel on an output)
- Borders and focus rings (should have a width equal to an integer number of physical pixels to stay crisp)
SolidColorRenderElement(does not anti-alias lines at fractional pixel positions)
Potential Artifacts
Thus, niri uses fractional logical coordinates for most of its layout. However, one needs to be very careful to keep things aligned to the physical grid to avoid artifacts like:How niri Handles Alignment
The way it’s handled in niri is:Round workspace sizes to physical pixels
All relevant sizes on a workspace are rounded to an integer physical coordinate according to the current output scale.Things that are rounded:
- Struts
- Gaps
- Border widths
- Working area location
It’s important to understand that they remain fractional numbers in the logical space, but these numbers correspond to an integer number of pixels in the physical space.
Rounding formula
The rounding looks something like:
Whenever a workspace moves to an output with a different scale (or the output scale changes), all sizes are re-rounded from their original configured values to align with the new physical space.
Don't round offsets initially
The view offset and individual column/tile render offsets are not rounded to physical pixels, but:
The Key Insight
Alignment preservation
The idea is that every tile can assume that it is rendered at an integer physical coordinate.Therefore when shifting the position by, say, border width (also rounded to integer physical coordinates), the new position will stay rounded to integer physical coordinates.The same logic works for the rest of the layout thanks to gaps, struts and working area being similarly rounded.
This way, the entire layout is always aligned, as long as it is positioned at an integer physical coordinate (which rounding the tile positions effectively achieves).
Visual Example
Summary
What gets rounded immediately?
What gets rounded immediately?
- Struts
- Gaps
- Border widths
- Working area location
What gets rounded during rendering?
What gets rounded during rendering?
- Tile positions (via
tiles_with_render_positions()) - Custom shader positions and sizes
What doesn't get rounded?
What doesn't get rounded?
- View offset
- Individual column/tile render offsets (until rendering)