Fullscreen Display Modes
Learn how to manage screen resolutions and refresh rates in SDL3 games using display modes.
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:
- 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.
- 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
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.
Starting Point
This lesson builds on our earlier work. To focus on display modes, we will start with a minimal project containing a Window class and a main function with a basic application loop.
We've removed the specific event handling logic from the previous lesson to keep the code focused on this lesson's topic.
Files
The SDL_DisplayMode Struct
SDL represents display modes using the SDL_DisplayMode type. This is a struct that contains several members, with the most important being:
displayId: TheSDL_DisplayIDthat the display mode is associated withw,h: integer values representing the width and height of the display mode in screen coordinates.refresh_rate: Afloatrepresenting how many times the screen updates per second (in Hz).pixel_density: Afloatrepresenting the scaling factor between screen coordinates and pixels. We'll cover this in more detail in the next lesson.format: TheSDL_PixelFormatof the display, describing how colors are represented.
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.
Listing all Display Modes
In SDL3, the way to get all available fullscreen display modes for a monitor has changed. Instead of getting a count and then iterating by index, we now get a complete list directly.
First, we get a list of all connected displays using SDL_GetDisplays(), which we introduced earlier in the chapter:
int DisplayCount{0};
SDL_DisplayID* Displays{
SDL_GetDisplays(&DisplayCount)
};Then, for each display, we call SDL_GetFullscreenDisplayModes() to get an array of pointers to SDL_DisplayMode structs. Below, we get the display modes available to the first display returned by SDL_GetDisplays():
int DisplayCount{0};
SDL_DisplayID* Displays{
SDL_GetDisplays(&DisplayCount)
};
int ModeCount{0};
SDL_DisplayMode** Modes{
SDL_GetFullscreenDisplayModes(
Displays[0], &ModeCount
)
};Both functions allocate memory that we are responsible for freeing with SDL_free().
Below, we combine all of these techniques to log out information about all of the available display modes, for each of our displays:
src/main.cpp
#include <iostream>
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include "Window.h"
int main(int, char**) {
SDL_Init(SDL_INIT_VIDEO);
int DisplayCount{0};
SDL_DisplayID* Displays{
SDL_GetDisplays(&DisplayCount)
};
if (Displays) {
for (int i{0}; i < DisplayCount; ++i) {
SDL_DisplayID DisplayID{Displays[i]};
std::cout << "\n-- Display " << DisplayID << ": "
<< SDL_GetDisplayName(DisplayID) << " --\n";
int ModeCount{0};
SDL_DisplayMode** Modes{
SDL_GetFullscreenDisplayModes(
DisplayID, &ModeCount
)
};
if (Modes) {
for (int j{0}; j < ModeCount; ++j) {
const SDL_DisplayMode* Mode{Modes[j]};
std::cout << " Mode " << j << ": "
<< int(Mode->w) << 'x' << int(Mode->h)
<< " @ " << Mode->refresh_rate << "Hz\n";
}
SDL_free(Modes); // Free the modes array
}
}
SDL_free(Displays); // Free the displays array
}
SDL_Quit();
return 0;
}SDL sorts the modes such that those with higher resolutions are first. Display modes with the same resolution are then sorted by refresh rate. Our program's output might look something like this:
-- Display 1: LG TV SSCR2 --
Mode 0: 4096x2160 @ 120Hz
Mode 1: 4096x2160 @ 119.88Hz
Mode 2: 4096x2160 @ 100Hz
Mode 3: 4096x2160 @ 75Hz
...Error Handling
If SDL_GetFullscreenDisplayModes() fails, it will return a nullptr. We can check for this and call SDL_GetError() for an explanation:
int ModeCount = 0;
if (!SDL_GetFullscreenDisplayModes(
0, // Invalid DisplayID
&ModeCount
)) {
std::cout << "Error getting display modes: "
<< SDL_GetError();
}Error getting display modes: Invalid displayManaging Window Display Modes
The recommended way to change a display's settings in SDL3 is to associate an SDL_DisplayMode with an SDL_Window. Then, whenever that window is running in fullscreen mode, the display will be updated to adopt that window's display mode.
Finding a Specific Mode
Instead of iterating through all available modes, we can ask SDL to find the best match for our desired settings. The SDL_GetClosestFullscreenDisplayMode() function does this.
Its arguments are:
- The
SDL_DisplayIDof the monitor we're configuring, - The desired resolution width
- The desired resolution height
- The desired refresh rate
- Whether high-DPI modes should be included in the search
- The
SDL_DisplayModepointer which will be updated with the best-matching display mode
Below, we ask SDL to search for something close to a 1920x1080 144hz mode for our primary display:
#include <iostream>
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include "Window.h"
int main(int, char**) {
SDL_Init(SDL_INIT_VIDEO);
SDL_DisplayID DisplayID{SDL_GetPrimaryDisplay()};
SDL_DisplayMode Closest;
SDL_GetClosestFullscreenDisplayMode(
DisplayID, 1920, 1080, 144.0f, true, &Closest
);
std::cout << "Closest Display Mode: "
<< Closest.w << "x" << Closest.h
<< " @ " << Closest.refresh_rate << "Hz";
SDL_Quit();
return 0;
}On my machine, it found one exactly matching our resolution, but with a slightly slower refresh rate:
Closest Display Mode: 1920x1080 @ 120HzThe SDL_GetClosestFullscreenDisplayMode() function returns a bool representing whether or not the search was successful. We can check this and call SDL_GetError() for information as usual:
SDL_DisplayMode Closest;
if (!SDL_GetClosestFullscreenDisplayMode(
DisplayID, 9999, 9999, 144.0f, true, &Closest
)) {
std::cout << "Error getting display mode:\n "
<< SDL_GetError();
}Error getting display mode:
Couldn't find any matching video modesSetting a Window's Fullscreen Mode
Once we have the SDL_DisplayMode we want, we associate it with a window by passing it's pointer to SDL_SetWindowFullscreenMode(). This function tells SDL which mode to use when the window enters fullscreen.
This function does not make the window fullscreen by itself. It only prepares it. To activate full screen mode, we use SDL_SetWindowFullscreen() as we covered in the previous lesson:
#include <iostream>
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include "Window.h"
int main(int, char**) {
SDL_Init(SDL_INIT_VIDEO);
Window GameWindow;
SDL_DisplayID DisplayID{
SDL_GetDisplayForWindow(GameWindow.GetRaw())
};
SDL_DisplayMode Closest;
if (!SDL_GetClosestFullscreenDisplayMode(
DisplayID, 1920, 1080, 144.0f, true, &Closest
)) {
std::cout << "Error getting display mode:\n "
<< SDL_GetError();
}
// Associate the mode with the window
SDL_SetWindowFullscreenMode(
GameWindow.GetRaw(), &Closest
);
// This will now use the mode we set
SDL_SetWindowFullscreen(GameWindow.GetRaw(), true);
SDL_Quit();
return 0;
}Using Desktop Fullscreen
Remember, if we haven't set a specific mode with SDL_SetWindowFullscreenMode(), calling SDL_SetWindowFullscreen(SomeWindow, true) will result in a desktop fullscreen window instead.
We can also go back to using desktop fullscreen by passing a nullptr to this function:
// We no longer want to use a full screen mode
SDL_SetWindowFullscreenMode(
GameWindow.GetRaw(), nullptr
);
// This will now use the "desktop" / "borderless"
// fullscreen approach
SDL_SetWindowFullscreen(GameWindow.GetRaw(), true);Getting a Window's Fullscreen Mode
We can retrieve the display mode associated with a window using SDL_GetWindowFullscreenMode(). It returns a const pointer to the SDL_DisplayMode, or nullptr if no specific mode is set (meaning it will use desktop fullscreen).
#include <iostream>
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include "Window.h"
int main(int, char**) {
SDL_Init(SDL_INIT_VIDEO);
Window GameWindow;
SDL_DisplayID DisplayID{
SDL_GetDisplayForWindow(GameWindow.GetRaw())
};
SDL_DisplayMode Closest;
if (!SDL_GetClosestFullscreenDisplayMode(
DisplayID, 1920, 1080, 144.0f, true, &Closest
)) {
std::cout << "Error getting display mode:\n "
<< SDL_GetError();
}
SDL_SetWindowFullscreenMode(
GameWindow.GetRaw(), &Closest
);
const SDL_DisplayMode* RetrievedMode{
SDL_GetWindowFullscreenMode(GameWindow.GetRaw())
};
if (RetrievedMode) {
std::cout << "Window Fullscreen Mode: "
<< RetrievedMode->w << "x"
<< RetrievedMode->h << " @ "
<< RetrievedMode->refresh_rate << "Hz";
}
SDL_Quit();
return 0;
}Window Fullscreen Mode: 1920x1080 @ 120HzRetrieving Monitor Display Modes
There are two other important display modes associated with a display:
- The current display mode is what the monitor is currently using.
- The desktop display mode is what the monitor uses by default when no application is in exclusive fullscreen.
SDL_GetCurrentDisplayMode()
SDL_GetCurrentDisplayMode() retrieves the current mode. In SDL3, it takes a SDL_DisplayID and returns a const pointer to the SDL_DisplayMode, or a nullptr on failure:
SDL_DisplayID Display{SDL_GetPrimaryDisplay()};
const SDL_DisplayMode* Mode{
SDL_GetCurrentDisplayMode(Display)
};
if (Mode) {
std::cout << "Current Display Mode: "
<< Mode->w << "x" << Mode->h << " @ "
<< Mode->refresh_rate << "Hz";
} else {
std::cout << "Couldn't get Current Display Mode: "
<< SDL_GetError();
}If the display currently has an exclusive fullscreen window, the display mode that SDL_GetCurrentDisplayMode() returns will be equivalent to what SDL_GetWindowFullscreenMode() would return for that window.
SDL_GetDesktopDisplayMode()
SDL_GetDesktopDisplayMode() retrieves the monitor's native/default mode. Its API is identical to SDL_GetCurrentDisplayMode().
SDL_DisplayID Display{SDL_GetPrimaryDisplay()};
const SDL_DisplayMode* DisplayMode{
SDL_GetCurrentDisplayMode(Display)
};
std::cout << "Current Display Mode: "
<< DisplayMode->w << "x" << DisplayMode->h << " @ "
<< DisplayMode->refresh_rate << "Hz";
const SDL_DisplayMode* DesktopMode{
SDL_GetCurrentDesktopMode(Display)
};
std::cout << "\nDesktop Display Mode: "
<< DesktopMode->w << "x" << DesktopMode->h << " @ "
<< DesktopMode->refresh_rate << "Hz";When the display does not have an exclusive fullscreen window, SDL_GetDesktopDisplayMode() and SDL_GetCurrentDisplayMode() are equivalent.
However, when a window is in exclusive fullscreen, they may be different. SDL_GetDesktopDisplayMode() will still report the monitor's native resolution, whilst SDL_GetCurrentDisplayMode() will match the SDL_GetWindowFullscreenMode() for the window currently controlling the display.
Current Display Mode: 2560x1440 (144Hz)
Desktop Display Mode: 2560x1440 (144Hz)
// (After entering 1920x1080 exclusive fullscreen)
Current Display Mode: 1920x1080 (120Hz)
Desktop Display Mode: 2560x1440 (144Hz)Summary
Display modes provide a way to manage screen resolution and refresh rate, primarily for exclusive fullscreen games. They allow us to adapt to different hardware, optimize performance, and respond to player preferences. Here are the key points for SDL3:
- Listing Modes: Use
SDL_GetDisplays()andSDL_GetFullscreenDisplayModes()to explore supported display modes. - Finding a Mode: Use
SDL_GetClosestFullscreenDisplayMode()to find the best match for desired settings. - Setting the Mode: Use
SDL_SetWindowFullscreenMode()to associate a display mode with a window for exclusive fullscreen. Then, useSDL_SetWindowFullscreen(window, true)to activate it. - Retrieving Modes: Understand the differences between a window's associated mode -
SDL_GetWindowFullscreenMode(), the current display mode -SDL_GetCurrentDisplayMode(), and the desktop mode -SDL_GetDesktopDisplayMode().
Pixel Density and High-DPI Displays
Learn how to create SDL3 applications that look great on modern displays across different platforms