SLProject  4.2.000
A platform independent 3D computer graphics framework for desktop OS, Android, iOS and online in web browsers
Emscripten

SLProject can be built to run in a web browser. We use the Emscripten toolchain, which compiles C/C++ code to a binary instruction format called WebAssembly. The generated Wasm module can be loaded by ordinary JavaScript at runtime. The browser then compiles the instructions to native machine code and runs it, making it possible to run high-performance code in the browser. To access OpenGL functions, browsers expose the WebGL API, which Emscripten wraps in standard OpenGL headers. SLProject uses WebGL 2, which is based on OpenGL 3.0 ES. Not all scenes from app-demo can run in the browser because OpenGL 4.0 functions are not available or because some OpenCV modules can't be compiled for WebAssembly.

How it works

The Emscripten toolchain contains a modified Clang compiler, some runtime libraries, an implementation of the C, C++ and POSIX APIs, and ports of some popular libraries such as SDL, GLFW, libpng or zlib. This allows us to take code written for desktop platforms and port it to the Web without much effort.

Emscripten uses standard browser APIs to implement its libraries. For example, a call to the C function printf might use the JavaScript function console.log internally. Here is how a few commonly used libraries are implented behind the scenes:

Browser Interaction

Even though Emscripten provides APIs for most browser features, it is sometimes still necessary to call some JavaScript functions directly. For example, there is currently no camera API. Emscripten provides a EM_ASM macro to embed JavaScript code directly into C/C++ code, which is comparable to inline assembly in native compilers. This allows us to call browser APIs directly in C++ and to pass values between JavaScript and C++.

JavaScript Is Asynchronous

JavaScript runs its code in a event loop. Code is executed when an event is triggered, such as a keypress or a new animation frame. The browser can only continue its work when the event is processed by the JavaScript code. This is why you can't run an infinite loop in JavaScript as this would block the browser window. JavaScript APIs usually don't block for this reason, but return a Promise that represents an asynchronous operation. The calling code can then specify callbacks that are run when the operation completes or fails.

This asynchronous thinking leads to some limitations when developing for Emscripten:

  • There is no main while loop in AppEmscripten.cpp. Instead, we submit a function to the browser that is called when a new animation frame is available. The Emscripten function to do this is emscripten_request_animation_frame.
  • I/O functions are not allowed on the main thread. I/O operations like downloading files from a server cannot run on the main thread because they run synchronously and block their thread. This problem is usually handled by submitting a task to the SLAssetLoader to download the files on its worker thread instead.
  • Core assets like fonts or common shaders are loaded asynchronously. On all platforms other than Emscripten, assets that are used in all scenes are loaded synchronously before starting the main loop. On Emscripten, this task is done on the worker thread of the SLAssetLoader because I/O is not allowed on the main thread.
  • The number of threads is limited. The Emscripten implementation of C++ threads can only start the backing Web Worker once we yield to the browser event loop. This means that after spawning a thread, we cannot use it directly because its Web Worker hasn't started yet. The solution is to create a pool of Web Workers before running the main function that are used when a new thread is created. This is done by adding the linker flag -sPTHREAD_POOL_SIZE=navigator.hardwareConcurrency + 4 that sets the Web Worker pool size to the number of logical processors available plus 4 for good measure. For more information, see the Pthreads page in the Emscripten docs.

File I/O

When running natively, SLProject uses the file system to load and store data. In the browser environment, the type of storage depends on the asset type.