Although you can do plenty of interesting things with just logic and text, to impress the man on the Clapham omnibus these days you generally need to do something graphical. To this end we’ll now write a program to load and show this fetching picture of an owl coughing up a pellet:
Solving this problem can be broken down into two broad stages: firstly how do we refer to the image of an owl stored on a server’s file system when it will be viewed in a web-page visited from the client’s computer where JavaScript runs in a sandboxed environment? Secondly, even if we can access the image file, how do we leverage the SDL2 library to load and display it on the screen?
Emscripten provides a number of ways to solve the first problem of making files on the server accessible to C/C++ programs when they are run on a client, we will look at two here. They both work in similar ways, a directory is specifed and the files in that directory serialised and uploaded to the client where Emscripten maps them to a virtual filesystem with the same layout as that of where you compiled the program. The difference between the two methods lays in where the files are serialised and how they are uploaded.
The first option involves embedding the files in the JavaScript itself, and the
files are loaded with the JavaScript. By default, the files to be loaded should
be stored in a directory nested inside the directory where you compile your
program from. You can then tell Emscripten to embed the files inside this
directory by passing the --embed-file <directory>
command-line argument to
emcc at compilation.
hello_owl
│
├── hello_owl.c
│
└── assets
└── images
└── owl.png
Take as an example this file hierarchy, if we are in the hello_owl
directory
and we compile hello_owl.c
with the command:
emcc --embed-file assets -o hello_owl.html
then the filesystem from the assets
directory downwards will be serialised and
compiled into the hello_owl.js
JavaScript script. hello_world.c
could now open
the file in the same way a native C/C++ program would by calling something like:
FILE * fp = fopen("assets/images/owl.png", "r");
This is all well and good, but embedding files straight in the JavaScript like this isn’t very efficient. The problem is that everything has to be loaded in one go over one connection. It is better if the files are stored in a separate file and loaded separately via a XML HTTP request, Emscripten can then make sure that the compiled JavaScript only runs once this XML HTTP request has completed and the virtual filesystem has been set up.
This is achieved in a very similar way to how a filesystem is embedded, only
this method is known as preloading the files and you would compile hello_owl.c
with the following command:
emcc --preload-file assets -o hello_owl.html
This would result in a file hello_owl.data
being produced upon compilation,
containing the filesystem information which would then be loaded via a XML HTTP
request on loading hello_owl.html
. One small caveat with this method and doing
local testing on the Google Chrome or Microsoft Edge browsers is that these
browsers cannot load local files via XML HTTP requests, so local testing must be
done using a web-server as opposed to just opening hello_owl.html
in the browser
(alternatively, you could just do local testing in Firefox).
A similar choice between embedding and preloading data occurs regarding
statically allocated memory in the C/C++ program (memory used for static
variables and such). In a program with a lot of local variables it becomes
inefficient to embed this memory in the JavaScript and upload over one
connection, in such a case it is a possible to do a thing similar to what we
just did with preloading file assets; by passing emscripten the --memory-init-file
1
command line argument emscripten will put the memory for static variables in a
separate file (hello_owl.html.mem
) which is loaded via a XML HTTP request on page
load. By telling Emscripten to use second level optimisation (the -O2
command
line argument) the memory-init-file 1
functionality will be turned on by default,
and that is what we will use from now on.
Regarding the second issue of how to access SDL2 to load an image, here we can
make use of a number of libraries that have been ported to, and can be used from
within, Emscripten. Using these ports is really easy, they are all hosted on
github, and by passing the command line
argument -s
followed by the appropriate port argument they can be accessed
from within your code. A list of ports and their corresponding names can be acquired
with the command:
emcc --show-ports
Doing this, we can see that Emscripten ports provides the SDL2 library and the
SDL2_image library and these are accessible by using the arguments -s
USE_SDL=2
and -s USE_SDL_IMAGE=2
. An additional subtlety that must be
observed with SDL2_image is that you must pass the image formats that you wish
SDL2_image to support, for example, to make SDL2_image support png images pass
the command -s SDL2_IMAGE_FORMATS='["png"]'
at compilation.
Putting all this together with the previous discussion we reach the following compilation command for a program that can load png images from a virtual filesystem using the SDL2 library:
emcc hello_owl.c -O2 -s USE_SDL=2 -s USE_SDL_IMAGE=2 -s SDL2_IMAGE_FORMATS='["png"]' \
--preload-file assets -o hello_owl.html
Now that we have the compilation command, all that remains for us to do is actually write the program!
If you’re familiar with SDL2 this step is straightforward because it’s
identical to how you’d do it for compiling for a native machine! You can
download the hello_owl.c
program source code below, it uses the SDL2_image
function IMG_Load
to load an image file (loaded from the preloaded virtual file
system) and copies that to a renderer that is then used to display the image to
the screen using SDL2 functions.