Neste post vou descrever como portei o motor de jogo Surreal Engine para WebAssembly.
Motor Surreal – um motor de jogo que implementa a maior parte das funcionalidades do Unreal Engine 1, jogos famosos neste motor – Torneio Unreal 99, Unreal, Deus Ex, Imortal. Refere-se a mecanismos clássicos que funcionavam principalmente em um ambiente de execução de thread único.
Inicialmente tive a ideia de assumir um projeto que não conseguiria concluir em um prazo razoável, mostrando assim aos meus seguidores do Twitch que existem projetos que nem eu consigo realizar. Durante minha primeira transmissão, de repente percebi que a tarefa de portar o Surreal Engine C++ para WebAssembly usando Emscripten é viável.

Depois de um mês posso demonstrar minha montagem de garfo e motor no WebAssembly:
https://demensdeum.com/demos/SurrealEngine/
O controle, como no original, é feito através das setas do teclado. Em seguida, pretendo adaptá-lo para controle móvel (tachi), adicionando iluminação correta e outros recursos gráficos da renderização do Unreal Tournament 99.
Por onde começar?
A primeira coisa que quero dizer é que qualquer projeto pode ser portado de C++ para WebAssembly usando Emscripten, a única dúvida é quão completa será a funcionalidade. Escolha um projeto cujas portas de biblioteca já estejam disponíveis para Emscripten, no caso do Surreal Engine, você tem muita sorte, pois o mecanismo usa as bibliotecas SDL 2, OpenAL – ambos foram portados para o Emscripten. No entanto, Vulkan é usado como uma API gráfica, que atualmente não está disponível para HTML5, o trabalho está em andamento para implementar WebGPU, mas também está em fase de rascunho, e também não se sabe quão simples será a porta adicional de Vulkan para WebGPU , depois de totalmente padronizado. Portanto, tive que escrever minha própria renderização básica OpenGL-ES/WebGL para Surreal Engine.
Construindo o projeto
Construir sistema no Surreal Engine – CMake, que também simplifica a portabilidade, porque Emscripten fornece aos seus construtores nativos – emcmake, emmake.
O porte do Surreal Engine foi baseado no código do meu último jogo em WebGL/OpenGL ES e C++ chamado Death-Mask, por isso o desenvolvimento foi muito mais simples, eu tinha todos os build flags necessários comigo e exemplos de código.
Um dos pontos mais importantes em CMakeLists.txt são os sinalizadores de construção do Emscripten. Abaixo está um exemplo do arquivo do projeto:
-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")
O próprio script de construção:
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
Em seguida, prepararemos o índice .html , que inclui o pré-carregador do sistema de arquivos do projeto. Para fazer upload para a web, usei o Unreal Tournament Demo versão 338. Como você pode ver no arquivo CMake, a pasta do jogo descompactada foi adicionada ao diretório de construção e vinculada como um arquivo de pré-carregamento para Emscripten.
Alterações no código principal
Então foi necessário alterar o loop do jogo, você não pode executar um loop infinito, isso faz com que o navegador congele, em vez disso você precisa usar emscripten_set_main_loop, escrevi sobre esse recurso em minha nota de 2017 “< a href="https://demensdeum.com /blog/ru/2017/03/29/porting-sdl-c-game-to-html5-emscripten/" rel="noopener" target="_blank">Portar jogo SDL C++ para HTML5 (Emscripten)”
Alteramos o código para sair do loop while para if, então exibimos a classe principal do mecanismo de jogo, que contém o loop do jogo, no escopo global, e escrevemos uma função global que chamará a etapa do loop do jogo do objeto global :
#include <emscripten.h>
Engine *EMSCRIPTEN_GLOBAL_GAME_ENGINE = nullptr;
void emscripten_game_loop_step() {
EMSCRIPTEN_GLOBAL_GAME_ENGINE->Run();
}
#endif
Depois disso, você precisa ter certeza de que não há threads em segundo plano no aplicativo, se houver, então prepare-se para reescrevê-los para execução de thread único ou use a biblioteca phtread no Emscripten.
O thread de segundo plano no Surreal Engine é usado para reproduzir música, os dados vêm do thread do mecanismo principal sobre a faixa atual, a necessidade de tocar música ou sua ausência, então o thread de segundo plano recebe um novo estado por meio de um mutex e começa a tocar nova música ou pausa-o. O fluxo de fundo também é usado para armazenar música em buffer durante a reprodução.
Minhas tentativas de construir o Surreal Engine para Emscripten com pthread não tiveram sucesso, porque as portas SDL2 e OpenAL foram construídas sem suporte a pthread e eu não queria reconstruí-las por causa da música. Portanto, transferi a funcionalidade do fluxo de música de fundo para execução de thread único usando um loop. Ao remover as chamadas pthread do código C++, movi o buffer e a reprodução da música para o thread principal, para que não houvesse atrasos, aumentei o buffer em alguns segundos.
A seguir, descreverei implementações específicas de gráficos e som.
Vulkan não é compatível!
Sim, Vulkan não é compatível com HTML5, embora todos os folhetos de marketing apresentem suporte multiplataforma e ampla plataforma como a principal vantagem do Vulkan. Por esse motivo, tive que escrever meu próprio renderizador gráfico básico para um tipo OpenGL simplificado – – ES, é usado em dispositivos móveis, às vezes não contém os recursos modernos do OpenGL moderno, mas porta muito bem para WebGL, que é exatamente o que o Emscripten implementa. A escrita da renderização básica de blocos, renderização bsp, para a exibição da GUI mais simples e renderização de modelos + mapas foi concluída em duas semanas. Esta foi talvez a parte mais difícil do projeto. Ainda há muito trabalho pela frente para implementar todas as funcionalidades da renderização do Surreal Engine, portanto, qualquer ajuda dos leitores é bem-vinda na forma de código e solicitações pull.
OpenAL compatível!
Grande sorte é que o Surreal Engine usa OpenAL para saída de áudio. Depois de escrever um hello world simples em OpenAL e montá-lo em WebAssembly usando Emscripten, ficou claro para mim como tudo era simples e comecei a portar o som.
Após várias horas de depuração, ficou óbvio que a implementação OpenAL do Emscripten possui vários bugs, por exemplo, ao inicializar a leitura do número de canais mono, o método retornou um número infinito, e após tentar inicializar um vetor de tamanho infinito, C++ trava com a exceção vector::length_error.
Conseguimos contornar isso codificando o número de canais mono para 2048:
alcGetIntegerv(alDevice, ALC_STEREO_SOURCES, 1, &stereoSources);
#if __EMSCRIPTEN__
monoSources = 2048; // for some reason Emscripten's OpenAL gives infinite monoSources count, bug?
#endif
Existe uma rede?
O Surreal Engine atualmente não suporta jogos online, jogar com bots é compatível, mas precisamos de alguém para escrever IA para esses bots. Teoricamente, você pode implementar um jogo em rede no WebAssembly/Emscripten usando Websockets.
Conclusão
Concluindo, gostaria de dizer que a portabilidade do Surreal Engine acabou sendo bastante tranquila devido ao uso de bibliotecas para as quais existem portas Emscripten, bem como à minha experiência anterior na implementação de um jogo em C++ para WebAssembly em Emscripten. Abaixo estão links para fontes de conhecimento e repositórios sobre o tema.
M-M-M-MATANÇA DE MONSTRO!
Além disso, se você quiser ajudar o projeto, de preferência com código de renderização WebGL/OpenGL ES, escreva para mim no Telegram:
https://t.me/demenscave
Links
https://demensdeum.com/demos/SurrealEngine/
https://github.com/demensdeum/SurrealEngine-Emscripten
https://github.com/dpjudas/SurrealEngine





