Trying to Port an SDL2 Program to Emscripten on Windows

2023-05-16

Recently, I attempted to port a game I made in C++ with SDL to Emscripten on Windows. There were a few bumps on the road, so I figured I should write down how I did it. I’m not an expert in these topics and this was my first ever encounter with Emscripten. I’m simply documenting what worked for me. Versions and setup:

Installation

Read https://emscripten.org/docs/getting_started/downloads.html. For these commands, I ran PowerShell as Administrator, though that may not have been necessary.

Clone the Git repo and cd into it:

git clone https://github.com/emscripten-core/emsdk.git
cd emsdk

You’ll need to hold on to emsdk/, so I suggest moving it somewhere permanently. I chose C:\.

Ignore emsdk and emsdk.bat and instead run:

python .\emsdk.py install latest

You may see a stack trace and an error message containing:

Error: Downloading URL 'https://storage.googleapis.com/webassembly/emscripten-releases-builds/deps/node-v15.14.0-win-x64.zip': [ASN1] nested asn1 error (_ssl.c:4004)
error: installation failed!

On one of my retries, I saw a slightly different message:

Error: Downloading URL 'https://storage.googleapis.com/webassembly/emscripten-releases-builds/deps/node-v15.14.0-win-x64.zip': not enough data: cadata does not contain a certificate (_ssl.c:4015)
error: installation failed!

Update your Python version and run:

pip install certifi

This will not actually fix anything. What finally helped me was this StackOverflow question. The user added code to emsdk.py that changes HTTPS URLs to HTTP. At the start of download_file(), they added:

    if url.startswith("https://storage.googleapis.com/"): # TODO fix/remove me
      url = "http"+url[5:]    # change https to http      # TODO fix/remove me

(Credits go to fgrieu on StackOverflow.) It’s hacky, but it worked! The install downloaded a bunch of files and finished.

As an aside, I was able to download the zip file myself, so, on the suggestion from #6275, I created a zips/ directory and put the file there. Rerunning emsdk.py simply emptied this directory and returned the same error message.

If you managed to install emsdk, run:

python .\emsdk.py activate latest --permanent

This command modifies the PATH variable. For some reason, restarting my PowerShell did not pick up the change, but restarting my computer did. Anyway, emcc -v and em++ -v should report the version successfully.

Code

emcc and em++ should be drop-in replacements for GCC, but some code modifications are required. These are useful reads:

Overall, my code ended up looking something like this:

// other includes

#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif

struct GameLoopData {
    // ...
};

// Returns whether the game should keep running.
bool oneIteration(GameLoopData &data) {
    // ...
}

#ifdef __EMSCRIPTEN__
void oneIterationEmscripten(void *data) {
    if (!oneIteration(*reinterpret_cast<GameLoopData*>(data))) {
        emscripten_force_exit(0);
    }
}
#endif

int main(int argc, char *argv[]) {
    // Init SDL, GameLoopData, and everything else.

#ifdef __EMSCRIPTEN__
    emscripten_set_main_loop_arg(oneIterationEmscripten, &gameLoopData, 0, true);
#else
    while (true) {
        if (!oneIteration(gameLoopData)) break;
    }
#endif

    return 0;
}

emscripten_set_main_loop_arg() has four arguments:

Notice I used emscripten_force_exit(). The reference mentions building with -sEXIT_RUNTIME. This function terminates the program, though I could still see the last frame of the game when I ran it in browser with emrun.

Compilation

I use Make, but my command to compile was similar to:

em++ main.cpp -O3 -std=c++20 -sUSE_SDL=2 -sUSE_SDL_IMAGE=2 -sUSE_SDL_TTF=2 -sUSE_SDL_MIXER=2 -sSDL2_IMAGE_FORMATS='["bmp","png"]' -sEXIT_RUNTIME --preload-file assets -o bin/game.html

I ran an experiment and I believe that warning flags (-Wall, etc.) have no effect in em++.

Instead of using -lSDL2 as you would in GCC or Clang, you build with -sUSE_SDL=2 (and the rest). Run emcc --show-ports for more options. Otherwise, your code will flat-out fail to compile or you’ll see these errors:

wasm-ld: error: unable to find library -lSDL2main
wasm-ld: error: unable to find library -lSDL2_image
wasm-ld: error: unable to find library -lSDL2_ttf

or something like:

wasm-ld: error: C:\Users\Foo\AppData\Local\Temp\emscripten_temp_abc123\main_0.o:
undefined symbol: TTF_OpenFont
wasm-ld: error: C:\Users\Foo\AppData\Local\Temp\emscripten_temp_abc123\main_0.o:
undefined symbol: IMG_Load
wasm-ld: error: C:\Users\Foo\AppData\Local\Temp\emscripten_temp_abc123\main_0.o:
undefined symbol: Mix_LoadWAV_RW

(The reason I’m including the errors is so that someone in the future may find them. SEO++)

-sSDL2_IMAGE_FORMATS is required to allow various image formats to be parsed. Otherwise, if your code logs SDL errors, the browser console may tell you: ERROR: PNG images are not supported.

On Windows, you may get argument parsing issues (https://github.com/emscripten-ports/SDL2_image/issues/4). If that’s the case, I suggest changing the terminal you use!

--preload-file lets you package files and directories for preloading: https://emscripten.org/docs/porting/files/packaging_files.html. Without it, you may see an SDL error like: ERROR: Couldn't open assets/ttf/sansation.ttf.

If all goes well, you may now see this error:

Traceback (most recent call last):
  File "C:\emsdk\upstream\emscripten\tools\ports\__init__.py", line 255, in retrieve
    import requests
ModuleNotFoundError: No module named 'requests'

One GitHub comment suggested running em++ --clear-ports, but that did not help. You want to use pip to install the module. Emscripten ships with its own Python environment, but, unfortunately, there is no pip in it. Inside the emsdk directory run this:

.\python.exe -m pip install requests
.\python.exe -m pip install certifi

With that, I was able to successfully compile with Emscripten.

If you want a favicon for your program, simply paste an ICO file called favicon.ico into the compiler output directory.

Running

I used emrun to run the program:

cd bin
emrun game.html

You can also use Node or start a local server with Python. See:

Reference

https://emscripten.org/docs/getting_started/downloads.html

https://emscripten.org/docs/getting_started/Tutorial.html

https://emscripten.org/docs/porting/emscripten-runtime-environment.html

https://emscripten.org/docs/api_reference/emscripten.h.html

https://emscripten.org/docs/porting/files/packaging_files.html

https://emscripten.org/docs/compiling/Running-html-files-with-emrun.html

https://emscripten.org/docs/getting_started/FAQ.html

https://github.com/emscripten-core/emscripten/issues/6275

https://github.com/emscripten-core/emsdk/issues/588

https://github.com/emscripten-core/emscripten/issues/16466

https://github.com/emscripten-core/emscripten/blob/main/site/source/docs/compiling/Building-Projects.rst

https://github.com/emscripten-core/emscripten/issues/16398

https://github.com/emscripten-core/emscripten/issues/5076

https://github.com/emscripten-ports/SDL2_image/issues/4

https://github.com/emscripten-core/emscripten/issues/11788

https://lyceum-allotments.github.io/2016/06/emscripten-and-sdl2-tutorial-part-4-look-owl

https://stackoverflow.com/questions/75983185/emsdk-install-fails-with-urlopen-error-ssl-certificate-verify-failed