Skip to content
Jonathan-Schnee edited this page Feb 4, 2021 · 7 revisions

⚠️ Pre-Release Content

WebAssembly

Introduction

WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable target for compilation of high-level languages like C/C++/Rust, enabling deployment on the web for client and server applications. (see: webassembly.org).

The Mono project managed to compile the dotnet runtime to Wasm (see: github.com/mono/sdks/wasm). Therefore it's possible to run native C# DLLs within the browser.

Furthermore, Wave Engine (waveengine.net) developers are providing their implementation of .NET bindings for WebGL through WebAssembly WebGL.NET to the public.

These tools enable Fusee to compile web applications without relying on JSIL cross compilation anymore (from v0.8 onward).

Usage

A minimal Fusee Wasm project needs, besides the actual Fusee core application, at least, the following two user generated files:

index.html

The following *.js files needs to be included within <head>:

<script src="runtime.js"></script>
<script defer src="Scripts/mono_support.js"></script>
<script defer src="Scripts/SkiaSharp.js"></script>
<script defer src="canvaskit.js"></script>
<script defer src="dotnet.js"></script>

The following javascript snippet starts the C# program inside <body>:

        var App = {
            init: function () {

                BINDING.call_static_method("[Fusee.NAMESPACE] Fusee.NAMESPACE.CLASS_NAME:METHOD_NAME", []);
            },
        };

A C# class with this or a similar content (replace NAMESPACE and CLASS_NAME accordingly)

namespace Fusee.NAMESPACE
{
    internal static class CLASS_NAME
    {
        public static void METHOD_NAME()
        {
            // This method takes care of everything
            WebAsmProgram.Start(new MyWasmImpl());
        }
    }

    public class MyWasmImpl : WebAsmBase
    {
        private RenderCanvasImp _canvasImp;
        private Core.MyFuseeApp _app;

        public override void Run()
        {
            base.Run();
            // Inject Fusee.Engine.Base InjectMe dependencies
            IO.IOImp = new Fusee.Base.Imp.WebAsm.IOImp();

            // AssetProvider
            // ...
            //

            // create an instance of the actual Fusee core application
            // which can be found in another project
            _app = new Core.MyFuseeApp();

            // Inject Fusee.Engine InjectMe dependencies (hard coded)
            _canvasImp = new RenderCanvasImp(canvas, gl, canvasWidth, canvasHeight);
            _app.CanvasImplementor = _canvasImp;
            _app.ContextImplementor = new RenderContextImp(_app.CanvasImplementor);
            Input.AddDriverImp(new RenderCanvasInputDriverImp(_app.CanvasImplementor));

            // Start the app
            _app.Run();
        }

        public override void Update(double elapsedMilliseconds)
        {
            if (_canvasImp != null)
                _canvasImp.DeltaTime = (float)(elapsedMilliseconds / 1000.0);
        }

        public override void Draw()
        {
            _canvasImp?.DoRender();
        }

        public override void Resize(int width, int height)
        {
            base.Resize(width, height);
            _canvasImp.DoResize(width, height);
        }
    }
}

Furthermore one needs the following files which can be copied from any example project:

  • canvaskit.js
  • canvaskit.wasm
  • Scripts/mono_support.js
  • Scripts/SkiaSharp.js

Anything else will be added/copied during compilation.

Please make sure to include the FuseeWasmCopyInheritedAssets target within your *.csproj file, as well as the WebAssembly.* references and the MonoWasmLinkSkip property. The easiest way is to just copy an example project *.csproj and modify it to your needs.

👷 Engine Developer

Short implementation overview of WebAssembly in Fusee

As written above, Fusee utilizes mono's dotnet for Wasm implementation on the one hand, on the other hand, WebGL.NET bindings generated by the WaveEngine team.

The architecture of Fusee allows separate implementations for arbitrary renderer and build targets. To implement the new WebGL Wasm backend, one needs to implement the RenderContextImp which implements the IRenderContextImp interface, which anon represents the methods within the non platform specific RenderContext. This implementation (and the WebGL.NET bindings) can be found within Base.Engine.Imp.Graphics.WebAsm. This implementation relies upon the Mono WebAssembly bindings which are currently under development and therefore not available via NuGet. The file $(FuseeRoot)\NuGet.config therefore adds a new package source wasm and the corresponding source folder $(FuseeRoot)\packages\*.* in which the Mono bindings can be found.

This suffices for VS 2019 and the corresponding *.csproj files to find and obtain the correct packages (as of today: WebAssembly.Bindings, WebAssembly.Net.Http and WebAssembly.Net.WebSockets).

Futhermore it contains the new build task Mono.WebAssembly.Sdk as a usable NuGet package.

Build via Mono.WebAssembly.Sdk

The build task gathers all used C# DLLs in a first step and tries to slim and optimize the code with the help of mono linker. This little tool steps through the generated IL code and tries to optimize.

Unfortunately the linker crashes while traversing System.ServiceModel and System.ServiceModel.Primitives. Therefore these dependencies are removed from our protobuf-net-fusee version.

Beyond that, it also needs a package reference to System.Collections.Immutable within the *.csproj file, as this dependency isn't gathered automatically during traversal.

In addition, we need to make sure, that the linking process skips the namespaces Fusee.Engine.Core, Fusee.Serialization, Fusee.Math.Core and protobuf-net.Core. This is done inside the *.csproj file via <MonoWasmLinkMode>Full</MonoWasmLinkMode> in combination with <MonoWasmLinkSkip>Fusee.Engine.Core;Fusee.Serialization;...</MonoWasmLinkSkip>. Otherwise the optimization process removes (empty) classes or (base) methods / attributes which are needed for de-serialization.

The C# DLLs are being copied to output_folder/managed after that, and all other necessary files (dotnet.wasm, etc.) are added, too.

The resulting build folder is self-contained.

Clone this wiki locally