{"id":109,"date":"2026-07-25T10:02:02","date_gmt":"2026-07-25T10:02:02","guid":{"rendered":"https:\/\/demensdeum.com\/blog\/2026\/07\/25\/surreal-engine-cpp-webassembly-port\/"},"modified":"2026-07-25T13:21:32","modified_gmt":"2026-07-25T13:21:32","slug":"surreal-engine-cpp-webassembly-port","status":"publish","type":"post","link":"https:\/\/demensdeum.com\/blog\/de\/2026\/07\/25\/surreal-engine-cpp-webassembly-port\/","title":{"rendered":"Portierung der Surreal Engine C++ auf WebAssembly"},"content":{"rendered":"<p>In diesem Beitrag beschreibe ich, wie ich die Surreal Engine-Spiel-Engine auf WebAssembly portiert habe.<br \/>\n<a href=\"https:\/\/demensdeum.com\/demos\/SurrealEngine\/\"\" rel=\"noopener\" target=\"_blank\">https:\/\/demensdeum.com\/demos\/SurrealEngine\/&#8221;<\/a><br \/>\nSurreal Engine &#8211; eine Spiel-Engine, die die meisten Funktionen der Unreal Engine 1 implementiert, ber\u00fchmte Spiele auf dieser Engine &#8211; Unreal Tournament 99, Unreal, Deus Ex, Undying. Es bezieht sich auf klassische Engines, die haupts\u00e4chlich in einer Single-Threaded-Ausf\u00fchrungsumgebung arbeiteten.<br \/>\nIch hatte urspr\u00fcnglich die Idee, ein Projekt zu \u00fcbernehmen, das ich nicht in einem angemessenen Zeitrahmen abschlie\u00dfen konnte, und so meinen Twitch-Followern zu zeigen, dass es Projekte gibt, die selbst ich nicht schaffen kann. W\u00e4hrend meines ersten Streams wurde mir pl\u00f6tzlich klar, dass die Aufgabe, Surreal Engine C++ mithilfe von Emscripten auf WebAssembly zu portieren, machbar war.<\/p>\n<p>Einen Monat sp\u00e4ter kann ich meine Gabel- und Motorbaugruppe auf WebAssembly demonstrieren:<br \/>\n<a href=\"https:\/\/demensdeum.com\/demos\/SurrealEngine\/\" rel=\"noopener\" target=\"_blank\">https:\/\/demensdeum.com\/demos\/SurrealEngine\/<\/a><br \/>\nDie Steuerung erfolgt wie im Original \u00fcber die Tastaturpfeile. Als n\u00e4chstes plane ich, es f\u00fcr die mobile Steuerung (Tachi) anzupassen und dabei die richtige Beleuchtung und andere grafische Funktionen des Unreal Tournament 99-Renderings hinzuzuf\u00fcgen.<\/p>\n<h3>Wo soll ich anfangen?<\/h3>\n<p>Als erstes m\u00f6chte ich sagen, dass jedes Projekt mit Emscripten von C++ nach WebAssembly portiert werden kann. Die Frage ist nur, wie vollst\u00e4ndig die Funktionalit\u00e4t sein wird. W\u00e4hlen Sie ein Projekt, dessen Bibliotheksports bereits f\u00fcr Emscripten verf\u00fcgbar sind; Im Fall der Surreal Engine haben Sie gro\u00dfes Gl\u00fcck, denn die Engine verwendet SDL 2, OpenAL &#8211; Bibliotheken. Sie sind beide auf Emscripten portiert. Als Grafik-API kommt allerdings Vulkan zum Einsatz, das derzeit f\u00fcr HTML5 nicht verf\u00fcgbar ist, an der Implementierung von WebGPU wird gearbeitet, befindet sich aber ebenfalls im Entwurfsstadium und es ist auch nicht bekannt, wie einfach die weitere Portierung von Vulkan auf WebGPU nach der vollst\u00e4ndigen Standardisierung sein wird. Daher musste ich meinen eigenen grundlegenden OpenGL-ES\/WebGL-Renderer f\u00fcr Surreal Engine schreiben.<\/p>\n<h3>Das Projekt erstellen<\/h3>\n<p>Build-System in Surreal Engine &#8211; CMake, was auch die Portierung vereinfacht, da Emscripten seine nativen Builder &#8211; emcmake, emmake.<br \/>\nDer Surreal Engine-Port basierte auf dem Code meines neuesten Spiels in WebGL\/OpenGL ES und C++ namens Death-Mask. Dadurch war die Entwicklung viel einfacher, ich hatte alle notwendigen Build-Flags und Codebeispiele dabei.<br \/>\nEiner der wichtigsten Punkte in CMakeLists.txt sind die Build-Flags f\u00fcr Emscripten, unten ist ein Beispiel aus der Projektdatei:<\/p>\n<div class=\"hcb_wrap\">\n<div class=\"hcb_wrap\">\n<pre class=\"prism undefined-numbers lang-unknown\" data-lang=\"unknown\"><code>set(CMAKE_CXX_FLAGS \"-s MIN_WEBGL_VERSION=2 \n-s MAX_WEBGL_VERSION=2 \n-s EXCEPTION_DEBUG \n-fexceptions \n--preload-file UnrealTournament\/ \n--preload-file SurrealEngine.pk3 \n--bind \n--use-preload-plugins \n-Wall \n-Wextra \n-Werror=return-type \n-s USE_SDL=2 \n-s ASSERTIONS=1 \n-w \n-g4 \n-s DISABLE_EXCEPTION_CATCHING=0 \n-O3 \n--no-heap-copy \n-s ALLOW_MEMORY_GROWTH=1 \n-s EXIT_RUNTIME=1\")\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p>Das Build-Skript selbst:<\/p>\n<div class=\"hcb_wrap\">\n<div class=\"hcb_wrap\">\n<pre class=\"prism undefined-numbers lang-unknown\" data-lang=\"unknown\"><code>clear\nemmake make -j 16\ncp SurrealEngine.data \/srv\/http\/SurrealEngine\/SurrealEngine.data\ncp SurrealEngine.js \/srv\/http\/SurrealEngine\/SurrealEngine.js\ncp SurrealEngine.wasm \/srv\/http\/SurrealEngine\/SurrealEngine.wasm\ncp ..\/buildScripts\/Emscripten\/index.html \/srv\/http\/SurrealEngine\/index.html\n<img decoding=\"async\" src=\"cp ..\/buildScripts\/Emscripten\/background.png \/srv\/http\/SurrealEngine\/background.png\"\/><\/code><\/pre>\n<\/div>\n<\/div>\n<p>Als N\u00e4chstes bereiten wir <a href=\"https:\/\/github.com\/demensdeum\/SurrealEngine-Emscripten\/blob\/master\/buildScripts\/Emscripten\/index.html\" target=\"_blank\" rel=\"noopener\">index.html<\/a> vor, das den Projektdateisystem-Preloader enth\u00e4lt. Zum Hochladen ins Web habe ich die Unreal Tournament Demo-Version 338 verwendet. Wie Sie der CMake-Datei entnehmen k\u00f6nnen, wurde der entpackte Spielordner zum Build-Verzeichnis hinzugef\u00fcgt und als Preload-Datei f\u00fcr Emscripten verkn\u00fcpft.<\/p>\n<h3>Hauptcode\u00e4nderungen<\/h3>\n<p>Dann mussten wir die Spielschleife des Spiels \u00e4ndern, man kann keine Endlosschleife ausf\u00fchren, das f\u00fchrt zum Einfrieren des Browsers, stattdessen muss man emscripten_set_main_loop verwenden, \u00fcber diese Funktion habe ich in meiner Notiz von 2017 geschrieben \u201e<a href=\"https:\/\/demensdeum.com\/blog\/ru\/2017\/03\/29\/porting-sdl-c-game-to-html5-emscripten\/\" rel=\"noopener\" target=\"_blank\">Portierung von SDL C++-Spielen nach HTML5 (Emscripten)<\/a>\u201c<br \/>\nWir \u00e4ndern den Code zum Beenden der while-Schleife in if, zeigen dann die Hauptklasse der Spiel-Engine, die die Spielschleife enth\u00e4lt, im globalen Bereich an und schreiben eine globale Funktion, die den Spielschleifenschritt vom globalen Objekt aus aufruft:<\/p>\n<div class=\"hcb_wrap\">\n<div class=\"hcb_wrap\">\n<pre class=\"prism undefined-numbers lang-unknown\" data-lang=\"unknown\"><code>#if __EMSCRIPTEN__\n#include &lt;emscripten.h&gt;\nEngine *EMSCRIPTEN_GLOBAL_GAME_ENGINE = nullptr;\nvoid emscripten_game_loop_step() {\n\tEMSCRIPTEN_GLOBAL_GAME_ENGINE-&gt;Run();\n}\n#endif\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p>Danach m\u00fcssen Sie sicherstellen, dass in der Anwendung keine Hintergrundthreads vorhanden sind. Wenn ja, dann machen Sie sich bereit, sie f\u00fcr die Single-Thread-Ausf\u00fchrung neu zu schreiben, oder verwenden Sie die phtread-Bibliothek in Emscripten.<br \/>\nDer Hintergrund-Thread in Surreal Engine wird zum Abspielen von Musik verwendet. Vom Haupt-Engine-Thread kommen Daten \u00fcber den aktuellen Titel, die Notwendigkeit, Musik abzuspielen, oder deren Fehlen. Anschlie\u00dfend erh\u00e4lt der Hintergrund-Thread \u00fcber einen Mutex einen neuen Status und beginnt mit der Wiedergabe neuer Musik oder pausiert. Der Hintergrundthread wird auch zum Puffern von Musik w\u00e4hrend der Wiedergabe verwendet.<br \/>\nMeine Versuche, die Surreal Engine f\u00fcr Emscripten mit pthread zu erstellen, waren erfolglos, da die SDL2- und OpenAL-Ports ohne pthread-Unterst\u00fctzung erstellt wurden und ich sie nicht aus Gr\u00fcnden der Musik neu erstellen wollte. Daher habe ich die Funktionalit\u00e4t des Hintergrundmusik-Streams mithilfe einer Schleife auf die Single-Threaded-Ausf\u00fchrung \u00fcbertragen. Durch das Entfernen von pthread-Aufrufen aus dem C++-Code habe ich die Pufferung und Musikwiedergabe in den Hauptthread verschoben, damit es keine Verz\u00f6gerungen gibt, und den Puffer um einige Sekunden erh\u00f6ht.<br \/>\nAls n\u00e4chstes werde ich spezifische Implementierungen von Grafik und Sound beschreiben.<\/p>\n<h3>Vulkan wird nicht unterst\u00fctzt!<\/h3>\n<p>Ja, Vulkan wird in HTML5 nicht unterst\u00fctzt, obwohl alle Marketingbrosch\u00fcren die plattform\u00fcbergreifende und breite Plattformunterst\u00fctzung als Hauptvorteil von Vulkan darstellen. Aus diesem Grund musste ich meinen eigenen grundlegenden Grafikrenderer f\u00fcr einen vereinfachten OpenGL-Typ schreiben &#8211; ES, es wird auf mobilen Ger\u00e4ten verwendet, enth\u00e4lt manchmal nicht die modischen Funktionen des modernen OpenGL, l\u00e4sst sich aber sehr gut auf WebGL portieren, was genau das ist, was Emscripten implementiert. Das Schreiben des grundlegenden Kachel-Renderings, des BSP-Renderings f\u00fcr die einfachste GUI-Anzeige und des Renderns von Modellen und Karten wurde in zwei Wochen abgeschlossen. Dies war vielleicht der schwierigste Teil des Projekts. Es liegt noch viel Arbeit vor uns, die volle Funktionalit\u00e4t des Surreal Engine-Renderings zu implementieren, daher ist jede Hilfe von Lesern in Form von Code und Pull-Requests willkommen.<\/p>\n<h3>OpenAL unterst\u00fctzt!<\/h3>\n<p>Das gro\u00dfe Gl\u00fcck ist, dass Surreal Engine OpenAL f\u00fcr die Audioausgabe verwendet. Nachdem ich eine einfache Hallo-Welt in OpenAL geschrieben und sie mit Emscripten in WebAssembly zusammengestellt hatte, wurde mir klar, wie einfach alles war, und ich machte mich an die Portierung des Sounds.<br \/>\nNach mehreren Stunden des Debuggens stellte sich heraus, dass die OpenAL-Implementierung von Emscripten mehrere Fehler aufweist. Beispielsweise gab die Methode beim Initialisieren des Lesens der Anzahl der Monokan\u00e4le eine unendliche Zahl zur\u00fcck, und nach dem Versuch, einen Vektor unendlicher Gr\u00f6\u00dfe zu initialisieren, st\u00fcrzte C++ mit der Ausnahme vector::length_error ab.<br \/>\nWir haben es geschafft, dies zu umgehen, indem wir die Anzahl der Monokan\u00e4le auf 2048 fest codiert haben:<\/p>\n<div class=\"hcb_wrap\">\n<div class=\"hcb_wrap\">\n<pre class=\"prism undefined-numbers lang-unknown\" data-lang=\"unknown\"><code>\t\talcGetIntegerv(alDevice, ALC_MONO_SOURCES, 1, &amp;monoSources);\n\t\talcGetIntegerv(alDevice, ALC_STEREO_SOURCES, 1, &amp;stereoSources);\n\n#if __EMSCRIPTEN__\n\t\tmonoSources = 2048; \/\/ for some reason Emscripten's OpenAL gives infinite monoSources count, bug?\n#endif\n\n<\/code><\/pre>\n<\/div>\n<\/div>\n<h3>Gibt es ein Netzwerk?<\/h3>\n<p>Surreal Engine unterst\u00fctzt derzeit kein Online-Spielen, das Spielen mit Bots wird unterst\u00fctzt, aber wir brauchen jemanden, der KI f\u00fcr diese Bots schreibt. Theoretisch k\u00f6nnen Sie mithilfe von Websockets ein Netzwerkspiel auf WebAssembly\/Emscripten implementieren.<\/p>\n<h3>Schlussfolgerung<\/h3>\n<p>Abschlie\u00dfend m\u00f6chte ich sagen, dass die Portierung der Surreal Engine aufgrund der Verwendung von Bibliotheken, f\u00fcr die es Emscripten-Portierungen gibt, sowie meiner bisherigen Erfahrung bei der Implementierung eines Spiels in C++ f\u00fcr WebAssembly auf Emscripten recht reibungslos verlief. Nachfolgend finden Sie Links zu Wissensquellen und Repositorien zum Thema.<br \/>\n<strong>M-M-M-MONSTER T\u00d6TEN!<\/strong><br \/>\nWenn Sie dem Projekt au\u00dferdem helfen m\u00f6chten, vorzugsweise mit WebGL\/OpenGL ES-Rendering-Code, dann schreiben Sie mir per Telegram:<br \/>\n<a href=\"https:\/\/t.me\/demenscave\" target=\"_blank\" rel=\"noopener\">https:\/\/t.me\/demenscave<\/a><\/p>\n<h3>Links<\/h3>\n<p><a href=\"https:\/\/demensdeum.com\/demos\/SurrealEngine\/\" target=\"_blank\" rel=\"noopener\">https:\/\/demensdeum.com\/demos\/SurrealEngine\/<\/a><\/p>\n<p><a href=\"https:\/\/github.com\/demensdeum\/SurrealEngine-Emscripten\" target=\"_blank\" rel=\"noopener\">https:\/\/github.com\/demensdeum\/SurrealEngine-Emscripten<\/a><\/p>\n<p><a href=\"https:\/\/github.com\/dpjudas\/SurrealEngine\" target=\"_blank\" rel=\"noopener\">https:\/\/github.com\/dpjudas\/SurrealEngine<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In diesem Beitrag beschreibe ich, wie ich die Surreal Engine-Spiel-Engine auf WebAssembly portiert habe.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8],"tags":[],"class_list":["post-109","post","type-post","status-publish","format-standard","hentry","category-notes"],"translation":{"provider":"WPGlobus","version":"3.0.6","language":"de","enabled_languages":["en","ru","zh","de","ja","fr","es","pt","hi"],"languages":{"en":{"title":true,"content":true,"excerpt":false},"ru":{"title":true,"content":true,"excerpt":false},"zh":{"title":true,"content":true,"excerpt":false},"de":{"title":true,"content":true,"excerpt":false},"ja":{"title":true,"content":true,"excerpt":false},"fr":{"title":true,"content":true,"excerpt":false},"es":{"title":false,"content":false,"excerpt":false},"pt":{"title":true,"content":true,"excerpt":false},"hi":{"title":true,"content":true,"excerpt":false}}},"_links":{"self":[{"href":"https:\/\/demensdeum.com\/blog\/de\/wp-json\/wp\/v2\/posts\/109","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/demensdeum.com\/blog\/de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/demensdeum.com\/blog\/de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/demensdeum.com\/blog\/de\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/demensdeum.com\/blog\/de\/wp-json\/wp\/v2\/comments?post=109"}],"version-history":[{"count":1,"href":"https:\/\/demensdeum.com\/blog\/de\/wp-json\/wp\/v2\/posts\/109\/revisions"}],"predecessor-version":[{"id":110,"href":"https:\/\/demensdeum.com\/blog\/de\/wp-json\/wp\/v2\/posts\/109\/revisions\/110"}],"wp:attachment":[{"href":"https:\/\/demensdeum.com\/blog\/de\/wp-json\/wp\/v2\/media?parent=109"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/demensdeum.com\/blog\/de\/wp-json\/wp\/v2\/categories?post=109"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/demensdeum.com\/blog\/de\/wp-json\/wp\/v2\/tags?post=109"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}