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.
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
DirectoriesIn 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:
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.
Window
ClassLet’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:
0
, but we cover it in detail laterReviewing the documentation, we see there are some helpers for the position arguments:
SDL_WINDOWPOS_CENTERED
will automatically center the window along its axisSDL_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:
nullptr
indicates we want the entire surface to be filledSDL_FillRect(
SDLWindowSurface,
nullptr,
SDL_MapRGB(SDLWindowSurface->format,
40, 40, 40)
);
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:
format
member variable.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:
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)
);
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);
}
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;
main
FunctionFinally, 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:
Window
class, thereby calling all the code in our default constructor.RenderFrame()
method on every iteration of our loop, thereby filling our rectangle with the grey color we defined in our Window
class.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.
In this lesson, we introduced the following topics:
SDL_Init()
and understanding subsystem initialization.SDL_CreateWindow()
and understanding the parameters involved.SDL_FillRect()
and manipulating colors with SDL_MapRGB()
.SDL_UpdateWindowSurface()
to reflect changes on the screen.Learn how to create and customize windows, covering initialization, window management, and rendering
Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games