Video Displays

Learn how to handle multiple monitors in SDL, including creating windows on specific displays.
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

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.

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
Screenshot of the Dishonored 2 options menu

Getting Display Count

We can retrieve the total number of displays available to our program using the SDL_GetNumVideoDisplays() function. Below, we invoke this function on a computer with 4 monitors:

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

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

  int DisplayCount{SDL_GetNumVideoDisplays()};
  std::cout << "Displays: " << DisplayCount;

  return 0;
}
Displays: 4

Error Handling

The SDL_GetNumVideoDisplays() function can return 0, or a negative error code. This typically indicates an error, which can be further investigated using SDL_GetError():

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

int main(int argc, char** argv) {
  SDL_Init(SDL_INIT_VIDEO); 
  int DisplayCount{SDL_GetNumVideoDisplays()};

  if (DisplayCount > 0) {
    std::cout << "Displays: " << DisplayCount;
  } else {
    std::cout << "Could not get displays: "
      << SDL_GetError();
  }

  return 0;
}
Could not get displays: Video subsystem has not been initialized

Getting 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 a display index to the SDL_GetDisplayName() function.

This will return a char* string containing the name of the display. The first display on our system will have a display index of 0:

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

int main(int argc, char** argv) {
  SDL_Init(SDL_INIT_VIDEO);
  std::cout << "First Display Name: "
    << SDL_GetDisplayName(0);

  return 0;
}
First Display Name: DELL S2721DGF

Listing All Display Names

Beyond the first monitor, whose display index is 0, additional monitors will have incrementing display indices. For example, if our system has four displays, their display indices will be 0, 1, 2, and 3.

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 combine the SDL_GetNumVideoDisplays() and SDL_GetDisplayName() functions.

We can use SDL_GetNumVideoDisplays() to get the number of displays, and then use a for loop to iterate over all of the display indices in that range:

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

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

  int displayCount = SDL_GetNumVideoDisplays();
  if (displayCount < 1) {
    std::cout << "Error: " << SDL_GetError() << '\n';
    return 1;
  }

  for (int i = 0; i < displayCount; ++i) {
    const char* displayName = SDL_GetDisplayName(i);
    if (displayName) {
      std::cout << "Display " << i << ": "
        << displayName << '\n';
    } else {
      std::cout << "Display " << i
        << ": Unknown (" << SDL_GetError() << ")\n";
    }
  }

  return 0;
}
Display 0: DELL S2721DGF
Display 1: DELL S2721DGF
Display 2: DELL U2515H
Display 3: DELL U2515H

Error Handling

The SDL_GetDisplayName() function will return a nullptr if it is unable to get the name of the display with the index we provide. We can call SDL_GetError() for an explanation of this failure. Below, we provide a display index of 100, when our system only has 4 displays:

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

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

  const char* Name{SDL_GetDisplayName(100)};
  if (Name) {
    std::cout << "Display 100: " << Name;
  } else {
    std::cout << "Cannot get display name:\n"
      << SDL_GetError();
  }

  return 0;
}
Cannot get display name:
displayIndex must be in the range 0 - 3

Creating Windows on Specific Monitors

As we’ve seen, we can set the horizontal and vertical positions of a window when creating it. We specify these coordinates using the second and third arguments to SDL_CreateWindow(), representing the horizontal and vertical position of the window:

SDL_CreateWindow(
  "First Monitor Window",
  100, // Horizontal position
  200, // Vertical position
  400,400, 0)};

We’ll cover how to precisely control position on a targeted display later in this lesson. For now, let’s introduce some SDL helpers that cover most use cases.

SDL_WINDOWPOS_UNDEFINED_DISPLAY(n)

Previously, we saw how we could use the SDL_WINDOWPOS_UNDEFINED macro to let the platform decide where to position our window:

SDL_CreateWindow(
  "First Monitor Window",
  SDL_WINDOWPOS_UNDEFINED, // Horizontal position
  SDL_WINDOWPOS_UNDEFINED, // Vertical position
  400,400, 0)};

We have a similar SDL_WINDOWPOS_UNDEFINED_DISPLAY() macro that lets us specify which display our window should be on, but let the platform decide the position within that display.

We provide the display index as an argument to SDL_WINDOWPOS_UNDEFINED_DISPLAY() to specify the monitor for our window. Below, we create a window on the display with index 0, and a second window on display with index 1:

#include <SDL.h>

int main(int argc, char** argv) {
  SDL_Init(SDL_INIT_VIDEO);
  SDL_Window* Window1{
    SDL_CreateWindow(
      "First Monitor Window",
      SDL_WINDOWPOS_UNDEFINED_DISPLAY(0),
      SDL_WINDOWPOS_UNDEFINED_DISPLAY(0),
      400,400, 0)};

  SDL_Window* Window2{
    SDL_CreateWindow(
      "Second Monitor Window",
      SDL_WINDOWPOS_UNDEFINED_DISPLAY(1),
      SDL_WINDOWPOS_UNDEFINED_DISPLAY(1),
      400, 400, 0)};

  SDL_Event E;
while (true) {/*...*/} }

SDL_WINDOWPOS_CENTERED_DISPLAY(n)

A display-targetting variation of the SDL_WINDOWPOS_CENTERED macro is also available, in the form of SDL_WINDOWPOS_CENTERED_DISPLAY()

Below, we create a window centered on the display with index 0, and a second window centered on the display with index 1:

#include <SDL.h>

int main(int argc, char** argv) {
  SDL_Init(SDL_INIT_VIDEO);
  SDL_Window* Window1{
    SDL_CreateWindow(
      "First Monitor Centered",
      SDL_WINDOWPOS_CENTERED_DISPLAY(0),
      SDL_WINDOWPOS_CENTERED_DISPLAY(0),
      400,400, 0)};

  SDL_Window* Window2{
    SDL_CreateWindow(
      "Second Monitor Centered",
      SDL_WINDOWPOS_CENTERED_DISPLAY(1),
      SDL_WINDOWPOS_CENTERED_DISPLAY(1),
      400, 400, 0)};

  SDL_Event E;
while (true) {/*...*/} }

Getting Display Bounds

To understand the size and layout of our user’s monitors, we can get their display bounds. This involves passing the display ID and a pointer to an SDL_Rect to the SDL_GetDisplayBounds() function:

SDL_Rect Bounds;
SDL_GetDisplayBounds(0, &Bounds);

As we’ve covered in the past, an SDL_Rect combines 4 integers to represent a rectangle. The member variables are:

  • x - The left edge of the rectangle
  • y - The top edge of the rectangle
  • w - The width of the rectangle
  • h - The height of the rectangle

SDL_GetDisplayBounds() updates these members of our SDL_Rect 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:

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

int main(int argc, char** argv) {
  SDL_Init(SDL_INIT_VIDEO);
  
  int DisplayCount{SDL_GetNumVideoDisplays()};
  SDL_Rect Bounds;
  for (int i{0}; i < DisplayCount; ++i) {
    SDL_GetDisplayBounds(i, &Bounds);
    std::cout << "[Display " << i
      << "] Left: " << Bounds.x
      << ", Top: " << Bounds.y
      << ", Width: " << Bounds.w
      << ", Height: " << Bounds.h << '\n';
  }

  SDL_Event E;
while (true) {/*...*/} }
[Display 0] Left: 0, Top: 0, Width: 2560, Height: 1440
[Display 1] Left: 2560, Top: 0, Width: 2560, Height: 1440
[Display 2] Left: 14, Top: -1440, Width: 2560, Height: 1440
[Display 3] Left: 2574, Top: -1440, Width: 2560, Height: 1440

Whilst the SDL_Rect only includes where the top left corner of a display is within the overall monitor layout, we can calculate the right and bottom edges using some basic arithmetic.

The right edge of a display can be calculated by adding its left edge to it’s width. In this example, the right edge of display 2 is $14 + 2560 = 2574$

The bottom edge can be calculated by adding its top edge to its height. The bottom edge of display 2 is $-1440 + 1440 = 0$

Moving 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 0 if the user presses 0 on their keyboard, and to the top left of display 1 if the user presses 1:

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

void HandleKeydownEvent(SDL_KeyboardEvent& E) {
  SDL_Window* Window{
    SDL_GetWindowFromID(E.windowID)};
    
  SDL_Rect Bounds;
  if (E.keysym.sym == SDLK_0) {
    SDL_GetDisplayBounds(0, &Bounds);
    SDL_SetWindowPosition(
      Window, Bounds.x, Bounds.y);
  } else if (E.keysym.sym == SDLK_1) {
    SDL_GetDisplayBounds(1, &Bounds);
    SDL_SetWindowPosition(
      Window, Bounds.x, Bounds.y);
  }
}

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

  SDL_Event E;
  while (true) {
    while (SDL_PollEvent(&E)) {
      if (E.type == SDL_KEYDOWN) {
        HandleKeydownEvent(E.key);
      } else if (E.type == SDL_QUIT) {
        SDL_Quit();
        return 0;
      }
    }
    GameWindow.Update();
    GameWindow.Render();
  }
}

Window Borders

In the previous chapter, we introduced the notion of window decorations, which SDL refers to as borders. These can include elements like the title bar. Below, we show a decorated window, and the equivalent undecorated window:

Screenshot comparing a bordered and borderless window

When setting a window position, we are setting the position of the window's top-left corner, excluding decorations like borders and the title bar. The borders are added outside this area, so we typically want to ensure we leave enough room for them.

In the previous chapter, we covered how to do this using the SDL_GetWindowBorderSize() function.

Below, we update our previous example where we move our window to the top left of a targetted monitor. But this time, we use SDL_GetWindowBorderSize() and some arithmetic to ensure the position we choose leaves enough room for the decorations:

void HandleKeydownEvent(SDL_KeyboardEvent& E) {
  SDL_Window* Window{
    SDL_GetWindowFromID(E.windowID)};
  SDL_Rect Bounds;

  int Top, Left;
  SDL_GetWindowBordersSize(
    Window, &Top, &Left, nullptr, nullptr);

  if (E.keysym.sym == SDLK_0) {
    SDL_GetDisplayBounds(0, &Bounds);
    SDL_SetWindowPosition(
      Window, Bounds.x + Left, Bounds.y + Top);
  } else if (E.keysym.sym == SDLK_1) {
    SDL_GetDisplayBounds(1, &Bounds);
    SDL_SetWindowPosition(
      Window, Bounds.x + Left, Bounds.y + Top);
  }
}

Get Display of Window

If we have a window and need to find out which display it is on, we can use the SDL_GetWindowDisplayIndex() function. We pass it an SDL_Window pointer, and retrieve an integer display index as the return value:

SDL_Window* Window(SDL_CreateWindow(
  "Example Window",
  SDL_WINDOWPOS_UNDEFINED,
  SDL_WINDOWPOS_UNDEFINED,
  300, 300, 0));

std::cout << "Display Index: "
  << SDL_GetWindowDisplayIndex(Window);
Display Index: 0

In this example, we log out the name of the display the window is currently on when the player presses their spacebar:

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

void HandleKeydownEvent(SDL_KeyboardEvent& E) {
  if (E.keysym.sym != SDLK_SPACE) return;

  SDL_Window* Window{
    SDL_GetWindowFromID(E.windowID)};
  int DisplayIndex{
    SDL_GetWindowDisplayIndex(Window)};
  std::cout << "\nWindow is on "
    << SDL_GetDisplayName(DisplayIndex);
}

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

  SDL_Event E;
  while (true) {
    while (SDL_PollEvent(&E)) {
      if (E.type == SDL_KEYDOWN) {
        HandleKeydownEvent(E.key);
      } else if (E.type == SDL_QUIT) {
        SDL_Quit();
        return 0;
      }
    }
    GameWindow.Update();
    GameWindow.Render();
  }
}
Window is on DELL S2721DGF
Window is on 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 with SDL_GetNumVideoDisplays().
  • Use SDL_GetDisplayName() to display monitor names for user-friendly interfaces.
  • Create windows on specific displays using SDL_WINDOWPOS_UNDEFINED_DISPLAY() and SDL_WINDOWPOS_CENTERED_DISPLAY() .
  • Understand display dimensions and layout with SDL_GetDisplayBounds().
  • Manage window decorations with SDL_GetWindowBordersSize() for better placement accuracy.

Was this lesson useful?

Next Lesson

Handling Mouse Scrolling

Learn how to detect and handle mouse scroll wheel events in SDL2, including vertical and horizontal scrolling, as well as scroll wheel button events.
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
    57.
    Video Displays

    Learn how to handle multiple monitors in SDL, including creating windows on specific displays.


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:

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

Handling Mouse Scrolling

Learn how to detect and handle mouse scroll wheel events in SDL2, including vertical and horizontal scrolling, as well as scroll wheel button events.
Abstract art representing computer programming
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved