#2 WebAssembly and C++: Standalone programming - fun with graphics
This post is part of a WebAssembly series focused on WASM and C++. The goal is to gain a thorough understanding of how WebAssembly works, how to use it as a compilation target for C++ code and hopefully have fun along the way. So, stick with me for this exciting journey.
Wherever mentioned, working WASM examples will be embedded directly on the page. If your browser supports it, you should be able to see them running.
In part 1, I’ve described absolute basics of WASM, runtimes and how to compile your code to WASM bytecode using clang. So far, these basic concepts don’t really have much practical use but in this instalment of the series, I want to have some fun before I go any further.
Retro graphics - Web 2.0
In part 1, the discussed example code was compiled in standalone mode, meaning without standard libraries. This is of course, limiting and, most likely, insufficient for any complex solution, still though, you’d be surprised how much stuff can be done without any dependencies at all.
Turns out, it’s enough to literally go back to 1990 and have some fun with retro graphics. Modern web APIs provide the canvas - which is perfect for software rendering. C++ compiled to WASM is perfect to render to the canvas.
In a sense, this brings me back to the 90s when I was having fun with graphics programming in DOS and my all time favourite interrupt 0x10. Mode 0x13 was changing the video mode to 320x200 256 colours and then, with direct access to video memory, you could render whatever you want.
Let’s get started! First, I need some canvas. Let’s create
the following content:
index.js is just gonna be the WASM loader. Here’s the needed code:
All right! I think the code is self-explanatory so, I’m not gonna focus too
much on it. Once the WASM module is loaded, I’m defining a rendering function
render_frame. This is initially called using
after that, further invocations are requested from within
using the same API. Additionally, the delay between current and previous frame
is calculated so, it’s possible to determine how much time has elapsed in
between the animation frames.
Let’s create two empty
cpp files and some simple script to actually build
touch sierp.cpp wasm.cpp
This script reveals something interesting. Specifically
-Wl,--allow-undefined. This silences the linker in case there are
undefined symbols in the resulting binary. Why? I’m gonna discuss that
The project layout should look like so:
build.sh, we should have
sierp.wasm generated. It’s
possible now to check if all that “works” - just start Python’s
python -m http.server
I’m gonna need some basic functions to retrieve the information about the buffer
Additionally, I’ll need a way to set pixels within this buffer.
The pixel format, in this case, is hard-coded to match what HTML canvas expects, which is ABGR.
This set of functions defines the “drawing SDK” that will make coding graphics a bit easier.
is a very simple fractal to generate. In most basic form, it can even be
generated using the logical
AND operation. Believe it or not but it’s as simple as:
This approach works but I like the “chaos game” algorithm better. This algorithm is described well on Wikipedia. The main advantage is that you can move the vertices of the triangle so, it’s possible to animate it in an arbitrary fashion.
Just to recap, generation of Sierpinski’s triangle using chaos game happens in the following steps:
- Pick 3 points in 2D space defining the vertices of the triangle:
- Pick a random point in 2D space (any point):
- In every frame
verticespick a random vertex:
- Put pixel in coordinates between
- Make that point the new
- Repeat these steps n-times per frame (more steps = better looking fractal)
Let’s start with some necessary declarations.
A function to select a random point is needed as well:
This is the tricky part. As you remember, I’m working in standalone
mode so, I can’t use random number generators from
<stdlib>. The beauty of WASM is that it’s possible to inject these
In the code above, I declared a function
js_random but never defined
it. This function can be injected into the WASM module using the
import object, when instantiating the module:
This is why we need
-Wl,--allow-undefined. When building the module,
this function is never defined - but will be patched in runtime. In
js_random is implemented using
Math.random. I’m gonna
focus on how that works in details in future posts, where we’ll
eventually land up learning what WASI is and why it is needed! For
now, all that we need to know is that missing functions can be
extern in C++ and their implementation can be provided in
Okay, I’ve got a random point. I need a function that’ll return a point that lies in between two points as well. Here it is:
This is pretty simple. Just a 2D interpolation.
pos can have values
in range: (0, 1>. The returned interpolated point in case of
pos = 0
will effectively be equal to
pos = 1 will be equivalent to
p2 and anything else will return a point lying in between the two,
in respective proportion of the distance.
With these functions at hand, the algorithm generating the triangle, looks like so:
You probably noticed that I’m using
js_floor here as well - again
something that I’m gonna patch in runtime.
The rendering loop in C++ will look like so:
To clarify, I’m creating
buf which is an instance of
Uint8ClampedArray. The underlying memory belongs to the WASM module
canvas_ptr is basically an offset in
Having the base pointer (
wasm_buff) and the offset (
canvas_ptr), the instance of
Uint8ClampedArray represents the WASM pixel buffer (
buffer in C++).
Having the buffer, I’m declaring an instance of
ImageData can be directly put to the canvas using the 2D context.
This approach follows the guidelines as described in the Web API documentation.
As I already mentioned, the advantage of the “chaos game” algorithm is that the vertices of the triangle can be moved freely in the screen space. It’s possible to animate the fractal. Smooth movements can be implemented using the Lissajous curves.
To make that happen I’m gonna implement one more function:
I’m gonna add this code to the rendering function so it is gonna be called on every frame.
cos functions which normally would be available
<cmath> but I’m gonna patch them in via the import object
similarly as with
Having all of that in place, it’s possible to run the resulting WASM code in the browser.
Compiled module is only slightly over 1kB which is quite impressive and probably smaller than the native code:
Complete code can be found on gitlab.
The discussed WASM module is included below. If your browser supports WASM, you should see it running.