In this lesson, we’ll get SDL installed, along with two extensions which we’ll need later in this chapter.
SDL
is a cross-platform library that allows us to create windows, access input devices, and create graphics.SDL_image
is an extension that allows us to work with images in all the common formats, such as jpeg and pngSDL_ttf
is an extension that we can use to render text at run-timeThis setup guide is designed for projects that use the CMake build system, where the SDL libraries will be stored in a subdirectory of our project.
The first step is to acquire the source code for SDL2. The latest version is available from the releases page on the official GitHub repository.
Important Note: this release page may include preview versions of SDL3. This course is using SDL2, so ensure the release you download starts with 2
(for example, 2.30.2
)
At the bottom of the latest version 2 release, we should see links to download the source code, as either a .zip
or a .tar.gz
file.
Once downloaded, we should extract the files somewhere on our hard drive where we can easily access them.
The next step is to extract this downloaded source code into a subdirectory of our project. For organizational reasons, third-party code that our project relies on is often stored in a directory called external
or vendor
.
We’ll use external
in this example, and by the end of the lesson our folder structure will look like this:
my-project/
├─ CMakeLists.txt
├─ main.cpp
├─ external/
│ ├─ SDL-release-2.30.2/
│ ├─ SDL-image-release-2.8.2/
│ ├─ SDL_ttf-release-2.22.0/
Those comfortable with git may want to retrieve the source code by cloning the repository into the desired directory, rather than downloading and extracting a zip file.
Note that the main
branch is used for ongoing development of SDL3, which has not been released yet. As such, we should ensure our local working directory is using an SDL2 branch or release tag, such as release-2.30.x
:
git clone -b release-2.30.x --depth 1 https://github.com/libsdl-org/SDL.git
If our main project is also a git repository, we may prefer to add SDL as a submodule instead, which can simplify our management further.
Let’s now update our CMake project to make use of this new subdirectory. In this example, we have a minimalist project called Sandbox, which has a single main.cpp
file:
cmake_minimum_required(VERSION 3.16)
set(CMAKE_CXX_STANDARD 20)
project(Sandbox VERSION 1.0.0)
add_executable(Sandbox main.cpp)
Note: In this example CMakeLists.txt
file, we’ve called our executable Sandbox
, but your name is likely to be different. Every time we use Sandbox
in this lesson, you should replace it with the name you gave your project.
Using the add_subdirectory()
function, we can tell CMake about our dependency:
add_subdirectory(external/SDL-release-2.30.2)
Because the SDL library also uses CMake, the add_subdirectory()
command can find its CMakeLists.txt
file, and understand the project. We simply need to declare that our executable has a dependency on SDL2.
target_link_libraries(Sandbox PRIVATE SDL2)
Simply using "SDL2" here works because SDL2 is the name of a project that is within SDL’s CMakeLists.txt
file, and CMake is aware of that file because of the add_subdirectory()
command we used.
SDL2main
Note that if we’re building our application for Windows, we should additionally add SDL2main
as a dependency: CMake supports conditionals that can help us here:
if (WIN32)
target_link_libraries(
Sandbox PRIVATE SDL2main
)
endif()
SDL needs to perform additional manipulation of our main
function to make our program compatible with Windows, and this small additional library provides that functionality.
Our CMakeLists.txt
file now looks like this:
cmake_minimum_required(VERSION 3.16)
set(CMAKE_CXX_STANDARD 20)
project(Sandbox VERSION 1.0.0)
add_executable(Sandbox main.cpp)
add_subdirectory(external/SDL-release-2.30.2)
target_link_libraries(Sandbox PRIVATE SDL2)
if (WIN32)
target_link_libraries(
Sandbox PRIVATE SDL2main
)
endif()
With our project configured through CMake, we can now write a simple program to verify that it compiles:
#include <SDL.h>
int main(int argc, char** argv) {
SDL_Init(SDL_INIT_VIDEO);
SDL_Window* Window{SDL_CreateWindow(
"Hello Window", 0, 0, 800, 300, 0)};
SDL_Event event;
while (true) {
SDL_PollEvent(&event);
}
return 0;
}
SDL.h
In some environments, we may need to qualify SDL.h
with the SDL2/
directory when including it:
#include <SDL2/SDL.h>
Alternatively, we can add an additional target_include_directories()
call to our CMakeLists.txt
, referencing the /include
directory within the SDL library code we downloaded:
target_include_directories(
Sandbox PRIVATE
external/SDL-release-2.30.2/include
)
Note that this program should compile at this point, but it may not run successfully. We may get an error similar to the following, which we’ll address next:
The code execution cannot proceed because SDL2.dll was not found
Our executable file now has a dependency on the SDL dynamically linked library, which may have a name like SDL2.dll
on Windows or SDL2.dylib
on macOS and Linux.
When we run our executable, the operating system searches for this library, and may not be able to find it.
One of the locations it searches is the same directory as the executable file that requested the library, so this is a common place where we’d put our dependencies. We could move our library files into this location manually, but we’d typically prefer to automate this, and CMake can help us.
In most operating systems, we can copy a file from one location to another using a terminal command, like the following:
copy "folder-a/file.txt" "folder-b"
In our CMakeLists.txt
, we can ask CMake to run this command after our Sandbox
project builds:
add_custom_command(
TARGET Sandbox POST_BUILD COMMAND
copy "folder-a/file.txt" "folder-b"
)
CMake comes with a range of built-in scripts that are generally useful when working on build automation tasks. These are available on the command line using cmake -E
, for example:
add_custom_command(
TARGET Sandbox POST_BUILD COMMAND
cmake -E copy_if_different
"folder-a/file.txt"
"folder-b"
)
There are two further benefits to using cmake -E
over raw commands. First, the cmake -E
scripts are platform agnostic. A command like copy
will require a different syntax depending on whether the user is on Windows or Mac. But a command like cmake -E copy_if_different
will work on any environment that has cmake
.
Secondly, this approach also allows us to use generator expressions which can be used to dynamically generate information. We can identify generator expressions by the $<>
syntax.
In this case, we can use generator expressions to figure out where the SDL library is located, and where it needs to be copied to.
$<TARGET_FILE:SDL2>
generates the location of the main file produced by the SDL2
project defined within the CMakeLists.txt
file of the subdirectory we added. That is, it generates the location of the .dll
or .dylib
file we need to copy.$<TARGET_FILE_DIR:Sandbox>
returns the directory where our Sandbox
project will output its executable. That is, generates the location where we need to copy the .dll
or .dylib
file toadd_custom_command(
TARGET Sandbox POST_BUILD COMMAND
cmake -E copy_if_different
$<TARGET_FILE:SDL2>
$<TARGET_FILE_DIR:Sandbox>
)
As a final step, we can replace cmake
with ${CMAKE_COMMAND}
. CMAKE_COMMAND
is a variable that stores the fully resolved path to the cmake
executable. Telling the terminal exactly where cmake
is located makes our command more resilient across different environments.
We also added the VERBATIM
flag, asking the platform to interpret the command exactly as we’ve written it, which further improves resilience across different environments:
add_custom_command(
TARGET Sandbox POST_BUILD COMMAND
${CMAKE_COMMAND} -E copy_if_different
$<TARGET_FILE:SDL2>
$<TARGET_FILE_DIR:Sandbox>
VERBATIM
)
Our CMakeLists.txt
file now looks like this:
cmake_minimum_required(VERSION 3.16)
set(CMAKE_CXX_STANDARD 20)
project(Sandbox VERSION 1.0.0)
add_executable(Sandbox main.cpp)
add_subdirectory(external/SDL-release-2.30.2)
target_link_libraries(Sandbox PRIVATE
SDL2
)
if (WIN32)
target_link_libraries(
Sandbox PRIVATE SDL2main
)
endif()
add_custom_command(
TARGET Sandbox POST_BUILD COMMAND
${CMAKE_COMMAND} -E copy_if_different
$<TARGET_FILE:SDL2>
$<TARGET_FILE_DIR:Sandbox>
VERBATIM
)
Additionally, after rebuilding our application, the SDL library should be copied into the correct location, allowing our program to run successfully.
SDL2main
need to be copied?SDLmain
is an example of a static library. Static libraries get included in our executable file, so there is no external dependency.
We can identify a static library typically by the .lib
extension on Windows, or the .a
(abbreviation of archive) extension on Linux and macOS
When we share our program for others to use, we must remember that these dependencies exist.
Our program is no longer just an executable - we also need to provide all the additional dependencies (such as .dll
files) so users can successfully run it.
SDL2 has a collection of official extensions, that can add further capabilities to our project. In this course, we’ll use SDL_image for handling images, and SDL_ttf for rendering text.
To add them to our project, we first retrieve the source code for the latest 2.x releases. These are available from GitHub:
SDL_Image
: https://github.com/libsdl-org/SDL_image/releasesSDL_TTF
: https://github.com/libsdl-org/SDL_ttf/releasesAfter downloading them, we can extract them into the same external
directory we placed the base SDL2 library. After doing this, our directory structure might look something like this:
my-project/
├─ CMakeLists.txt
├─ main.cpp
├─ external/
│ ├─ SDL-release-2.30.2/
│ ├─ SDL-image-release-2.8.2/
│ ├─ SDL_ttf-release-2.22.0/
As before, those comfortable with git may want to retrieve the source code by cloning the repository, rather than downloading and extracting a zip file.
Again we should ensure our working directory for each of these repositories is using a version 2 branch or release tag. For example:
git clone -b release-2.22.x --depth 1 https://github.com/libsdl-org/SDL_ttf.git
git clone -b release-2.8.x --depth 1 https://github.com/libsdl-org/SDL_image.git
If our main project is also a git repository, we may want to add these libraries as submodules, rather than standalone repositories.
Unlike the main SDL2 library, the extensions like SDL_image and SDL_ttf have their own dependencies. These dependencies are not included in the main download - we have to acquire them separately, and place them in the external
folder within each library:
my-project/
├─ CMakeLists.txt
├─ main.cpp
├─ external/
│ ├─ SDL-release-2.30.2/
│ ├─ SDL-image-release-2.8.2/
│ | ├─ external/
│ ├─ SDL_ttf-release-2.22.0/
| | ├─ external/
Within these directories, the SDL team has provided scripts that can automate this process for us.
macOS and Linux users can right-click the external
folder, select Open Terminal Here, and then run the command:
./download.sh
Windows users can right-click the external
folder, select Open in Terminal, and then run the command:
.\Get-GitModules.ps1
Depending on your environment, attempting to run a PowerShell script may result in an error similar to the following:
Get-GitModules.ps1 cannot be loaded because running scripts is disabled on this system. For more information, see about_Execution_Policies at
https:/go.microsoft.com/fwlink/?LinkID=135170.
This is a security feature, as running a PowerShell script from an untrusted source can compromise your security.
To allow PowerShell to run scripts, you will first need to run PowerShell as an administrator. This can be done by finding PowerShell in the start menu, right-clicking and selecting Run as Administrator
We can then remove the restriction using the following command:
Set-ExecutionPolicy -ExecutionPolicy Unrestricted
After running the Get-GitModules.ps1
: script in the external
directory, we can then reenable the restrictions as an administrator if preferred:
Set-ExecutionPolicy -ExecutionPolicy Default
If removing the restrictions is not an option, we can alternatively open a regular PowerShell window in the external
directory, and copy and paste the following command: which will have the same effect as running the Get-GitModules.ps1
script:
foreach ($Line in Get-Content ..\.gitmodules) {
[String] $PRegex = "path\s*=\s*(?<path>.*)"
[String] $URLRegex = "url\s*=\s*(?<url>.*)"
[String] $BRegex = "branch\s*=\s*(?<Branch>.*)"
if ($Line -match $PRegex) {
$Match = Select-String -InputObject $Line `
-Pattern $PRegex
$Path = $Match.Matches[0].Groups[1].Value
}
elseif ($Line -match $URLRegex) {
$Match = Select-String -InputObject $Line `
-Pattern $URLRegex
$URL = $Match.Matches[0].Groups[1].Value
}
elseif ($Line -match $BRegex) {
$Match = Select-String -InputObject $Line `
-Pattern $BRegex
$B = $Match.Matches[0].Groups[1].Value
Write-Host `
"git clone $URL $Path -b $B --recursive" `
-ForegroundColor Blue
git clone $URL ../$Path -b $B --recursive
}
}
The external dependencies are registered as git submodules, so if we cloned the repository, downloading the dependencies to the correct directory is slightly easier. We can simply use this command in the root of each of our repositories:
git submodule update --init --recursive
Once we’ve downloaded all the extensions we need into subdirectories, and provided all their dependencies, we can now add them to our project by updating our CMakeLists.txt
.
We follow the same three steps we did when installing the initial SDL library:
add_subdirectory()
functiontarget_link_libraries()
listOur complete CMakeLists.txt
file might look something like this:
cmake_minimum_required(VERSION 3.16)
set(CMAKE_CXX_STANDARD 20)
project(Sandbox VERSION 1.0.0)
add_executable(Sandbox main.cpp)
add_subdirectory(external/SDL-release-2.30.2)
add_subdirectory(external/SDL_image-release-2.8.2)
add_subdirectory(external/SDL_ttf-release-2.22.0)
target_link_libraries(Sandbox PRIVATE
SDL2
SDL2_image
SDL2_ttf
)
if (WIN32)
target_link_libraries(
Sandbox PRIVATE SDL2main
)
endif()
add_custom_command(
TARGET Sandbox POST_BUILD COMMAND
${CMAKE_COMMAND} -E copy_if_different
"$<TARGET_FILE:SDL2>"
"$<TARGET_FILE:SDL2_image>"
"$<TARGET_FILE:SDL2_ttf>"
"$<TARGET_FILE_DIR:Sandbox>"
VERBATIM
)
We can validate everything is set up correctly by compiling and running the following program:
#include <SDL.h>
#include <SDL_image.h>
#include <SDL_ttf.h>
int main(int argc, char** argv) {
SDL_Init(SDL_INIT_VIDEO);
IMG_Init(IMG_INIT_PNG);
TTF_Init();
SDL_Window* Window{SDL_CreateWindow(
"Hello Window", 0, 0, 800, 300, 0)};
SDL_Event event;
while (true) {
SDL_PollEvent(&event);
}
return 0;
}
In this lesson, we covered how to integrate the SDL2 library and its extensions into a C++ project using CMake.
By downloading the SDL source code into a subdirectory and updating our CMakeLists.txt
file, we can build SDL alongside our project and easily manage dependencies. Key takeaways:
external/
) of our projectadd_subdirectory()
and link the libraries to our executable with target_link_libraries()
add_custom_command()
and generator expressionsSDL2main
library must be linked to our executable for compatibilityWith SDL2 set up in our project, we're ready to start developing our project!
A step-by-step guide on setting up SDL2 and useful extensions in a project that uses CMake as its build system
Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games