{"id":3575,"date":"2024-07-16T15:01:26","date_gmt":"2024-07-16T12:01:26","guid":{"rendered":"https:\/\/demensdeum.com\/blog\/?p=3575"},"modified":"2024-12-16T22:32:13","modified_gmt":"2024-12-16T19:32:13","slug":"surreal-engine-cpp-webassembly-port","status":"publish","type":"post","link":"https:\/\/demensdeum.com\/blog\/pt\/2024\/07\/16\/surreal-engine-cpp-webassembly-port\/","title":{"rendered":"Portando Surreal Engine C++ para WebAssembly"},"content":{"rendered":"<p>Neste post vou descrever como portei o motor de jogo Surreal Engine para WebAssembly.<br \/>\n<a href=\"https:\/\/demensdeum.com\/demos\/SurrealEngine\/\" rel=\"noopener\" target=\"_blank\"><\/a><\/p>\n<p>Motor Surreal &#8211; um motor de jogo que implementa a maior parte das funcionalidades do Unreal Engine 1, jogos famosos neste motor \u2013 Torneio Unreal 99, Unreal, Deus Ex, Imortal. Refere-se a mecanismos cl\u00e1ssicos que funcionavam principalmente em um ambiente de execu\u00e7\u00e3o de thread \u00fanico.<\/p>\n<p>Inicialmente tive a ideia de assumir um projeto que n\u00e3o conseguiria concluir em um prazo razo\u00e1vel, mostrando assim aos meus seguidores do Twitch que existem projetos que nem eu consigo realizar. Durante minha primeira transmiss\u00e3o, de repente percebi que a tarefa de portar o Surreal Engine C++ para WebAssembly usando Emscripten \u00e9 vi\u00e1vel.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/demensdeum.com\/blog\/wp-content\/uploads\/2024\/07\/Screenshot_20240716_162120.png\" alt=\"Surreal Engine Emscripten Demo\" width=\"737\" height=\"364\" class=\"alignnone wp-image-3581\" srcset=\"https:\/\/demensdeum.com\/blog\/wp-content\/uploads\/2024\/07\/Screenshot_20240716_162120.png 1555w, https:\/\/demensdeum.com\/blog\/wp-content\/uploads\/2024\/07\/Screenshot_20240716_162120-300x148.png 300w, https:\/\/demensdeum.com\/blog\/wp-content\/uploads\/2024\/07\/Screenshot_20240716_162120-1024x505.png 1024w, https:\/\/demensdeum.com\/blog\/wp-content\/uploads\/2024\/07\/Screenshot_20240716_162120-768x379.png 768w, https:\/\/demensdeum.com\/blog\/wp-content\/uploads\/2024\/07\/Screenshot_20240716_162120-1536x758.png 1536w\" sizes=\"auto, (max-width: 737px) 100vw, 737px\" \/><\/p>\n<p>Depois de um m\u00eas posso demonstrar minha montagem de garfo e motor no WebAssembly:<br \/>\n<a href=\"https:\/\/demensdeum.com\/demos\/SurrealEngine\/\" target=\"_blank\" rel=\"noopener\">https:\/\/demensdeum.com\/demos\/SurrealEngine\/<\/a><\/p>\n<p>O controle, como no original, \u00e9 feito atrav\u00e9s das setas do teclado. Em seguida, pretendo adapt\u00e1-lo para controle m\u00f3vel (tachi), adicionando ilumina\u00e7\u00e3o correta e outros recursos gr\u00e1ficos da renderiza\u00e7\u00e3o do Unreal Tournament 99.<\/p>\n<h3>Por onde come\u00e7ar?<\/h3>\n<p>A primeira coisa que quero dizer \u00e9 que qualquer projeto pode ser portado de C++ para WebAssembly usando Emscripten, a \u00fanica d\u00favida \u00e9 qu\u00e3o completa ser\u00e1 a funcionalidade. Escolha um projeto cujas portas de biblioteca j\u00e1 estejam dispon\u00edveis para Emscripten, no caso do Surreal Engine, voc\u00ea tem muita sorte, pois o mecanismo usa as bibliotecas SDL 2, OpenAL &#8211; ambos foram portados para o Emscripten. No entanto, Vulkan \u00e9 usado como uma API gr\u00e1fica, que atualmente n\u00e3o est\u00e1 dispon\u00edvel para HTML5, o trabalho est\u00e1 em andamento para implementar WebGPU, mas tamb\u00e9m est\u00e1 em fase de rascunho, e tamb\u00e9m n\u00e3o se sabe qu\u00e3o simples ser\u00e1 a porta adicional de Vulkan para WebGPU , depois de totalmente padronizado. Portanto, tive que escrever minha pr\u00f3pria renderiza\u00e7\u00e3o b\u00e1sica OpenGL-ES\/WebGL para Surreal Engine.<\/p>\n<h3>Construindo o projeto<\/h3>\n<p>Construir sistema no Surreal Engine &#8211; CMake, que tamb\u00e9m simplifica a portabilidade, porque Emscripten fornece aos seus construtores nativos &#8211; emcmake, emmake.<br \/>\nO porte do Surreal Engine foi baseado no c\u00f3digo do meu \u00faltimo 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\u00e1rios comigo e exemplos de c\u00f3digo.<\/p >\n<p>Um dos pontos mais importantes em CMakeLists.txt s\u00e3o os sinalizadores de constru\u00e7\u00e3o do Emscripten. Abaixo est\u00e1 um exemplo do arquivo do projeto:<\/p>\n<div class=\"hcb_wrap\">\n<div class=\"hcb_wrap\">\n<pre class=\"prism line-numbers lang-unknown\" data-lang=\"unknown\"><code>\n-s MAX_WEBGL_VERSION=2 \\\n\n-s EXCEPTION_DEBUG \\\n\n-fexceptions \\\n\n--preload-file UnrealTournament\/ \\\n\n--preload-file SurrealEngine.pk3 \\\n\n--bind \\\n\n--use-preload-plugins \\\n\n-Wall \\\n\n-Wextra \\\n\n-Werror=return-type \\\n\n-s USE_SDL=2 \\\n\n-s ASSERTIONS=1 \\\n\n-w \\\n\n-g4 \\\n\n-s DISABLE_EXCEPTION_CATCHING=0 \\\n\n-O3 \\\n\n--no-heap-copy \\\n\n-s ALLOW_MEMORY_GROWTH=1 \\\n\n-s EXIT_RUNTIME=1\")\n\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p>O pr\u00f3prio script de constru\u00e7\u00e3o:<\/p>\n<div class=\"hcb_wrap\">\n<div class=\"hcb_wrap\">\n<pre class=\"prism line-numbers lang-unknown\" data-lang=\"unknown\"><code>\nemmake make -j 16\n\ncp SurrealEngine.data \/srv\/http\/SurrealEngine\/SurrealEngine.data\n\ncp SurrealEngine.js \/srv\/http\/SurrealEngine\/SurrealEngine.js\n\ncp SurrealEngine.wasm \/srv\/http\/SurrealEngine\/SurrealEngine.wasm\n\ncp ..\/buildScripts\/Emscripten\/index.html \/srv\/http\/SurrealEngine\/index.html\n\ncp ..\/buildScripts\/Emscripten\/background.png \/srv\/http\/SurrealEngine\/background.png\n\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p>Em seguida, prepararemos o <a href=\"https:\/\/github.com\/demensdeum\/SurrealEngine-Emscripten\/blob\/master\/buildScripts\/Emscripten\/index.html\" target=\"_blank\" rel=\"noopener\">\u00edndice .html <\/a>, que inclui o pr\u00e9-carregador do sistema de arquivos do projeto. Para fazer upload para a web, usei o Unreal Tournament Demo vers\u00e3o 338. Como voc\u00ea pode ver no arquivo CMake, a pasta do jogo descompactada foi adicionada ao diret\u00f3rio de constru\u00e7\u00e3o e vinculada como um arquivo de pr\u00e9-carregamento para Emscripten.<\/p>\n<h3>Altera\u00e7\u00f5es no c\u00f3digo principal<\/h3>\n<p>Ent\u00e3o foi necess\u00e1rio alterar o loop do jogo, voc\u00ea n\u00e3o pode executar um loop infinito, isso faz com que o navegador congele, em vez disso voc\u00ea precisa usar emscripten_set_main_loop, escrevi sobre esse recurso em minha nota de 2017 \u201c< 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)<\/a>\u201d<br \/>\nAlteramos o c\u00f3digo para sair do loop while para if, ent\u00e3o exibimos a classe principal do mecanismo de jogo, que cont\u00e9m o loop do jogo, no escopo global, e escrevemos uma fun\u00e7\u00e3o global que chamar\u00e1 a etapa do loop do jogo do objeto global :<\/p>\n<div class=\"hcb_wrap\">\n<div class=\"hcb_wrap\">\n<pre class=\"prism line-numbers lang-unknown\" data-lang=\"unknown\"><code>\n#include &lt;emscripten.h&gt;\n\nEngine *EMSCRIPTEN_GLOBAL_GAME_ENGINE = nullptr;\n\nvoid emscripten_game_loop_step() {\n\n\tEMSCRIPTEN_GLOBAL_GAME_ENGINE-&gt;Run();\n\n}\n\n#endif\n\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p>Depois disso, voc\u00ea precisa ter certeza de que n\u00e3o h\u00e1 threads em segundo plano no aplicativo, se houver, ent\u00e3o prepare-se para reescrev\u00ea-los para execu\u00e7\u00e3o de thread \u00fanico ou use a biblioteca phtread no Emscripten.<br \/>\nO thread de segundo plano no Surreal Engine \u00e9 usado para reproduzir m\u00fasica, os dados v\u00eam do thread do mecanismo principal sobre a faixa atual, a necessidade de tocar m\u00fasica ou sua aus\u00eancia, ent\u00e3o o thread de segundo plano recebe um novo estado por meio de um mutex e come\u00e7a a tocar nova m\u00fasica ou pausa-o. O fluxo de fundo tamb\u00e9m \u00e9 usado para armazenar m\u00fasica em buffer durante a reprodu\u00e7\u00e3o.<br \/>\nMinhas tentativas de construir o Surreal Engine para Emscripten com pthread n\u00e3o tiveram sucesso, porque as portas SDL2 e OpenAL foram constru\u00eddas sem suporte a pthread e eu n\u00e3o queria reconstru\u00ed-las por causa da m\u00fasica. Portanto, transferi a funcionalidade do fluxo de m\u00fasica de fundo para execu\u00e7\u00e3o de thread \u00fanico usando um loop. Ao remover as chamadas pthread do c\u00f3digo C++, movi o buffer e a reprodu\u00e7\u00e3o da m\u00fasica para o thread principal, para que n\u00e3o houvesse atrasos, aumentei o buffer em alguns segundos.<\/p>\n<p>A seguir, descreverei implementa\u00e7\u00f5es espec\u00edficas de gr\u00e1ficos e som.<\/p>\n<h3>Vulkan n\u00e3o \u00e9 compat\u00edvel!<\/h3>\n<p>Sim, Vulkan n\u00e3o \u00e9 compat\u00edvel 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\u00f3prio renderizador gr\u00e1fico b\u00e1sico para um tipo OpenGL simplificado &#8211; &#8211; ES, \u00e9 usado em dispositivos m\u00f3veis, \u00e0s vezes n\u00e3o cont\u00e9m os recursos modernos do OpenGL moderno, mas porta muito bem para WebGL, que \u00e9 exatamente o que o Emscripten implementa. A escrita da renderiza\u00e7\u00e3o b\u00e1sica de blocos, renderiza\u00e7\u00e3o bsp, para a exibi\u00e7\u00e3o da GUI mais simples e renderiza\u00e7\u00e3o de modelos + mapas foi conclu\u00edda em duas semanas. Esta foi talvez a parte mais dif\u00edcil do projeto. Ainda h\u00e1 muito trabalho pela frente para implementar todas as funcionalidades da renderiza\u00e7\u00e3o do Surreal Engine, portanto, qualquer ajuda dos leitores \u00e9 bem-vinda na forma de c\u00f3digo e solicita\u00e7\u00f5es pull.<\/p>\n<h3>OpenAL compat\u00edvel!<\/h3>\n<p>Grande sorte \u00e9 que o Surreal Engine usa OpenAL para sa\u00edda de \u00e1udio. Depois de escrever um hello world simples em OpenAL e mont\u00e1-lo em WebAssembly usando Emscripten, ficou claro para mim como tudo era simples e comecei a portar o som.<br \/>\nAp\u00f3s v\u00e1rias horas de depura\u00e7\u00e3o, ficou \u00f3bvio que a implementa\u00e7\u00e3o OpenAL do Emscripten possui v\u00e1rios bugs, por exemplo, ao inicializar a leitura do n\u00famero de canais mono, o m\u00e9todo retornou um n\u00famero infinito, e ap\u00f3s tentar inicializar um vetor de tamanho infinito, C++ trava com a exce\u00e7\u00e3o vector::length_error.<br \/ ><br \/>\nConseguimos contornar isso codificando o n\u00famero de canais mono para 2048:<\/p>\n<div class=\"hcb_wrap\">\n<div class=\"hcb_wrap\">\n<pre class=\"prism line-numbers lang-unknown\" data-lang=\"unknown\"><code>\n\t\talcGetIntegerv(alDevice, ALC_STEREO_SOURCES, 1, &amp;stereoSources);\n\n\n\n#if __EMSCRIPTEN__\n\n\t\tmonoSources = 2048; \/\/ for some reason Emscripten's OpenAL gives infinite monoSources count, bug?\n\n#endif\n\n\n\n<\/code><\/pre>\n<\/div>\n<\/div>\n<h3>Existe uma rede?<\/h3>\n<p>O Surreal Engine atualmente n\u00e3o suporta jogos online, jogar com bots \u00e9 compat\u00edvel, mas precisamos de algu\u00e9m para escrever IA para esses bots. Teoricamente, voc\u00ea pode implementar um jogo em rede no WebAssembly\/Emscripten usando Websockets.<\/p>\n<h3>Conclus\u00e3o<\/h3>\n<p>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 \u00e0 minha experi\u00eancia anterior na implementa\u00e7\u00e3o de um jogo em C++ para WebAssembly em Emscripten. Abaixo est\u00e3o links para fontes de conhecimento e reposit\u00f3rios sobre o tema.<br \/>\n<strong>M-M-M-MATAN\u00c7A DE MONSTRO!<\/strong><\/p>\n<p>Al\u00e9m disso, se voc\u00ea quiser ajudar o projeto, de prefer\u00eancia com c\u00f3digo de renderiza\u00e7\u00e3o WebGL\/OpenGL ES, escreva para mim no 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><br \/>\n<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>Neste post vou descrever como portei o motor de jogo Surreal Engine para WebAssembly. Motor Surreal &#8211; um motor de jogo que implementa a maior parte das funcionalidades do Unreal Engine 1, jogos famosos neste motor \u2013 Torneio Unreal 99, Unreal, Deus Ex, Imortal. Refere-se a mecanismos cl\u00e1ssicos que funcionavam principalmente em um ambiente de<a class=\"more-link\" href=\"https:\/\/demensdeum.com\/blog\/pt\/2024\/07\/16\/surreal-engine-cpp-webassembly-port\/\">Continue reading <span class=\"screen-reader-text\">&#8220;Portando Surreal Engine C++ para WebAssembly&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[81,61,52],"tags":[231,119],"class_list":["post-3575","post","type-post","status-publish","format-standard","hentry","category-demos","category-techie","category-tutorials","tag-ut99","tag-emscripten","entry"],"translation":{"provider":"WPGlobus","version":"3.0.2","language":"pt","enabled_languages":["en","ru","zh","de","fr","ja","pt"],"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},"fr":{"title":true,"content":true,"excerpt":false},"ja":{"title":true,"content":true,"excerpt":false},"pt":{"title":true,"content":true,"excerpt":false}}},"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/demensdeum.com\/blog\/pt\/wp-json\/wp\/v2\/posts\/3575","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/demensdeum.com\/blog\/pt\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/demensdeum.com\/blog\/pt\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/demensdeum.com\/blog\/pt\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/demensdeum.com\/blog\/pt\/wp-json\/wp\/v2\/comments?post=3575"}],"version-history":[{"count":24,"href":"https:\/\/demensdeum.com\/blog\/pt\/wp-json\/wp\/v2\/posts\/3575\/revisions"}],"predecessor-version":[{"id":3862,"href":"https:\/\/demensdeum.com\/blog\/pt\/wp-json\/wp\/v2\/posts\/3575\/revisions\/3862"}],"wp:attachment":[{"href":"https:\/\/demensdeum.com\/blog\/pt\/wp-json\/wp\/v2\/media?parent=3575"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/demensdeum.com\/blog\/pt\/wp-json\/wp\/v2\/categories?post=3575"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/demensdeum.com\/blog\/pt\/wp-json\/wp\/v2\/tags?post=3575"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}