Open eddebaby opened 10 months ago
What platform is this happening on? Windows? X11? Mac?
Windows 10.
Note: The code above is dummy code, let me know if you need a built/buildable example and I'll work on one.
If it will suffice, you can see the above snippet in practice here: https://github.com/dkfans/keeperfx/blob/2a1f91b7331d53e3a82df207f0583eb2091c89f8/src/bflib_video.c#L549
I have a similar problem with this feature. Very often, the resolution is not applied correctly, and this function never correctly sets the desired mode on a display other than the primary one. The case concerns SDL 2.28.5, on updated Windows 10.
The test program looks like below and has the minimum code needed to demonstrate the problem. If any function returns an error code, its content will be displayed in the console (along with the code line number). I don't have any error, so only logs appear in the console.
Changing display:
F1
— show window on the 1st display,F2
— show window on the 2nd display,F12
key.Changing window mode:
1
— change window mode to the 1st (index 0
, so most probably with native resolution),2
— change window mode to the 2nd,9
key.Press Esc
to quit.
If the display or video mode with the requested index does not exist, nothing is changed. If a window was requested to be moved to a display other than the current one, the video mode is reset to index 0
, i.e. the mode with the highest available resolution is used. After starting the program, you have 3 seconds to move the console window — in case you want to have it on a different screen and see the logs all the time.
Release (Win x64) — Exclusive.zip Source code:
{$mode objfpc}{$H+}
uses
SDL2;
label
Cleanup;
var
Window: PSDL_Window;
WindowRect: TSDL_Rect;
Renderer: PSDL_Renderer;
Event: TSDL_Event;
DisplayMode: TSDL_DisplayMode;
DisplayIndex: Int32 = 0;
ModeIndex: Int32 = 0;
InputIndex: Int32;
begin
SDL_Init (SDL_INIT_VIDEO);
SDL_Delay (3000);
Window := SDL_CreateWindow ('Demo', SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 600, 450, 0);
Renderer := SDL_CreateRenderer (Window, -1, SDL_RENDERER_ACCELERATED or SDL_RENDERER_PRESENTVSYNC);
if SDL_GetDisplayMode (DisplayIndex, ModeIndex, @DisplayMode) < 0 then WriteLn({$I %LINE%}, ' - ', SDL_GetError());
if SDL_SetWindowDisplayMode (Window, @DisplayMode) < 0 then WriteLn({$I %LINE%}, ' - ', SDL_GetError());
if SDL_SetWindowFullscreen (Window, SDL_WINDOW_FULLSCREEN) < 0 then WriteLn({$I %LINE%}, ' - ', SDL_GetError());
SDL_GetWindowPosition (Window, @WindowRect.X, @WindowRect.Y);
SDL_GetWindowSize (Window, @WindowRect.W, @WindowRect.H);
WriteLn('Initial mode: ', DisplayMode.W, 'x', DisplayMode.H, ', ', DisplayMode.Refresh_Rate, 'Hz');
WriteLn('Actual: ', WindowRect.W, 'x', WindowRect.H, ' at ', WindowRect.X, ',', WindowRect.Y, ' (display: ', SDL_GetWindowDisplayIndex(Window), ')'#13#10);
while True do
begin
while SDL_PollEvent(@Event) = 1 do
case Event.Type_ of
SDL_KEYDOWN:
if Event.Key.Repeat_ = 0 then
case Event.Key.KeySym.Scancode of
SDL_SCANCODE_F1 .. SDL_SCANCODE_F12:
begin
InputIndex := Event.Key.KeySym.Scancode - SDL_SCANCODE_F1;
if (InputIndex <> DisplayIndex) and (InputIndex < SDL_GetNumVideoDisplays()) then
begin
DisplayIndex := InputIndex;
ModeIndex := 0;
if SDL_GetDisplayMode (DisplayIndex, ModeIndex, @DisplayMode) < 0 then WriteLn({$I %LINE%}, ' - ', SDL_GetError());
if SDL_SetWindowDisplayMode (Window, @DisplayMode) < 0 then WriteLn({$I %LINE%}, ' - ', SDL_GetError());
SDL_GetWindowPosition (Window, @WindowRect.X, @WindowRect.Y);
SDL_GetWindowSize (Window, @WindowRect.W, @WindowRect.H);
WriteLn('New mode: ', DisplayMode.W, 'x', DisplayMode.H, ', ', DisplayMode.Refresh_Rate, 'Hz (display: ', DisplayIndex, ')');
WriteLn('Actual: ', WindowRect.W, 'x', WindowRect.H, ' at ', WindowRect.X, ',', WindowRect.Y, ' (display: ', SDL_GetWindowDisplayIndex(Window), ')'#13#10);
end;
end;
SDL_SCANCODE_1 .. SDL_SCANCODE_9:
begin
InputIndex := Event.Key.KeySym.Scancode - SDL_SCANCODE_1;
if (InputIndex <> ModeIndex) and (ModeIndex < SDL_GetNumDisplayModes(DisplayIndex)) then
begin
ModeIndex := InputIndex;
if SDL_GetDisplayMode (DisplayIndex, ModeIndex, @DisplayMode) < 0 then WriteLn({$I %LINE%}, ' - ', SDL_GetError());
if SDL_SetWindowDisplayMode (Window, @DisplayMode) < 0 then WriteLn({$I %LINE%}, ' - ', SDL_GetError());
SDL_GetWindowPosition (Window, @WindowRect.X, @WindowRect.Y);
SDL_GetWindowSize (Window, @WindowRect.W, @WindowRect.H);
WriteLn('New mode: ', DisplayMode.W, 'x', DisplayMode.H, ', ', DisplayMode.Refresh_Rate, 'Hz (display: ', DisplayIndex, ')');
WriteLn('Actual: ', WindowRect.W, 'x', WindowRect.H, ' at ', WindowRect.X, ',', WindowRect.Y, ' (display: ', SDL_GetWindowDisplayIndex(Window), ')'#13#10);
end;
end;
SDL_SCANCODE_ESCAPE: goto Cleanup;
end;
SDL_QUITEV: goto Cleanup;
end;
WindowRect.X := 0;
WindowRect.Y := 0;
SDL_GetWindowSize(Window, @WindowRect.W, @WindowRect.H);
SDL_SetRenderDrawColor (Renderer, 0, 0, 0, 255);
SDL_RenderClear (Renderer);
SDL_SetRenderDrawColor (Renderer, 100, 100, 100, 255);
SDL_RenderFillRect (Renderer, @WindowRect);
SDL_RenderPresent (Renderer);
end;
Cleanup:
SDL_DestroyRenderer (Renderer);
SDL_DestroyWindow (Window);
SDL_Quit ();
end.
When you run the program, it retrieves information about the highest resolution mode of the primary display, then turns on the exclusive video mode and sets the window to this mode. Everything works fine here and the result is as follows:
The log appears in the console:
Initial mode: 1280x800, 60Hz
Actual: 1280x800 at 0,0 (display: 0)
Now I press the 9
key to change the resolution to a smaller one (still to primary display). On my laptop, this mode has a resolution of 640x400
. Unfortunately, it doesn't work properly, the result is as follows:
In the console, I see:
New mode: 640x400, 60Hz (display: 0)
Actual: 640x400 at 0,0 (display: 0)
The window size is changed to 640x400
, but the screen resolution is not adjusted to the window size and is still the same as before, as seen by the mouse pointer size (i.e. 1280x800
). Interestingly, SDL changes the window size and screen resolution, but for some reason it immediately changes the resolution to the previous one, leaving the target window size. During the change, you can see that the resolution changes (the mouse cursor becomes large for a moment), see the video:
https://github.com/libsdl-org/SDL/assets/8170730/da32824c-2e43-41cf-849a-b8f20988257c
If I now press the 1
key, the resolution will be correctly set to native. So setting a lower resolution doesn't work properly, because SDL apparently changes the window size but can't set the appropriate resolution (or accidentally reverts it back to the previous one).
The window has a native size of 1280x800
and is displayed on the primary screen. I press the F2
key and the program is supposed to get information about the first available video mode of the second display (with the highest resolution and refresh rate) and show the window on the second display using this mode. The highest resolution mode on my monitor is 1280x1024 (75Hz)
.
This is where the problems arise. SDL does not move the window and does not change its resolution at all. Instead, it tries to set this mode on the primary display, but since it does not support 1280x1024
resolution (and 75Hz
), nothing happens. The following log appears in the console:
New mode: 1280x1024, 75Hz (display: 1)
Actual: 1280x800 at 0,0 (display: 0)
As you can see in the log, the window stays in place. For anything to change, I must select a monitor mode that has a resolution no greater than the resolution of the laptop screen. So I press the 9
key, the monitor mode with parameters 1024x768 75Hz
is selected and this mode is activated not on the monitor, but on the laptop:
The log appears in the console:
New mode: 1024x768, 75Hz (display: 1)
Actual: 1024x768 at 0,0 (display: 0)
The program knows that the window is on the monitor, but it is displayed on the laptop screen. The line with the Actual
log gives the position and size read by the SDL functions on the fly, so SDL sees that the window is on the laptop screen, inconsistent with the set mode.
Now if I press the F1
key, the program will read the first available display mode of the laptop, which is 1280x800
, and set it for the window. And while the window size is set correctly (as seen by the program and SDL), the laptop screen resolution still remains the same as before, i.e. 1024x768
. The situation only changes when I minimize the window, e.g. by clicking on something on the second screen, and then restore it from the taskbar. Only then will the screen resolution be set to the correct one.
Setting the window mode using SDL_SetWindowDisplayMode
almost never works properly. Changing the resolution of the window displayed on the primary display is done and the window changes size, but the screen resolution either remains unchanged (and the window is smaller than the screen) or is set to incorrect.
Trying to set a designated video mode on a secondary display never works properly — SDL always displays the window on the primary display anyway (or does not change anything if you choose a higher resolution than the highest resolution of the primary display). The position of the secondary display does not matter - by default I have the monitor on the left of the laptop (coordinates -1280,0
), moving the monitor to the right of the laptop (coordinates 1280,0
) does not change anything.
My display layout looks like this:
I'm just checking whether the SDL_SetWindowDisplayMode
function worked well on previous versions of SDL2. I have downloaded and tested literally every single release version published on GitHub (all that had .dlls compiled for Windows 64-bit).
In short, changing the window mode worked correctly in SDL 2.0.18, however, only if the window was displayed on the primary display. This stopped working properly in SDL 2.24.0 and remained so until the current version.
Displaying a window in an exclusive video mode on a non-primary screen worked just as badly as it does now (same symptoms). I haven't found any version of SDL2 where this works properly (at least on my laptop and setup).
I hope this information will help you find bugs and fix them.
Renderer := SDL_CreateRenderer (Window, -1, SDL_RENDERER_ACCELERATED or SDL_RENDERER_PRESENTVSYNC);
By default, SDL2 creates a direct3d 9 render backend, and d3d9, for some reason, is quite broken when switching to exclusive fullscreen modes on the non-primary display, moving fullscreen windows, etc… If you use create a renderer with a newer d3d version or opengl (try the envvar SDL_RENDER_DRIVER=direct3d12), does it behave as expected?
SDL3 moved the d3d9 renderer down in priority due to issues like this (and since XP is basically dead at this point).
If you use create a renderer with a newer d3d version or opengl (try the envvar SDL_RENDER_DRIVER=direct3d12), does it behave as expected?
Setting the hint SDL_HINT_RENDER_DRIVER
to direct3d
/direct3d12
does not change anything at all — changing the resolution still does not work correctly on either the primary or secondary display. However, setting this hint to opengl
allows to correctly change the resolution on the primary display, but there is still exactly the same problem with the secondary display.
So even if I use the OpenGL backend, secondary display support still doesn't work at all.
I found another problem with fullscreen mode. If I set the hint SDL_HINT_RENDER_DRIVER
to opengl
, it is impossible to set desktop fullscreen mode — SDL runs exclusive fullscreen anyway. On the other hand, if I do not set the opengl
driver, desktop fullscreen can be set, but then changing the window/display resolution on the main screen will no longer work (as I showed earlier).
Fullscreen support is seriously f*****d up. :(
Ok, I finally dealt with it and implemented exclusive fullscreen support in my engine, for any display and any display mode it supports. Unfortunately, I couldn't get the Direct3D backend to do this, but if I set OpenGL it works quite well. In case anyone is interested in this issue or if someone came here from a search engine, I wrote below how I did it.
The backend should be set to OpenGL by setting the SDL_RENDER_DRIVER
hint to opengl
.
Fullscreen dekstop can be set directly using the SDL_SetWindowFullscreen function and specifying the SDL_WINDOW_FULLSCREEN_DESKTOP
flag. You don't need to do anything extra and it should work on any display.
Fullscreen exclusive, regardless of which display it is to be launched on and with what display mode, should be launched in as follows:
SDL_WINDOW_FULLSCREEN
flag is in the flags set, disable fullscreen using the SDL_SetWindowFullscreen function,SDL_WINDOW_FULLSCREEN_DESKTOP
flag is in the flags set, disable fullscreen using the SDL_SetWindowFullscreen function,SDL_WINDOW_FULLSCREEN
flag in the parameter.The order in which the above functions are called is crucial. While exclusive fullscreen should work properly on any display and in any supported display mode, each time you change the display mode, it will redundantly disable fullscreen and restore it at the end, which causes additional screen flashing when changing modes (it takes two or three seconds at most).
OpenGL set as the backend does not support desktop fullscreen in the normal way. On the main screen, it runs a semi-exclusive fullscreen anyway, while on the secondary display this mode works as standard.
The second thing is that displaying the window in exclusive fullscreen on any screen and with a lower resolution than the native one causes a strange problem. When the mode setting is completed, simply move the mouse cursor to another screen (outside the window area), and when the cursor returns to the window area, SDL spontaneously sets the window size to native (and queues window resize events) and a large part of it does not fit in screen. This happens every time, for any display and any display mode. To avoid this, it is necessary to completely turn off fullscreen and turn it on again each time you change the display mode (I described it in the points above).
Thank you for the detailed information. I don't want to introduce any regressions, so I'm moving this out of the 2.30 milestone.
SDL 3.0 has completely revamped fullscreen support, are you seeing the same issues there?
I don't want to introduce any regressions, so I'm moving this out of the 2.30 milestone.
Ok, that's understandable. Overall, I'm happy with what I have now, because although there are some inconveniences (a little more screen flashing when changing display modes), ultimately the exclusive fullscreen works very well for the OpenGL backend.
SDL 3.0 has completely revamped fullscreen support, are you seeing the same issues there?
I haven't tested SDL 3.0 yet because I develop my engine in Free Pascal and I don't touch C at all. To be able to test anything, I need a compiled x64 .dll, and I also need to (re)write my headers for Free Pascal. I'm currently using headers that work well with SDL2 and I wouldn't want to mislead anyone in case my headers and not SDL are to blame.
I will switch to SDL3 only when its first stable version is published. I will then rewrite my headers, make sure they are correct, and start testing in conjunction with my engine. And I will definitely switch to SDL3 because of the new functionality that SDL2 does not have, so it's only a matter of time.
If you want to be sure that fullscreen support works properly on various displays, backends and platforms, I suggest to prepare a tester and simply check whether everything is fine. If necessary, I will be happy to carry out tests — just send me a compiled test program (e.g. similar to the one shown in this post). I am also on SDL's Discord, if anyone need to discuss and exchange files (testers, screenshots, recordings, etc.).
Great, thanks!
When switching from fullscreen to windowed mode in SDL2 on Windows 10 (150% display scaling), I noticed that occasionally the window size is not set to its pre-fullscreen size. So for example, if my window is 800x800, and I set it to fullscreen and then back to windowed mode, it would sometimes return to 812x834 with a black bar below the window border and to the right of the drawing area. Image to illustrate the effect:
Relevant code that led to this behavior:
const auto set_windowed = [](SDL_Window* window) {
int flags = SDL_GetWindowFlags(window);
if ((flags & SDL_WINDOW_FULLSCREEN) == 0) {
return;
}
SDL_SetWindowFullscreen(window, 0);
};
const auto set_fullscreen = [](SDL_Window* window) {
int flags = SDL_GetWindowFlags(window);
if ((flags & SDL_WINDOW_FULLSCREEN) == SDL_WINDOW_FULLSCREEN) {
return;
}
SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN);
};
if (/* key press check */) {
set_windowed(window);
}
if (/* key press check */) {
set_fullscreen(window);
}
For anyone arriving here due to searching for a fix for the above issue, I played around with @furious-programming's list of steps mentioned above and noticed that adding
SDL_DisplayMode mode;
SDL_GetCurrentDisplayMode(0, &mode);
SDL_SetWindowDisplayMode(window, &mode);
prior to setting the window to fullscreen fixes the black bar issue. The fixed code to switch between windowed mode and exclusive fullscreen is then:
const auto set_windowed = [](SDL_Window* window) {
int flags = SDL_GetWindowFlags(window);
if ((flags & SDL_WINDOW_FULLSCREEN) == 0) {
return;
}
SDL_SetWindowFullscreen(window, 0);
};
const auto set_fullscreen = [](SDL_Window* window) {
int flags = SDL_GetWindowFlags(window);
if ((flags & SDL_WINDOW_FULLSCREEN) == SDL_WINDOW_FULLSCREEN) {
return;
}
SDL_DisplayMode mode;
SDL_GetCurrentDisplayMode(0, &mode);
SDL_SetWindowDisplayMode(window, &mode);
SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN);
};
Usage as before:
if (/* key press check */) {
set_windowed(window);
}
if (/* key press check */) {
set_fullscreen(window);
}
I think I have found a bug in SDL2 2.28.5. I have done some research, but I didn't reach a conclusion yet.
The bugs seems very similar to #3869 which I though might have been fixed by #4392...
However, I only get the bug in a far more narrow circumstance than described in #3869.
I believe the circumstances required to see the bug are:
And only when changing to a real fullscreen mode that is a higher resolution than the previous fullscreen mode.
I dont't see the bug if I switch to a real fullscreen mode in any other circumstance.
This is what it looks like after I have switched to a 1080p mode from a 800x600 mode (with the above circumstances being true):
Leaving the window and then returning to the window fixes it.
The workaround provided in #3869 works for me, see code sample below.