This feature is in development now. You can compile for WebAssembly, but the resulting application is not functional yet. Stay tuned!
The goal of the web target in Castle Game Engine is to allow you to recompile more-or-less 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.
TCastleWindow
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.
compileserver
Our build tool takes care of everything. You usually want to execute 2 commands:
castle-engine compile --target=web --mode=debug
This command:
generates web-specific files (in castle-engine-output/web/),
castle-engine-output/web/
compiles the glue code using Pas2js,
compiles the application code using FPC for WebAssembly.
castle-engine run --target=web
runs web server (compileserver) on http://localhost:3000/ to serve castle-engine-output/web/dist/,
http://localhost:3000/
castle-engine-output/web/dist/
runs web browser (default web browser on your system, logic using our OpenUrl) to open http://localhost:3000/.
OpenUrl
It is also possible to just compile using FPC + WebAssembly to a .wasm file. This does a subset of work performed by castle-engine compile --target=web. To do this, just execute:
.wasm
castle-engine compile --target=web
castle-engine compile --os=wasi --cpu=wasm32
You likely don’t need to use above options, unless you have your own idea how to deploy the .wasm file. The --target=web does everything for you.
--target=web
TODO: Our editor also does the above steps, just change the platform using "Run → Platform → Web" menu item and hit "Build and Run" (F9).
TODO: We render using WebGL 1.0, with optional WebGL 2.0 features. This is very similar to current rendering on mobile, where we use OpenGL ES 2.0, with optional OpenGL ES 3.0 features.
The WebGL API is available in the browser to Pas2js (that is, any JavaScript code in the browser). We can expose this API for the WebAssembly, following the canvas example described in the 2nd part of this article.
TODO: 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.
TODO: 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).
TCastleDownload
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.
platformer.wasm
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. How is the debug build actually useful on the web — I am not certain now :)
We could also use Brotli, a newer compression method also commonly supported by web browsers and servers.
Pas2js.
You can get it along with FPC or install it separately from here. We don’t have any special version requirements, just get the latest stable version.
Test that it works. Create the following simple program, save it as hello_pas2js.lpr:
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.
hello_pas2js.js
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:
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.
http://localhost:…/
file:///…/index.html
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.
Wasi
Wasm32
Test that it works. Create the following simple program, saved as hello_wasm.lpr:
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.
hello_wasm.wasm
Branch webassm_platformer_test in CGE contains the work on WebAssembly. To test it:
Get the webassm_platformer_test branch of CGE.
Follow compiling from source docs to compile at least new CGE build tool from this CGE branch.
Enter any CGE project and compile and run it for WebAssembly. For example, to compile the examples/platformer project:
examples/platformer
castle-engine compile --target=web --mode=debug castle-engine run --target=web
Currently, the project executes, printing logs (using our CastleLog) to the browser console. The rendering is not yet working.
CastleLog
Investigate next Exception class: object , message: indirect call to null from platformer.wasm error cause.
Exception class: object , message: indirect call to null
Then make the rendering work, by routing calls to WebGL on the web.
We could use WebGL from Pasj2s (see pas2js/packages/rtl/src/webgl.pas and pasj2s demos using it). We can make these calls available to our WebAssembly code (using canvas example from Pas2js and explanation from https://www.freepascal.org/~michael/articles/fpcwasm1/fpcwasm1.pdf how to expose JS functionality to WebAssembly).
pas2js/packages/rtl/src/webgl.pas
… but actually it seems unnecessary: FPC includes a unit job_web with TJSWebGL2RenderingContext. It does seem that the hard work is done for us, it should allow to expose WebGL API from Pas2js to WebAssembly. Learn how to use it. See wiki, https://wiki.freepascal.org/WebAssembly/DOM .
job_web
TJSWebGL2RenderingContext
Define CastleWebGL unit, exposing these calls in a flat way, to be similar to OpenGLES, and add macro CastleGLES:=CastleWebGL in castleconf.conf.
CastleWebGL
CastleGLES:=CastleWebGL
castleconf.conf
Test rendering first on some simple application without any data, just do some DrawRectangle from CGE in TCastleUserInterface.Draw. Hack any CGE example for this.
DrawRectangle
TCastleUserInterface.Draw
Once we got this rendering, we need to pass resources, from our data, to web application — preferably as one big zip, to allow synchronous reading of it in WebAssembly. See "How" plans.
Then finish the plans in "How" section above: data from zip, sound, automatic webserver, etc.
Trung Le (Kagamma) has been doing lots of work with CGE + FPC WebAssembly. His fork contains a branch wasm32-wasi-port. Merge and/or cherry-pick this work to CGE as needed. 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.