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

[SDL3, X11] fullscreen transitions cause displays to behave as though disconnected; fullscreen flag not set correctly #9560

Closed
MDZhB opened this issue Apr 16, 2024 · 4 comments
Assignees

Comments

@MDZhB
Copy link

MDZhB commented Apr 16, 2024

Switching a window to exclusive fullscreen mode and back to windowed mode sometimes leads to various misbehaviour.

I am using Ubuntu 22.04.4 LTS, and SDL 3.1.1 with the X11 video driver. My setup is two displays in "join displays" mode. Display A is set to 1920 x 1080 @ 60.0 Hz; display B is set to 3840 x 2160 @ 60.0 Hz.

I expect that:

  1. When I switch a window to fullscreen mode on the primary display, the primary display will switch to the fullscreen display mode, and the window will take over the primary display;
  2. While fullscreen mode is active, the secondary display will remain active and its contents visible;
  3. After switching to fullscreen mode and calling SDL_SyncWindow(), SDL_GetWindowFlags() will report that the SDL_WINDOW_FULLSCREEN bit is set;
  4. Exiting fullscreen mode will revert the primary display to its original display mode, and place the window on the primary display;
  5. After switching back to windowed mode, SDL_GetWindowFlags() will report that the SDL_WINDOW_FULLSCREEN bit is not set.

If only one display is connected, and the desktop display mode differs from the fullscreen display mode, transitions between fullscreen and windowed mode work correctly, but SDL_GetWindowFlags() reports that the SDL_WINDOW_FULLSCREEN bit is 0 in fullscreen mode, and 1 in windowed mode -- the opposite of what I expect.

If both displays are connected and a resolution change is necessary, things really fall apart. In addition to the SDL_WINDOW_FULLSCREEN bit being set incorrectly, displays disconnect (indicate "no signal," fall asleep) during fullscreen transitions. The secondary display sometimes disconnects during the transition to fullscreen, and both displays always disconnect during the transition back to windowed mode. Both displays continue to behave as though disconnected indefinitely; waiting for the program to terminate and restarting the GNOME shell with the keyboard returns the displays to normal. Additionally, the program prints one or both of the following messages, sometimes repeatedly:

ERROR: Time out elapsed after mode switch on display 1 with no window becoming fullscreen; reverting

X Error of failed request:  BadRRCrtc (invalid Crtc parameter)
  Major opcode of failed request:  140 (RANDR)
  Minor opcode of failed request:  20 (RRGetCrtcInfo)
  Crtc id in failed request: 0x0
  Serial number of failed request:  3969
  Current serial number in output stream:  3969

My test program behaves entirely as expected if only one display is connected, and the desktop display mode is the same as the fullscreen display mode.

Other applications I have tested do not exhibit problems with fullscreen transitions. I also wrote a similar test program using GLFW; it does not restore the desktop display mode upon exiting fullscreen, but otherwise behaves correctly. It therefore seems plausible that the issue is either with my program, or with SDL3.

My SDL test program is as follows:

#include <SDL.h>
#include <cstdio>
#include <thread>

using namespace std::chrono_literals;

int main(int argc, char** argv) {
    SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);

    std::printf("using driver: %s\n", SDL_GetCurrentVideoDriver());

    SDL_Window *window = SDL_CreateWindow("Test Window", 1280, 720, SDL_WINDOW_HIDDEN);
    SDL_ShowWindow(window);

    int display_count = 0;
    SDL_DisplayID *displays = SDL_GetDisplays(&display_count);

    for (int i=0; i<display_count; i++) {
        const SDL_DisplayMode *mode = SDL_GetCurrentDisplayMode(displays[i]);
        std::printf(
            "display %d: %s, %d x %d @ %.2f Hz\n",
            displays[i], SDL_GetDisplayName(displays[i]), mode->w, mode->h, mode->refresh_rate
        );
    }

    SDL_DisplayID display_id = SDL_GetPrimaryDisplay();
    std::printf("primary display: %d %s\n", display_id, SDL_GetDisplayName(display_id));

    #if 1
    const SDL_DisplayMode *mode = SDL_GetClosestFullscreenDisplayMode(display_id, 1280, 720, 60.0f, SDL_FALSE);
    #else
    const SDL_DisplayMode *mode = SDL_GetCurrentDisplayMode(display_id);
    #endif

    if (mode == nullptr) {
        std::printf("setting borderless windowed mode\n");
    } else {
        std::printf(
            "setting fullscreen mode %d x %d @ %.2f Hz on %s\n",
            mode->w, mode->h, mode->refresh_rate, SDL_GetDisplayName(mode->displayID)
        );
    }

    SDL_SetWindowFullscreenMode(window, mode);
    SDL_SetWindowFullscreen(window, SDL_TRUE);
    SDL_SyncWindow(window);

    std::printf("expected fullscreen bit 1; actual %d\n", 0 != (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN));
    std::printf("expected fullscreen display %d; actual %d\n", display_id, SDL_GetDisplayForWindow(window));

    for (int i=0; i<500; i++) {
        SDL_PumpEvents();
        std::this_thread::sleep_for(16ms);
    }

    SDL_SetWindowFullscreen(window, SDL_FALSE);
    SDL_SyncWindow(window);

    std::printf("expected fullscreen bit 0; actual %d\n", 0 != (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN));
    std::printf("expected windowed display %d; actual %d\n", display_id, SDL_GetDisplayForWindow(window));

    for (int i=0; i<500; i++) {
        SDL_PumpEvents();
        std::this_thread::sleep_for(16ms);
    }

    SDL_Quit();
}

My GLFW test program is as follows:

#include <GLFW/glfw3.h>
#include <cstdio>
#include <thread>

using namespace std::chrono_literals;

static void glfw_error_callback(int error, const char* description) {
    std::fprintf(stderr, "glfw error: %s (%d)\n", description, error);
}

int main(int argc, char** argv) {
    if (!glfwInit()) {
        std::fprintf(stderr, "failed to initialize glfw");
        return -1;
    }

    glfwSetErrorCallback(glfw_error_callback);

    glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
    GLFWwindow *window = glfwCreateWindow(1280, 720, "Test Window", nullptr, nullptr);
    glfwShowWindow(window);

    GLFWmonitor *monitor = glfwGetPrimaryMonitor();
    const GLFWvidmode *mode = glfwGetVideoMode(monitor);

    glfwSetWindowMonitor(window, monitor, 0, 0, 1280, 720, 60);
    for (int i=0; i<500; i++) {
        glfwPollEvents();
        std::this_thread::sleep_for(16ms);
    }
    std::printf("expected fullscreen bit 1; actual %d\n", glfwGetWindowMonitor(window) == monitor);

    glfwSetWindowMonitor(window, nullptr, 0, 0, mode->width, mode->height, mode->refreshRate);
    for (int i=0; i<500; i++) {
        glfwPollEvents();
        std::this_thread::sleep_for(16ms);
    }
    std::printf("expected fullscreen bit 0; actual %d\n", glfwGetWindowMonitor(window) == monitor);

    glfwTerminate();
}

@Kontrabant
Copy link
Contributor

In the case of the incorrect window flags, the sync function just needs to allow for extra time if a mode switch is in progress. I'll fix that up tomorrow.

As for everything exploding when switching modes on multi-monitor setups, I'll have to ping @icculus for help on that. I've never touched the XRandR code, and, honestly, I don't think I've ever had X11 work correctly when switching modes on anything but the primary display, and even then, occasionally the other displays would just disappear and need to be re-enabled, or end up in a weird state.

To be fair, the X11 mode switching code does say:

/* I'm becoming more and more convinced that the application should never
 * use XRandR, and it's the window manager's responsibility to track and
 * manage display modes for fullscreen windows.  Right now XRandR is completely
 * broken with respect to window manager behavior on every window manager that
 * I can find.  For example, on Unity 3D if you show a fullscreen window while
 * the resolution is changing (within ~250 ms) your window will retain the
 * fullscreen state hint but be decorated and windowed.
 *
 * However, many people swear by it, so let them swear at it. :)
 */

@Kontrabant
Copy link
Contributor

The incorrect flags should be fixed now.

@MDZhB
Copy link
Author

MDZhB commented Apr 17, 2024

Fullscreen flag looks fixed on my end as well. Thank you very much for taking care of it so quickly, I appreciate it.

@Kontrabant
Copy link
Contributor

I looked into the mode switching issue, and, unless someone can point out something specific that SDL is doing wrong, I don't think there is anything to be done here.

Anything that does mode switching, be it SDL, GLFW, WINE, etc… seems to randomly leave my multi-display setup in a broken state, particularly when switching the mode on a non-primary display. Sometimes the display won't switch back, sometimes a display will behave as if disconnected until entering the settings and re-enabling it, sometimes there are weird scrolling issues, and so on. It's likely just a case of X mode switching being a janky pile of excrement in general.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants