Creating a Window
Learn how to create and customize windows using SDL3, covering initialization, window management, and handling properties.
With SDL3 successfully configured in our project, we can now create and manipulate windows.
This lesson goes over the steps to do this. We'll break down and explain every function in the process, as well as describe the most common options we have for customizing our window.
The previous chapter ended with the following program, which we confirmed can compile and create a window:
src/main.cpp
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <SDL3_image/SDL_image.h>
#include <SDL3_ttf/SDL_ttf.h>
int main(int, char**) {
SDL_Init(SDL_INIT_VIDEO);
SDL_Window* Window{SDL_CreateWindow(
"Hello Window", 800, 300, 0
)};
bool IsRunning = true;
SDL_Event Event;
while (IsRunning) {
while (SDL_PollEvent(&Event)) {
if (Event.type == SDL_EVENT_QUIT) {
IsRunning = false;
}
}
}
SDL_DestroyWindow(Window);
SDL_Quit();
return 0;
}

We'll explain every line of this program throughout the first few chapters of this course. For now, let's remove the SDL_image
and SDL_ttf
includes to keep things simple. SDL_image
and SDL_ttf
are for rendering images and text, respectively. We'll cover those soon, but we don't need them for now:
src/main.cpp
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <SDL3_image/SDL_image.h>
#include <SDL3_ttf/SDL_ttf.h>
int main(int, char**) {
SDL_Init(SDL_INIT_VIDEO);
SDL_Window* Window{SDL_CreateWindow(
"Hello Window", 800, 300, 0
)};
bool IsRunning = true;
SDL_Event Event;
while (IsRunning) {
while (SDL_PollEvent(&Event)) {
if (Event.type == SDL_EVENT_QUIT) {
IsRunning = false;
}
}
}
SDL_DestroyWindow(Window);
SDL_Quit();
return 0;
}
C++ Language Standards
The C++ language evolves over time with new features. These updates are currently made every 3 years, and are informally given names like C++17, C++20, and C++23.
The main implication for us is that we need to make sure the feature we want to use is available in the language standard we're using.
This is particularly important for Visual Studio users as, at the time of writing, Visual Studio uses C++14 by default. That is quite old now, so we should configure our project to something more recent.
If you are using Visual Studio without CMake, you can do this by opening the Property Pages window (from the View menu on the top bar) and setting the C++ Language Standard within the General section. We'd recommend setting it to C++20 or later:

Initializing SDL
Before we use any SDL functions, we need to initialize the library. SDL has a lot of features, and few programs need all of them. As such, the features are broken into subsystems, which allows us to load only the things our program needs.
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, which we'll need in almost every lesson in this course. Checking the official documentation, we see that SDL_INIT_VIDEO
will also initialize the events subsystem, which we'll be using later in this chapter.
The documentation explains 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. 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 it in more detail in .
Quitting SDL
It's also a best practice to shut down SDL when we no longer need it. We can do this using the SDL_Quit()
function.
In most applications that use SDL, we need to keep it running through the entire lifecycle of our program, so we'd only call SDL_Quit()
just before our program ends. In our current program, this is right before the return 0;
statement in main
.
Window Management
As we've likely noticed, programs running on our computer can be opened within their own dedicated windows. The companies that created the platforms (such as Apple for macOS and Microsoft for Windows) provide APIs to create and manage those windows.
These APIs are quite complex, and they're also completely different from one platform to the next. If we create an application for Windows by directly using a Windows API, and then wanted to release it on macOS too, we'd have a lot of work to do.
One of the main reasons for using SDL is that it is cross-platform. If we write code using the SDL APIs, then, behind the scenes, SDL can interpret that to work with the Windows API, the macOS API, and about 20 other platforms.
For the rest of this lesson, we'll focus on window creation. Currently, that's done through the SDL_CreateWindow()
invocation in our main
function. Let's add a dedicated Window
class to take care of this, and to keep our main
function organized:
Files
The Window
constructor contains the code we previously had in our main
function, so our program should still behave as it did before.
The SDL_CreateWindow()
Function
We're passing four arguments to our SDL_CreateWindow()
function call:
SDL_CreateWindow(
"Hello Window",
800, 300, 0
);
Let's break down what these arguments are:
- The window title tells the platform what our window should be called. How this is used depends on the underlying platform. For example, it might get displayed on the title bar or in the task bar.
- The width of the window. In this example, we create a window that is
800
units wide. - The height of the window. In this example, we create a window that is
300
units tall. - "Flags" to modify the behavior of the window. For now, we've set it to
0
indicating we want to use the default behavior, but we cover window flags in more detail later in this section and through the rest of the course.
Window Flags
The last argument to SDL_CreateWindow()
is for SDL window flags. Window flags give us some control over how our window should behave.
For example, we can make our window resizable by passing SDL_WINDOW_RESIZABLE
:
src/Window.h
#pragma once
#include <SDL3/SDL.h>
class Window {
public:
Window(){
SDL_CreateWindow(
"Hello Window",
800, 300,
SDL_WINDOW_RESIZABLE
);
}
};
Window flags are a bit mask, so we can combine multiple flags using the bitwise |
operator. Below, we make our window both resizable and minimized.
src/Window.h
#pragma once
#include <SDL3/SDL.h>
class Window {
public:
Window(){
SDL_CreateWindow(
"Hello Window",
800, 300,
SDL_WINDOW_RESIZABLE | SDL_WINDOW_MINIMIZED
);
}
};
The flags we pass to SDL_CreateWindow()
only apply to the initial state of the window.
These flags can be changed later - sometimes using specific SDL functions that we'll cover throughout the course, or sometimes SDL will change them automatically.
Whilst our previous window will start minimized, the player can indirectly remove that flag later by, for example, clicking on their window in the taskbar.
The SDL_WindowFlags
Type
We can store a set of window flags using the SDL_WindowFlags
type if needed:
SDL_WindowFlags Flags{
SDL_WINDOW_RESIZABLE | SDL_WINDOW_MINIMIZED
};
The official documentation lists all of the available flags, and we cover most of them throughout this course.
The SDL_Window
Type
The SDL_CreateWindow()
function returns a pointer to the SDL_Window
it created. We should store this pointer, as we need it for future window management. Let's add it as a member variable to our class:
src/Window.h
#pragma once
#include <SDL3/SDL.h>
class Window {
public:
Window() {
SDLWindow = SDL_CreateWindow(
"Hello Window",
800, 300, 0
);
}
private:
SDL_Window* SDLWindow{nullptr};
};
SDL_Window
Memory Management
Unfortunately, SDL_Window
objects require us to do some memory management. This section may be a little annoying, but don't get too disheartened - it's somewhat uncommon that this level of manual intervention is required.
Behind the scenes, SDL allocates dynamic memory to support each SDL_Window
. When we no longer need a window, we need to notify SDL so it can release that memory and prevent a memory leak.
To do this, we call SDL_DestroyWindow()
, passing a pointer to the SDL_Window
we want to get rid of. Let's add a destructor to our Window
class for this:
src/Window.h
#pragma once
#include <SDL3/SDL.h>
class Window {
public:
Window() {
SDLWindow = SDL_CreateWindow(
"Hello Window",
800, 300, 0
);
}
~Window() {
SDL_DestroyWindow(SDLWindow);
}
private:
SDL_Window* SDLWindow{nullptr};
};
The Rule of Three
As we add a destructor to our class, the rule of three should remind us that we also need to consider our copy constructor and assignment operators. In this case, we don't need our Window
objects to be copyable, so we'll simplify things and delete the copy operations:
src/Window.h
#pragma once
#include <SDL3/SDL.h>
class Window {
public:
Window() {
SDLWindow = SDL_CreateWindow(
"Hello Window",
800, 300, 0
);
}
Window(const Window&) = delete;
Window& operator=(const Window&) = delete;
~Window() {
SDL_DestroyWindow(SDLWindow);
}
private:
SDL_Window* SDLWindow{nullptr};
};
We covered object copying and the rule of three in more detail in our .
Safety Checks
There are two further scenarios we should consider in our Window
memory management, specifically within our destructor:
- Our
SDL_Window
pointer may be anullptr
. It is technically safe to pass anullptr
toSDL_DestroyWindow()
, but SDL will report it as an error, so let's not do it. We will introduce SDL errors and why ourSDL_Window
might be anullptr
later in this chapter. - SDL is no longer initialized - that is,
SDL_Quit()
has been called.SDL_Quit()
will delete all the resources SDL is managing, including windows, and will leave ourGameWindow
with a dangling pointer. It is not safe to callSDL_DestroyWindow()
on a dangling pointer, so we should check for this.
We can check if an SDL system is currently initialized using SDL_WasInit()
. We pass the same initialization flag we passed to SDL_Init()
. For example, if we wanted to check if the video subsystem is initialized, we'd use this:
SDL_WasInit(SDL_INIT_VIDEO);
If we want to check if any subsystem is initialized, we can pass 0
as the argument:
SDL_WasInit(0);
Let's update our destructor to include a nullptr
and SDL_WasInit()
check:
src/Window.h
#pragma once
#include <SDL3/SDL.h>
#include <iostream>
class Window {
public:
Window() {
SDLWindow = SDL_CreateWindow(
"Hello Window",
800, 300, 0
);
}
Window(const Window&) = delete;
Window& operator=(const Window&) = delete;
~Window() {
if (SDLWindow && SDL_WasInit(SDL_INIT_VIDEO)) {
SDL_DestroyWindow(SDLWindow);
} else {
std::cout << "Skipping SDL_DestroyWindow\n";
}
}
private:
SDL_Window* SDLWindow{nullptr};
};
In our current program, SDL_Quit()
is called near the end of our main
function, and our GameWindow
is destroyed slightly later, once main
ends. As such, we should see our safety check being triggered when we close our program:
Skipping SDL_DestroyWindow
Advanced: SDL_Window
Smart Pointers
If we prefer, we can automate the management of our SDL_Window
using a smart pointer, such as a std::unique_ptr
. We covered std::unique_ptr
in a .
Using it for an SDL_Window
is a little more complex, as std::unique_ptr
doesn't know how to delete an SDL_Window
- that is, it doesn't know about the SDL_DestroyWindow()
function.
However, std::unique_ptr
has an alternative constructor that allows us to supply a custom deleter. A custom deleter is a function, or something that behaves like a function. When the std::unique_ptr
wants to delete the resource, it will call this function and pass the memory address as an argument. Within the function body, we implement our custom deletion logic.
Changing our Window
class to apply this technique would look like the following example. We cover custom deleters and the syntax we use in this code in more detail later in the course, so don't worry if it doesn't entirely make sense yet:
#include <SDL3/SDL.h>
#include <memory>
// Define a custom deleter for SDL_Window*
struct SDLWindowDeleter {
void operator()(SDL_Window* Ptr) const {
if (Ptr && SDL_WasInit(SDL_INIT_VIDEO)) {
SDL_DestroyWindow(Ptr);
}
}
};
// Alias for convenience
using UniqueSDLWindow = std::unique_ptr<
SDL_Window, SDLWindowDeleter
>;
class Window {
public:
Window() {
// Get the raw pointer
SDL_Window* Ptr{SDL_CreateWindow(
"Smart Pointer Window",
800, 300, 0
)};
// Store in the smart pointer
SDLWindow = UniqueSDLWindow(Ptr);
}
// We still need a way to get the raw pointer
// for interactions with other SDL functions
SDL_Window* GetRaw() const {
return SDLWindow.get();
}
private:
// Store the window resource in a unique_ptr
UniqueSDLWindow SDLWindow{nullptr};
};
To keep things simple, we'll continue to use the raw pointer approach for now.
Advanced: Window Properties
The SDL2 version of SDL_CreateWindow()
allowed us to specify the position of the window, but that is no longer the case as of SDL3. This is because in modern windowing systems, it's often best to let the system decide the optimal placement.
However, if we do need to specify a position, we can use the more powerful SDL_CreateWindowWithProperties()
function added in SDL3.
To use it, we first create a set of properties using SDL_CreateProperties()
, then set the values we need, and finally pass the properties to the creation function:
// Window.h
#pragma once
#include <SDL3/SDL.h>
class Window {
public:
Window(){
SDL_PropertiesID props{SDL_CreateProperties()};
// Title
SDL_SetStringProperty(
props,
SDL_PROP_WINDOW_CREATE_TITLE_STRING,
"Hello Window"
);
// Width
SDL_SetNumberProperty(
props,
SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER,
800
);
// Height
SDL_SetNumberProperty(
props,
SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER,
300
);
// Horizontal Position
SDL_SetNumberProperty(
props,
SDL_PROP_WINDOW_CREATE_X_NUMBER,
100
);
// Vertical Position
SDL_SetNumberProperty(
props,
SDL_PROP_WINDOW_CREATE_Y_NUMBER,
100
);
SDL_CreateWindowWithProperties(props);
}
};
Any properties we create are automatically cleaned up when SDL_Quit()
is called or, in more complex scenarios where we need to manually intervene, we can use SDL_DestroyProperties()
:
SDL_DestroyProperties(props);
Positioning Helpers
SDL provides helper constants for common positioning needs. We can use SDL_WINDOWPOS_UNDEFINED
to let the system decide, or SDL_WINDOWPOS_CENTERED
to center the window on the screen.
// Set X and Y to center the window
SDL_SetNumberProperty(
props,
SDL_PROP_WINDOW_CREATE_X_NUMBER,
SDL_WINDOWPOS_CENTERED
);
SDL_SetNumberProperty(
props,
SDL_PROP_WINDOW_CREATE_Y_NUMBER,
SDL_WINDOWPOS_CENTERED
);
Unless we have a compelling reason to specify the position, letting the platform decide using SDL_WINDOWPOS_UNDEFINED
(or using the simpler SDL_CreateWindow()
function) is recommended.
Platform owners have put thought into what makes for a good user experience. For example, they might open the window near the user's cursor, or they might remember where the user moved the window to the last time they ran our program, and open it in that same position.
To keep things simple, we'll stick to the SDL_CreateWindow()
approach for now, but we have a dedicated chapter on more complex window management later in the course.
Complete Code
The state of our program at the end of this lesson is as follows. We'll continue to build on this program throughout the rest of the chapter.
Files
After these updates, our program should compile and run as it did before:

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. - Using
SDL_CreateWindowWithProperties()
to set advanced options like window position. - Managing window properties and behaviors using SDL flags.
SDL3 Surfaces and Colors
Explore SDL3 surfaces, the canvases for drawing, understand pixel formats, colors, and set your window's background.