Rework pixel snapping, take two.#1803
Draft
xStrom wants to merge 5 commits into
Draft
Conversation
995d59f to
dfd8e97
Compare
dfd8e97 to
e5a5ed0
Compare
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Background
The old pixel snapping system rounded both the origin (top-left) and end point (bottom-right) of the layout border-box in the parent widget's layout border-box coordinate space. Specifically this happened during the layout pass, when the parent called
place_child.The major benefit of this approach is that adjacent geometry will be pixel snapped in a compatible way where there won't be any overlap or empty gaps. This has the promise of a very polished and sharp looking render.
The cost of this approach is that the layout size and pixel snapped size may diverge. The box might shrink, remain the same, or grow.
The problems keep coming
As we've lived with this system, more problems have become clear.
Masonry supports arbitrary transforms on widgets. Doing the pixel snapping in the parent's coordinate space means that none of the transforms that convert from the parent's space to the window's space are taken into account. This is a critical flaw, because the idea of snapping is to align to device pixels, not to parent widget local pixels.
Because snapping happens in the parent's space, there is no consistent rounding of boxes across nested widget levels. It's easy to run into situations where both parent and child have the exact same fractional size, yet they get snapped to different integer sizes.
We've known that the cost of this approach is that snapped box sizes will differ from layout sizes. However, what has not been as obvious and definitely not documented, is that this also effectively introduces a whole new coordinate space. The
(1,1)of the layout box isn't the same window pixel location as the(1,1)of the snapped box. Yet there is a bunch of code that ignores this shift and mixes layout and snapped coordinates. Sometimes that can be ok, but thus far very little of it seems intentional.Improving the situation
We can achieve much improved pixel snapping if we resolve the layout border-box via the full window transform, apply the DPI scale factor to get device pixels, round, and map the snapped box back into local layout border-box space. The rounding will also be consistent across arbitrary nested transforms, so equally sized parent-child combos will result in equal snapped sizes. Pixel snapping will be skipped for a specific widget if the transform has rotation or shear, as it will just add noise and won't be correct. We can also skip the snapping of specific widgets during animation.
Going beyond the first attempt
#1792 implemented the improvements, but notably kept the the differences between layout and snapped coordinates. It works, but the extra comprehension required to make sense of all the coordinate systems is non-trivial. That is where this PR here comes in. Now the pixel snapping is not only transform-aware and consistent-across-nesting-levels, but it also happens earlier in the box lifecycle, even before
Widget::layoutruns. This allows us to greatly simplify the box lifecycle and coordinate system complexity.Changes in this PR
LayoutCtx::run_layout(size)andLayoutCtx::place_child(origin)withLayoutCtx::layout_child(origin, size)and an optionalLayoutCtx::move_child(origin). This enables pixel snapping to happen beforeWidget::layoutis called, while still enabling the widget to be moved later.LayoutCtx::snap_transformwhich is a stripped down version ofwindow_transform. It doesn't contain translations which can be guaranteed to be integer device pixels, allowing snapping to account for transforms while still enabling the widget to be moved (by an integer device pixel delta) either viaLayoutCtx::move_childor via scrolling.ComposeCtx::set_animated_child_scroll_translationas the regularset_child_scroll_translationis now universal.WindowEvent::Rescalehandler now also requests layout and runs rewrite passes so snapped geometry is up-to-date before the next pointer event.Screenshot changes
All test screenshot changes are due to pixel snapping rounding changes. Specifically that pixel snapping is now consistent across widget nesting levels as it is done in the window's coordinate space.
Follow-up work
These are related things that are either certainly or potentially broken that didn't receive full attention in this PR here yet, in order to limit PR size.
Draft because this PR depends on #1811, #1813, #1814, #1815 and currently contains those commits as well.