Unity WebGL Secrets

Oren De-Panther Weizman
5 min readSep 21, 2021

--

or Tricks and Tips for Unity WebGL and WebAssembly.

Intro

Unity WebGL exists for a few years now, but there are still some less known features and options.
Through my work at Resonai in the last couple of years on Web AR, and by working in my personal time on the WebXR Export Unity package, I learned some of those.

In this article, I’ll demonstrate some tricks I used for communication / interoperability between the web page and the Unity WebGL build, demonstrated using my repository.

Unity WebGL

First, what is Unity WebGL?
I think that Unity WebGL should actually be named Unity Web.
Yes, Unity does use WebGL to render the projects, but the code is not compiled to WebGL, as WebGL is only a graphical library for rendering. In the past Unity compiled the code using emscripten to ASM.JS and now it mainly compiles the code using emscripten to WebAssembly.
And for input/output Unity uses SDL which has a port for emscripten.

Wait wait wait, there were lots of name drops here.
emscripten is a toolchain that enables C/C++ code to run on the web using WebAssembly.
SDL, Library designed to provide a hardware abstraction layer for computer multimedia hardware components. emscripten has a port of this library, and Unity use it in some ways to more easily port their code to WASM.
WebAssembly or WASM, a binary-code format that can run on modern browsers.

And from time to time I might mention WebXR, which is a standard for running AR/VR experiences on the web.

So let’s dig into Unity and WebGL.

Unity Documentation

Unity docs have a manual page with basic stuff, like how to send a message to the page, or how to send a message from the page. The samples there are mostly to show the possibilities, some features and options are missing.
We know that Unity uses emscripten and SDL, so we can look at those tools and libraries, and learn how they implemented interop.

Recently the docs updated with some new samples, one of them is passing arrays from Unity to JavaScript. Understanding the concept there will be handy for some of the next parts.

Important Paths

Some important paths to look at, are:

  • UNITY_INSTALL_FOLDER/Editor/Data/PlaybackEngines/WebGLSupport/BuildTools — The build tools folder, where the WebGLTemplates, emscripten, JavaScript libs and other web platform tools are.
  • UNITY_INSTALL_FOLDER/Editor/Data/PlaybackEngines/WebGLSupport/BuildTools/Emscripten — The emscripten toolchain Unity uses to build for the web.
  • UNITY_INSTALL_FOLDER/Editor/Data/PlaybackEngines/WebGLSupport/BuildTools/Emscripten/emscripten-version.txt — The emscripten version, in Unity 2020.x it’s 1.38.11.
  • UNITY_PROJECT_FOLDER/Temp/emcc_arguments.resp — The command line arguments when calling emscripten’s emcc compiler.
  • PROJECT_BUILD_FOLDER/Build — Output folder with the .wasm file and the *.framework.* file (contains the WASM Module JavaScript code).

JSLIB and JSPRE

Unity has info about .jslib files on the docs, but digging the WebGL forum, you’ll hear about .jspre files.
They are both JavaScript files, they are both appended to the final JS Module *.framework.* file (one of the outputs of Unity when building for web).
As described in Unity’s docs, .jslib is there to add JavaScript methods that you can interact with from C# (or C/C++ if you need).
The .jspre file is there to add JavaScript code at the start of the Module in the framework file, it’s useful when you want to add code to re-use in a number of methods in the .jslib file, or if you want to add a large library and use its methods.
In WebXR Export there’s webxr.jspre that contains almost all the WebXR functionality that we need on the JavaScript side. There’s also webxr.jslib that contains a few methods to call from C#.

Looking at emscripten, the emcc command and the emcc_arguments.resp file, the .jspre files are added to the build using the --pre-js option, while the .jslib are added using the --js-library option.

All that means that we can actually edit the way that Unity communicates with the web page, from within Unity, with no custom changes to the engine.

We can override some existing calls of the different Web APIs that emscripten wraps. Do you want a transparent background and see the web page behind the WebGL canvas? Override the WebGL clear call and ignore it when needed, Unity has an article that explains how to. WebXR Export uses a modified version of the code in the article for transparent background when in WebXR AR session.

var LibraryGLClear = {
glClear: function (mask) {
if (mask == 0x00004000 && GLctx.dontClearAlphaOnly) {
var v = GLctx.getParameter(GLctx.COLOR_WRITEMASK);
if (!v[0] && !v[1] && !v[2] && v[3])
// We are trying to clear alpha only -- skip.
return;
}
GLctx.clear(mask);
}
};
mergeInto(LibraryManager.library, LibraryGLClear);

If you look at the code, glClear is declared. In build time it overrides the existing call, and in the final *.framework.* file it shows as _glClear.

Calling C# from JavaScript

The docs mention the SendMessage() method, this method looks for strings of GameObjecs, components and methods names. This can be ok for one time event, or events that rarely occur. It also has the disadvantage of making sure that in the Unity scene there’s only one GameObject with a specific name, and that it has the required component. Not so friendly when working on tools to distribute.

Depending on the needs, we can use different emscripten methods.
Looking at the emscripten docs there are the methods ccall() and cwrap(). They can help when you want to call a C method, they are faster than SendMessage() but they still have some overhead as they rely on strings. As they can only be used with C/C++ code in Unity, and already have samples in the emscripten docs, I won’t add a sample here.

A Lesser known option is the Module.dynCall_sig(methodPtr, optionalArgs...) methods. Where sig is the C#/C/C++ method signature.

Signature table from emscripten docs:
'v': void type
'i': 32-bit integer type
'j': 64-bit integer type (currently does not exist in JavaScript)
'f': 32-bit float type
'd': 64-bit float type

Looking at the table, a signature of a void method that gets an integer, would be vi and we can call this method with Module.dynCall_vi(methodPtr, number).
On the Unity side, we need to make sure that the method is static and has the MonoPInvokeCallback attribute.
Passing C# method pointer to JavaScript is similar to passing a float array pointer, this is how we can get the methodPtr.
In WebXR Export, SetWebXREvents is called in WebXRSubsystem and caching the methods pointers in webxr.jslib then we can call those methods in webxr.jspre.

Debugging

The emcc command has the --profiling-funcs option, in some cases you would want to debug your build, without using the development build option of Unity. The --profiling-funcs option will keep somewhat readable names for the classes and methods in the WASM file, so you’ll be able to trace the code when using the browser dev tools.
To activate this option, you can add it in Unity to an Editor C# script to the PlayerSettings.WebGL.emscriptenArgs parameter.

PlayerSettings.WebGL.emscriptenArgs = "--profiling-funcs";

Alternatively, you can look for a parameter named webGLEmscriptenArgs in UNITY_PROJECT_FOLDER/ProjectSettings/ProjectSettings.asset file and set it.

webGLEmscriptenArgs: --profiling-funcs
Chrome Performance DevTools, zoom in on some of the Unity Canvas and AudioSource components timeline
Chrome Performance DevTools, zoomed in on some of the Unity Canvas and AudioSource components timeline

Final thought

Unity uses open source tools to build for the web, familiarization with them can help for interop with the web page, debugging purposes and other advanced needs.

If you liked this article and would like more of those, please comment.

Cheers,
Oren

--

--

Oren De-Panther Weizman

Co-Founder and Game Developer at Total-Viz. Making AR/VR/WebXR related stuff