Porting a simple SDL2 game to Emscripten
All notes in this series:
- Setting up Emscripten with CMake in Git Bash on Windows 10
- Setting up Emscripten with CMake on Linux
- Porting a simple SDL2 game to Emscripten
- Setting up CMocka with CMake for Linux and Windows
In these notes, we take a simple SDL2 game written in C, which builds on both Linux and Windows 10, and port it to Emscripten so that the game works in the browser. This assumes we have already set up the Emscripten build, which was covered earlier for Windows and Linux. This requires a few changes to the game’s source code, which are:
- Fixing the compiler errors and warnings.
- Adding some conditional
#ifdef
for Emscripten-specific code. - Add data (such as images) to the build, so that images display in the browser.
- Update the main event loop to use the Emscripten
emscripten_set_main_loop
function, so that we do not cause the browser to freeze. - Increase the memory size to permit the game to run.
We’ll cover these points in more detail below.
Our first goal is simply to get a successful build, and our second goal is to ensure that build plays nicely in the browser.
Correctly include SDL2 §
First attempt. Having just set up Emscripten with CMake, let’s try building and see what happens:
An error, but that’s no surprise. It fails because the common_sdl2.h
header raises a build error:
The purpose of this file is to be a cross-platform header file for including SDL2, since typically SDL2 is included in Linux with #include <SDL2/SDL.h>
and on Windows with #include <SDL.h>
. Since Emscripten uses the same include style as Linux, we can fix this by using the __EMSCRIPTEN__
define:
Fix build warnings §
Second attempt. Now the SDL2 include is fixed, let’s try again:
Looks like Emscripten is strict about handling all cases in a switch
statement, and about printf
format specifiers. These are easily resolved.
That achieves the first goal, though as we see when we try and actually run the game, a successful build by itself isn’t enough!
Fix image support §
Third attempt. This time, the game builds and runs, but aborts immediately with an error, so we just get a black screen:
This error originated from graphics.c
in the game’s own code:
I found out here that you have to specify -s SDL2_IMAGE_FORMATS='["png"]'
at compilation to enable the image formats you desire.
However, this did not work. It gave this peculiar message when compiling:
Then and runtime gave the same PNG images are not supported
error.
I searched some more, and found here that for Windows you need to throw more quotes at it, e.g. SDL2_IMAGE_FORMATS = "[""png""]"
. So the correct solution involves fun multi-level escaping in CMakeLists.txt
:
We can place this snippet inside our conditional Emscripten
section in CMakeLists.txt
, since it only applies to Emscripten builds:
Update main event loop §
Fourth attempt. This time the game builds and runs, and doesn’t abort immediately - that’s progress! Have we achieved the second goal?
Unfortunately not: the browser soon points out that the page has hung, and asks if we would like to kill the tab. This is because the game has reached the main event loop, where it is merrily spinning away forever.
To fix this, we need to convert our SDL2 main event loop into an Emscripten main event loop.
This means removing the while
loop and replacing it with emscripten_set_main_loop
which calls a main loop function, e.g.:
The first argument (main_loop
) is the function to be called at the defined number of frames per second.
The second argument (0
) is the number of frames per second (FPS). Setting it to 0
is a special value which tells the browser to decide the FPS, which is recommended.
The third argument (true
), documented as simulate_infinite_loop
, is a little strange. Setting it to true
means no code after the call to emscripten_set_main_loop
will ever run, in which case you can think of the function call as a while (true)
loop that you can never break
out of. Setting it to false
means that the code execution does carry on. See the official documentation for more details:
Include data (assets) §
Fifth attempt. We can now run our game in the browser without it locking up, however we just have a blank screen. This is because none of the game’s data (or assets), such as images or fonts, have been included.
For starters, let’s add an optional Emscripten section in app/CMakeLists.txt
that includes the font used by the game:
Note that the data
directory must be placed in build-em/app/
, since --preload-file
looks for the file relative to the target (in this case, mygame
, which is in build-em/app/
).
This works! No sprites, but the fps counter shows with the font.
Now let’s add the spritesheet:
Now we have sprites!
Memory error §
As a bonus error, by increasing the size of an array I managed to trigger the following error, which appeared in the JavaScript console in the browser:
As the error suggests, this can be fixed by adding -s ALLOW_MEMORY_GROWTH=1
to set_target_properties
in the CMakeLists.txt
:
You can also use -s INITIAL_MEMORY=X
if you have an accurate understanding of how much memory your game needs.
Alternatively, you may change your game to reduce the amount of memory it requires!
Wrap-up §
And with that, the game builds and is playable in the browser! That’s the second and final (for now) goal achieved.
There are further improvements that could be made, such as automating how assets in the data/
directory are pre-loaded, but this was a good first pass.
All notes in this series:
- Setting up Emscripten with CMake in Git Bash on Windows 10
- Setting up Emscripten with CMake on Linux
- Porting a simple SDL2 game to Emscripten
- Setting up CMocka with CMake for Linux and Windows