Surreal Engine C++ WebAssembly Port

In this note, I will describe how I ported the Surreal Engine game engine to WebAssembly.

Surreal Engine is a game engine that implements most of the functionality of the Unreal Engine 1. Well-known games on this engine include Unreal Tournament 99, Unreal, Deus Ex, and Undying. It is a classic engine that primarily worked in a single-threaded execution environment.

Initially, I had the idea to take on a project that I would not be able to complete in any reasonable timeframe, thus showing my Twitch followers that there are projects that even I cannot accomplish. On the very first stream, I suddenly realized that porting the Surreal Engine C++ to WebAssembly using Emscripten was achievable.

Surreal Engine Emscripten Demo

A month later, I can demonstrate my fork and build of the engine on WebAssembly:
https://demensdeum.com/demos/SurrealEngine/

The controls, as in the original, are carried out on the keyboard arrows. Next, I plan to adapt for mobile control (touch), add correct lighting, and other graphical features of the Unreal Tournament 99 renderer.

Where to Start?

The first thing I want to say is that any project can be ported from C++ to WebAssembly using Emscripten; the question is only how complete the functionality will be. Choose a project whose library ports are already available for Emscripten. In the case of Surreal Engine, it was very fortunate because the engine uses the SDL 2 and OpenAL libraries, both of which are ported to Emscripten. However, the graphical API used is Vulkan, which is currently not available for HTML5. Work is underway to implement WebGPU, but it is also in draft stage, and it is unknown how simple the further port from Vulkan to WebGPU will be after its full standardization. Therefore, I had to write my own basic OpenGL ES / WebGL renderer for Surreal Engine.

Project Build

The build system in Surreal Engine is CMake, which also simplifies porting since Emscripten provides its native builders – emcmake, emmake.
The Surreal Engine port was based on the code of my latest game on WebGL/OpenGL ES and C++ called Death-Mask, which made the development much easier; all necessary build flags and code examples were with me.

One of the most important moments in CMakeLists.txt is the build flags for Emscripten. Below is an example from the project file:

set(CMAKE_CXX_FLAGS "-s MIN_WEBGL_VERSION=2 \
-s MAX_WEBGL_VERSION=2 \
-s EXCEPTION_DEBUG \
-fexceptions \
--preload-file UnrealTournament/ \
--preload-file SurrealEngine.pk3 \
--bind \
--use-preload-plugins \
-Wall \
-Wextra \
-Werror=return-type \
-s USE_SDL=2 \
-s ASSERTIONS=1 \
-w \
-g4 \
-s DISABLE_EXCEPTION_CATCHING=0 \
-O3 \
--no-heap-copy \
-s ALLOW_MEMORY_GROWTH=1 \
-s EXIT_RUNTIME=1")

The build script itself:

clear
emmake make -j 16
cp SurrealEngine.data /srv/http/SurrealEngine/SurrealEngine.data
cp SurrealEngine.js /srv/http/SurrealEngine/SurrealEngine.js
cp SurrealEngine.wasm /srv/http/SurrealEngine/SurrealEngine.wasm
cp ../buildScripts/Emscripten/index.html /srv/http/SurrealEngine/index.html
cp ../buildScripts/Emscripten/background.png /srv/http/SurrealEngine/background.png

Next, prepare index.html, which includes the project file system preloader. For web deployment, I used the Unreal Tournament Demo version 338. As can be seen from the CMake file, the unpacked game folder was added to the build directory and linked as a preload-file for Emscripten.

Main Code Changes

Then it was necessary to change the game’s main loop; launching an infinite loop is not allowed as it causes the browser to hang. Instead, you need to use emscripten_set_main_loop. I wrote about this feature in my 2017 note “Porting an SDL C++ game to HTML5 (Emscripten)
Change the while loop exit condition to if, then output the main game engine class that contains the game loop to the global scope, and write a global function that will call the game loop step from the global object:

#if __EMSCRIPTEN__
#include 
Engine *EMSCRIPTEN_GLOBAL_GAME_ENGINE = nullptr;
void emscripten_game_loop_step() {
	EMSCRIPTEN_GLOBAL_GAME_ENGINE->Run();
}
#endif

After this, make sure that there are no background threads in the application. If there are, be prepared to rewrite them for single-threaded execution or use the pthread library in Emscripten.
A background thread in Surreal Engine is used for music playback. From the main engine thread, data about the current track and the need to play or stop music is received. The background thread then gets the new state via mutex and starts playing new music or pauses. The background thread is also used for music buffering during playback.
My attempts to build Surreal Engine under Emscripten with pthread were unsuccessful because the SDL2 and OpenAL ports were built without pthread support, and I didn’t want to rebuild them for the sake of music. Therefore, I transferred the background music thread functionality to single-threaded execution using a loop. By removing pthread calls from the C++ code, I moved buffering and music playback to the main thread, increasing the buffer by a few seconds to avoid delays.

Next, I will describe the specific implementations of graphics and sound.

Vulkan is Not Supported!

Yes, Vulkan is not supported in HTML5, although all advertising brochures present cross-platform and wide support on platforms as the main advantage of Vulkan. For this reason, I had to write my own basic graphics renderer for the simplified type of OpenGL – ES. It is used on mobile devices, sometimes lacking modern OpenGL features, but it is very well portable to WebGL, which is implemented by Emscripten. Writing the basic renderer for tiles, bsp rendering, simple GUI display, and models + maps took two weeks. This was perhaps the most challenging part of the project. There is still a lot of work ahead to implement the full rendering functionality of Surreal Engine, so any help from readers in the form of code and pull requests is welcome.

OpenAL is Supported!

It was very fortunate that Surreal Engine uses OpenAL for sound output. Writing a simple hello world in OpenAL and building it on WebAssembly with Emscripten made it clear how simple everything is, and I set off to port the sound.
After a few hours of debugging, it became apparent that there are several bugs in the Emscripten OpenAL implementation. For example, when initializing to read the number of mono channels, the method returned an infinite number, and after attempting to initialize a vector of infinite size, C++ crashed with a vector::length_error exception.
This was bypassed by hardcoding the number of mono channels to 2048:

		alcGetIntegerv(alDevice, ALC_MONO_SOURCES, 1, &monoSources);
		alcGetIntegerv(alDevice, ALC_STEREO_SOURCES, 1, &stereoSources);
#if EMSCRIPTEN
monoSources = 2048; // for some reason Emscripten's OpenAL gives infinite monoSources count, bug?
#endif

Is there a Network?

Surreal Engine currently does not support network play. Bot play is supported, but someone needs to write AI for these bots. Theoretically, network play on WebAssembly/Emscripten can be implemented using Websockets.

Conclusion

In conclusion, I want to say that porting Surreal Engine was quite smooth due to the use of libraries that have Emscripten ports, as well as my previous experience in implementing a C++ game for WebAssembly on Emscripten. Below are links to sources of knowledge and repositories on the topic.
M-M-M-MONSTER KILL!

If you want to help the project, preferably with WebGL/OpenGL ES renderer code, write to me on Telegram:
https://t.me/demenscave

Links

https://demensdeum.com/demos/SurrealEngine/
https://github.com/demensdeum/SurrealEngine-Emscripten
https://github.com/dpjudas/SurrealEngine

Flash Forever – Interceptor 2021

Recently, it turned out that Adobe Flash works quite stably under Wine. During a 4-hour stream, I made the game Interceptor 2021, which is a sequel to the game Interceptor 2020, written for the ZX Spectrum.

For those who are not in the know – the Flash technology provided interactivity on the web from 2000 to around 2015. Its shutdown was prompted by an open letter from Steve Jobs, in which he wrote that Flash should be consigned to history because it lagged on the iPhone. Since then, JS has become even more sluggish than Flash, and Flash itself has been wrapped in JS, making it possible to run it on anything thanks to the Ruffle player.

You can play it here:
https://demensdeum.com/demos/Interceptor2021

Video:
https://www.youtube.com/watch?v=-3b5PkBvHQk

Source code:
https://github.com/demensdeum/Interceptor-2021

Masonry-AR Game Demo

Masonry-AR is an augmented reality game where you need to move around the city in the real world and collect Masonic knowledge from books, obtaining currency and capturing territory for your Masonic order. The game has no relation to any real organizations, all matches are random.

Game demo:
https://demensdeum.com/demos/masonry-ar/client

Wiki:
https://demensdeum.com/masonry-ar-wiki-en/

Source code:
https://github.com/demensdeum/Masonry-AR

Flame Steel Engine Runner

I present to your attention Flame Steel Engine Runner – a platform for running multimedia applications based on the Flame Steel Engine toolkit. Supported platforms are Windows, MacOS, Linux, Android, iOS, HTML 5. The focus of application code development has shifted towards scripting – at the moment JavaScript support has been added using TinyJS, the toolkit itself and the engine will continue to be developed in languages ​​close to hardware (C, C ++, Rust, etc.)
Flame Steel Engine Runner Demo
On the page below, you can spin the cube, write JavaScript code, upload models, sounds, music, code using the Upload files button, and start from the main.js file using the Run button.
https://demensdeum.com/demos/FlameSteelEngineRunner/

Space Jaguar Action RPG 0.0.4

First Space Jaguar Action RPG prototype for Webassembly:

Space Jaguar Action RPG – space pirate simulator. Features available right now:

  • fly around
  • die
  • eat
  • sleep
  • recruit people
  • look at unstoppable flow of time

Can’t walk in 3D scenes right now, due to bad optimization for web version. It will available in next versions.

Game engine source code, scripts are available under MIT license. All comments and feedback are welcome:

https://gitlab.com/demensdeum/space-jaguar-action-rpg

Linux version screenshots:

Write in C (ZX Spectrum) GameDev

This ne’er-do note is devoted to the development of games for the ZX Spectrum old computer to C. Let’s take a look at the handsome:

He began producing in 1982, and was produced until 1992. Technical data: 8-bit processor Z80, 16-128kb memory and other are extensions such as audio chipAY-3-8910.

The competition Yandex Retro Games Battle 2019 for this machine I wrote a game called Interceptor 2020. Since learning assembler for the Z80 did not have time, I decided to develop it in C language. As tulcheyna I chose the Quick Set – z88dk, which includes C compilers, and many support libraries to accelerate the implementation of applications for the Spectrum. It also supports many other Z80 machines, such as MSX, Texas Instruments calculators.

Next I will describe his flight over the surface computer architecture tulcheynom z88dk, show how it was possible to implement OOP approach is to use design patterns.

Special features

z88dk installation should be performed by a manual from the repository, but for Ubuntu users, I would like to mention feature – if you have already installed compilers for Z80 from the deb package, you should remove them as z88dk will default to access them from the bin folder of the -this version incompatibility tulcheyn compiler, you probably will not be able to collect anything.

Hello World

Write Hello World is very simple:


#include void main()
{
    printf("Hello World");
}

Assemble the tap in the file is even easier:


zcc +zx -lndos -create-app -o helloworld helloworld.c

To start using any emulator ZX Spectrum tap supporting files, such as Online:

http://jsspeccy.zxdemo.org/

Draw on the image to full screen

tl; dr Pictures drawn tiles, tiles 8×8 pixels size, the tiles themselves are embedded in the font Spectrum, then the string of index picture is printed.

O library sprites and tiles sp1 displays tiles using UDG. The picture is translated into a set of separate UDG (tiles) is then collected on the screen using the indices. It should be remembered that UDG is used to display the text, and if your image contains very large set of tiles (eg more than 128 tiles), you have to go beyond the set boundaries and to erase the default font Spectrum. To work around this limitation, I used a base of 128 – 255 using the simplified representation, leaving the original font on the spot. On simplification of the pictures below.

To draw a full-screen images you need to arm the three utilities:
Gimp
img2spec
png2c-z88dk

There is a way ZX real men, real retro warriors is to open the editing palette using Spectrum, especially knowing the output image, prepare it and manually unload using png2c-z88dk or png2scr.

Way easier – take a 32-bit image, switch to Gimp colors to 3-4, slightly to edit, then import into img2spec not to work by hand with color restrictions, export png and transferred to the C array with png2c-z88dk.

It should be remembered that successful export each tile can not contain more than two colors.

As a result, you get the h file that contains a number of unique tiles, if more than ~ 128, simplify the Gimp in a picture (increase repeatability) and spend on a new procedure for export.

After exporting, you literally download a “Font” from the tiles and typing “text” of the indices of tiles on the screen. Here is an example of the “class” of the rendering:


// loading font into memory
    unsigned char *pt = fullscreenImage->tiles;

    for (i = 0; i < fullscreenImage->tilesLength; i++, pt += 8) {
            sp1_TileEntry(fullscreenImage->tilesBase + i, pt);
    }

    // set cursor into 0,0
    sp1_SetPrintPos(&ps0, 0, 0);

    // print string
    sp1_PrintString(&ps0, fullscreenImage->ptiles);

Drawing sprites on the screen

Next, I will describe a way of drawing sprites of 16×16 pixels on the screen. Before the animation and change colors I came because corny at this stage, I guess I ran out of memory. Therefore, in the game there are only transparent monochrome sprites.

Draw in Gimp monochrome png image 16×16, etc. using png2sp1sprite translate it into assembly asm file in C code declare arrays of the assembly file, add the file at build time.

After the declaration of a resource of the sprite, it is necessary to add the screen to the desired position, then the sample code “class” game object:


    struct sp1_ss *bubble_sprite = sp1_CreateSpr(SP1_DRAW_MASK2LB, SP1_TYPE_2BYTE, 3, 0, 0);
    sp1_AddColSpr(bubble_sprite, SP1_DRAW_MASK2,    SP1_TYPE_2BYTE, col2-col1, 0);
    sp1_AddColSpr(bubble_sprite, SP1_DRAW_MASK2RB,  SP1_TYPE_2BYTE, 0, 0);
    sp1_IterateSprChar(bubble_sprite, initialiseColour);

From the names of some functions can understand the meaning – allotsiruem memory sprite, add two columns 8×8, add color for a sprite.

Each frame is affixed position of the sprite:


sp1_MoveSprPix(gameObject->gameObjectSprite, Renderer_fullScreenRect, gameObject->sprite_col, gameObject->x, gameObject->y);

OOP simulations

In C, there is no syntax for OOP, what do you do if you still really want to? It is necessary to connect the Dumka and illumined thought that such a thing as the PLO equipment does not exist, everything eventually comes to a machine architectures in which there is simply no concept of the object and other related abstractions.

This fact bothered me for a long time to understand why do you need the PLO, why you need to use it if in the end everything comes to machine code.

However, having worked in the product development, I opened the charm of this programming paradigms, primarily of course the development of the flexibility mechanisms of the protective code, with the right approach entropy reduction, simplification of teamwork. All of these advantages derive from the three pillars – polymorphism, encapsulation, inheritance.

Also worth noting is the simplification of addressing issues related to the architecture of the application, because 80% of architectural problems were solved by computer-scientists in the last century and described in the literature devoted to the design pattern. Next, I will describe how to add like OOP syntax in C.

For instance data storage more convenient to take the basis of class C structure. Of course, you can use a byte buffer to create its own structure for the classes, methods, but why reinvent the wheel? After all, we already reinventing syntax.

These classes

An example of the data fields “class” GameObject:


struct GameObjectStruct {
    struct sp1_ss *gameObjectSprite;
    unsigned char *sprite_col;
    unsigned char x;
    unsigned char y;
    unsigned char referenceCount;
    unsigned char beforeHideX;
    unsigned char beforeHideY;
};
typedef struct GameObjectStruct GameObject;

We maintain our class as “GameObject.h” do #include “GameObject.h” in the right place and use.

Class methods

Take into service experience Objective-C language development, the signature method of the class will be from a function in the global osprey, the first argument will always transmitted data structure, method arguments go further. Next, an example of “the method” “class” GameObject:


void GameObject_hide(GameObject *gameObject) {
    gameObject->beforeHideX = gameObject->x;
    gameObject->beforeHideY = gameObject->y;
    gameObject->y = 200;
}

Method call is as follows:


GameObject_hide(gameObject);

Constructors and destructors are implemented in the same manner. It can be implemented as an allocator constructor and field initializers, but I prefer separate methods for that,

Working with memory

Manual memory management type using malloc and free macros wrapped in new and delete operators for compliance with C ++:


#define new(X) (X*)malloc(sizeof(X))
#define delete(X) free(X)

For objects that are used by multiple classes at once, realized semi-manual memory management based on reference counting, in the image of the old mechanism of Objective-C Runtime ARC:


void GameObject_retain(GameObject *gameObject) {
    gameObject->referenceCount++;
}

void GameObject_release(GameObject *gameObject) {
    gameObject->referenceCount--;

    if (gameObject->referenceCount < 1) { sp1_MoveSprAbs(gameObject->gameObjectSprite, &Renderer_fullScreenRect, NULL, 0, 34, 0, 0);
        sp1_DeleteSpr(gameObject->gameObjectSprite);
        delete(gameObject);
    }
}

Thus, each class must declare the use of a common object using the retain, release the possession through release. In the modern version of ARC uses an automatic affixing call retain / release.

I sound!

Spectrum has a tweeter capable of reproducing 1-bit music, the composers of the time were able to play on it for up to 4 audio channels simultaneously. Spectrum 128k comprises a separate sound chip AY-3-8910, which can reproduce music tracker. To use the Tweeters in z88dk proposed library sound.h

What is to be learned

I was interested to read the Spectrum, to realize the game z88dk means, learn a lot of interesting things. I much remains to be explored, such as the assembler Z80, as it allows you to use the full power of Spectrum, the work of memory banks, working with the sound chip AY-3-8910. I hope to participate in the competition for next year!

References

https://rgb.yandex
https://vk.com/sinc_lair
https://www.z88dk.org/forum/

Source Code

https://gitlab.com/demensdeum/zx-projects/tree/master/interceptor2020

Death-Mask Wild Beta

Game Death-Mask goes into the status of a public beta (wild beta)
Reworked the main menu screen of the game, added a view of the blue zone of the techno-labyrinth, with pleasant music in the background.

Next, I plan to rework the gameplay controller, add smooth movement like in old shooters, high-quality 3D models of boxes, weapons, enemies, the ability to move to other levels of the techno-maze not only through portals (elevators, doors, fall through holes in the floor, holes in the walls), add a little variety to the environment of the generated maze. I will also work on the game balance.
Skeletal animation will be added at the polishing stage before release.