Web / Emscripten

micro supports building for the browser via Emscripten. The game loop, window, and renderer work transparently — no changes to your game code are required.

Prerequisites

  • Emscripten SDK installed and activated: bash emsdk install latest emsdk activate latest source ./emsdk_env.sh # Linux / macOS emsdk_env.bat # Windows
  • CMake 3.15+

Building

Use emcmake to wrap the CMake configure step so that the Emscripten toolchain is applied automatically:

emcmake cmake -S . -B build-web -DMICRO_BUILD_EXAMPLES=ON
cmake --build build-web

Each example is compiled to a self-contained .html file alongside a .js and .wasm file in the build output directory.

Running in the browser

Browsers block local file:// access for security reasons, so the output must be served from a static file server:

# Python — usually the quickest option
python -m http.server 8080 --directory build-web/examples/minimal

Then open http://localhost:8080/minimal.html.

How the loop works

window::run() uses a plain blocking while loop on all platforms. On Emscripten this works transparently via Asyncify (-sASYNCIFY=1), a compile-time transformation that rewrites the Wasm binary so the loop can yield to the browser between frames without any changes to your code. The resulting binary uses only standard WebAssembly and is supported by all modern browsers.

// Identical code on native and in the browser — no #ifdefs, no callbacks
win.run([&](float dt) {
    pos += vel * dt;
    rend.clear(micro::color::black);
    // draw things…
    rend.present();
});

Fullscreen

Calling window::set_fullscreen(true) delegates to the browser Fullscreen API. The browser requires this call to originate from a user gesture (a key press or a mouse click); requests made outside one are silently ignored.

win.run([&](float dt) {
    if (kb.just_pressed(micro::key::f)) {
        win.set_fullscreen(!win.fullscreen());
    }
    // …
});

Asset loading

std::filesystem paths are resolved inside the Emscripten virtual file system (a simple in-memory FS that exists only for the duration of the page). To bundle assets with the build, add a --preload-file link option to your target:

if(EMSCRIPTEN)
  target_link_options(mygame PRIVATE
    "SHELL:--preload-file ${CMAKE_SOURCE_DIR}/assets@/assets"
  )
endif()

The src@dst syntax mounts your local assets/ directory at /assets inside the VFS. Use the absolute VFS path in your C++ code to be explicit:

auto tex = micro::texture::load(rend, "/assets/sprite.png");
auto snd = micro::audio::sound::load(mix, "/assets/theme.ogg");

!!! note Emscripten's default working directory is /, so the relative path "assets/sprite.png" also resolves correctly — but the leading / makes the intent unambiguous.

Complete example

A minimal project with web support enabled:

CMakeLists.txt

cmake_minimum_required(VERSION 3.15)
project(mygame)

add_subdirectory(micro)

add_executable(mygame main.cpp)
target_link_libraries(mygame PRIVATE micro)

if(EMSCRIPTEN)
  set_target_properties(mygame PROPERTIES SUFFIX ".html")
  target_link_options(mygame PRIVATE
    -sASYNCIFY=1
    -sALLOW_MEMORY_GROWTH=1
    "SHELL:--preload-file ${CMAKE_SOURCE_DIR}/assets@/assets"
  )
endif()

Configure and build:

emcmake cmake -S . -B build-web
cmake --build build-web

Serve and open:

python -m http.server 8080 --directory build-web
# → http://localhost:8080/mygame.html

The build directory will contain mygame.html, mygame.js, and mygame.wasm (plus mygame.data if --preload-file was used). Opening the .html file is all that is needed.

Limitations

Feature Status
Window & renderer ✅ Fully supported
Keyboard & mouse input ✅ Fully supported
Logical resolution / fullscreen ✅ Supported (fullscreen requires user gesture)
Image & texture loading ✅ Supported (use --preload-file for assets)
Font rendering ✅ Supported
Audio ✅ Supported (use --preload-file for audio assets)
Threading ℹ️ Single-threaded by default

Further reading