Display Modes

Learn how to manage screen resolutions and refresh rates in SDL games using display modes.
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Free, Unlimited Access
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Posted

When our game is running in a window, we typically don’t need to care that much about things like the resolution and refresh rate of that screen. Our window is just one of potentially many that are running on that display.

However, when we’re running in exclusive full screen mode, we need to be more aware of the characteristics of that screen, and potentially even change them.

These characteristics are grouped together into a display mode, which includes the resolution (width and height in pixels), refresh rate (how often the screen updates per second, measured in Hz), and pixel format (how color data is stored).

Reasons to Change Display Modes

There are two main reasons why we need to care about display modes:

  1. Optimizing Performance: Choosing an appropriate display mode ensures that the game runs smoothly on the user's hardware. Higher resolutions and refresh rates can be more demanding, while lower settings might improve performance on less powerful systems.
  2. Ensuring Compatibility: Display modes can affect visual fidelity and gameplay responsiveness. For instance, choosing a mode with an unsupported refresh rate might result in tearing or other visual artifacts.

Games typically give players control over some aspects of the display mode, such as the screen resolution:

Screenshot of the Dishonored 2 options menu
Screenshot of the Dishonored 2 options menu

SDL provides a collection of functions to manage display modes, including querying the available modes, setting the display mode for a window, and retrieving details about the current configuration.

The SDL_DisplayMode Struct

SDL represents display modes using the SDL_DisplayMode type. This is a struct that contains 5 members:

  • format: The pixel format of the display, describing how colors are represented. We cover pixel formats in more detail later in the course
  • w: An integer representing the horizontal size of the display
  • h: An integer representing the vertical height of the display
  • refresh_rate: An integer representing the refresh rate of the display - how many times it updates per second
  • driverdata: A void pointer storing additional information that’s helpful for SDL’s inner workings. We should consider this to be private and not modify it. When creating an SDL_DisplayMode ourselves, we always initialize this member to a nullptr.

In most cases, we won’t be defining the properties of an SDL_DisplayMode directly. More often, we’ll just be default-constructing an SDL_DisplayMode, and using various SDL functions to populate it.

SDL_GetNumDisplayModes()

We can get the number of display modes that a specific display supports by passing its display index to SDL_GetNumDisplayModes():

#include <iostream>
#include <SDL.h>

int main(int argc, char* argv[]) {
  SDL_Init(SDL_INIT_VIDEO);
  std::cout << "Display Modes: "
    << SDL_GetNumDisplayModes(0);

  SDL_Quit();
  return 0;
}
Display Modes: 130

Error Handling

In the case of an error, SDL_GetNumDisplayModes() can return 0 or a negative number. We can check for this outcome, and call SDL_GetError() for an explanation:

#include <iostream>
#include <SDL.h>

int main(int argc, char* argv[]) {
  SDL_Init(SDL_INIT_VIDEO);

  if (SDL_GetNumDisplayModes(100) <= 0) {
    std::cout << "Error getting display modes: "
      << SDL_GetError();
  }

  SDL_Quit();
  return 0;
}
Error getting display modes: displayIndex must be in the range 0 - 3

SDL_GetDisplayMode()

A displays supported display modes are represented by incremented indices, starting from 0. For example, if we use SDL_GetNumDisplayModes() to find a display supports 100 display modes, those display modes will span from index 0 to index 99.

To retrieve a specific display mode, we can use the SDL_GetDisplayMode() function. This function requires three arguments:

  1. The display index
  2. The display mode index
  3. A pointer to an SDL_DisplayMode, which will be updated with the requested display mode.

Below, we retrieve details about the second display mode supported by the first display:

#include <iostream>
#include <SDL.h>

int main(int argc, char* argv[]) {
  SDL_Init(SDL_INIT_VIDEO);

  SDL_DisplayMode Mode;
  SDL_GetDisplayMode(0, 1, &Mode);

  std::cout << "DisplayIndex: 0, ModeIndex 1: "
    << Mode.w << 'x' << Mode.h << " - "
    << Mode.refresh_rate << "fps";

  SDL_Quit();
  return 0;
}
DisplayIndex: 0, ModeIndex 1: 3620x2036 - 120fps

Listing all Display Modes

By using the value returned by SDL_GetNumDisplayModes() in a for loop, we can iterate through all the display’s available display modes:

#include <iostream>
#include <SDL.h>

int main(int argc, char* argv[]) {
  SDL_Init(SDL_INIT_VIDEO);

  int Count{SDL_GetNumDisplayModes(0)};
  SDL_DisplayMode Mode;
  for (int i{0}; i<Count; ++i) {
    SDL_GetDisplayMode(0, i, &Mode);
    std::cout << "\nDisplayIndex: 0, ModeIndex "
      << i << ": " << Mode.w << 'x' << Mode.h
      << " - " << Mode.refresh_rate << "FPS";
  }

  SDL_Quit();
  return 0;
}

SDL sorts the modes such that those with higher resolutions are first - that is, they have lower indices. Display modes with the same resolution are then sorted by refresh rate, so our previous program’s output might look something like this:

DisplayIndex: 0, ModeIndex 0: 3620x2036 - 144FPS
DisplayIndex: 0, ModeIndex 1: 3620x2036 - 120FPS
DisplayIndex: 0, ModeIndex 2: 3620x2036 - 60FPS
...
DisplayIndex: 0, ModeIndex 129: 640x480 - 59FPS

Error Handling

SDL_GetDisplayMode() returns an integer representing whether it was successful or encountered a problem. It will return 0 on success, and a negative error code otherwise. We can call SDL_GetError() to find out what went wrong:

#include <iostream>
#include <SDL.h>

int main(int argc, char* argv[]) {
  SDL_Init(SDL_INIT_VIDEO);

  SDL_DisplayMode Mode;
  if (SDL_GetDisplayMode(0, 400, &Mode) < 0) {
    std::cout << "Error getting display mode: "
      << SDL_GetError();
  }

  SDL_Quit();
  return 0;
}
Error getting display mode: index must be in the range of 0 - 129

Managing Window Display Modes

The typical way to change a display’s settings within SDL is to associate an SDL_DisplayMode with an SDL_Window. Then, whenever that window is running in exclusive fullscreen mode, the display will be updated to adopt that window’s display mode.

SDL_GetClosestDisplayMode()

To retrieve a supported display mode, we can choose one (or have our player choose one) from the options enumarated by our previous for loop example.

Alternatively, we can construct a SDL_DisplayMode struct with the desired characteristics, and find the closest mode that our display supports.

The SDL_GetClosestDisplayMode() function can help us with the second approach. It accepts three arguments:

  1. The index of the display we want to use
  2. An SDL_DisplayMode pointer describing the display mode we want to use
  3. An SDL_DisplayMode pointer that will be updated with the closest display mode that the display supports

SDL_GetClosestDisplayMode() prioritises our resolution preferences first, followed by our pixel format, followed by the refresh rate. If we have no preference for refresh rate of pixel format, we can set these values to 0 within our SDL_DisplayMode struct:

#include <iostream>
#include <SDL.h>

int main(int argc, char* argv[]) {
  SDL_Init(SDL_INIT_VIDEO);

  SDL_DisplayMode Desired{0, 1920, 1080, 260, nullptr};
  SDL_DisplayMode Closest;

  SDL_GetClosestDisplayMode(0, &Desired, &Closest);

  std::cout << "Closest Display Mode: "
    << Closest.w << "x" << Closest.h
    << " (" << Closest.refresh_rate << "FPS)";

  SDL_Quit();
  return 0;
}
Closest Display Mode: 1920x1080 (144FPS)

The return value of SDL_GetClosestDisplayMode() is also a pointer to the closest display mode - that is, the same pointer we provided as the third argument. However, if the function failed, it will instead return a nullptr. A common pattern is to use this return value in a condition to check if the operation succeeded.

The most common reason SDL_GetClosestDisplayMode() will fail is when we attempt to find a resolution larger than the display supports. Below, we attempt to find an 8k display mode on a display that doesn’t support it:

#include <iostream>
#include <SDL.h>

int main(int argc, char* argv[]) {
  SDL_Init(SDL_INIT_VIDEO);

  SDL_DisplayMode Desired{0, 7680, 4320, 0, nullptr};
  SDL_DisplayMode Closest;

  if (SDL_GetClosestDisplayMode(0, &Desired, &Closest)) {
    // ...
  } else {
    std::cout << "Failed to find a closest mode";
  }

  SDL_Quit();
  return 0;
}
Failed to find a closest mode

SDL_GetClosestDisplayMode() can also fail for other reasons, such as invalid inputs or the video subsystem not being initialized (SDL_INIT_VIDEO). In these cases, an error message will be provided, which we can retrieve through SDL_GetError():

#include <iostream>
#include <SDL.h>

int main(int argc, char* argv[]) {
  // SDL_Init(SDL_INIT_VIDEO); 

  SDL_DisplayMode Desired{0, 7680, 4320, 0, nullptr};
  SDL_DisplayMode Closest;

  if (SDL_GetClosestDisplayMode(
    0, &Desired, &Closest
  )) {
    std::cout << "Closest Display Mode: "
      << Closest.w << "x" << Closest.h
      << " (" << Closest.refresh_rate << "FPS)";
  } else {
    std::cout << "Failed to find a closest mode: "
      << SDL_GetError();
  }

  SDL_Quit();
  return 0;
}
Failed to find a closest mode: Video subsystem has not been initialized

SDL_SetWindowDisplayMode()

Once we have decided the display mode we want our monitor to have when our window is running fullscreen on it, we can set that up using the SDL_SetWindowDisplayMode().

This function accepts the SDL_Window* we want to update, and the SDL_DisplayMode* we want to associate with the window. SDL_SetWindowDisplayMode() copies the requested SDL_DisplayMode, so it can safely be deleted.

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

SDL_DisplayMode Mode{0, 1920, 1080, 0, nullptr};

SDL_SetWindowDisplayMode(Window, &Mode);

When a window is running in exclusive fullscreen mode, both the window and the display that the window is running on will be updated to adopt that display mode:

#include <iostream>
#include <SDL.h>

void LogWindowSize(SDL_Window* Window) {
  int w, h;
  SDL_GetWindowSize(Window, &w, &h);
  std::cout << "\nWindow size: "
    << w << 'x' << h;
}

void LogDisplaySize(SDL_Window* Window) {
  SDL_GetWindowDisplayIndex(Window);
  SDL_Rect Bounds;
  SDL_GetDisplayBounds(
    SDL_GetWindowDisplayIndex(Window), &Bounds);

  std::cout << "\nDisplay Size: "
    << Bounds.w << 'x' << Bounds.h;
}

int main(int argc, char* argv[]) {
  SDL_Init(SDL_INIT_VIDEO);

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

  SDL_DisplayMode Mode{0, 1920, 1080, 0, nullptr};

  SDL_SetWindowDisplayMode(Window, &Mode);

  LogWindowSize(Window);
  LogDisplaySize(Window);

  SDL_SetWindowFullscreen(Window, SDL_WINDOW_FULLSCREEN);

  LogWindowSize(Window);
  LogDisplaySize(Window);

  SDL_Quit();
  return 0;
}
Window size: 800x600
Display Size: 2560x1440
Window size: 1920x1080
Display Size: 1920x1080

SDL_SetWindowDisplayMode() returns 0 if it was successful, and a negative error code otherwise:

SDL_DisplayMode Mode{0, 1920, 1080, 0, nullptr};

if(SDL_SetWindowDisplayMode(nullptr, &Mode) < 0) {
  std::cout << "Failed to set display mode: "
    << SDL_GetError();
}
Failed to set display mode: Invalid window

SDL_GetWindowDisplayMode()

We can retrieve the display mode associated with a window using the SDL_GetWindowDisplayMode() function. We pass the SDL_Window* that we want to query, and an SDL_DisplayMode* that will be updated with the window’s current display mode configuration:

#include <iostream>
#include <SDL.h>

int main(int argc, char* argv[]) {
  SDL_Init(SDL_INIT_VIDEO);

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

  SDL_DisplayMode Mode;
  SDL_GetWindowDisplayMode(Window, &Mode);
  std::cout << "Window Display Mode: "
    << Mode.w << "x" << Mode.h << " ("
    << Mode.refresh_rate << "FPS)";

  SDL_Quit();
  return 0;
}
Window Display Mode: 800x600 (144FPS)

SDL_GetWindowDisplayMode() returns 0 if it was successful, and a negative error code otherwise:

SDL_DisplayMode Mode;

if(SDL_GetWindowDisplayMode(nullptr, &Mode) < 0) {
  std::cout << "Failed to get display mode: "
    << SDL_GetError();
}
Failed to get display mode: Invalid window

Retrieving Monitor Display Modes

There are two display modes that are associated with a specific display:

  • The current display mode is what the monitor is currently using
  • The desktop display mode is what the monitor will use when it is not being overridden by an exclusive fullscreen window

When the display is not being used by an exclusive fullscreen window, the current display mode and the desktop display mode will be the same.

SDL_GetCurrentDisplayMode()

We use the SDL_GetCurrentDisplayMode() function to retrieve the current display mode. We pass the display index of the monitor we’re querying, and an SDL_DisplayMode pointer that will be updated with the display’s current configuration:

#include <iostream>
#include <SDL.h>

int main(int argc, char* argv[]) {
  SDL_Init(SDL_INIT_VIDEO);

  SDL_DisplayMode Mode;
  SDL_GetCurrentDisplayMode(0, &Mode);
  std::cout << "Current Display Mode: "
    << Mode.w << "x" << Mode.h
    << " (" << Mode.refresh_rate << "FPS)";

  SDL_Quit();
  return 0;
}
Current Display Mode: 2560x1440 (144FPS)

SDL_GetCurrentDisplayMode() returns 0 if it was successful, or a negative error code otherwise. We can call SDL_GetError() for an explanation of errors:

#include <iostream>
#include <SDL.h>

int main(int argc, char* argv[]) {
  SDL_Init(SDL_INIT_VIDEO);

  SDL_DisplayMode Mode;
  if (SDL_GetCurrentDisplayMode(100, &Mode) < 0) {
    std::cout << "Error getting display mode: "
      << SDL_GetError();
  }

  SDL_Quit();
  return 0;
}
Error getting display mode: displayIndex must be in the range 0 - 3

When a window is running in exclusive fullscreen mode on a display, that monitor’s display mode is updated. When that window is destroyed or gives up exclusive fullscreen control, the monitor reverts to its previous display mode:

#include <iostream>
#include <SDL.h>

void LogCurrentDisplayMode() {
  SDL_DisplayMode Mode;
  SDL_GetCurrentDisplayMode(0, &Mode);
  std::cout << "\nCurrent Display Mode: "
    << Mode.w << "x" << Mode.h << " ("
    << Mode.refresh_rate << "FPS)";
}

int main(int argc, char* argv[]) {
  SDL_Init(SDL_INIT_VIDEO);
  LogCurrentDisplayMode();

  SDL_Window* Window{SDL_CreateWindow(
    "Window",
    SDL_WINDOWPOS_UNDEFINED_DISPLAY(0),
    SDL_WINDOWPOS_UNDEFINED_DISPLAY(0),
    1920, 1080,
    SDL_WINDOW_FULLSCREEN
  )};

  LogCurrentDisplayMode();
  SDL_DestroyWindow(Window);
  LogCurrentDisplayMode();
  
  SDL_Quit();
  return 0;
}
Current Display Mode: 2560x1440 (144FPS)
Current Display Mode: 1920x1080 (144FPS)
Current Display Mode: 2560x1440 (144FPS)

SDL_GetDesktopDisplayMode()

The display mode a monitor uses when it is not being overridden by an exclusive fullscreen window is called its desktop display mode.

SDL_GetDesktopDisplayMode() has a similar API to SDL_GetCurrentDisplayMode(). It receives the display index of the monitor we’re querying as the first argument, and an SDL_DisplayMode pointer that will be updated with the display mode properties:

#include <iostream>
#include <SDL.h>

int main(int argc, char* argv[]) {
  SDL_Init(SDL_INIT_VIDEO);

  SDL_DisplayMode Mode;
  SDL_GetDesktopDisplayMode(0, &Mode);
  std::cout << "Desktop Display Mode: "
    << Mode.w << "x" << Mode.h
    << " (" << Mode.refresh_rate << "FPS)";

  SDL_Quit();
  return 0;
}
Desktop Display Mode: 2560x1440 (144FPS)

When the monitor isn’t being controlled by an exclusive fullscreen monitor, the desktop display mode and current display mode are the same.

However, when a window has exclusive control of a display, SDL_GetCurrentDisplayMode() will retrieve the current display mode, whilst SDL_GetDesktopDisplayMode() will retrieve the mode that the display will adopt once that window releases control:

#include <iostream>
#include <SDL.h>

void LogDesktopDisplayMode() {
  SDL_DisplayMode Mode;
  SDL_GetDesktopDisplayMode(0, &Mode);
  std::cout << "\nDesktop Display Mode: "
    << Mode.w << "x" << Mode.h << " ("
    << Mode.refresh_rate << "FPS)";
}

int main(int argc, char* argv[]) {
  SDL_Init(SDL_INIT_VIDEO);
  LogDesktopDisplayMode();

  SDL_Window* Window{SDL_CreateWindow(
    "Window",
    SDL_WINDOWPOS_UNDEFINED_DISPLAY(0),
    SDL_WINDOWPOS_UNDEFINED_DISPLAY(0),
    1920, 1080,
    SDL_WINDOW_FULLSCREEN
  )};

  LogDesktopDisplayMode();

  SDL_DestroyWindow(Window);
  SDL_Quit();
  return 0;
}
Current Display Mode: 2560x1440 (144FPS)
Desktop Display Mode: 2560x1440 (144FPS)

An exclusive fullscreen window is now controlling display 0
Current Display Mode: 1920x1080 (144FPS)
Desktop Display Mode: 2560x1440 (144FPS)

The SDL_GetDesktopDisplayMode() function implements error handling in the familiar way. It returns 0 if successful, or a negative error code on failure. We can call SDL_GetError() to understand why the retrieval failed:

#include <iostream>
#include <SDL.h>

int main(int argc, char* argv[]) {
  SDL_Init(SDL_INIT_VIDEO);

  SDL_DisplayMode Mode;
  if (SDL_GetDesktopDisplayMode(100, &Mode) < 0) {
    std::cout << "Error getting display mode: "
      << SDL_GetError();
  }

  SDL_Quit();
  return 0;
}
Error getting display mode: displayIndex must be in the range 0 - 3

Summary

Display modes provide a way to manage screen resolution, refresh rate, and pixel format for both fullscreen and windowed games. They allow us to adapt to different hardware, optimize performance, and respond to player preferences. Here are the key points:

  • SDL_DisplayMode: The struct representing a display mode's resolution, refresh rate, and pixel format.
  • Querying Modes: Use SDL_GetNumDisplayModes() and SDL_GetDisplayMode() to explore supported display modes.
  • Retrieving Modes: Understand differences between desktop (SDL_GetDesktopDisplayMode()) and current modes (SDL_GetCurrentDisplayMode()).
  • Managing Modes: Use SDL_GetWindowDisplayMode() and SDL_SetWindowDisplayMode() for window-specific configurations.

Was this lesson useful?

Next Lesson

Brightness and Gamma

Learn how to control display brightness and gamma correction using SDL's window management functions
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Posted
sdl2-promo.jpg
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Free, Unlimited Access
Monitors and Display Modes
sdl2-promo.jpg
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Free, unlimited access

This course includes:

  • 71 Lessons
  • 100+ Code Samples
  • 91% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Next Lesson

Brightness and Gamma

Learn how to control display brightness and gamma correction using SDL's window management functions
Abstract art representing computer programming
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved