Skip to content

Conversation

@azrogers
Copy link
Contributor

@azrogers azrogers commented Jul 2, 2025

What is this?

We get a lot of requests for WebGL support in Unity. Unfortunately, that would require a WebAssembly build of Cesium Native, which we don't have. We have always put this task somewhere in the far future on account of it seeming like a huge amount of work, but after getting so many requests for WebGL at the Cesium Developer Conference last week, we thought it might be worth spending a few days testing it out to get an idea of how much work it would really be.

It turns out: not nearly as much as you might think!

This PR adds a fully working build of Cesium Native for WebAssembly using Emscripten. You can open up build/CesiumNativeTests/cesium-native-tests.html in your web browser (using npm run test-wasm to spin up a web server with the correct HTTP headers for WASM threading) and see the entire test suite* run right there in the browser.

But this does NOT mean we are ready to integrate with Unity WebGL. The primary problem is that we are using Wasm64**, and Unity seems to use Wasm32 (as it has much more support than Wasm64). Building for Wasm32 is theoretically possible, but Cesium Native has never been designed to build on anything other than a 64-bit architecture. Attempting to build for Wasm32 quickly fails on account of us assuming throughout the codebase that sizeof(size_t) == 8.

But I think this is a good proof-of-concept. A WebAssembly build to support Unity WebGL builds is entirely within the realm of possibility.

* The entire test suite, minus one test in CesiumGltfWriter which tries to allocate a >4GB buffer to see if the writer will reject it. In theory, since we're building for Wasm64, this should work, but it does not. But because this test isn't really testing any essential features, it doesn't seem worth the time to debug.

** I'd just like to interject for a moment. What you're referring to as Wasm64, is in fact, Wasm32/Memory64, or as I've recently taken to calling it, Wasm32 plus Memory64.

What did it cost?

Now that I've gotten all that preamble out of the way, here's a non-exhaustive list of some of the horrible hacks I had to perform to make CMake, vcpkg, and Emscripten happy with what I was trying to do:

  • Because vcpkg works via toolchain file, and Emscripten also works via toolchain file, the Emscripten toolchain is just straight included into the CMakeLists.txt at the top. This is not how toolchains are supposed to work, but you don't seem to be able to use two at once.
  • And because we force included the Emscripten toolchain, we have to reset the CC and CXX variables because vcpkg, in its infinite wisdom, tries to reset them to use whatever normal C & C++ compiler you use.
  • CMAKE_SIZEOF_VOID_P needs to be manually overridden to 8 (as does SIZEOF_SIZE_T in the vcpkg triplet) because CMake can't tell that the -sMEMORY64=1 parameter makes this a wasm64 build, not a wasm32 build.
  • spdlog had to be told to use std::format instead of fmt::format because the latter was giving constexpr errors.
  • KTX, despite being designed to build for WebAssembly, fails to build for WebAssembly. Ironically, the part that fails to build is the bit in the C++-to-JS interface. There's no reason this should fail, but it does. So I just excised that bit from the overlay port.
  • The executable extension is set to .html, because that will prompt Emscripten to generate a harness for running the built wasm and js in the browser. This breaks doctest, which tries to run the built binary to generate a list of tests for CTest and fails because it doesn't know how to possibly execute an HTML file.
  • But even without that, doctest would fail to run the file because Emscripten, for whatever reason, forces the minimum node version to run the built code to be Node 23 or above. This is a problem because there is no version of the Emscripten SDK that ships with a Node version higher than 22. So we manually override the minimum Node version check, incurring a warning in the process, so doctest won't crash in the final stretch of the build. But this is moot because we commented it out to solve the previous issue.

Besides that, everything was pretty straightforward! spdlog was the only library that required code changes, for whatever reason. There's also a bit of code in there to run the file_packager script, which turns all of the test data on the file system into an object file we can link with the generated WebAssembly so the tests actually work.

How do you use it?

It should be pretty simple! Just grab yourself a copy of the Emscripten SDK and run your CMake commands as normal, just prepending an emcmake to the configure call to set up the correct context. For example:

emcmake cmake -B build -S . -DCMAKE_BUILD_TYPE=Debug
cmake --build build --config debug --target cesium-native-tests -j22

Once it's built, you should be able to run the generated WebAssembly using either node (node build/CesiumNativeTests/cesium-native-tests.js) or using your browser (either Chrome or Firefox, which support Memory64). When testing through the browser, you'll need to spin up a local server using the npm run test-wasm command. This is because, to be able to access the SharedArrayBuffer for sharing memory between threads, the server needs to send the correct Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy headers (read more here).

@timoore
Copy link
Contributor

timoore commented Jul 2, 2025

  • Because vcpkg works via toolchain file, and Emscripten also works via toolchain file, the Emscripten toolchain is just straight included into the CMakeLists.txt at the top. This is not how toolchains are supposed to work, but you don't seem to be able to use two at once.

If you want to do this the vcpkg way, you can use the VCPKG_CHAINLOAD_TOOLCHAIN_FILE variable to arrange for vcpkg to load another toolchain file after its own. Unfortunately, this toolchain file won't affect all the ports that vcpkg builds! To do this right you need to define a triplet for vcpkg that also loads the toolchain file...

@azrogers
Copy link
Contributor Author

azrogers commented Jul 3, 2025

@timoore That makes sense! For some reason I'd assumed that does the opposite - use the toolchain for the ports and not for the rest of the CMake build.

@duckblaster
Copy link

vcpkg already has a community supported wasm triplet, so you should be able to change the triplet and set the required environment vars to point to the emscripten sdk.

@azrogers
Copy link
Contributor Author

azrogers commented Jul 3, 2025

@duckblaster It doesn't have a community-supported triplet that supports 64-bit wasm builds, as far as I can tell. But we are using a triplet to load Emscripten for vcpkg. The part that's tricky is loading Emscripten out of vcpkg, to use in the rest of the build.

@duckblaster
Copy link

@azrogers ah, makes sense.
I have only used vcpkg to build a couple of ports that already had wasm support.

@JesseGuerrero
Copy link

JesseGuerrero commented Aug 23, 2025

Just showing interest. I would love to be able to use WebGL with Unity Cesium :D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Investigate the use of cesium-native in a WebAssembly build

5 participants