Painting primitives at non-integer pixel coordinates produces blurry output. Pixel snapping converts layout coordinates into integer device-pixel coordinates so painted edges land exactly on physical pixel boundaries. Non-integer coordinates can arise for several reasons, including: - flex distribution, percentages, centering, and text measurement can produce fractional element sizes and positions; - at fractional scale factors (for example 125% or 150%), integer logical-pixel values can map to non-integer device-pixel values. We pixel-snap by rounding in device-pixel space, after multiplying by `scale_factor`, so that snapping targets physical pixels. Bounds are divided by `scale_factor` before being returned to GPUI. Midpoints are rounded toward zero. This is a stylistic choice: a 1-logical-pixel line at 150% scale should render as 1 dp rather than 2 dp. Pixel snapping is done in two phases: 1. Pre-layout metric snapping. Before Taffy computes layout, all authored absolute lengths are rounded in `to_taffy`. This includes borders, padding, gaps, and explicit sizes. Custom-measured leaf nodes have their measured sizes rounded up to integer device-pixel lengths. 2. Post-layout edge snapping. After Taffy resolves the tree, layout relationships such as flex shares, grid tracks, percentages, and centering can produce new fractional edge positions. Boxes now have edges in absolute coordinates, and snapping must decide where those edges land on the device-pixel grid. Ideally, post-layout snapping would satisfy: - Edge closure. Two raw layout edges at the same absolute position should snap to the same pixel column. - Translation stability. A component's internal geometry should not change when it moves to a new absolute position. These goals are in tension because rounding is not associative. The simple local schemes make different tradeoffs: - Absolute edge rounding gives each window coordinate one answer, so coincident edges always close globally. But a span's snapped length is `round(far) - round(near)`, which may change by 1 dp as its absolute origin moves. - Parent-relative edge rounding rounds each child inside its parent's coordinate space. This guarantees translation stability, but a shared edge reached through different parents can accumulate different rounding, causing non-closure between cousins. - Length rounding rounds each width, height, and thickness independently and then places boxes from those rounded lengths. Sizes stay stable under translation, but neighboring boxes derive their shared boundary from different sources, so closure is not guaranteed. We apply absolute edge rounding for each element's outer box in post-layout rounding to preserve closure. Border and padding widths are not touched by post-layout rounding; they keep their pre-layout rounded value so that they remain stable under translation. This gives both closure and translation stability in the case that all local metrics are integer device-pixel lengths. Pre-layout rounding covers that in most cases. The exception is metrics resolved by layout relationships, such as percentages. Outer box edges will still close globally, and painted border widths are still snapped independently, but the raw content-box origin can carry a 1 dp residual into descendants. --- Fixes https://github.com/zed-industries/zed/issues/46360 Fixes https://github.com/zed-industries/zed/issues/44528 Fixes https://github.com/zed-industries/zed/issues/40282 Fixes https://github.com/zed-industries/zed/issues/42257 --- Release Notes: - Fixed potentially blurry appearance of UI elements when using fractional display scaling.
12 files changedLine totals unavailable