kettle11 / kapp

A pure Rust window and input library for Windows, Mac, and Web. (Work in progress)
Apache License 2.0
56 stars 4 forks source link

Investigate borderless fullscreen for MacOS / Windows #7

Open kettle11 opened 4 years ago

kettle11 commented 4 years ago

Borderless fullscreen is a useful and common feature for games.

Investigation is needed to find common implementations for Windows and MacOS.

lunabunn commented 3 years ago

Unless I'm mistaken, "borderless fullscreen" involves making the window borderless and then scaling the window to fill up the whole screen? In this case, do we want to store the window position and size when borderless fullscreen is activated so that we can actually restore the window? or should we just let the user handle it? If we were going to let the user handle it anyway, can't we just implement borderless and call it a day?

AFAICT in Windows, borderless is achieved by setting the window style to WS_POPUP | WS_VISIBLE.

kettle11 commented 3 years ago

I think the reason I hesitated on this is that MacOS is different than Windows and I wasn't sure what to call things and how to reconcile options.

After some investigation here's my thoughts:

Borderless-fullscreen is mostly a useful feature on Windows because with true-fullscreen it can be hard to alt-tab or swap to another program quickly. I think this is in part because Windows actually does special things to give true full-screen app more direct rendering access.

On Mac true-fullscreen doesn't do anything special (as far as I know) so it still behaves nicely and allows alt-tabs and all that. The only annoyance (for games) is that the dock and menubar can show up when the mouse moves near the top or bottom of the screen. (This API needs to be used somewhere in kapp to prevent that: : https://developer.apple.com/documentation/appkit/nswindowdelegate/1419144-window?language=objc)

So a "true" borderless full-screen implementation on Mac would feel awkward and non-native and not really solve any goal.

Quickly launching a Unity game on Mac shows that they treat "borderless fullscreen" and "fullscreen" the same as far as I can tell.

There's also some winit discussion about the issue here: https://github.com/rust-windowing/winit/issues/1195

As for what to do I'm going to think about it. Do you have thoughts?

lunabunn commented 3 years ago

That makes sense. So then, how about something like this? a) Have a fullscreen_borderless function that just calls fullscreen() on Mac b) In the Windows implementation of fullscreen_borderless, store the window position and size, set some flag to mark the window as "borderless fullscreen" c) In the Windows implementation of restore_window, go back to the window position and size stored in step b if the flag was set

This would introduce even more "overhead" (really, I'm sure the runtime overhead of setting some numbers is minimal, I guess it's more about the ugly static mut atomics lying around everywhere) for the Windows implementation, but I can't think of any other way to satisfy the fullscreen needs on Windows.

kettle11 commented 3 years ago

That sounds great!

And yeah the overhead of storing some numbers is trivial.

One thing is I have been trying to keep multiple windows working even if it's not an explicit promise of kapp. Adding static muts that are specific to one window would break that. Probably each window should store its own extra data specific to kapp.

Various techniques for that are discussed here: https://stackoverflow.com/questions/117792/best-method-for-storing-this-pointer-for-use-in-wndproc

lunabunn commented 3 years ago

Ah, right, multiple windows. Sorry, that completely escaped my mind. That link seems to suggest the only "correct" way to store window-specific data here is to have a HashMap<HWND, Option<WindowState>> where struct WindowState { x: u32, y: u32, width: u32, height: u32 } or something (arbitrary nomenclature for the struct). Does that sound OK?

One issue with this approach: assuming we expose an API for setting borderless, the user could manually "restore" the window. Then, if they, say, exclusive fullscreen the window and then restore it, the window will assume its position/size from before the initial fullscreen_borderless call. We need to set the Option to None when borderless is set to false or when the window size/position is modified. Somewhat messy :(

kettle11 commented 3 years ago

A HashMap with HWND as the key sounds good to me.

One issue with this approach: assuming we expose an API for setting borderless, the user could manually "restore" the window.

If I'm following correctly then I don't think we should worry about the user doing weird things like this. If it simplifies kapp code to ignore clearly strange usage of the API then we should do that. If people run into issues then they can report an issue.

We also could choose to ignore calls to set_window_size when fullscreen because resizing a window when fullscreen doesn't make sense anyways.

kettle11 commented 3 years ago

@MasonRemaley wrote-up a really good investigation of full-screen support on Windows: https://www.anthropicstudios.com/2021/02/20/fullscreen-exclusive-is-a-lie/

Their conclusion is that exclusive full screen support is spotty across games. This is leading me to think that kapp shouldn't try to support exclusive fullscreen (at least initially). It's a "nice to have" but not "need to have" feature for most applications.

lunabunn commented 3 years ago

If that's the case, could we just have one fullscreen() function (at least initially) that does borderless fullscreen on Windows and normal fullscreen on other platforms? We could simply ignore any calls to set_borderless or set_window_size while fullscreen.

kettle11 commented 3 years ago

Yup, that seems like the best call. Easy to get right without hacks and covers most use cases.