Fullscreen Windows

Learn how to create and manage fullscreen windows in SDL3, including desktop and exclusive fullscreen modes.

Ryan McCombe
Updated

In this lesson, we'll explore SDL3's window management features, learning how to implement both desktop and exclusive fullscreen modes. We'll cover:

  • Creating borderless fullscreen windows that seamlessly cover the entire desktop.
  • Using exclusive fullscreen mode, which can change the display's resolution for optimal performance.
  • Using SDL_SetWindowFullscreen() to change modes at runtime.
  • Best practices and error handling.
  • Synchronizing window operations with SDL_SyncWindow() to ensure state changes complete before proceeding.
  • Responding to fullscreen changes on the event loop

Desktop Fullscreen

The easiest way to create a fullscreen experience is to render our game in a borderless window that has the same position and dimensions as the monitor's display bounds.

Conceptually, we could achieve this by getting the display bounds with SDL_GetDisplayBounds() and then creating a borderless window with the same size and position:

// Get size and position of a display
SDL_Rect Bounds;
SDL_GetDisplayBounds(DisplayID, &Bounds);

SDL_PropertiesID props{SDL_CreateProperties()};
SDL_SetNumberProperty(
  props,
  SDL_PROP_WINDOW_CREATE_X_NUMBER,
  Bounds.x);
SDL_SetNumberProperty(
  props,
  SDL_PROP_WINDOW_CREATE_Y_NUMBER,
  Bounds.y);
SDL_SetNumberProperty(
  props,
  SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER,
  Bounds.w);
SDL_SetNumberProperty(
  props,
  SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER,
  Bounds.h);
SDL_SetBooleanProperty(
  props,
  SDL_PROP_WINDOW_CREATE_BORDERLESS_BOOLEAN,
  true);

SDL_Window* Window{
  SDL_CreateWindowWithProperties(props)};
SDL_DestroyProperties(props);

This technique is often called windowed fullscreen, desktop fullscreen, or borderless fullscreen. With this approach, our game is just another window, which makes multitasking easier and provides a smooth experience when the player switches between our game and other applications.

However, this approach has two potential issues:

  • We may want to render our game at a different resolution than what the monitor is currently using.
  • The way our game represents colors may be different from what the monitor is currently using.

SDL automatically handles both of these problems by rescaling each frame to match the display resolution and reformatting it to match the monitor's color requirements. This additional processing means each frame takes slightly longer to complete. So, while windowed fullscreen offers a smooth multitasking experience, it may come with a slight performance cost.

Exclusive Fullscreen

Instead of having our frames rescaled and reformatted to meet the monitor's requirements, we could instead update the monitor's settings to match what our game is outputting.

This allows each frame to be displayed directly on the screen. This technique is often called exclusive fullscreen or simply fullscreen, and it can eliminate the performance overhead of windowed fullscreen.

However, changing a monitor's display mode can take a few seconds, during which the screen may go blank. This can be frustrating for users who frequently switch to other windows. We cover display modes in detail in the next lesson.

Most games allow players to choose their preferred mode: windowed, borderless fullscreen, or exclusive fullscreen.

Screenshot of the Dishonored 2 options menu

Once we know the player's preference, we can create or update our window accordingly.

Changing Fullscreen State

We can update the fullscreen mode of an existing window using SDL_SetWindowFullscreen(). In SDL3, this function takes the SDL_Window pointer and a boolean:

SDL_Window* Window{SDL_CreateWindow(
  "Window",
  800, 600, 0
)};

// Set window to be fullscreen
SDL_SetWindowFullscreen(Window, true);

By default, windows use the non-exclusive, desktop full screen technique when we set SDL_SetWindowFullscreen() to true. We'll learn how to control this using SDL_SetWindowFullscreenMode() in the next lesson.

Exiting Fullscreen

To return to windowed mode from any fullscreen state, simply call SDL_SetWindowFullscreen() with false.

// Disable fullscreen
SDL_SetWindowFullscreen(Window, false);

Error Handling

The SDL_SetWindowFullscreen() function returns true on success and false on failure. We can check the return value and call SDL_GetError() for more information:

if (!SDL_SetWindowFullscreen(nullptr, true)) {
  std::cout << "Error changing fullscreen mode: "
            << SDL_GetError();
}
Error changing fullscreen mode: Invalid window

Complete Example

Here is an example of toggling full screen in a complete program, where we can enter and exit full screen mode using the up and down arrows respectively:

src/main.cpp

#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <iostream>
#include "Window.h"

void HandleKeyboardEvent(
  const SDL_KeyboardEvent& E, SDL_Window* Window
) {
  if (E.key == SDLK_UP) {
    if (!SDL_SetWindowFullscreen(Window, true)) {
      std::cout << "Failed to set fullscreen: "
        << SDL_GetError() << '\n';
    } else {
      std::cout << "Entered fullscreen mode\n";
    }
  } else if (E.key == SDLK_DOWN) {
    if (!SDL_SetWindowFullscreen(Window, false)) {
      std::cout << "Failed to exit fullscreen: "
        << SDL_GetError() << '\n';
    } else {
      std::cout << "Exited fullscreen mode\n";
    }
  }
}

int main(int, char**) {
  SDL_Init(SDL_INIT_VIDEO);
  Window GameWindow;

  std::cout << "Window created. Press UP arrow for "
    "fullscreen, DOWN arrow to exit fullscreen.\n";

  bool IsRunning = true;
  SDL_Event E;
  while (IsRunning) {
    while (SDL_PollEvent(&E)) {
      if (E.type == SDL_EVENT_QUIT) {
        IsRunning = false;
      } else if (E.type == SDL_EVENT_KEY_DOWN) {
        HandleKeyboardEvent(E.key, GameWindow.GetRaw());
      }
    }
    
    GameWindow.Render();
    GameWindow.Update();
  }

  SDL_Quit();
  return 0;
}

Checking if a Window is Full Screen

To check a window's current fullscreen state, we can use the SDL_GetWindowFlags() function, and then check the SDL_WINDOW_FULLSCREEN bit.

In the following program, we make our window full screen using the up arrow, revert it to a regular window using the down arrow, and log whether it is currently full screen using the spacebar

#include <iostream>
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include "Window.h"

void HandleKeydownEvent(
  const SDL_KeyboardEvent& E, SDL_Window* Window
) {
  if (E.key == SDLK_UP) {
    SDL_SetWindowFullscreen(Window, true);
  } else if (E.key == SDLK_DOWN) {
    SDL_SetWindowFullscreen(Window, false);
  } else if (E.key == SDLK_SPACE) {
    SDL_WindowFlags Flags{SDL_GetWindowFlags(Window)};
    if (Flags & SDL_WINDOW_FULLSCREEN) {
      std::cout << "Fullscreen\n";
    } else {
      std::cout << "Windowed\n";
    }
  }
}

int main(int, char**) {
  SDL_Init(SDL_INIT_VIDEO);
  Window GameWindow;

  SDL_Event E;
  bool IsRunning = true;
  while (IsRunning) {
    while (SDL_PollEvent(&E)) {
      if (E.type == SDL_EVENT_QUIT) {
        IsRunning = false;
      } else if (E.type == SDL_EVENT_KEY_DOWN) {
        HandleKeydownEvent(E.key, GameWindow.GetRaw());
      }
    }

    GameWindow.Render();
    GameWindow.Update();
  }

  SDL_Quit();
  return 0;
}
Window
Fullscreen
Window

Using SDL_SyncWindow()

On some platforms, certain window operations in like changing the fullscreen state are asynchronous. This means the function call - e.g., SDL_SetWindowFullscreen() - returns immediately, but the actual operation might still be in progress. The operating system could be performing an animation or other tasks before the window's state is fully updated.

Depending on our program and the platforms we're targetting, continuing to execute arbitrary instructions when the window itself is transitioning to a new state can cause issues.

To help with this, we can use SDL_SyncWindow(). This function will attempt to pause our program's execution until all pending changes for the specified window have been applied.

In our previous example, we could call SDL_SyncWindow() after setting or unsetting fullscreen mode like this:

void HandleKeydownEvent(
  const SDL_KeyboardEvent& E, SDL_Window* Window
) {
  if (E.key == SDLK_UP) {
    SDL_SetWindowFullscreen(Window, true);
    // Wait for the change to complete
    SDL_SyncWindow(Window);
  } else if (E.key == SDLK_DOWN) {
    SDL_SetWindowFullscreen(Window, false);
    // Wait for the change to complete
    SDL_SyncWindow(Window);
  } else if (E.key == SDLK_SPACE) {
    SDL_WindowFlags Flags{SDL_GetWindowFlags(Window)};
    if (Flags & SDL_WINDOW_FULLSCREEN) {
      std::cout << "Fullscreen\n";
    } else {
      std::cout << "Windowed\n";
    }
  }
}

Full Screen Events

Instead of polling the window flags, we can react to fullscreen state changes using SDL's event system. When a window enters or leaves fullscreen mode, SDL dispatches one of two top-level events:

  • SDL_EVENT_WINDOW_ENTER_FULLSCREEN: Fired when the window successfully enters any fullscreen mode.
  • SDL_EVENT_WINDOW_LEAVE_FULLSCREEN: Fired when the window leaves any fullscreen mode and returns to a windowed state.

Here's how you can handle these events in your event loop:

src/main.cpp

#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <iostream>
#include "Window.h"

int main(int, char**) {
  SDL_Init(SDL_INIT_VIDEO);
  Window GameWindow;
  SDL_Event E;

  bool IsRunning = true;
  while (IsRunning) {
    while (SDL_PollEvent(&E)) {
      if (E.type == SDL_EVENT_WINDOW_ENTER_FULLSCREEN) {
        std::cout << "Window entered fullscreen\n";
      } else if (E.type == SDL_EVENT_WINDOW_LEAVE_FULLSCREEN) {
        std::cout << "Window left fullscreen\n";
      } else if (E.type == SDL_EVENT_QUIT) {
        IsRunning = false;
      }
    }
    GameWindow.Render();
    GameWindow.Update();
  }

  SDL_Quit();
  return 0;
}

These events are triggered whether the change was initiated by your code using SDL_SetWindowFullscreen() or by some platform-specific mechanism.

To let users toggle the full screen state through their platform-specific controls, such as the green pill button on macOS, we generally need to declare the window to be .

We can do that using the SDL_WINDOW_RESIZABLE flag, or the SDL_SetWindowResizable() function:

SDL_Window* SomeWindow{SDL_CreateWindow(
  "My Program",
  600, 300,
  SDL_WINDOW_RESIZABLE
)};

// Alternatively:
SDL_SetWindowResizable(SomeWindow, true);

Summary

In this lesson, we've learned how to implement fullscreen functionality. We introduced both desktop and exclusive fullscreen modes, understanding their benefits and tradeoffs. Key takeaways:

  • Desktop Fullscreen: Offers seamless multitasking
  • Exclusive Fullscreen: Can offer better performance by changing the monitor's resolution. We cover this in more detail in the next lesson
  • Runtime Switching: SDL_SetWindowFullscreen(window, bool) is the primary function for entering and exiting fullscreen modes.
  • State Checking: To determine the current state, check for the SDL_WINDOW_FULLSCREEN flag.
  • Async Operations: Synchronize window operations with SDL_SyncWindow() to ensure state changes are complete before proceeding.
  • Events: Monitor fullscreen state changes using the SDL_EVENT_WINDOW_ENTER_FULLSCREEN and SDL_EVENT_WINDOW_LEAVE_FULLSCREEN events.
Next Lesson
Lesson 68 of 69

Fullscreen Display Modes

Learn how to manage screen resolutions and refresh rates in SDL3 games using display modes.

Have a question about this lesson?
Answers are generated by AI models and may not be accurate