Enabling Unity WebGL

@Shehzan_Mohammed As promised on our meeting, I will describe here our experience with trying to enable Cesium for WebGL.

We use a customized version of the Cesium 1.20 package to create the WebGL build on our CI server. We have applied that change to make it possible to build with WebGL target from our Linux server.

As a first step, we enabled C++ Multithreading support for WebGL in the Unity project, as the WebGL build fails otherwise.

Next, we configured our server to use the needed COEP / CORP / COOP headers to make C++ multithreading possible. This was not enough, as we were including some external javascript in our index page, and these were failing to load because of the COEP. This was solved by adding the crossorigin="anonymous" attribute within the script tag.

But our app still fails to load. More specifically, it tries to load the url https://ourserver.com/undefined, but fails. That is a resource that was not being fetched before these changes, and we are still trying to figure out why that happens. From that ‘undefined’, it seems that the url was formed from an undefined javascript variable.

I will update here further if I have more information.

More specifically, it tries to load the url https://ourserver.com/undefined, but fails.

I have found why that happens. That was actually the fetch of the worker javascript file.
We use a customized index.html. We had to add the following line in there:

      var config = {
        ...
#if USE_THREADS
        workerUrl: buildUrl + "/{{{ WORKER_FILENAME }}}" + cachehash,
#endif

That problem probably won’t occur for someone that uses Unity’s default index.html page.

Another problem was that a third party javascript library would fail loading: it tries to access the window object, which is undefined. It seems that the worker.js file loads that script too, where there is no window context. This was fixed by adding the following line to jslib before any window access occurs:

    if (!ENVIRONMENT_IS_WEB)
      return;

We are not done yet. This time our app loads further, but we get a stack overflow somewhere. To be investigated further.

Please note that all this happens before any Cesium is executed at all. These problems are a consequence of enabling multithreaded C++ support in our app.

I’m curious about the server setup challenges you’re facing for the multithreaded Wasm. It’s unfortunate the server requirements is such a pain, due to the past Meltdown and Spectre exploits that plagued browsers. If you could send me a postmortem of what you’ve had to go through, I can share it with the Unity web team so hopefully it could help guide documentation to help guide others.

Hi Brendan.

I had to put this task aside for some time because of priorities. I still have to debug why our app fails to start when we enable multithreaded C++ (it actually hangs somewhere at our loading screen. The loading animation keeps running). My impression is that it is not about COEP now, but some interaction of multithreaded C++ support with our app’s code flow. Please note that we don’t do explicit use of multithreading or Jobs system.

It wasn’t a stack overflow, as I initially thought. Chrome Developer Tools misled me by showing what looked like endless recursive calls from the rendering loop, but it turns out it was simply showing scheduled tasks in the trace (such as scheduler → scheduled task).

I should debug this hang further, maybe through a debug-enabled WebGL build. But we are close to function count limits even on our release build, and it is hard to iterate on WebGL builds locally (because it takes hours to build). I will see what I can do.

Ok, I think I came closer.

It seems that I have overlooked that some Auth0 script still had some cross origin error, such that authentication did not work. It was a request like this that was failing: appserver/index.html → cdn/auth0.js → externalAuthServer/authorize. I got that working temporarily.

The following issues were related to the fact that enabling native C++ multithreading automatically enabled some other flags:

  1. Option ‘Enable BigInt’

We were getting the following error in the console:

Uncaught exception from main loop:

Uncaught TypeError: Cannot convert 4278190080 to a BigInt

Solution: return BigInt(number) instead of number from a .jslib function.

  1. Option ‘Use WebAssebly.Table’

We were getting such an error in the console:

Invoking error handler due toTypeError: Module.dynCall_vi is not a function
at instance.ws.onopen (WebGL.framework.js.gz…)

Solution: replace Module.dynCall_* with {{{ makeDynCall(....) }}} in .jslib files. See Unity - Manual: Replace deprecated browser interaction code.

This time our app itself works ok. When I enable a tileset, I see that root.json file is requested repeatedly from the google server. But I don’t see any tiles on screen.
This is printed repeatedly on the web console:

WebGL.framework.js.g…3f6aa0b5f189a94b:14 The referenced script (Unknown) on this Behaviour is missing!
WebGL.framework.js.g…3f6aa0b5f189a94b:14 The referenced script on this Behaviour (Game Object ‘CesiumCreditSystem’) is missing!

Update on our progress with Unity WebGL builds from Linux CI

We’ve made progress on getting Cesium for Unity working in WebGL builds cross-compiled from Linux. Here’s a summary of what we found and our workarounds.

1. “Missing script” issue on Linux CI builds

PR #634 (adding !UNITY_EDITOR_LINUX defineConstraint to the asmdef) was sufficient to fix cross-compilation from Linux to macOS, but it does not fix Linux-to-WebGL cross-compilation. It makes the build process succeed for both platforms, but the resulting WebGL build throws exceptions at runtime and doesn’t show any Cesium.
It is unclear to us why the same mechanism works for macOS but not WebGL, as the asset serialization process should be the same for both targets.

The root cause: the Cesium package includes resource assets (CesiumCreditSystem.prefab, CesiumRuntimeSettings.asset, ion.cesium.com.asset) that reference CesiumForUnity MonoBehaviours. When the assembly is excluded on the Linux editor, these assets are loaded into memory with broken script references. Unity then uses these broken in-memory representations when building for WebGL, baking the broken references into the output. The on-disk prefab files remain unchanged (Unity does not re-serialize them), but the build uses the in-memory state.

At runtime in the browser, this results in:

The referenced script (Unknown) on this Behaviour is missing!
The referenced script on this Behaviour (Game Object 'CesiumCreditSystem') is missing!

Notably, Resources.Load<GameObject>("CesiumCreditSystem") returns a non-null GameObject, but with null MonoBehaviour components. The prefab is there, but the scripts are gone.

Our workaround: We override ResourcesAPI.Load to intercept Cesium resource requests and create the objects from code at runtime (CesiumCreditSystem with CesiumCreditSystemUI + UIDocument components, CesiumIonServer, CesiumRuntimeSettings). At runtime in the WebGL player, the CesiumForUnity assembly is compiled and available, so this works. The pure Unity assets referenced by the UIDocument (PanelSettings, VisualTreeAsset) survive the Linux build without issues since they have no script references.

2. Physics.BakeMesh crash in WebGL

After fixing the missing scripts, Cesium loads and starts rendering in WebGL. However, the app crashes after some time with:

RuntimeError: memory access out of bounds
    at il2cpp::icalls::mscorlib::System::Diagnostics::StackFrame::get_frame_info(...)
    ...
    at Physics::CollisionMeshCooking::CookMeshFromUnityMesh(...)
    at PhysicsManager::BakeMesh(...)
    at CesiumForUnityNative::UnityPrepareRendererResources::prepareInLoadThread(...)

The crash chain: Cesium calls Physics.BakeMesh during tile preparation. PhysX mesh cooking fails on large terrain triangles (>500 units). Unity tries to log the error. The stack trace extraction itself crashes with an out-of-bounds memory access in WASM. The original PhysX error might have been recoverable, but the crash in the error-reporting path kills the app.

Our workaround: Setting createPhysicsMeshes = false on the tileset for WebGL builds. This means no physics raycasting against Cesium terrain on WebGL, but it prevents the crash.

Note on desktop: The same BakeMesh path produces ~27,000 warning lines in the log within seconds of navigating (“Detected one or more triangles where the distance between any 2 vertices is greater than 500 units”). This doesn’t crash on desktop but has significant performance and disk usage implications.

Related: Errors in Playmode after Updateing to Unity 6