godot: Enabling fullscreen via code shows window border at screen edges (DisplayServer regression)
Godot version
4.0.dev (835da447)
System information
Windows 10, Vulkan Clustered, 3 monitor setup
Issue description
When changing fullscreen mode via code, the window’s border is visible at the edges of the screen. This does not happen when enabling the fullscreen project setting instead.
Launching the MRP with a high contrast theme (where window borders are white) with x10 zoom on the top left corner of my screen:
When using the default Windows 10 theme:
This confirms that you see the window’s borders.
Steps to reproduce
Create a fresh project. Then either in _ready
, or when pressing a key, run:
get_tree().root.mode = Window.MODE_FULLSCREEN
# This does not help:
#get_tree().root.borderless = true
Minimal reproduction project
About this issue
- Original URL
- State: open
- Created 2 years ago
- Reactions: 28
- Comments: 41 (18 by maintainers)
Obviously releasing a game with a white border is not an option. But neither is releasing a game with input lag.
This white border appears no matter if you set “fullscreen” from the project settings or in code. If you enable the so called “borderless” setting and the white border disappears, you are actually just silently engaging fullscreen exclusive which gives you input lag. You can verify this by using PresentMon.
Please take a look at this page: https://learn.microsoft.com/en-us/windows/win32/direct3ddxgi/for-best-performance--use-dxgi-flip-model
Exclusive fullscreen on Windows should not even be an option as it is a deprecated function that doesn’t have any of advancements of borderless fullscreen. By using PresentMon you can see that using Godot with exclusive fullscreen adds over 30 ms of presentation delay (at 120 fps) which gives an awful feel for all kinds of games, especially first person games. By using what Godot calls “fullscreen” with the white border, it uses GDI copy presentation which is not optimal, and an ancient feature, but it slightly reduces the latency [1]. This white border is obviously by all intentions not something that any user or developers wants. A user doesn’t care about some technical detail why it is visible, and will instead blame the game developer about something they cannot control.
Exclusive fullscreen is also the culprit that may lock your computer since no window is able to get shown in front of it in case of a crash or hitting a breakpoint when debugging. It also adds flickering when the window becomes focused or unfocused, not to mention alt tabbing feels terrible.
Also sorry but trying to shape the Linux way of doing things on to Windows is the wrong approach. These are separate operating systems and the DirectX team at Microsoft has gone to great lengths to make these optimizations on Windows 10 and later, and Godot is not using any of them. The state of the Windows “display server” in Godot 4 is not in a production state for game developers as it is now: you either have a white border or you have severe input lag. No serious game studio would ever release a product with either of those issues present.
For DirectX applications the only thing you have to do to engage DirectFlip (which is a low latency zero copy direct-to-dwm presentation) is to create any window that matches the monitor size and with the
WS_POPUP
window style, and using either flip swap effects. It appears that Godot 4 is using Vulkan on WIndows now so is that what is holding all Windows developers back? Will https://github.com/godotengine/godot/pull/70315 resolve this by actually making use of the features that Microsoft give us? There is no secret to this or driver specific (unless you use MPO but you don’t need to) since presentation to DWM is managed by Windows.[1] I should point out that this white border alone doesn’t make the latency much better, you have to really fiddle with the VSync values and engine variables to reduce it to something not-super-bad. But that is a large other topic not related to this issue.
I created a new MRP to track down the best way to get around this and what I discovered is that borderless windows might be more busted than I thought. I got WEIRD behavior but if you’re here for the solution the TL;DR is that this works (yes, you need to set the window mode TWICE):
Okay, so here’s my findings… I made a MRP where you can change the window mode with hotkeys. Windowed and exclusive windowed work as expected. Exclusive fullscreen has a size of (1920, 1080). Borderless fullscreen has a size of (1918, 1078) due to the 1px border around it.
Maximizing the window without a border makes NO sense however and it might be where everything breaks. My first thought was to do this:
What this code actually does is just turn on borderless fullscreen mode. As in,
get_window().mode
is literallyWindow.MODE_FULLSCREEN
and not maximized, so this might be what borderless fullscreen literally does under the hood. The window size is (1918, 1078) again. What about maximizing first, then setting borderless?The first time you run this it works as expected. It maximizes then turns borderless, not going over the taskbar. HOWEVER if you run this code again (ie
maximize->borderless->maximize->borderless
), the window size becomes (1922, 1082). There is no visible border but it might be getting rendered around the window, hence the extra two pixels on each axis.Running that code a third time just turns it back into borderless fullscreen.
What’s interesting is that (
maximize->borderless->maximize
) gives us a window size of (1920, 1080) but actually turns on exclusive fullscreen despite saying it’s windowed. If you add anotherget_window().borderless = true
after that does it add 2px to each axis, giving us a borderless windowed mode that has borders on the outside rather than inside which is probably as good as we’re going to get. I have no idea why that is and maybe someone familiar with Godot source code can pick this up from here and figure out a solution.Tested with Godot 4.2.1, Windows 11 23H2. I went insane writing this. Make it make sense.
borderless_test.zip
Edit 1: Actually, as pointed out by ManpreetSingh2004 in the other thread,
maximize->borderless->maximize
turns on exclusive fullscreen but reports it as windowed. Another bug.max->bord->max->bord
does work but might get changed if that PR goes through, check out this comment for another workaroundI guess we can add a third mode instead (
WS_OVERLAPPED
+ screen size), which will work as “borderless full-screen” on NVIDIA and Intel GPUs, and not guarantee multi-window support, like this:MODE_MULTIWINDOW_FULLSCREEN
- (WS_OVERLAPPED | WS_BORDER
+ screen size), with border, multi-window, used by editor.MODE_FULLSCREEN
- (WS_OVERLAPPED
+ screen size), w/o border, might be “borderless” or “exclusive” depending on GPU.WINDOW_MODE_EXCLUSIVE_FULLSCREEN
- (WS_POPUP
+ screen size), always “exclusive”.It is not fixed in 4.2 and is still an issue. The border being present also breaks the canvas items scale mode, since the border reduces the size of the window and the UI will become a blurry mess.
All that was changed in 4.2 was that the color of the border changes according to the clear color. Sorry, but seriously? Again, this is entirely a flaw in the Godot code, and yet again not any driver specific thing. The way to present graphics on Windows is to use DXGI. Stating that users can override the presentation behavior in the Nvidia control panel is not useful. Are you going to prompt in your game to tell all your users to go dig in the control panel to change a specific setting to play the game?
For what it’s worth, you can actually get around this and achieve “true” borderless fullscreen by setting the window mode to a standard window (i.e. not maximized or minimized, not borderless, not unresizable), changing its size to your screen resolution, and setting its position to the monitor position (which is going to be 0,0 if you have only one monitor). It’ll take up the full screen without triggering exclusive mode. Sometimes it leaves behind the taskbar and you have to alt tab a couple of times, but I find that’s an issue with windows on multiple games anyway.
I have a hunch that’s just how windows handles borderless fullscreen - if a window has a position and size equal to a monitor, it’ll just fullscreen on that monitor. Enabling settings such as windowless or non-resizable actually stops this fix from working, though I don’t know how much of that is windows getting confused and how much of it is Godot trying to be clever with your settings and doing the wrong thing by mistake.
Incidentally, exclusive fullscreen introduces a delay on mouse movements, which is quite frustrating if you’ve got an object tracking the mouse.
The border is added deliberately, since it’s the only reliable way to ensure non-exclusive mode on all hardware, and Godot editor (or any other Godot app that is using multiple windows) require non-exclusive mode to work.
No it won’t, at least no on all GPUs (it is controlled by the driver), only the window client area size/position matter, so window with the border outside the screen will still trigger exclusive fullscreen.
There’s
MODE_FULLSCREEN_EXCLUSIVE
which is equivalent to 3.x fullscreen (or creating borderless window and change it to match screen size), which will not have a border (but won’t work with multiple windows).Some update:
This mode exists specifically for the Godot editor (and similar non-game applications), with the intention to ensure support for multiple windows in all cases. It’s not intended for games and should not be used in most cases.
Also note: “exclusive” mode is just a
WS_POPUP
window with the size of the screen, it’s not true exclusive with video mode change. How it’s actually handled is up to Vulkan driver implementation.I have analyzed borderless full-screen in some games, and might find a combination of window style flags that is working, but it needs testing on various GPUs to make sure it is https://github.com/godotengine/godot/pull/72787
The only possible fix is maximize_window + borderless creating a “fake fullscreen”. Works perfectly!
I’ve noticed this on my system w/ Windows 11, Vulkan, RTX3070 and GD4a14. Dual monitor set up (1920x1200, 1920x1080). It exists on all games, including the editor. It’s not just via code. It is the window mode set by DisplayServer.
The title of this ticket should be changed to something like
Border on Full Screen
.DisplayServer.WINDOW_MODE_FULLSCREEN
results in a 1px wide border in games and the editor when in full screen mode (Shift+11).DisplayServer.WINDOW_MODE_EXCLUSIVE_FULLSCREEN
removes the border.I can fix it for my game, but I don’t see an option to change the editor to exclusive mode. Its hard to see in a screenshot, so I added black borders around so you can see the white border.
After testing the project a-johnston sent in 4.2 (beta 1) I have some notes:
RenderingServer.set_default_clear_color(Color.PURPLE) DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)
As someone totally new to Windows’ window management, I wonder if other projects approaches could be of help?
An open source project that performs similar window management (minus the multi-window aspect, focusing on the gaming experience) that I use frequently on Windows is RetroArch. They allow windowed or exclusive fullscreen, and at least on my setup (Windows 11 + NVIDIA + Vulkan) the windowed variant doesn’t result in the anoying mode switching discussed earlier (OpenGL is a different story though). They seem to achieve both fullscreen modes with
WS_POPUP
, so maybe worth checking if they’re doing something that could be of use in this context?Docs are a bit out of sync (https://github.com/godotengine/godot/pull/82179), DisplayServer has more up-to-date description than Window.
I use DisplayServer.WINDOW_MODE_EXCLUSIVE_FULLSCREEN and have no border, nor delay on mouse input whether the cursor is visible or not.
I connect this to F11 to switch between windowed and full screen mode:
And my project settings:
And the border cannot trigger mouse click event