Overview
Cub3D can be compiled to WebAssembly (WASM) using Emscripten, allowing it to run in modern web browsers. The web version uses SDL2 compiled to JavaScript with full audio, graphics, and gamepad support.
Prerequisites
Install Emscripten
Emscripten is required to compile C/C++ code to WebAssembly:
Download Emscripten SDK
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
Install Latest Version
./emsdk install latest
./emsdk activate latest
Set Environment Variables
Add this to your ~/.bashrc or ~/.zshrc to make it permanent: source "/path/to/emsdk/emsdk_env.sh"
Verify Installation
Should output something like: emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 3.1.x
Building for Web
Compile Command
Use the dedicated Emscripten Makefile:
This generates three main files:
cub3d.html HTML shell and entry point
cub3d.js JavaScript loader (~368KB)
cub3d.wasm WebAssembly binary (~1.7MB)
Plus a data file:
cub3d.data - Preloaded assets (~12MB)
Emscripten Configuration
The emcc.Makefile is specifically configured for web builds:
Compiler Settings
CC = emcc
CFLAGS = -Wall -Wextra -Werror -O3 -D W_WIDTH=400 -D W_HEIGHT=300
The web build uses a smaller resolution (400x300) for better performance. You can adjust W_WIDTH and W_HEIGHT as needed.
Threading Configuration
CFLAGS += -pthread
LDLIBS += -s USE_PTHREADS=1 -s PTHREAD_POOL_SIZE=5
Enables multi-threading with a pool of 5 worker threads.
SDL2 Integration
Uses Emscripten’s SDL2 port, which translates SDL calls to WebGL and WebAudio.
Safety and Debugging
LDLIBS += -s ASSERTIONS=1 -s SAFE_HEAP=1 -s STACK_OVERFLOW_CHECK=2
ASSERTIONS : Runtime checks for common errors
SAFE_HEAP : Memory access validation
STACK_OVERFLOW_CHECK : Detects stack overflows
These flags add runtime overhead. For production builds, consider disabling them: LDLIBS += -s ASSERTIONS=0 -s SAFE_HEAP=0 -s STACK_OVERFLOW_CHECK=0
Asyncify
Enables synchronous-looking code to work with asynchronous browser APIs.
Asset Preloading
LDLIBS += --preload-file maps --preload-file assets/42lisboa
LDLIBS += --preload-file assets/fonts --preload-file assets/sounds
LDLIBS += --preload-file assets/textures --preload-file assets/wolf3d
LDLIBS += --preload-file assets/pt/textures/dommiguelilow.bmp
LDLIBS += --preload-file assets/pt/textures/portugal.bmp
Embeds game assets into the virtual filesystem (cub3d.data).
The --preload-file flag packages assets so they’re available via normal file I/O in the WASM module.
Memory Configuration
LDLIBS += -s INITIAL_MEMORY=268435456 -s STACK_SIZE=2MB
INITIAL_MEMORY : 256MB initial heap
STACK_SIZE : 2MB stack per thread
If you encounter “out of memory” errors, increase INITIAL_MEMORY: -s INITIAL_MEMORY=536870912 # 512MB
ES6 Module Export
LDLIBS += -s ENVIRONMENT='web,worker'
LDLIBS += -s EXPORT_ES6=1 -s MODULARIZE=1
Exports as an ES6 module for modern JavaScript integration.
Custom HTML Shell
LDLIBS += --shell-file minimal.html
Uses minimal.html as the template for the final HTML page.
Custom HTML Shell
The minimal.html file provides the page structure. You can customize it:
<! DOCTYPE html >
< html >
< head >
< meta charset = "utf-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1" >
< title > Cub3D </ title >
< style >
body {
margin : 0 ;
padding : 0 ;
background : #000 ;
display : flex ;
justify-content : center ;
align-items : center ;
min-height : 100 vh ;
}
canvas {
image-rendering : pixelated ;
image-rendering : crisp-edges ;
}
</ style >
</ head >
< body >
< canvas id = "canvas" ></ canvas >
{{{ SCRIPT }}}
</ body >
</ html >
The {{{ SCRIPT }}} placeholder is replaced by Emscripten with the module loader code.
Local Testing
Python HTTP Server
The project includes a simple server:
This starts a local server (typically on http://localhost:8000) with proper MIME types and CORS headers.
Opening cub3d.html directly via file:// won’t work due to:
CORS restrictions on loading .wasm and .data files
SharedArrayBuffer requiring specific headers
For SharedArrayBuffer support (needed for threading), serve with:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
The included server.py handles this automatically.
Production Deployment
Build for Production
Optimize the build:
CFLAGS = -Wall -Wextra -Werror -O3 -D W_WIDTH=1024 -D W_HEIGHT=768
LDLIBS += -s ASSERTIONS=0 -s SAFE_HEAP=0
Build:
Deploy these files:
cub3d.html
cub3d.js
cub3d.wasm
cub3d.data
Server Configuration
Nginx
Apache
Cloudflare Pages
server {
listen 80 ;
server_name your-domain.com;
root /var/www/cub3d;
index cub3d.html;
# Required for SharedArrayBuffer
add_header Cross-Origin-Opener-Policy same-origin;
add_header Cross-Origin-Embedder-Policy require-corp;
# Correct MIME types
types {
application/wasm wasm;
application/javascript js;
application/octet-stream data;
}
# Compression
gzip on ;
gzip_types application/javascript application/wasm;
}
< VirtualHost *:80 >
ServerName your-domain.com
DocumentRoot /var/www/cub3d
< Directory /var/www/cub3d >
# Required for SharedArrayBuffer
Header set Cross-Origin-Opener-Policy "same-origin"
Header set Cross-Origin-Embedder-Policy "require-corp"
# Correct MIME types
AddType application/wasm .wasm
AddType application/javascript .js
AddType application/octet-stream .data
</ Directory >
# Compression
< IfModule mod_deflate.c >
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/wasm
</ IfModule >
</ VirtualHost >
Create a _headers file: /*
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
/*.wasm
Content-Type: application/wasm
/*.data
Content-Type: application/octet-stream
Reduce Asset Size
Compress Textures
Optimize BMP files or convert to WebP (requires code changes)
Limit Preloaded Assets
Only include essential maps and textures: LDLIBS += --preload-file maps/hub.cub
LDLIBS += --preload-file assets/textures
Use Lazy Loading
For large asset packs, implement runtime loading instead of preloading
Compilation Optimizations
# Link-time optimization
CFLAGS += -flto
# Aggressive optimization (careful: may break some code)
CFLAGS += -Oz # Optimize for size
# Closure compiler (advanced JS optimization)
LDLIBS += --closure 1
Test thoroughly after enabling aggressive optimizations, as they can introduce subtle bugs.
Troubleshooting
Check browser console for errors. Common causes:
Missing COOP/COEP headers (required for SharedArrayBuffer)
Failed to load .wasm or .data files (check network tab)
CORS errors (use proper HTTP server, not file://)
Increase heap size: LDLIBS += -s INITIAL_MEMORY=536870912 # 512MB
LDLIBS += -s ALLOW_MEMORY_GROWTH=1 # Allow dynamic growth
Browsers require user interaction before playing audio. Add a “Click to Start” overlay.
Ensure gamepad is connected before page loads
Check browser gamepad support: gamepad-tester.com
Press any button to wake up the gamepad
Browser Compatibility
Supported
Required Features
Chrome/Edge 90+
Firefox 90+
Safari 15.2+
Opera 76+
WebAssembly
SharedArrayBuffer
WebGL 2.0
WebAudio API
Gamepad API (optional)
Makefile Targets
Build
Clean
Full Clean
Rebuild
make -f emcc.Makefile
# or
make -f emcc.Makefile all
Next Steps
Building Native Build for Linux/macOS desktop
Controls Learn keyboard and gamepad controls