Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update Window's width & height methods to return f32 #1033

Merged
merged 4 commits into from
Dec 13, 2020

Conversation

blunted2night
Copy link
Contributor

@blunted2night blunted2night commented Dec 9, 2020

This allows for better precision of the width and height when the scaling factor and physical resolution come together to produce a logical resolution that is not an integer. Admittedly, this will have little effect on most usages. An issue would occur if things were being laid out relative to the right or bottom edge. In this case it would be common to subtract some offset from the width or height.

It was requested to provide a demonstration of the ill effects this PR would resolve. Producing an example in engine is difficult (for me at least) and unreliable due to having no control over the scaling factor. Instead I created the following illustration of a scenario where trouble could occur.

In the example below imagine a line is being placed along the right most edge by subtracting 0.4 logical pixels (0.5 physical pixels) from the width to get the line to fall in the middle of the last pixel. Because the width is not precise enough the bounds of the line do not align with the pixel grid. This causes the line to bleed into the neighboring pixels and not achieve the target color.

pixel-alignment

@Moxinilian Moxinilian added C-Feature A new feature, making something new possible A-Windowing Platform-agnostic interface layer to run your app in labels Dec 9, 2020
@smokku
Copy link
Member

smokku commented Dec 9, 2020

Is there an Operating System that creates non-integer sized windows?

@blunted2night
Copy link
Contributor Author

When running in high DPI monitors the logical resolution is scaled down from the physical resolution (physical pixels). On my current workstation, I run a 125% scaling factor so each physical pixel is 0.8 logical pixels, then depending on what size my window is, the resulting logical width may not be integral.

@cart
Copy link
Member

cart commented Dec 9, 2020

Cool cool having looked into the details of winit a bit (and considering the example above), this looks like its the right call.

@cart
Copy link
Member

cart commented Dec 9, 2020

I think we need to align WindowDescriptor::width/height and set_resolution. They are the "same" property in my mind (they are both logical resolutions). We should either allow people to specify fractional sizes (which I expect to behave counterintuitively for computers with a scale factor of 1) or we shouldn't.

They also currently behave differently right now. If i set my os scale factor to 2.0, then create a window that is initially 1280u32 x 720u32, I get a window that is 2560u32 x 1387u32 physical units. (note that my display has a physical height of 1440). I'm assuming this gets "clamped" to a slightly smaller max height to account for window decorations (and maybe desktop shell menus).

Calling set_resolution(1280., 720.0) causes wgpu to fail to create window textures (and the app fails to render):

wgpu_core::device: Requested size 2560x1440 is outside of the supported range: Extent2D { width: 2560, height: 1387 }..=Extent2D { width: 2560, height: 1387 }

Changing set_resolution back to u32, removing the manual scaling "up" to a physical scale, then in the winit backend using LogicalScale instead of PhysicalScale when responding to SetResolution commands resolves the bug (and make the behavior consistent).

@blunted2night
Copy link
Contributor Author

After Looking into this a little bit, its a catch 22. We can't know what the quantization of the logical size will be until we know the scale factor, but we can't know the scale factor until we have created the window on a particular monitor. Specifying the size in logical values will always be a guess, and we will find out the truth once the system has created or resized the window.

This creates a some challenges. For one, prior to creation we currently have no way to store the guess for the size to use during the create. It also raises some questions about the behavior of the width and height properties with respect to their values once we learn the scale factor.

From where we are now, it seems to me that the solution is to store the "requested" logical size and an optional physical size that is updated when we learn the truth about it. The width and height functions would return the requested size always, and the logical_width and logical_height methods would come back to life to report the true value if we know them. This would require that logical_width, logical_height, physical_width, physical_height all return Optionals, since until the window is created we don't know what they will turn out to be.

In the scenario @cart highlights where the OS clamps one of the dimensions, the width or height could diverge from the truth significantly which seems counter-intuitive.

In my mind the difference between f32 and u32 is fairly minor (sans potential layout alignment issues). The real issue is that what is passed via the window descriptor or the set_resolution call is only a request, and what you actually get could be quite different dependent on you environment.

I have begun implementing what I described above, but I fear that this effort has opened a small can of worms that should have saved for later.

@cart
Copy link
Member

cart commented Dec 10, 2020

Maybe we can sort of "punt" this problem:

  1. WindowDescriptor doesn't need to care about physical size. Its just a request for a new window
  2. We can rename set_resolution to something like queue_resize(logical_width, logical_height). This wouldn't update the fields until the "real" values are known
  3. Users that care about reacting to the actual new size can listen for resized events.

This way, both apis can be consistent, and both apis can just be "requests".

also removes `logical_width` & `logical_height` as they are not longer different
* values in the `WindowDescriptor` and the `set_resolution` call are now "requested" sizes
* once the window is created, the actual width and height are learned along with the scale factor
* `width` and `height` return the actual size or the requested size if the window has not been created yet
* add some documentation for the window size APIs
* update a handfull of examples to change the window size of f32
@blunted2night
Copy link
Contributor Author

I have update this PR based on comments from @cart. It seems like a little much for something so minor, but I think something like this is necessary to cover to properly handle high DPI.

While playing with this I started to notice an issue with the UI layout. In the squash crate, all coordinates are rounded to the nearest whole number, and we pass logical coordinates. On a high DPI monitor this prevents that final placements from aligning to the physical pixel grid which produces artifacts.

I have a commit based on this PR that converts everything to physical coordinates before passing to squash, then converts back to logical coordinates once squash produces a layout. This aligns everything to the pixel grid which cleans up the issues. It still needs a little work though before it is ready for review.

@blunted2night
Copy link
Contributor Author

My changes to flex are ready for review. I was going to wait until this PR is resolved before submitting them as a separate PR since the issue it addresses is related but independent of what this PR addresses. I could add it to this PR though, if that is preferred.

@cart
Copy link
Member

cart commented Dec 13, 2020

Awesome! Looking now. Lets do the stretch changes in a separate PR.

@cart
Copy link
Member

cart commented Dec 13, 2020

Turns out we can remove the "option" returns and simplify the code by making winit/bevy window creation "atomic". Theres no need to "wait".

Everything works as expected for me in all permutations of 1x + 2x scale factors / windowed / fullscreen

@blunted2night
Copy link
Contributor Author

I tested it on some non-integral scaling factors, and it looks good in my setup. The window creation change really did the trick!

@cart
Copy link
Member

cart commented Dec 13, 2020

Awesome. Thanks for adding the WASM fix :)

@cart cart merged commit d2e4327 into bevyengine:master Dec 13, 2020
@blunted2night blunted2night deleted the f32-window-size branch December 14, 2020 02:56
@fopsdev fopsdev mentioned this pull request Jan 24, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Windowing Platform-agnostic interface layer to run your app in C-Feature A new feature, making something new possible
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants