It’s been a while since we announced our intention to switch to WebAssembly (a.k.a. Wasm) as the output format for the Unity WebGL build target. Since Unity 2018.2 is the release that finally delivers this change, we would like to explain how we got to this point and what this means for all of you who make interactive web content with Unity.
The Road to Wasm
We released WebAssembly support in Unity 5.6 as an experimental feature, more or less when it also became available in the four major desktop browsers. Since then, several improvements and bug fixes have been implemented in Unity as well as in the browsers. In the meantime, user adoption increased, and the feedback we received was positive. So the next step was obviously to support it officially: Unity 2018.1 marked the removal of the experimental label and, at the same time, we made it possible to make Wasm-only builds.
Then in 2018.2, Wasm finally replaced asm.js as the default linker target.
This means Unity 2018 LTS will default to Wasm when you’re publishing for the Unity WebGL build target.
This is an important milestone for us since we’ve been working towards this goal for a while. We had a few requirements: we needed to make sure Unity’s implementation and browser support were stable, and we needed internal test coverage for Wasm, which involved upgrading Emscripten and fixing several issues in our code.
Today we have Wasm variations of all our test suites, so any changes that will be merged into our mainline have been tested against WebAssembly:
Note that we still maintain and run asm.js test suite, but now every change that goes into the mainline is tested against Wasm.
WebAssembly vs asm.js
Let’s take this opportunity to talk about WebAssembly more in detail and go over the major differences compared to asm.js.
Wasm is faster, smaller and more memory-efficient than asm.js, which are all pain points of the Unity WebGL export. Wasm may not solve all the existing problems, but it certainly improves the platform in all areas. Nevertheless, please be mindful that performance may vary depending on the browser implementation. The good news is that all vendors are committed to supporting and improving it.
When you open a Wasm file, you will immediately notice that it’s a binary file, as opposed to asm.js, which is text. This is a more compact way to deliver your code, but it also makes it impossible to read or change for debugging purposes.
Wasm has its own instruction-set, whereas asm.js is a “highly optimizable” subset of Javascript. In Development builds, WebAssembly adds more precise error-detection in arithmetic operations, which can throw exceptions on things like division by zero, rounding a large float to an int, and so on. In non-development builds, this kind of detection of arithmetic errors is masked, so the user experience is not affected.
Code Size
To generate WebAssembly, we have a complex toolchain (based on IL2CPP, emscripten and binaryen) that will transform C/C++ and C# code to WebAssembly. This produces a binary file (<build name>.wasm.code.unityweb), which results in smaller builds than asm.js.
Whereas, the code size for development builds is tens of MBs smaller, for non-development builds, it’s smaller by several hundred KBs. Just to give you an idea of the baseline, the code size for an empty project is ~12% smaller or ~18% if 3D physics is included.
Note that this has been measured excluding all unnecessary packages, excluding all built in shaders and using Brotli as the compression format.
As with any improvement (performance, memory, load-times), your mileage may vary depending on your project.
Memory
One of the limitations we had with asm.js was the restriction on the size of the Unity Heap: Its size had to be specified at build-time and could never change. WebAssembly enables the Unity Heap size to grow at runtime, which lets Unity content memory-usage exceed the initial heap size specified at startup.
This means you can make your content start with a small heap (let’s say 32mb) and let it grow as needed, which was not possible before.
Think of the Memory Size value as the initial size that your content starts with. This is a feature built in 2018.2, so you can take advantage of it today. However, this approach is not possible if you are targeting asm.js as well, since the Heap can’t resize.
Just keep in mind that the browser might still run out of memory if the Heap grows too much. How much is “too much” depends on the browser. To get consistent behavior across browsers, set up the maximum size to the Unity Heap. You can do this by setting the emscripten argument “-s WASM_MEM_MAX=<value>” in editor script, for example:
Note that the Maximum Memory Size is 2032 and that any value larger than that will result in a run-time error in the browser.
Lastly, Wasm will be more memory-efficient at load time. Therefore reducing out of memory problems that many users experienced with asm.js, especially on 32-bit browsers.
For more information on how memory works in Unity WebGL, read this blog post.
Performance
The performance difference between Wasm and asm.js depends on the browser. As a binary format, Wasm has the potential to load up much faster than asm.js, which is parsed as a JavaScript text file.
In addition, wasm-code modules that have already been compiled can be stored into an IndexedDB cache, resulting in a really fast startup when reloading the same content. To take advantage of Wasm caching, just make sure the Data Caching option is enabled.
After startup, execution speed will be comparable to asm.js on browsers that are already optimized for the asm.js style of code in their JavaScript engines. If you are running Wasm on a browser that previously did not recognize asm.js, Wasm should noticeably speed things up.
Depending on the code, some instructions might be faster in Wasm, such as 64-bit integer arithmetics, which asm.js does not have specific instructions for.
Multi-Threading
WebAssembly multi-threading support is probably the most awaited feature, and the one which will improve performance the most. It was supposed to ship in browsers earlier this year but SharedArrayBuffer support, one of the building blocks to make this possible, had to be disabled because of security concerns due to Spectre and Meltdown. Thankfully, in the last few months, browsers have been putting in place a number of security measures in order to be able to re-enable SAB, and we are seeing signs that they are ready to ship in upcoming versions.
On the Unity side, we want to be ready for when that happens so we are actively working on Wasm multi-threading support, which will initially be released as an experimental feature in the next few months and will only be limited to internal native threads (no C# threads yet). By internal, we mean job threads for skinning, animation, culling, AI pathfinding and other subsystems. They might not be all enabled at the beginning, but our long-term goal is to take advantage of multi-threading as much as possible.
Debugging
Debugging has always been a challenge with asm.js. Unfortunately, it hasn’t gotten better with WebAssembly yet. While browsers have begun to provide WebAssembly debugging in their devtools suites, these debuggers do not yet scale well to Unity3D sizes of content. The good news is that Wasm has been designed to be “open and debuggable” so you can expect in the future that browsers will provide better tools for this purpose. In the meantime, you can use other debugging techniques:
- Often times, issues in Unity WebGL builds come about in the layer where the built game interacts with the browser APIs. This interaction surface resides in UnityLoader.js and <build name>.asm/wasm.framework.unityweb, which contain easily readable JavaScript code, that is readily debuggable via in-browser devtools.
- For debugging C# code, Debug.Log() is often the only option, so it’s really recommended to debug on other platforms when possible.
- For advanced debugging, try exporting to asm.js to be able to annotate the generated asm.js content with console.log().
It’s also worth mentioning that 2018.2 just added Managed code debugging support for IL2CPP. which we will start experimenting with as soon as we have WebAssembly multi-threading support implemented.
Future
Browser vendors are committed to continue improving WebAssembly support. In fact, since they shipped the MVP (Minimum Viable Product), they kept working on new features as well as optimizations that improve startup times and performance, such as:
- Asynchronous Wasm instantiation (supported in Unity)
- Wasm structured cloning, which allows compiled Wasm to be cached in the browser (supported in Unity)
- Baseline and tiered compilation, to speed-up instantiation (automatically supported when running Unity content)
- Streaming instantiation to compile Wasm code while downloading it (support in Unity is under consideration).
- Multi-Threading (support in Unity is in progress).
Note that some of the features above are already implemented, depending on the browser. For more information about future feature specifications and their status, check this page.
In conclusion, we strongly believe in WebAssembly and we encourage developers to use it by default too. If needed, it’s possible to keep asm.js as a runtime fallback for old browsers. This can be achieved by selecting WebGLLinkerTarget.Both in the WebGL Player Settings.
Just be aware that we plan on deprecating asm.js in 2018.3. This means that going forward, asm.js will not get any of the Wasm-specific improvements, such as multi-threading, SIMD and so on. Having said that, it will still be available in 2018 LTS, which we’re going to officially support for two years, following its release date at the end of the year.
Check here if you want to know if a specific browser supports WebAssembly.
In the next blog post, we will look at some benchmarks to see how the browsers compare to each other. In the meantime, we look forward to hearing your feedback.
Thrawn
8月 16, 2018 10:52 amBringing multi-threading support and dynamically sized heap alone are great improvements!
Peter Steinberger
8月 16, 2018 8:26 amWe‘ve seen quite mixed results, and asm.js still overall being faster for certain browsers in the WebAssembly benchmark we built: http://iswebassemblyfastyet.com
Interested to compare notes.
Santiago Machad
8月 16, 2018 3:30 amGreat step! each time more nearly to produce game for FB-InstantGame, (?) =D Tell me yes please!
Marco Trivellato
8月 16, 2018 10:29 amFor Messaging Apps games you may want to wait for Unity for Small Things (https://unity.com/solutions/unity-for-small-things) which has been designed for that use-case.
Kamil
8月 15, 2018 10:56 pmI assume Wasm implementation in 5.6.x is completely different (and obsolete) vs 2017 vs 2018? Or was it relatively stable and mostly dependant on browser vendors?
We are stuck on 5.6.6 at the moment but would love to use Wasm. Recommendations on it’s evolution since it’s initial introduction?
Marco Trivellato
8月 16, 2018 10:10 amHi Kamil, It’s not completely different and the number WebAssembly-specific bugs we received since we released 5.6 is relatively low. However, we did upgrade Emscripten and Binaryen in 2018.2 which come with a lot of bug fixes. In addition, in 5.6 we did not have Wasm caching and async instantiation support. Anyway, I would recommend you to try it on your project to see if you run into any issue.
chan
8月 15, 2018 10:21 pmNice to see that WebAssembly is now in use. I was curious about getting around with 2 GB browser memory limit. Is there a function to get how much memory is in use during the gameplay? If that would be possible to determine, the game could stop downloading or generating content when current memory usage is getting close to 2 GB. Download or generation could be resumed once old content is unloaded and more memory is available. This could prevent “out of memory” crashes in any circumstances. Once again, is there a function to get current memory use while in WebGL runtime?
De-Panther
8月 15, 2018 9:14 pmSo we’ll have FMOD support once browsers will support Multi-Threading?
Marco Trivellato
8月 15, 2018 10:02 pmThat is a very good question. It’s should be possible to compile FMOD with Emscripten (like we do for for PhysX) but this is something we haven’t looked at yet.
David`
8月 15, 2018 4:16 pmHi,
Just a small question : does it change anything about networking for webgl ? I mean feature or design wise not talking about performances.
Thanks!
Marco Trivellato
8月 15, 2018 7:19 pmHi David,
at the moment nothing changes in terms of feature set.
Ricardo
8月 15, 2018 4:05 pmNow the WebGL exporter (or should I say Wasm exporter maybe?) is getting serious in Unity. Looking forward to see this tech evolving.
Kevin
8月 15, 2018 3:24 pmSo what about using Unity WebGL on mobile phones? currently it is not supported yet, it gives a warning that it is not officially supported.
Will there be a time where this will be supported?
It is always still more performant to make an iOS / Android build but for maybe a lightweight project it would be nice if it were supported.
Marllon Vilano
8月 15, 2018 5:33 pmIn the “browsers supported” page, I see that “Chrome for Android version 67” is supported by WebAssembly. I’m curious to know if is it possible to run the Unity WebGL build in that version :)
Marco Trivellato
8月 15, 2018 8:02 pmHi Kevin, success-rate on mobile browsers depends a lot on which browser/os/device as well as on the type of content. This is the main reason for not supporting mobile browsers yet. Having said that, there is nothing preventing you from running on mobile, apart from the warning which can be disabled.
Francois Laberge
8月 15, 2018 2:46 pmIs there a way to use this to generate WebXR experiences?
Kevin Reynolds
8月 15, 2018 11:38 pmMozilla has released an asset store package for using Unity WebGL builds for WebVR