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).
There are two main reasons why we need to care about display modes:
Games typically give players control over some aspects of the display mode, such as the screen resolution:
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.
SDL_DisplayMode
StructSDL 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 coursew
: An integer representing the horizontal size of the displayh
: An integer representing the vertical height of the displayrefresh_rate
: An integer representing the refresh rate of the display - how many times it updates per seconddriverdata
: 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
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:
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
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
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
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:
SDL_DisplayMode
pointer describing the display mode we want to useSDL_DisplayMode
pointer that will be updated with the closest display mode that the display supportsSDL_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
There are two display modes that are associated with a specific display:
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
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.SDL_GetNumDisplayModes()
and SDL_GetDisplayMode()
to explore supported display modes.SDL_GetDesktopDisplayMode()
) and current modes (SDL_GetCurrentDisplayMode()
).SDL_GetWindowDisplayMode()
and SDL_SetWindowDisplayMode()
for window-specific configurations.Learn how to manage screen resolutions and refresh rates in SDL games using display modes.
Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games