Web Target

1. Introduction

Warning

This feature is in intensive development now. As such, this page will change as we go forward. Stay tuned!

Web target allows you to recompile any application you wrote using Castle Game Engine (using TCastleWindow) to the web. So you can put your application on a website, as part of a regular HTML webpage, and users can play it instantly, without the need to download / install any native application or browser plugin.

Castle Game Engine - Viewport example (in Firefox) Castle Game Engine - Invaders example (in Firefox) Castle Game Engine - Simplest example (in Firefox)

2. Demos

Demos:

Warning

These are early demos, so don’t judge the final quality by these demos (yet) :) We have some known TODOs, including:

  • Text looks a bit "fuzzy" (likely due to missing texture swizzle on WebGL which makes our image alpha not exactly applied how we want).

  • The demos use only 3D / 2D things set up from code (because we don’t load data files, not even designs, yet) so they don’t look impressive.

  • No shadow volumes on web yet (we need to initialize with stencil to have them).

3. Compatibility

Absolute :)

  • The resulting application works in any modern web browser and does not require user to install anything special (like old plugins). Everything we need is already built-in in all modern web browsers, in particular WebAssembly and WebGL support are completely standard now.

  • All the web browsers should be good. Firefox, Google Chrome and derivatives (like Vivaldi) are all good.

  • Mobile web browsers are supported as well. We support touches in a way consisten with mouse events, just like on non-web mobile targets. We tested with both Firefox and Google Chrome on Android.

    Warning
    To make the resulting web application working also in mobile (like Android) web browsers, one needs to apply a 1-line hack to pas2js sources, see below for details.

4. How it works (or will work)

4.1. Already working

  • We’re using FPC WebAssembly target.

    Huge thank you go to the whole FPC team for making it possible! For general documentation (unrelated to CGE), see this article by Michael Van Canneyt.

  • To run the WebAssembly in a web browser, there is additional "glue" code, which we also write in Pascal and compile using Pas2js.

    The integration between Pas2js and FPC + WebAssembly is extremely easy, again thanks to the work of everyone involved!

  • For development purposes, we can run a simple webserver on the localhost. Pas2js features a ready compileserver, we just use it.

    Note
    FpWeb also can instantiate a standalone HTTP server in just a few lines of code. But it seems we don’t need even this, compileserver is all that we need.
  • Our build tool takes care of everything. You usually want to execute 2 commands:

    1. castle-engine compile --target=web --mode=debug

      This command:

      • generates web-specific files (in castle-engine-output/web/),

      • compiles the glue code using Pas2js,

      • compiles the application code using FPC for WebAssembly.

    2. castle-engine run --target=web

      This command:

    Note

    For some specific applications (like our tests/) you can also compile the regular program file (like xxx_standalone.dpr, whatever is indicated by standalone_source in CastleEngineManifest.xml) using FPC + WebAssembly to a .wasm file using this command:

    castle-engine compile --os=wasi --cpu=wasm32

    This is not equivalent to castle-engine compile --target=web, which generates and compiles a special library file with WebAssembly (and also generates and compiles the glue code using Pas2js, and generates HTML template).

    Compiling the standalone program with castle-engine compile --os=wasi --cpu=wasm32 only makes sense if application was prepared to be run this way. E.g. it allows to run our testsuite using Wasmtime.

  • Our editor can also build and run for web. Underneath, it just calls the build tool commands described above (which you can see in the "Console").

    Just change the platform using "Run → Platform → Web" menu item and hit "Build and Run" button or menu item (F9).

  • We create a HTML file with the <canvas id="castle-canvas"> element (see canvas docs at MDN). This HTML runs the JS code (compiled by Pas2js) that will in turn run the WASM code of your application, which will initialize and use WebGL context on this canvas.

    This HTML file can be customized / replaced / rejected as you wish. It only needs to contain a reference to the generated JavaScript file using <script> and a <canvas> element with ID castle-canvas. You can surround it with any content and style.

    For nice look, our generated HTML also uses Bootstrap, but again: this can be customized / removed. We don’t depend on any special CSS or JS libraries.

    Some alternative HTML templates may be available in the future, as well as the ability to provide your own template. For now, just (before building) customize the engine’s tools/build-tool/data/web/dist/index.html file or (after building) customize the generated castle-engine-output/web/dist/index.html (e.g. by sed).

  • We use WebGL API from WebAssembly. All our rendering code has been adjusted to WebGL!

    First step to do this was the JOB units implemented in Pas2js + FPC. This is a collection of units (on both Pas2js and FPC sides) that cooperate with each other. The goal is to allow our WASM applications to access WebGL API (defined in this WEBIDL file) plus a few other JavaScript APIs we need (for HTML canvas, window.requestAnimationFrame etc.).

    Note
    The WebGL API is available in the browser to Pas2js (that is, any JavaScript code in the browser). If not for JOB, we would expose this API for the WebAssembly, following the canvas example described in the 2nd part of this article. But, well, using JOB makes things even easier.
    Note
    FPC ships an example job_web.pas that already contains everything we need, but it’s a huge unit. It has ~184 thousands of lines and compiling it is slow (this is esp. problematic as current FPC 3.3.1 recompiles units more often than it should — it seems that every change even to implementation causes rebuild of everything derived, as if we changed interface). That’s why we generate our own unit, CastleInternalJobWeb (merely ~19 thousands of lines) that contains only what we need.

    Second step: We then generate include file castleinternalwebgl_flat_api.inc that "flattens" the API exposed in CastleInternalJobWeb, making it more similar to the OpenGL ES API in the CastleGLES unit. This generation also consults WEBIDL, to process all WebGL methods we need. The tool that does this is tools/internal/generate_webgl_flat_api.

    Third step: We then use CastleInternalWebGL unit as (to a large extent) a drop-in replacement for the CastleGLES unit.

    Lastly, we manually adjusted rendering code to account for final, unavoidable differences between OpenGL ES (CastleGLES) and WebGL (CastleInternalWebGL). Mostly straightforward work, differences stem mostly from the fact that OpenGL ES is a C API (so: no such thing as Variant; and occasional pointer usage to pass arrays or "raw" buffers), while WebGL is a JavaScript API (so: everything is an array, even "raw" buffers; some reasonable usage of Variant types, like getParameter results). Just a small number of {$ifdef CASTLE_WEBGL} clauses in strategic places got the job done.

    In the end, we render using WebGL 1.0, with optional WebGL 2.0 features. This is very similar to our current rendering approach on mobile, where we use OpenGL ES 2.0, with optional OpenGL ES 3.0 features.

  • Input: We handle pointer events on the canvas (down, motion, up) thus we handle mouse and touch input. We also handle key events (keydown, keyup) on the whole DOM window.

    Warning
    TODO: key handling may change to rely on focus instead, to allow to e.g. have 2 web applications using CGE on the same page.

4.2. Plans (TODOs)

  • Reproduce with clean sample and submit to FPC:

    Running on mobile requires now hacking pas2js, in pas2js/packages/job/src/job_browser.pp, find and comment out RegisterGlobalObject(caches,'caches');. Otherwise both Firefox and Chrome on Android fail with ReferenceError: caches is not defined.

  • For data delivery, we know we’ll have to invent a simple format to carry our game data as one big binary blob. The simple plan is to just pack game data into zip.

    Both FPC and Delphi have built-in support for ZIP handling, we even use it in one example, we’ll likely create a more full-featured wrapper that works on both FPC and Delphi.

    Packing resources into ZIP is beneficial over just placing all data files separately in the webserver directory. It allows to download the ZIP once (at application load) and then our WebAssembly code can load data from it synchronously, thus it can work similarly to how a non-web game data is loaded. Naturally we also want to have TCastleDownload working and being able to load things asynchronously (see our network docs).

    TODO: FPC provides some ready file implementation in WASI RTL, it seems. How it works? In browser, in wasmtime?

  • For audio, we will add a new sound backend using WebAudio. This will be the default sound backend on the web (like the OpenAL is now the default backend on non-web platforms).

    It is also possible our FMOD sound backend will also be ported to web, as FMOD supports HTML5. This would make FMOD a truly cross-platform sound backend, working on every platform we support in CGE.

    Note
    About WebAudio in the context of X3D 4.0. Supporting advanced X3D 4.0 audio nodes is not our priority, but since WebAudio will happen anyway, on the web…​ they will open this possibility. There is also LabSound that provides WebAudio-like API on non-web platforms, and in principle it could one day replace OpenAL, making web and non-web audio handling closer.
  • Possibly throw in additional compression of data and/or wasm executable?

    So far, file-size tests are promising. Compilation of examples/platformer, which practically uses 100% of CGE units, yields a binary platformer.wasm that has 16 MB. Gzipped it has 3.4 MB.

    The gzipped size is really what matters — both web browsers and servers support gzip-(de)compression on the fly, you can also just put ready-gzipped version on the server and tell the browser to just decompress. So in all practical cases, users will download 3.4 MB, not 16 MB.

    Note that above is for a release build. The debug build weights 51 MB, and gzipped 12 MB. (Debug build contains debugging symbols.)

    We could also use Brotli, a newer compression method also commonly supported by web browsers and servers.

  • Allow to go fullscreen on web.

  • Implement TCastleDownload for web.

  • Is it good we capture keys by listening to keydown and keyup events on the whole window? Could conflict with other things on the same page trying to handle keys (like 2nd CGE web application). Should we rather rely on canvas being focused, or maybe some canvas div wrapper? (but will need code to make it focused, because out-of-the-box canvas doesn’t get any keydown / keyup).

  • Save the user settings / save games (CastleConfig) to webpage persistent storage.

  • Open / save files by browser dialog (TCastleWindow.FileDialog).

5. Prerequisites

Note

Things below look complicated? We have plans to make them easier:

  • We plan to extend our Docker images to provide pas2js and FPC with WebAssembly support out-of-the-box. Don’t know yet what Docker is? Then we advise to play and learn, Docker images and containers are nice way to share "development environments".

  • We also will provide recipes how to use Github Actions and GitLab CI to build applications for the web. So if you host your repository on GitHub or GitLab, you can let the build be done for you.

Installing the tools:

  • Pas2js.

    You can get it along with FPC or install it separately from here.

    Warning
    We test with the latest Pas2js from GIT right now, so this is also what we advise to use. The stable release 3.0.1 from https://getpas2js.freepascal.org/ is not supported right now (due to incompatibilities around Job.Js and friends usage, which are all solved in latest Pas2js + FPC). With time we may change this (adjust to older Pas2js) and/or a new release of Pas2js may happen which will make things easier :)

    Test that it works. Create the following simple program, save it as hello_pas2js.lpr:

    begin
      Writeln('Hello from pas2js!');
    end.

    If everything is installed and configured OK, then you should be able to compile it using this from the command-line:

    pas2js -Jc -Jirtl.js -Tbrowser hello_pas2js.lpr

    If everything went well, you should get a file hello_pas2js.js in the same directory.

    Note

    If pas2js reports an error like Error: can’t find unit "System", it most likely means that the pas2js.cfg configuration file is missing or wrong. You can either generate it using createconfig tool or take and adjust the sample from pas2js.cfg wiki page, then place the resulting file in a system-specific location indicated on that wiki page.

    It does seem that fpcupdeluxe, although it provides pas2js and compileserver binaries (good!), but it doesn’t provide the createconfig tool nor does it contain the relevant pas2js package structure (you need files like packages/rtl/system.js). So instead get pas2js from https://getpas2js.freepascal.org/ and make sure it’s the first on $PATH. TODO: Submit issue to fpcupdeluxe about it.

  • compileserver from Pas2js.

    This is a very useful tool distributed along with Pas2js. Make sure you got it, test it by executing on the command-line this:

    compileserver

    Leave it running and access http://localhost:3000/ in a web browser.

    Kill it by Ctrl+C in the terminal.

    Why is this useful? Most dynamic webpages should be tested through a webserver running on your system (like compileserver, though you can also spin a full Apache or Nginx) that serves the HTML, JS, WASM and other files. You view them using a http://localhost:…​/ URL, which means you access the webserver on your own system. In contrast, just opening the HTML files in a web browser (which results in URLs like file:///…​/index.html) will usually not work, because of security: JavaScript code on such pages will not be able to load extra resources (like WASM files). It’s possible to configure your browser to circumvent this (disable CORS checking) but it’s a hassle. Using a "temporary webserver" like compileserver is the easiest way to test your applications.

  • FPC with WebAssembly support.

    In our opinion, it’s easiest to get this using fpcupdeluxe: install FPC "trunk" version, then install FPC cross-compiler for OS=Wasi and CPU=Wasm32. You can also just get it from GIT and compile manually, following FPC wiki about WebAssembly.

    Test that it works. Create the following simple program, saved as hello_wasm.lpr:

    begin
      Writeln('Hello from WebAssembly!');
    end.

    If everything is installed and configured OK, then you should be able to compile it using this command:

    fpc -Twasi -Pwasm32 hello_wasm.lpr

    If everything went well, you should get hello_wasm.wasm in the same directory.

6. How to test what we have now

Branch webassm_platformer_test in CGE contains the work on WebAssembly. To test it:

  1. Get the webassm_platformer_test branch of CGE.

  2. Follow compiling from source docs to compile at least new CGE build tool from this CGE branch.

  3. Enter any CGE project in examples/web/ and compile and run it for WebAssembly.

    You can do it using the editor. Switch the platform using "Run → Platform → Web", then click "Compile And Run" (F9).

    Or you can do it from the command-line, using our build tool:

    castle-engine compile --target=web --mode=debug
    castle-engine run --target=web

    TODO: It is our plan to allow compiling any project from examples/ to the web. But for now, only projects in examples/web/ are prepared for this.

Warning

Beware of the cache!

Browsers may cache the contents of everything (HTML, JS, WASM files, application data…​). We counter this somewhat (by randomizing the URL by which we access the WASM file), but still cache affects us, as other files are cached. Just refresh using Ctrl + Shift + R if it seems like you look at an old application version (or even a different application that was run previously from the same server).

TODO: We could counter cache more, by accessing http://localhost:3000/?random=XXX , and using random URL params for everything (JS, HTML, application data). Also, we should make it configurable, to enable turning off "fighting with cache" for production reproducible-builds.

7. Running WASM code outside of the browser

WebAssembly is not limited to running in a web browser. We support running the compiled WASM binaries also using Wasmtime, though without rendering.

Warning

Trying to render anything in Wasmtime will fail, as we don’t have access to WebGL API and context (or the rest of JS API, like window.requestAnimationFrame). So you cannot even create TCastleWindow, i.e. it will fail inside Wasmtime with

TODO update this error message

and we don’t have immediate plans to make it work. But, at least everything else (all the code up to the rendering) will work on Wasmtime.

Build your application like this (on the command-line):

castle-engine compile --os=wasi --cpu=wasm32 --mode=debug

Run it like this:

export WASMTIME_BACKTRACE_DETAILS=1 # get a more detailed backtrace
wasmtime <project-name>.wasm

The main use-case of this is additional debugging approach. In case of a crash, the wasmtime will give you a useful stack trace with line numbers (if only you compiled with --mode=debug, as shown above).

7.1. Running engine test-suite on WASM

As a particular application of the above ("you can run engine code using wasmtime, except rendering"), you can run our test suite on WASM.

Warning
Making castle-tester.wasm work this way is a work-in-progress. The time_measurements_tests.wasm mentioned below works already.
cd tests/
castle-engine compile --os=wasi --cpu=wasm32 --mode=debug
wasmtime castle-tester.wasm --console --no-window-create

cd time_measurements_tests/ # enter tests/time_measurements_tests/ in CGE
castle-engine compile --os=wasi --cpu=wasm32 --mode=debug
wasmtime time_measurements_tests.wasm

8. Customize default canvas size

TODO: Move this to CastleEngineManifest.xml docs.

The default area available for rendering is determined by the <canvas> size in the generated HTML file. You can customize this by editing the CastleEngineManifest.xml of your project and adding there lines like this:

<web>
  <canvas width="800" height="600" />
</web>
Note
You can also just edit / replace the generated index.html file completely. As we mentioned above, we don’t assume much about this HTML, it only must have <canvas id="castle-canvas">.

9. Run the project by default for the web

In the editor: Select the new platform, like "Web", in the "Run → Platforms → …​" menu. Then you can save it for the future using menu item "Run → Platforms → Save Chosen Platform As Default (in Project Local Settings)".

Alternatively and equivalently:

You can create a file CastleProjectLocalSettings.json in your project. Place it at the top-level of the project, so it is next to the CastleEngineManifest.xml file. The CastleProjectLocalSettings.json can contain settings that are specific to your local user or machine.

For example, it can specify that the default target is web like this:

{
  "$$ClassName" : "TCastleProjectLocalSettings",
  "UsePlatformDefaults" : true,
  "DefaultTarget": "targetWeb"
}

Changing the default platform is useful if you want to always build and run for the web, without specifying --target=web every time. Now using just castle-engine compile and castle-engine run will build and run for the web. You can still override this at command-line, using the --target, --os, --cpu options (see build tool docs).

Note
The CastleProjectLocalSettings.json support is not strictly a feature of the web target, it’s useful in every case when you want to change the default platform (and we plan to add there more things). By default, we build and run for the current desktop target, with current OS and CPU.

All the possibilities are described by the TCastleProjectLocalSettings.

Note that you can also open the CastleProjectLocalSettings.json as a design in the editor and edit the component in the object inspector. That’s because TCastleProjectLocalSettings is a regular serializable component for our engine. Right now, this doesn’t really imply any new features, but it may, and it will be compatible with future CastleProject.json.

Warning
The file CastleProjectLocalSettings.json should not be committed to the version control, it should be ignored by file like .gitignore (if you use GIT).

10. Thank you

I want to thank everyone involved in this and let’s push forward! Web target is a really cool feature, from my talks I know it’s an important feature for many CGE users, and I feel we have it in our reach. Let’s keep coding and enjoy making games :)


To improve this documentation just edit this page and create a pull request to cge-www repository.