Fern applications can be compiled to WebAssembly and run directly in the browser using Emscripten. The Web renderer uses HTML5 Canvas for rendering and JavaScript event listeners for input.
Prerequisites
Install Emscripten
Emscripten is required to compile C++ to WebAssembly:
# Clone the Emscripten SDK
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
# Install and activate the latest version
./emsdk install latest
./emsdk activate latest
# Add to PATH (run this in each terminal, or add to ~/.bashrc)
source ./emsdk_env.sh
Verify installation:
Building for Web
Using the Build Script
The simplest way to build for Web:
Build a specific example:
./build.sh web platform_test
Using Fern CLI (Recommended)
The Fern CLI provides live development with hot reload:
./fern-cli.sh example.cpp
This will:
Compile your C++ code to WebAssembly
Start a local web server on port 8000
Open your browser automatically
Enable interactive recompilation (press ārā to rebuild)
Use a custom port with --port: ./fern-cli.sh example.cpp --port 9000
Using CMake with Emscripten
mkdir build-web && cd build-web
emcmake cmake ..
emmake make
Emscripten Compilation Flags
Build Script Flags
The build script uses these Emscripten settings:
WEB_FLAGS = "-std=c++17 -D__EMSCRIPTEN__ -I $SRC_DIR /include"
WEB_SETTINGS = "-s WASM=1 -s ALLOW_MEMORY_GROWTH=1 -s USE_WEBGL2=1"
WEB_SETTINGS = " $WEB_SETTINGS -s EXPORTED_FUNCTIONS='[_main]'"
Enable WebAssembly output (instead of asm.js)
Allow the WASM heap to grow dynamically
Enable WebGL 2.0 support for future GPU rendering
Functions to export to JavaScript (includes _main)
Fern CLI Flags
The CLI includes additional flags for development:
emcc " $source_file " $cpp_sources -o " $dist_dir /${ output_base }.html" --shell-file template.html \
$std_flag $include_path \
-s WASM= 1 \
-s EXPORTED_RUNTIME_METHODS=['cwrap','HEAPU8'] \
-s ALLOW_MEMORY_GROWTH= 1 \
-s NO_DISABLE_EXCEPTION_CATCHING \
--embed-file fonts
Runtime methods accessible from JavaScript
NO_DISABLE_EXCEPTION_CATCHING
Preserve C++ exception handling in WASM
Embed font files into the WASM binary
CMake Configuration
CMake applies these link flags when building with Emscripten:
if (EMSCRIPTEN)
set_target_properties (fern PROPERTIES
COMPILE_FLAGS "-s USE_WEBGL2=1"
LINK_FLAGS "-s WASM=1 -s ALLOW_MEMORY_GROWTH=1 -s USE_WEBGL2=1 -s EXPORTED_FUNCTIONS='[_main]' -s EXPORTED_RUNTIME_METHODS='[ccall,cwrap]'"
)
endif ()
Web Renderer Implementation
The Web renderer (web_renderer.cpp) uses HTML5 Canvas and JavaScript interop.
Canvas Initialization
The renderer creates or finds a canvas element:
EM_ASM ({
var canvas = document . getElementById ( 'canvas' );
if ( ! canvas) {
canvas = document . createElement ( 'canvas' );
canvas . id = 'canvas' ;
document . body . appendChild (canvas);
}
canvas . width = $ 0 ;
canvas . height = $ 1 ;
canvas . fernRenderer = $ 2 ; // Store renderer pointer
}, width, height, this );
Pixel Buffer Rendering
Pixels are transferred from WASM memory to Canvas:
void present ( uint32_t* pixelBuffer , int width , int height ) override {
EM_ASM ({
var canvas = document . getElementById ( 'canvas' );
var ctx = canvas . getContext ( '2d' );
var imageData = ctx . createImageData ($ 0 , $ 1 );
var data = imageData . data ;
var buffer = $ 2 ;
var size = $ 0 * $ 1 ;
for (var i = 0 ; i < size; i ++ ) {
var pixel = HEAP32 [buffer / 4 + i];
var b = pixel & 0x FF ;
var g = (pixel >> 8 ) & 0x FF ;
var r = (pixel >> 16 ) & 0x FF ;
var a = (pixel >> 24 ) & 0x FF ;
var j = i * 4 ;
data [j + 0 ] = r;
data [j + 1 ] = g;
data [j + 2 ] = b;
data [j + 3 ] = a;
}
ctx . putImageData (imageData, 0 , 0 );
}, width, height, pixelBuffer);
}
Event Listeners
JavaScript event listeners are set up for user input:
canvas . addEventListener ( 'mousemove' , function (e) {
var rect = canvas . getBoundingClientRect ();
var x = Math . floor (( e . clientX - rect . left ) * ( canvas . width / rect . width ));
var y = Math . floor (( e . clientY - rect . top ) * ( canvas . height / rect . height ));
Module . _webRendererMouseMove (renderer, x, y);
});
Exported C Functions
C++ methods are exposed to JavaScript via extern āCā functions:
platform_factory.cpp:33-43
extern "C" {
EMSCRIPTEN_KEEPALIVE
void webRendererMouseMove ( Fern :: WebRenderer * renderer , int x , int y ) {
renderer -> onMouseMove (x, y);
}
EMSCRIPTEN_KEEPALIVE
void webRendererMouseClick ( Fern :: WebRenderer * renderer , bool down ) {
renderer -> onMouseClick (down);
}
}
Running Web Applications
Using Fern CLI
./fern-cli.sh example.cpp
Interactive commands while the server is running:
r - Recompile and reload
c - Clear console
q - Quit server
h - Show help
Manual Server
Start a simple HTTP server in the build directory:
cd build/web
python3 -m http.server 8000
Then open http://localhost:8000/example.html in your browser.
You must use a web server. Opening the HTML file directly (file://) will not work due to CORS restrictions.
Build Output
Emscripten generates three files:
example.html
example.js
example.wasm
The main HTML file with embedded canvas
Custom HTML Template
The Fern CLI uses a template file for the HTML shell:
emcc " $source_file " ... --shell-file template.html
Create your own template.html to customize the page:
<! DOCTYPE html >
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
< title > Fern Graphics Application </ title >
< style >
body { margin : 0 ; background-color : #f0f0f0 ; }
canvas { display : block ; margin : 20 px auto ; border : 1 px solid #ccc ; }
</ style >
</ head >
< body >
< div class = "container" >
< h1 > Fern Graphics </ h1 >
{{{ CANVAS }}}
</ div >
</ body >
</ html >
The {{{ CANVAS }}} placeholder is replaced by Emscripten with the canvas element and loading scripts.
Browser Compatibility
Fern WebAssembly applications require:
WebAssembly support (all modern browsers)
Canvas 2D API
ES6 JavaScript features
Supported browsers:
Chrome 57+
Firefox 52+
Safari 11+
Edge 16+
Debugging Web Builds
Enable Debug Mode
Build with debug flags:
if [[ " $BUILD_TYPE " == "Debug" ]]; then
WEB_FLAGS = " $WEB_FLAGS -g -O0 -DDEBUG"
WEB_SETTINGS = " $WEB_SETTINGS -s ASSERTIONS=1"
fi
Open browser DevTools (F12)
Check Console for WASM errors
Use Network tab to verify file loading
Source maps are enabled in debug builds
Optimize for Size
emcc example.cpp -O3 -s WASM= 1 --closure 1
Optimize for Speed
emcc example.cpp -O3 -s WASM= 1 -s ALLOW_MEMORY_GROWTH= 0
Disabling memory growth (ALLOW_MEMORY_GROWTH=0) can improve performance but requires setting -s TOTAL_MEMORY=<bytes>.
auto renderer = Fern :: createRenderer ();
std ::cout << renderer -> getPlatformName () << std ::endl;
// Output: "Web (Emscripten)"
Next Steps
Linux Platform Build native Linux applications
Platform Overview Understand cross-platform architecture