Skip to content

Web Builds with Emscripten

Cute Framework supports web builds with a GLES3 (WebGL 2) backend renderer. Web builds can also carry the optional WebGPU backend, which CF picks at startup when the browser supports WebGPU and falls back to WebGL 2 otherwise. Getting started with Emscripten is a bit challenging, so hopefully this page can help get you started. Once you get your game building for the web it's usually quite a breeze after the initial setup.

GLES3 Backend Capabilities

The GLES3 backend (web builds, and CF_APP_OPTIONS_GFX_OPENGL_BIT on desktop for debugging it) tracks the SDL_GPU backends closely. The differences that matter:

Feature SDL_GPU (Vulkan/D3D/Metal) GLES3 / Web
Draw API (2D + 3D), meshes, canvases, MRT ✔ ✔
Depth/comparison samplers, cube/3D/array textures ✔ ✔
Render into faces/layers/mips (attach_target) ✔ ✔
Per-target blend states ✔ ✘ (all targets share blends[0])
Standalone samplers (CF_Sampler) ✔ ✔
Range draws / geometry arenas ✔ ✔
Storage buffers (read-only, vertex/fragment) ✔ ✔ (emulated via texture fetches, within the documented pattern: anonymous readonly single runtime vec4/uvec4 array blocks)
Compute shaders ✔ ✔ (emulated with draws, see below)
GPU-writable storage (compute_writable) ✔ ✔ (written by emulated compute shaders)
Indirect draws ✔ ✘
cf_push_gpu_label capture regions ✔ no-op

Everything in the ✔ column is exercised by the test suite on both backends. Shaders following the documented contracts need no changes -- cute_spirv emits GLSL ES 300 with the emulations baked in (storage blocks outside the emulatable shape get clear compile errors).

GLES3 has no compute shaders, so CF runs each dispatch as a couple of draws instead. This covers the common cases, but a compute shader for GLES3 can't use:

  • Shared memory (shared variables)
  • Barriers
  • Atomics
  • Writes to images or storage buffers inside a loop, or outside main()
  • vec3 writes to storage buffers (write a vec4, or one component at a time)
  • and a few shape rules; the compile error names each

Breaking one of these rules is a compile error on GLES3 that names the problem and the line. The WebGPU backend has none of these limits, and its own capability table compares all three backends.

Note

Emscripten builds automatically disable CF's HTTPS support, since web builds suffer from very poor support of this feature.

Networking on Web

Browsers cannot open UDP sockets, so a web build's client sends its datagrams through a wire instead of a socket: call cf_client_web_wire after cf_make_client and before cf_client_connect, and every packet rides a browser transport to a tiny relay next to your game server, which forwards them to the server's normal UDP port -- the server cannot tell the difference. See the Web Clients section of the networking topic for the full architecture, including what the relay has to do (very little). Servers do not run in browsers; cf_server_start fails cleanly on web.

CF_Client* client = cf_make_client(0, APP_ID, false);
#ifdef CF_EMSCRIPTEN
    // Usually derived from location.origin: the host that served the page runs the relay.
    cf_client_web_wire(client, "https://mygame.example/wire/5601");
#endif
cf_client_connect(client, token);

Install Emscripten

Make sure emscripten is installed on your machine, along with Perl.

Tip

Be sure to install any platform-specific dependencies first.

Note

For windows users performing setup with the command prompt you should omit ./ from the Emscripten docs. Instead of typing ./emsdk install latest, try typing emsdk install latest.

  1. Run ./emsdk install latest (or emsdk install latest for Windows users).
  2. Run ./emsdk activate latest (or emsdk activate latest for Windows users).
  3. Windows uers only: Optionally run step 2 with --permanent flag (recommended), e.g. emsdk activate latest --permanent.

You will likely need to call source ./emsdk_env.sh on Linux/MacOS to setup environment variables. For Windows users --permanent may be buggy and not correctly, in this case you must call emsdk_env.bat manually each time you open a new command prompt.

Build CF

If on Windows go ahead and run the web.cmd file. This configures a Ninja build of CF in the build_web folder; then run cmake --build build_web to build it. On other platforms run the same emcmake cmake command from web.cmd yourself. Consult the emscripten docs if you need help.

To include the optional WebGPU backend, add -DCF_WEBGPU=ON to the configure command. See WebGPU for details.

Build your Game

Additionally you can add something like the following to your cmake build script for your own project.

if(${CMAKE_SYSTEM_NAME} MATCHES "Emscripten")
    set(CMAKE_EXECUTABLE_SUFFIX ".html")
    target_compile_options(your_game PUBLIC -O1 -fno-rtti -fno-exceptions)
    target_link_options(your_game PRIVATE -o your_game.html --preload-file ${CMAKE_SOURCE_DIR}/content --emrun -O1)
endif()

Also don't forget to call emscripten_set_main_loop from your main function! The easiest way is to use the macro CF_EMSCRIPTEN to setup your main loop like so:

#ifdef CF_EMSCRIPTEN
    // Receives a function to call and some user data to provide it.
    emscripten_set_main_loop(update, 60, true);
#else
    while (app_is_running()) {
        update();
    }
    destroy_app();
#endif

Where upduate will be run once per game tick, and replace your usually main loop body.

Example Game Project

The Cute Snake game project is a good example of how to setup an Emscripten build with CMake.