raylib
raylib copied to clipboard
[core] Multi-monitor fullscreen forces mode change
Presently, I'm just trying to make a HighDPI (per monitor) aware window; which can also toggle between fullscreen and windowed. My system setup is irregular in that there are two monitors with different DPI and different resolutions. All the example code works well on the primary display.
Windows 11 Pro 21H2, 22000.1098 (x64)
INFO: Initializing raylib 4.5-dev INFO: Supported raylib modules: INFO: > rcore:..... loaded (mandatory) INFO: > rlgl:...... loaded (mandatory) INFO: > rshapes:... loaded (optional) INFO: > rtextures:. loaded (optional) INFO: > rtext:..... loaded (optional) INFO: > rmodels:... loaded (optional) INFO: > raudio:.... loaded (optional) INFO: DISPLAY: Trying to enable VSYNC INFO: DISPLAY: Device initialized successfully INFO: > Display size: 2560 x 1440 INFO: > Screen size: 800 x 400 INFO: > Render size: 800 x 400 INFO: > Viewport offsets: 0, 0 INFO: GLAD: OpenGL extensions loaded successfully INFO: GL: Supported extensions count: 401 INFO: GL: OpenGL device information: INFO: > Vendor: NVIDIA Corporation INFO: > Renderer: NVIDIA GeForce 1080 Ti/PCIe/SSE2 INFO: > Version: 3.3.0 NVIDIA 522.25 INFO: > GLSL: 3.30 NVIDIA via Cg compiler
Following the examples, my test application also works well on the primary display. Initially, I was using SetWindowSize
prior to ToggleFullscreen
, to enter fullscreen mode - because it's desirable not to have a change in monitor mode. Whenever the window is located on the secondary display this is problematic in two ways:
- windows on the primary display are being redrawn and moved
- secondary display is changing modes still
I did find a way around disruptions in the primary display by using SetWindowMonitor
to directly move the window to the secondary monitor's mode size, but for some reason it's still changing the secondary monitor mode -- almost like it thinks the second monitor is the same resolution as the primary.
In a perfect world the window would maintain it's size across several varied displays and going fullscreen would not impact any other display. I'm open to the idea that this is beyond the scope of raylib, or that I've missed something in the documentation or code.
Tested against several builds of raylib: release, main repo (built with VS and built with clang).
Although the core_window_flags
example demonstrates the same problem, I'm going to attach my little example of the error so others can try it out. Mostly, because it has a work-around for the primary monitor interference present in core_window_flags
. Sorry, the source code is in x86 assembler, but the EXE does the thing.
Finally and most of all, thank you for your work on this - I've had a lot of fun playing with it.
@bitRAKE Thank you very much for the detailed report. I'm afraid there have already been some related issues but it's very difficult to find a proper solution for all them, here we have multiple variables involved: monitors, resolutions, dpi and OS.
Also all the desktop window/display mechanisms are controlled by underlying library GLFW. Taking into considderation all involved variables could require considering all the possible cases inside raylib and it could increase the code complexity considerable.
It would be great if it could be solved from user side. I'm pointing to @daipom because he has also been dealing with windowing/fullscreen issues recently, maybe he has some recommendation to approach your issue.
I recently had issues with fullscreen on Linux and multi-monitors. Changing to fullscreen needs several seconds, both monitors disconnect, then you wait, etc. My solution was to implement what SDL calls WINDOW_FULLSCREEN_DESKTOP
, i.e. you store the window position, then set FLAG_WINDOW_UNDECORATED|FLAG_WINDOW_TOPMOST
and then you set the window size to monitor size. When toggling clear the flags, change the size, and reposition where it was.
Perhaps raylib can support such a mode via some flag or option to make it easier for users. I saw a similar one in Ebitengine
, there it is the only option, you cannot change monitor resolution at all.
I am not familiar with the behavior of multiple monitors, but I have recently touched on the fullscreen specifications of GLFW and raylib.
I understand that it is raylib's specification to try to change the resolution while keeping the window size (framebuffer size) when going fullscreen. raylib intentionally tries to keep the current window size as much as possible at each point.
- https://github.com/raysan5/raylib/blob/d91f30958f5c9cb617c2738722c1966b8cd3ce67/src/rcore.c#L4064-L4076
- https://github.com/raysan5/raylib/blob/d91f30958f5c9cb617c2738722c1966b8cd3ce67/src/rcore.c#L1210-L1213
I understand that this is the preferred specification for creating video games.
On the other hand, the GLFW sample code generally makes it fullscreen without changing the resolution.
It is not difficult for the GLFW API to make it fullscreen without changing the resolution and delays or screen blinks that @gen2brain mentioned.
monitor = glfwGetPrimaryMonitor();
GLFWvidmode* mode = glfwGetVideoMode(monitor);
glfwCreateWindow(mode->width;, mode->height, "Sample fullscreen", monitor, NULL);
For toggling fullscreen without changing the resolution, this code is helpful:
- https://github.com/glfw/glfw/blob/dd8a678a66f1967372e5a5e3deac41ebf65ee127/tests/window.c#L126-L144
if (nk_button_label(nk, "Toggle Fullscreen"))
{
if (glfwGetWindowMonitor(window))
{
glfwSetWindowMonitor(window, NULL,
windowed_x, windowed_y,
windowed_width, windowed_height, 0);
}
else
{
GLFWmonitor* monitor = glfwGetPrimaryMonitor();
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
glfwGetWindowPos(window, &windowed_x, &windowed_y);
glfwGetWindowSize(window, &windowed_width, &windowed_height);
glfwSetWindowMonitor(window, monitor,
0, 0, mode->width, mode->height,
mode->refreshRate);
}
}
@bitRAKE @raysan5 I have confirmed that the fullscreen toggling without changing the resolution does not work as expected in a multiple-display environment. I'd be happy too if we could fix this! I'll try to find the cause!
Thats funny, i experienced the same as @bitRake because i also have such an irregular monitor setup. Primary display is 2560x1440 native resolution and secondary display is an older one with 1920x1080 native resolution.
I coded a little module to use virtual resolution with proper scaling based on the "raylib game template" and the example "core_window_letterbox.c". It should work in a window but also as borderless fullscreen (window).
Works like a charm at the primary display including virtual mouse coordinates, but moving the window to the secondary display and toggle to fullscreen, results in a fullscreen window with an internal resolution of the primary display.
Keeping the window dimension internally maybe fine and understandable regarding the rendering context, but in these cases the display resolution has clearly changed, which should be recognized.
The only way to get it right were to use CloseWindow() within my module and then call GetCurrentMonitor() followed by GetMonitorWidth()/GetMonitorHeight() to get the new resolution width/height and then calling InitWindow again... works but is absolute ugly, cause it destroys the window and rendering context just to get a new window and rendering context.
Sorry for the delay.
I am checking this problem, and I do not see any problem with the implementation of raylib.
GetCurrentMonitor
can select the correct monitor, and glfwSetWindowMonitor
is called with the expected parameters.
I am checking the implementation of GLFW now.
I'm unsure of the root cause, but I found out why this problem is occurring. The monitor logic of GLFW is not working as expected.
- https://github.com/glfw/glfw/blob/dd8a678a66f1967372e5a5e3deac41ebf65ee127/src/win32_monitor.c#L257-L260
If the window size is matched to the screen size, it is expected that best
and current
video mode matches here.
However, when the monitor is the second display, they do not match, so ChangeDisplaySettingsExW
is called.
This causes screen blinks and delays similar to changing resolution.
I found that their sizes match, but their refreshRate
do not match.
In my environment, best->refreshRate
becomes 60
, but current.refreshRate
becomes 59
only when using a second display.
I don't understand why refreshRate
is 1 smaller...
Here are my conclusions.
- This seems to be a platform-specific issue. (Win32)
- This seems to be a issue to be handled in GLFW.
- If we can find out why the refresh rate of the sub-display is 1 smaller, we may be able to find a workaround.
Oh, I can set the refresh rate of the display in the detailed setting of the display.
My second display's refresh rate was 59.7.
When I set it 60
, this problem no longer occurs.
@bitRAKE @Shalindor Could you try this?
Unfortunally there is nothing for me to set, cause my secondary display is already set to 60 Hz refresh rate in the display settings. I had no delays or blinks when switching the fullscreen window between the monitors.
The problem occurs when switching a fullscreen window from primary display (2560x1440) to secondary display (1920x1080) . It does not change the size/resolution according to the videomode of the new monitor the window is now located. Meaning there is now a 2560x1440 fullscreen window clipped at the right and bottom area on the secondary display. Switching back to primary everything is fine as the resolution matches. Now, even better creating a fullscreen window at the secondary display in 1920x1080 and then switching that fullscreen window to the primary display shows a borderless (undecorated) window with exactly 1920x1080 on the primary display and at the right and bottom edges you see whatever is beneath the window at the primary display. The different sizes/resolutions of the displays does not match after the switch but the fullscreen window itself is not resized.
I think that is a problem in glfw and not in raylib. I have submitted 2 pull requests to the current glfw master, but there were no response from the maintainers as of today.
https://github.com/glfw/glfw/pull/2220 and https://github.com/glfw/glfw/pull/2221
The second pull request addresses exactly the problem with not matching resolutions when switching fullscreen windows.
@Shalindor So your problem is a little different from @bitRAKE?
Switching a fullscreen window from primary display (2560x1440) to secondary display (1920x1080)
How do you do this? I thought that to do this, I would have to change the fullscreen back to a window and then move the window to another display and go fullscreen again.
こんばんは @daipom
Doing this depends on a function to detect at which monitor the window is currently located as in the pull request https://github.com/glfw/glfw/pull/2220 and that means not just checking the monitor value of the GLFWwindow (or whatever you get back from glfwGetWindowMonitor) because that value is only the monitor the window were created with or the last monitor set by a previous call to glfwSetWindowMonitor. The monitor must be detected at runtime.
Then, in case of glfw, i modified the internal _glfwInputWindowPos in window.c, which is called internally from glfw to call possible window position callbacks, that is in https://github.com/glfw/glfw/pull/2221
But what i do in the internal _glfwInputWindowPos, could also be done in your own window_pos_callback.
- Get the window monitor
- If window monitor == NULL, not a fullscreen window, skip to 6
- If window monitor != NULL, detect the monitor the window is currently located (runtime)
- If window monitor == detected monitor, we are already at that monitor, skip to 6
- If window monitor != detected monitor, window has switched to another monitor, so get the video mode for the detected monitor and call glfwSetWindowMonitor with the new monitor, videomode width, videomode height and videomode refreshrate.
- Done No extra switching back to window mode and all of this uses the platform indepent function calls of glfw
How do you do this? I thought that to do this, I would have to change the fullscreen back to a window and then move the window to another display and go fullscreen again.
The window has already been moved at this point
@Shalindor So your problem is a little different from @bitRAKE?
Not really, because the root cause is the same.
@bitRAKE wrote in the OP I did find a way around disruptions in the primary display by using SetWindowMonitor to directly move the window to the secondary monitor's mode size, but for some reason it's still changing the secondary monitor mode -- almost like it thinks the second monitor is the same resolution as the primary.
And that is exactly what happened, because everything is based on the monitor value of the GLFWwindow (or monitor value from glfwGetWindowMonitor), which were in his case the primary monitor. ToggleFullscreen used the videomode from the primary monitor, because it depends on GetCurrentMonitor, which by itself depends on glfwGetWindowMonitor, which returns the monitor value from the GLFWwindow and not the monitor the window is currently located.
TLDR The monitor value in a GLFWwindow is NOT updated automatically by glfw
こんばんは! @Shalindor
Thanks for the explanation!
I understand glfwGetWindowMonitor
returns not the monitor of the current display but the monitor of the initial display.
However, the raylib function GetCurrentMonitor
has a logic to search the monitor of the current display.
- https://github.com/raysan5/raylib/blob/2fd6d7e8c09ac9a13d1e8a9d36741934cb6a1f09/src/rcore.c#L1723-L1745
When I try to reproduce this issue on Windows10, raylib can select the second display in ToggleScreen
.
- Create the window in the main display.
- Move the window to the second display.
- Toggle to the fullscreened state without changing the resolution as follows.
const int monitorIndex = GetCurrentMonitor();
SetWindowSize(GetMonitorWidth(monitorIndex), GetMonitorHeight(monitorIndex));
ToggleFullscreen();
Whether this updates the video mode or not depends on the refresh rate on my environment.
こんばんは @daipom
Your advice is appreciated, but have you tested the following scenario:
- Create a fullscreen window at any display (raylib uses the primary display by default)
- Switch that fullscreen window to another display (WIN + Shift + Arrow Left or Right)
- Important to understand the displays must have different resolutions for the problem to occur, if all involved displays have the same resolution there is no problem in the first place.
Your advice works only for "windowed" windows. After doing what you had adviced, we now have a fullscreen window at another display. What do we do to switch that fullscreen window back to the primary or to another display? The monitor detection in rcore.c GetCurrentMonitor
- https://github.com/raysan5/raylib/blob/2fd6d7e8c09ac9a13d1e8a9d36741934cb6a1f09/src/rcore.c#L1707-L1720
fails for fullscreen windows because it relies on glfwGetWindowMonitor, which just returns the initial display.
And you should recognize we do not have the control of the window movements as that depends on the user. So we must use a window pos callback to get informed. But what should we do now with the fullscreen window?
All that depends on glfwGetWindowMonitor to return not the initial display but instead returning the current display and that is why i suggested the two pull requests glfw#2220 and glfw#2221
For tests i have forked raylib and modified the bundled glfw according to these two pull requests. https://github.com/Shalindor/raylib-latest/tree/raylib-latest-with-modified-glfw Link with a raylib compiled from that fork, the fullscreen window switches are working without us to do the mambo jumbo magic.
But raylib uses his own variables for screen width/height, display width/height (and framebuffer info) in CORE which are not updated when switching between monitors with different resolutions. That was the point i gave up, updating redundant variables with values which are retrievable directly from the underlying GLFWwindow is just a chore.
@daipom Fun fact, right now i recognized even the "king of the hill" engine Unreal has that problem with irregular monitor setups and fullscreen windows. For example, in Fortnite moving the fullscreen game window to my secondary monitor with lower resolution all is fine, but then moving back to the primary display with higher resolution, the so called fullscreen game window is suddenly shown with the lower resolution from the secondary monitor at the primary monitor.
Some games hide the problem by making other displays black (No Man Sky, for example) - it is indeed an industry wide problem to universally support varied displays in an elegant way. It seems to need a coordination of hardware, OS, and software layers.
@Shalindor @bitRAKE Thanks for the explanation! It seems I misunderstood how to reproduce this issue.
we now have a fullscreen window at another display. What do we do to switch that fullscreen window back to the primary or to another display?
The application-side toggle logic I had assumed is as follows. I had assumed that we use this kind of logic to toggle the fullscreen to the windowed state, and then move the window to another display.
if (IsWindowFullscreen())
{
ToggleFullscreen();
SetWindowSize(s_WindowedWidth, s_WindowedHeight); // Keep the window size on the application side.
}
else
{
const int monitorIndex = GetCurrentMonitor();
SetWindowSize(GetMonitorWidth(monitorIndex), GetMonitorHeight(monitorIndex));
ToggleFullscreen();
}
Switch that fullscreen window to another display (WIN + Shift + Arrow Left or Right)
So we need to move the display without returning the fullscreen to the windowed state?
I could certainly confirm that some behaviors go wrong in the following steps. I will do some more research on this one.
1. Create a fullscreen window at any display (raylib uses the primary display by default) 2. Switch that fullscreen window to another display (WIN + Shift + Arrow Left or Right) 3. Important to understand the displays must have different resolutions for the problem to occur, if all involved displays have the same resolution there is no problem in the first place.
If you don't need to move the display while keeping the fullscreened state, you may work around this problem by toggling it to the windowed state once and then moving it.
Even separate from the more complex multimon with multiple dpi and multiple resolutions situation, I just wanted to add that I would also really appreciate a "WINDOW_FULLSCREEN_DESKTOP"-style fullscreen.
I tried something like @gen2brain suggested, but I couldn't get it to work properly on Windows without bypassing raylib. (e.g. I don't think window decorations are properly restored when calling SetWindowState(0)
?). And then the render scale is not compensated for because the window has actually gotten larger, which is also more fiddling.
(I assume the benefit of real fullscreen is that it should improve fillrate? Personally, I'd go almost as far as saying FULLSCREEN_DESKTOP should probably be the default style of fullscreen for raylib, as the hassle of real fullscreen with a mode change, screwing up every other application's window locations when the desktop changes size, etc. isn't really worth it for small games/apps.)
@sgraham Excuse the late response and thanks for the feedback!
I agree that a WINDOW_FULLSCREEN_DESKTOP
mode must be definitely added.
SetWindowState(0)
actually clear ALL window flags, probably the function you need is (ClearWindowState(FLAG_WINDOW_UNDECORATED)
). Flags usage example.
Personally, I'd go almost as far as saying FULLSCREEN_DESKTOP should probably be the default style of fullscreen for raylib, as the hassle of real fullscreen with a mode change, screwing up every other application's window locations when the desktop changes size, etc. isn't really worth it for small games/apps.
Probably. Exclusive fullscreen is too platform-dependant and generates many issues, I don't know if many users actually use it.
Thank you! Yes, I realized I was using the Set/Clear incorrectly later, but forgot to update here.
I feel it would still be nice to have an alternate fullscreen mode to handle scale/zoom when the window size is changed for fake fullscreen.
How would you go about entering a windowed/non exclusive fullscreen mode?
By doing what was said here, you can already get it to work on Windows if you make the window 1 pixel smaller on either side: https://github.com/raysan5/raylib/issues/2764#issuecomment-1293482800
My solution was to implement what SDL calls
WINDOW_FULLSCREEN_DESKTOP
, i.e. you store the window position, then setFLAG_WINDOW_UNDECORATED|FLAG_WINDOW_TOPMOST
and then you set the window size to monitor size. When toggling clear the flags, change the size, and reposition where it was.
But I'm not sure how to avoid it turning into exclusive fullscreen if you make the window match monitor size, this isn't even about multiple monitors btw. I'd just like to be able to avoid flashing when alt-tabbing.
~Never mind, it works if you set it to fullscreen after window creation!~
False alarm, it only worked because it was offset by 1 pixel even though it was 1920x1080... :(
This issue should have been addressed by now.