Video Displays
Learn how to handle multiple monitors in SDL3, including creating windows on specific displays.
Modern games and applications often require precise control over display management. In this lesson, you'll learn to retrieve monitor counts, display names, and manage window placement across different displays using SDL3.
In the context of games, the concepts we cover in this lesson are primarily useful for letting players choose which monitor they want our game to run on:

Screenshot of the Dishonored 2 options menu
Starting Point
This lesson is the first in a new chapter focused on advanced windowing and display topics. To keep the examples clear and focused, we'll start with a minimal project structure similar to what we used at the beginning of the course.
This includes a main.cpp with a basic application loop and a simplified Window.h class:
Files
Getting Display Information
SDL associates an SDL_DisplayID with every connected display. This type is a simple integer, which we pass to various other SDL functions when we're querying or configuring our displays.
We can get the SDL_DisplayID of the primary display using the SDL_GetPrimaryDisplay() function:
SDL_DisplayID DisplayID{SDL_GetPrimaryDisplay()};An SDL_DisplayID is a simple integer, and the SDL API uses the 0 value to represent errors. So, if SDL_GetPrimaryDisplay() fails, it will return 0:
SDL_DisplayID DisplayID{SDL_GetPrimaryDisplay()};
if (!DisplayID) {
std::cout << "Couldn't get primary display: "
<< SDL_GetError();
}Working with Multiple Displays
To understand how many displays we're working with, and what IDs we use to identify them, we can call the SDL_GetDisplays() function.
The SDL_GetDisplays() function returns a pointer to a C-style array of SDL_DisplayIDs and provides the count of displays through an output parameter.
We need to prompt SDL to free this memory when we no longer require it, by passing the pointer to SDL_free():
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);
int DisplayCount{0};
SDL_DisplayID* Displays{
SDL_GetDisplays(&DisplayCount)
};
if (Displays) {
std::cout << "Displays: " << DisplayCount;
// Remember to free the memory!
SDL_free(Displays);
}
SDL_Quit();
return 0;
}We can see how many IDs are in the array - that is, how many monitors are detected on the system we're running on - by examining the output parameter.
If we have 4 connected displays, for example, our program should detect that:
Displays: 4Handling SDL_GetDisplays() Errors
If SDL_GetDisplays() fails, it will return a nullptr. This typically indicates an error, which can be further investigated using SDL_GetError(). Below, we attempt to get the displays before we have initialized the video subsystem, which results in an error:
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);
int DisplayCount{0};
SDL_DisplayID* Displays{
SDL_GetDisplays(&DisplayCount)
};
if (Displays) {
std::cout << "Displays: " << DisplayCount;
SDL_free(Displays);
} else {
std::cout << "Could not get displays: "
<< SDL_GetError();
}
SDL_Quit();
return 0;
}Could not get displays: Video subsystem has not been initializedGetting a Display Name
When presenting options for our player to select a monitor, it can be helpful to identify the displays by name. We can do this by passing an SDL_DisplayID to the SDL_GetDisplayName() function.
This will return a const char* string containing the name of the display.
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <iostream>
#include "Window.h"
int main(int, char**) {
SDL_Init(SDL_INIT_VIDEO);
int DisplayCount{0};
SDL_DisplayID* Displays{
SDL_GetDisplays(&DisplayCount)
};
if (Displays) {
std::cout << "First Display Name: "
<< SDL_GetDisplayName(Displays[0]);
SDL_free(Displays);
}
SDL_Quit();
return 0;
}First Display Name: DELL S2721DGFListing All Display Names
Knowing the names of all connected displays can be useful for games or applications that allow players to select a monitor. To achieve this, we can iterate over the array of SDL_DisplayIDs returned by SDL_GetDisplays() and pass each of their SDL_DisplayIDs to SDL_GetDisplayName():
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);
int DisplayCount{0};
SDL_DisplayID* Displays{
SDL_GetDisplays(&DisplayCount)
};
if (Displays) {
for (int i = 0; i < DisplayCount; ++i) {
const char* DisplayName{
SDL_GetDisplayName(Displays[i])
};
if (DisplayName) {
std::cout << "Display " << Displays[i]
<< ": " << DisplayName << '\n';
} else {
std::cout << "Display " << Displays[i]
<< ": Unknown (" << SDL_GetError() << ")\n";
}
}
SDL_free(Displays);
} else {
std::cout << "Error: " << SDL_GetError();
SDL_Quit();
return 1;
}
SDL_Quit();
return 0;
}Display 1: DELL S2721DGF
Display 2: DELL S2721DGF
Display 3: DELL U2515H
Display 4: DELL U2515HHandling SDL_GetDisplayName() Errors
The SDL_GetDisplayName() function will return a nullptr if it is unable to get the name of the display with the ID we provide. We can call SDL_GetError() for an explanation of this failure. Below, we provide an invalid display ID of 0:
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);
const char* Name{SDL_GetDisplayName(0)};
if (Name) {
std::cout << "Display 0: " << Name;
} else {
std::cout << "Cannot get display name: "
<< SDL_GetError();
}
SDL_Quit();
return 0;
}Cannot get display name: Invalid displayCreating Windows on Specific Monitors
As before, to set the position of our window, we use the more flexible properties system with SDL_CreateWindowWithProperties().
To create a window on a specific monitor, we can explicity chose x and y values that are within the bounds of that display. We cover how to do that in the next section.
A simpler alternative is to use two helper macros that SDL provides:
SDL_WINDOWPOS_CENTERED_DISPLAY(x)to center the window on a specific display , wherexis theSDL_DisplayID.SDL_WINDOWPOS_UNDEFINED_DISPLAY(x)to let the platform choose the exact position on that display , wherexis theSDL_DisplayIDof that display.
For example, the following program creates two windows, each centered on a different monitor. We'll get the list of displays, and then use the ID of the first two displays to create our windows:
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);
int DisplayCount{0};
SDL_DisplayID* Displays{
SDL_GetDisplays(&DisplayCount)
};
if (!Displays || DisplayCount < 2) {
std::cout << "Need at least two displays.\n";
SDL_Quit();
return 1;
}
// Properties for Window 1 on Displays[0]
SDL_PropertiesID Props1{SDL_CreateProperties()};
// Positioning Window 1 on Displays[0]
SDL_SetNumberProperty(Props1,
SDL_PROP_WINDOW_CREATE_X_NUMBER,
SDL_WINDOWPOS_CENTERED_DISPLAY(Displays[0]));
SDL_SetNumberProperty(Props1,
SDL_PROP_WINDOW_CREATE_Y_NUMBER,
SDL_WINDOWPOS_CENTERED_DISPLAY(Displays[0])
);
// Other Window 1 Properties
SDL_SetStringProperty(Props1,
SDL_PROP_WINDOW_CREATE_TITLE_STRING,
"First Monitor");
SDL_SetNumberProperty(Props1,
SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, 400);
SDL_SetNumberProperty(Props1,
SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, 400);
// Create the window
SDL_Window* Window1{
SDL_CreateWindowWithProperties(Props1)};
SDL_DestroyProperties(Props1);
// Properties for Window 2 on Displays[1]
SDL_PropertiesID Props2{SDL_CreateProperties()};
SDL_SetNumberProperty(Props2,
SDL_PROP_WINDOW_CREATE_X_NUMBER,
SDL_WINDOWPOS_CENTERED_DISPLAY(Displays[1]));
SDL_SetNumberProperty(Props2,
SDL_PROP_WINDOW_CREATE_Y_NUMBER,
SDL_WINDOWPOS_CENTERED_DISPLAY(Displays[1])
);
// Other Window 2 Properties
SDL_SetStringProperty(Props2,
SDL_PROP_WINDOW_CREATE_TITLE_STRING,
"Second Monitor");
SDL_SetNumberProperty(Props2,
SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, 400);
SDL_SetNumberProperty(Props2,
SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, 400);
// Create the Window
SDL_Window* Window2{
SDL_CreateWindowWithProperties(Props2)};
SDL_DestroyProperties(Props2);
SDL_free(Displays);
SDL_Quit();
return 0;
}Getting Display Bounds
To understand the size and layout of our user's monitors, we can get their display bounds. This involves passing the SDL_DisplayID and a pointer to an SDL_Rect to the SDL_GetDisplayBounds() function:
SDL_Rect Bounds;
SDL_GetDisplayBounds(DisplayID, &Bounds);As we've covered in the past, an SDL_Rect combines 4 integers to represent a rectangle. SDL_GetDisplayBounds() updates these members with values representing the position and size of a given display. These values are useful if we want some operations, such as moving a window, to target a specific monitor. We'll demonstrate this use case in the next section.
Below, we iterate through all of our displays, and log out their bounds:
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) {
SDL_Rect Bounds;
for (int i = 0; i < DisplayCount; ++i) {
SDL_GetDisplayBounds(Displays[i], &Bounds);
std::cout << "[Display " << Displays[i]
<< "] Left: " << Bounds.x
<< ", Top: " << Bounds.y
<< ", Width: " << Bounds.w
<< ", Height: " << Bounds.h << '\n';
}
SDL_free(Displays);
}
SDL_Quit();
return 0;
}[Display 1] Left: 0, Top: 0, Width: 2560, Height: 1440
[Display 2] Left: 2560, Top: 0, Width: 2560, Height: 1440
[Display 3] Left: 14, Top: -1440, Width: 2560, Height: 1440
[Display 4] Left: 2574, Top: -1440, Width: 2560, Height: 1440Moving Windows to Specific Monitors
Once we've used SDL_GetDisplayBounds() to understand the size and position of a monitor, we can move a window onto that monitor by setting its position to within those bounds.
Below, we move the window to the top left of display 1 if the user presses 1 on their keyboard, and to the top left of display 2 if the user presses 2:
src/main.cpp
#include <iostream>
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include "Window.h"
void HandleKeydownEvent(
const SDL_KeyboardEvent& E,
SDL_DisplayID* Displays,
int DisplayCount
) {
SDL_Window* Window{
SDL_GetWindowFromID(E.windowID)
};
SDL_Rect Bounds;
if (E.key == SDLK_1 && DisplayCount > 0) {
SDL_GetDisplayBounds(Displays[0], &Bounds);
SDL_SetWindowPosition(Window, Bounds.x, Bounds.y);
} else if (E.key == SDLK_2 && DisplayCount > 1) {
SDL_GetDisplayBounds(Displays[1], &Bounds);
SDL_SetWindowPosition(Window, Bounds.x, Bounds.y);
}
}
int main(int, char**) {
SDL_Init(SDL_INIT_VIDEO);
Window GameWindow;
int DisplayCount{0};
SDL_DisplayID* Displays{
SDL_GetDisplays(&DisplayCount)
};
SDL_Event E;
bool IsRunning = true;
while (IsRunning) {
while (SDL_PollEvent(&E)) {
if (E.type == SDL_EVENT_KEY_DOWN) {
HandleKeydownEvent(E.key, Displays, DisplayCount);
} else if (E.type == SDL_EVENT_QUIT) {
IsRunning = false;
}
}
GameWindow.Update();
}
if (Displays) {
SDL_free(Displays);
}
SDL_Quit();
return 0;
}Window Borders
In a previous chapter, we introduced the notion of window decorations, which SDL refers to as borders. These can include elements like the title bar.
When setting a window position, we are setting the position of the window's top-left corner, excluding decorations. The borders are added outside this area, so we typically want to ensure we leave enough room for them.
We can do this using the SDL_GetWindowBordersSize() function, which we covered in our lesson on .
Get Display of Window
If we have a window and need to find out which display it is on, we can use the SDL_GetDisplayForWindow() function. We pass it an SDL_Window pointer, and retrieve an SDL_DisplayID as the return value:
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_DisplayID Display{
SDL_GetDisplayForWindow(GameWindow.GetRaw())
};
std::cout << "Window is on display " << Display
<< " (" << SDL_GetDisplayName(Display) << ")";
SDL_Quit();
return 0;
}Window is on display 1 (DELL S2721DGF)In this example, we log out the name of the display the window is currently on when the player presses their spacebar:
src/main.cpp
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <iostream>
#include "Window.h"
void HandleKeydownEvent(const SDL_KeyboardEvent& E) {
if (E.key != SDLK_SPACE) return;
SDL_Window* Window{
SDL_GetWindowFromID(E.windowID)
};
SDL_DisplayID Display{
SDL_GetDisplayForWindow(Window)
};
std::cout << "Window is on display " << Display
<< " (" << SDL_GetDisplayName(Display) << ")\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_KEY_DOWN) {
HandleKeydownEvent(E.key);
} else if (E.type == SDL_EVENT_QUIT) {
IsRunning = false;
}
}
GameWindow.Render();
GameWindow.Update();
}
SDL_Quit();
return 0;
}Window is on display 1 (DELL S2721DGF)
Window is on display 3 (DELL U2515H)Summary
This lesson covers detecting displays, fetching their properties, and dynamically creating and positioning windows across multiple screens. Key takeaways:
- Access the number of connected monitors and their IDs with
SDL_GetDisplays(). - Use
SDL_GetDisplayName()to display monitor names for user-friendly interfaces. - Create windows on specific displays using
SDL_CreateWindowWithProperties()and theSDL_PROP_WINDOW_CREATE_DISPLAY_ID_NUMBERproperty. - Understand display dimensions and layout with
SDL_GetDisplayBounds(). - Manage window decorations with
SDL_GetWindowBordersSize()for better placement accuracy.
Fullscreen Windows
Learn how to create and manage fullscreen windows in SDL3, including desktop and exclusive fullscreen modes.