Creating a Window

Learn how to create and customize windows, covering initialization, window management, and rendering
This lesson is part of the course:

Building Minesweeper with C++ and SDL2

Apply what we learned to build an interactive, portfolio-ready capstone project using C++ and the SDL2 library

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

With SDL2 successfully installed and added to our project, we should now be able to create and manipulate windows.

This lesson goes over the steps in how we can do this. We break down and explain every function in the process, as well as describe the most common options we have for customizing our window.

Creating a Window

Let's start by creating a class to manage our window. We’ll break down this code line by line in the following section:

// Window.h
#pragma once
#include <SDL.h>

class Window {
public:
  Window(){
    SDLWindow = SDL_CreateWindow(
      "My Program", SDL_WINDOWPOS_UNDEFINED,
      SDL_WINDOWPOS_UNDEFINED, 200, 200, 0);
  }
  
  SDL_Surface* GetSurface(){
    return SDL_GetWindowSurface(SDLWindow);
  }

  void Render(){
    SDL_FillRect(GetSurface(), nullptr,
      SDL_MapRGB(
        GetSurface()->format, 50, 50, 50
      )
    );
  }

  void Update(){
    SDL_UpdateWindowSurface(SDLWindow);
  }
  
  ~Window() {
    SDL_DestroyWindow(SDLWindow);
  }
  
  Window(const Window&) = delete;
  Window& operator=(const Window&) = delete;

private:
  SDL_Window* SDLWindow;
};

#include Directories

In the code examples in this course, we’ll be using #include directives in the following form:

#include <SDL.h>

However, you may need to use an alternative format, such as #include <SDL2/SDL.h>. This depends on how your include directories (sometimes also called search paths) were configured in the previous chapter.

With our Window class created, over in main.cpp we can include the header file and create our window by calling the constructor:

// main.cpp
#include <SDL.h>
#include "Window.h"

int main(int argc, char** argv){
  SDL_Init(SDL_INIT_VIDEO);
  Window GameWindow;
  SDL_Event event;
  while (true) {
    SDL_PollEvent(&event);
    GameWindow.RenderFrame();
  }
  
  SDL_Quit();
  return 0;
}

Compiling and running this code, we should see our application opens a window which we can move around:

Screenshot of a window with a dark background

Closing the Window

You may have noticed the window doesn’t respond to being closed from the title bar - it remains open.

We’ll fix this a little later in the chapter, where we cover events. For now, if you’re running the code through your IDE, hitting the stop button within your editor will terminate the process and close the window.

Otherwise, you can force your operating system to close it. That can be done using the Ctrl + Alt + Del menu in Windows, or the Opt + Cmd + Esc menu on Mac

Let's break down everything that is going on in our Window class and main function, line by line.

The Window Class

Let’s break down our Window class, line by line.

SDL_Window and SDL_CreateWindow()

The first step in our Window constructor is to call SDL_CreateWindow(). SDL windows have a type of SDL_Window. and we’ll almost always be interacting with it through a pointer. The SDL_CreateWindow() function returns such a pointer, which we’re storing in a private class variable called SDLWindow:

SDLWindow = SDL_CreateWindow(
  "Hello Window", 0, 0, 700, 300, 0
);

The SDL_CreateWindow() function accepts 6 arguments:

  • The window title - "Hello Window"
  • The horizontal position of the window - 100, indicating we want it to be opened 100 pixels from the left-most edge of our screen
  • The vertical position of the window - 100, indicating we want it to be opened 100 pixels from the top edge of our screen
  • The width of the window - 700 pixels in this example
  • The height of the window - 300 pixels in this example
  • "Flags" to modify the behavior of the window. In the previous example, we just set it to 0, but we cover it in detail later

Reviewing the documentation, we see there are some helpers for the position arguments:

  • SDL_WINDOWPOS_CENTERED will automatically center the window along its axis
  • SDL_WINDOWPOS_UNDEFINED will let the operating system choose the best place to open the window. Unless we have a compelling reason to specify the position, letting the platform decide using SDL_WINDOWPOS_UNDEFINED tends to result in a better user experience than us guessing what a good position would be:
SDLWindow = SDL_CreateWindow(
  "Hello Window",
  SDL_WINDOWPOS_UNDEFINED,
  SDL_WINDOWPOS_UNDEFINED,
  700, 300, 0
);

The final argument to SDL_CreateWindow gives us more control over how the window behaves. The documentation provides a list of "flags" that can be used with this argument.

Similarly to before, these flags can be combined using the | operator. For example, we could make our window borderless and resizable like this:

SDLWindow = SDL_CreateWindow(
  "Hello Window",
  SDL_WINDOWPOS_UNDEFINED,
  SDL_WINDOWPOS_UNDEFINED,
  700, 300,
  SDL_WINDOW_BORDERLESS | SDL_WINDOW_RESIZABLE
);

SDL_Surface

An SDL_Surface is one of the ways SDL lets us render content. Later in this chapter, we will create our own surfaces.

However, the windows we create come with a surface by default. We can access that surface using the SDL_GetWindowSurface() function, passing in a pointer to the window.

In our class, we’ve made that surface available through a public GetSurface() method:

SDL_Surface* GetSurface(){
  return SDL_GetWindowSurface(SDLWindow);
}

SDL_FillRect()

This function fills a rectangle with a solid color. It needs three arguments:

  • A pointer to the surface the rectangle is on
  • A pointer to the rectangle to fill. We’ll learn how to define custom rectangles later - for now, passing a nullptr indicates we want the entire surface to be filled
  • The color to fill the rectangle with
SDL_FillRect(
  SDLWindowSurface,
  nullptr,
  SDL_MapRGB(SDLWindowSurface->format,
    40, 40, 40)
);

Colors and SDL_MapRGB()

There are many different ways of representing colors. SDL provides some utility functions to help us standardize this, such as SDL_MapRGB() in the previous example.

This function accepts four arguments:

  1. The format we want the color to be output in. The color format used by a surface is available through the format member variable.
  2. How red we want our color to be
  3. How green we want our color to be
  4. How blue we want our color to be

Typically, the red, green, and blue components of a color are each represented by an 8-bit integer, which is a value from 0 to 255. In the previous example, we set each component (or "channel") to the same number, which will result in a grey color.

Additionally, we chose 40, which is relatively low in the 0-255 range. As such, our grey was relatively dark:

Screenshot of a window with a dark background

In the following example, we increase our blue value (the final argument) from 40 to 100, shifting the resulting color towards blue:

SDL_FillRect(
  SDLWindowSurface,
  nullptr,
  SDL_MapRGB(WindowSurface->format,
    40, 40, 100)
);
Screenshot of a window with a blue background

SDL_UpdateWindowSurface()

Once we’ve made all the changes we need to our surface, we are ready to update the screen, letting the user see our changes.

This is sometimes referred to as rendering the next frame, and we cover the theory behind this later in the chapter.

Our class has a public RenderFrame method to do just that.

Within that function, for now, all we need to do is ask SDL to update the window surface with our changes. We aren’t making any changes yet, but we will soon.

To update the window surface, we call SDL_UpdateWindowSurface function, passing in a pointer to our window:

void RenderFrame() {
  SDL_UpdateWindowSurface(SDLWindow);
}

Preview: Frames and Buffers

Behind the scenes, video output it simply a series of still images, usually called frames. If we show these frames in a quick enough succession - that is, at a high enough frame rate - we create the illusion of motion.

Frame rates are typically represented in frames per second. For motion to appear smooth, we typically want our programs to be capable of generating at least 30 new frames per second.

SDL_UpdateWindowSurface() is how we tell SDL that we’re ready to show users the next frame. We cover the theory behind this in more detail in our lesson on frames and buffers later in the chapter.

SDL_DestroyWindow()

When we no longer need an SDL_Window, we should call SDL_DestroyWindow(), passing a pointer to the window we want to get rid of. This prompts SDL to release the memory associated with that window.

As we’re calling this in a destructor, the rule of three should remind us that we need to consider our copy constructor and assignment operators too. In this case, we don’t need our Window objects to be copyable, so we just delete them:

~Window() {
    SDL_DestroyWindow(SDLWindow);
  }
  
  Window(const Window&) = delete;
  Window& operator=(const Window&) = delete;

The main Function

Finally, let’s take a look at our main function. It looks like this:

1// main.cpp
2#include <SDL.h>
3#include "Window.h"
4
5int main(int argc, char** argv){
6  SDL_Init(SDL_INIT_VIDEO);
7  Window GameWindow;
8  SDL_Event event;
9  while (true) {
10    SDL_PollEvent(&event);
11    GameWindow.RenderFrame();
12  }
13  
14  SDL_Quit();
15  return 0;
16}

The first thing we do in our main function is call SDL_Init(), which receives the subsystems we want to initialize as an argument:

SDL_Init(SDL_INIT_VIDEO);

Here, we’re just initializing the video subsystem. Checking the SDL_Init documentation, we see that this will also initialize the events subsystem.

The documentation claims we can initialize multiple subsystems by "OR-ing them together". This refers to the bitwise OR operator, |

So, for example, we could initialize 3 subsystems like this:

SDL_Init(
  SDL_INIT_VIDEO |
  SDL_INIT_AUDIO |
  SDL_INIT_TIMER
);

Note, the | operator is not the same as the more common || operator. The single | refers to the bitwise or, and it’s used quite a lot in SDL, and game development more generally. We can just treat it as a way to combine multiple options but, if you want to go a little deeper on how it works, we cover in more detail here:

The rest of our main function performs these steps:

  • On line 7, we create an instance of our Window class, thereby calling all the code in our default constructor.
  • On line 9, we have an infinite loop, causing our program to keep running
  • On line 11, we call the RenderFrame() method on every iteration of our loop, thereby filling our rectangle with the grey color we defined in our Window class.
  • On line 14, we call SDL_Quit(). This is how we communicate to SDL that our program is about to end, prompting it to perform any cleanup actions it needs to do. This function call is currently unreachable as our infinite loop never ends, but it will become relevant later in the chapter.

There are two further lines we haven’t covered - the SDL_Event type and SDL_PollEvent() function. As their name suggests, these deal with events.

In this program, they currently have no effect, but we need to use them to keep our program running. Events will have a large role in our program, as they’re the main way we receive and react to user input. We’ll cover this in much more detail later in the chapter.

Summary

In this lesson, we introduced the following topics:

  • Initializing SDL with SDL_Init() and understanding subsystem initialization.
  • Creating a window using SDL_CreateWindow() and understanding the parameters involved.
  • Managing window properties and behaviors using SDL flags.
  • Drawing on the window surface with SDL_FillRect() and manipulating colors with SDL_MapRGB().
  • Updating the window with SDL_UpdateWindowSurface() to reflect changes on the screen.
  • Basic event handling setup that will be expanded in later sections.

Was this lesson useful?

Next Lesson

Detecting and Managing Errors

Discover techniques for detecting and responding to SDL runtime errors
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Updated
3D art representing computer programming
This lesson is part of the course:

Building Minesweeper with C++ and SDL2

Apply what we learned to build an interactive, portfolio-ready capstone project using C++ and the SDL2 library

Free, Unlimited Access
3D art representing computer programming
This lesson is part of the course:

Building Minesweeper with C++ and SDL2

Apply what we learned to build an interactive, portfolio-ready capstone project using C++ and the SDL2 library

Free, unlimited access

This course includes:

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

Detecting and Managing Errors

Discover techniques for detecting and responding to SDL runtime errors
Abstract art representing computer programming
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved