> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/niri-wm/niri/llms.txt
> Use this file to discover all available pages before exploring further.

# Fractional Layout

> Understanding how niri handles fractional scaling and physical pixel alignment

## Coordinate Spaces

There are two main coordinate spaces in niri:

<CardGroup cols={2}>
  <Card title="Physical space" icon="desktop">
    Pixels of every individual output
  </Card>

  <Card title="Logical space" icon="vector-square">
    Shared among all outputs, takes into account the scale of every output
  </Card>
</CardGroup>

<Info>
  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.
</Info>

## The Physical Pixel Problem

However, many things need to be sized or positioned at integer physical coordinates.

<Tabs>
  <Tab title="Requirements">
    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)
  </Tab>

  <Tab title="The challenge">
    Integer physical coordinates do not necessarily correspond to integer logical coordinates.

    **Example with integer scale = 2:**

    * A physical pixel at `(1, 1)` will be at the logical position of `(0.5, 0.5)`

    **With fractional scale factors:**

    * This problem becomes much worse
    * Most integer logical coordinates will fall on fractional physical coordinates
  </Tab>
</Tabs>

## 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:

<Warning>
  * Border width alternating 1 px thicker/thinner
  * Border showing 1 px off from the window at certain positions
  * 1 px gaps around rounded corners
  * Slightly blurry window contents during resizes
  * And so on...
</Warning>

## How niri Handles Alignment

The way it's handled in niri is:

<Steps>
  <Step title="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

    <Info>
      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.
    </Info>
  </Step>

  <Step title="Rounding formula">
    The rounding looks something like:

    ```rust theme={null}
    (logical_size * scale).round() / scale
    ```

    <Note>
      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.
    </Note>
  </Step>

  <Step title="Don't round offsets initially">
    The view offset and individual column/tile render offsets are **not** rounded to physical pixels, but:
  </Step>

  <Step title="Round during rendering">
    `tiles_with_render_positions()` rounds tile positions to physical pixels as it returns them.

    Custom shaders like opening, closing and resizing windows, are also careful to keep positions and sizes rounded to the physical pixels.
  </Step>
</Steps>

## The Key Insight

<Card title="Alignment preservation" icon="align-left">
  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.
</Card>

<Info>
  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).
</Info>

## Visual Example

```
Logical coordinates:          Physical coordinates (scale=2):
┌─────────────┐              ┌─────────────────────────┐
│   0.0, 0.0  │              │      0, 0               │
│             │              │                         │
│  Window     │    ───>      │    Window               │
│   at 1.5    │              │     at 3 (rounded)      │
│             │              │                         │
└─────────────┘              └─────────────────────────┘

Border: 0.5 logical          Border: 1 physical
        (rounded from                (crisp!)
         configured value)
```

## Summary

<AccordionGroup>
  <Accordion title="What gets rounded immediately?">
    * Struts
    * Gaps
    * Border widths
    * Working area location

    All rounded to integer physical coordinates while stored as fractional logical coordinates.
  </Accordion>

  <Accordion title="What gets rounded during rendering?">
    * Tile positions (via `tiles_with_render_positions()`)
    * Custom shader positions and sizes

    Rounded to physical pixels as they're used for rendering.
  </Accordion>

  <Accordion title="What doesn't get rounded?">
    * View offset
    * Individual column/tile render offsets (until rendering)

    These remain fractional but everything still aligns because the rendering step rounds them.
  </Accordion>
</AccordionGroup>
