v0.6.0: WebAssembly build with a WebGL backend (playable in the browser)
- ra3.webgl: a Web peer of Vulkan/Direct3D (SDL3 canvas + GLES3/WebGL2), with 2D blit and the GPU terrain raymarch; GLSL ES shaders; null fallback off Emscripten. backend::webgl + default_backend() (webgl under Emscripten). - Emscripten toolchain + Dockerfile.wasm + scripts/build-wasm.sh; import std, global -fexceptions, and Asyncify so the blocking frame loop yields to the browser (display::sleep_frame calls emscripten_sleep). - apps/web/shell.html + apps/web/serve.py (Range-capable dev server). - Assets: browsers forbid synchronous on-demand reads on the main thread (and FS.createLazyFile / the WasmFS fetch backend are worker-only; SDL3's Emscripten backend is main-thread DOM only, so the engine cannot run in a worker). The wasm build therefore preloads a compact per-map set via OPENRA3_WEB_ASSETS. - New openra3 textures --map ID lists the loose terrain TGAs a map resolves to (terrain::resolve_texture_files, shared with load_textures_from_dir). - Fixes: webgl heightmap used GL_R16 = 0x8229 (that is R8) -> upload rejected, terrain flattened to water; now 0x822A. Logger uses a stdout console sink on the web (stderr maps to console.error); a GL error check logs bad uploads. - CI: wasm image + build jobs.
This commit is contained in:
+101
-11
@@ -7,8 +7,9 @@ set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "d0edc3af-4c50-42ea-a356-e2862fe7a444")
|
||||
|
||||
# Path to the standard-library module description. A cross toolchain overrides
|
||||
# this before the compiler is probed; this default targets the Linux LLVM
|
||||
# package.
|
||||
if(NOT CMAKE_CXX_STDLIB_MODULES_JSON)
|
||||
# package. Skipped for Emscripten, whose toolchain supplies its own sysroot
|
||||
# modules.json.
|
||||
if(NOT CMAKE_CXX_STDLIB_MODULES_JSON AND NOT DEFINED ENV{EMSDK})
|
||||
set(CMAKE_CXX_STDLIB_MODULES_JSON "/usr/lib/llvm-21/lib/libc++.modules.json")
|
||||
endif()
|
||||
|
||||
@@ -25,7 +26,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set(CMAKE_CXX_SCAN_FOR_MODULES ON)
|
||||
|
||||
project(OpenRA3 VERSION 0.5.0 LANGUAGES C CXX)
|
||||
project(OpenRA3 VERSION 0.6.0 LANGUAGES C CXX)
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
|
||||
@@ -55,7 +56,13 @@ set(OPENRA3_HAS_SDL3 OFF)
|
||||
# The triple-named subdirectory inside a MinGW SDL3 development package
|
||||
# (`<root>/<triple>/{include,lib,bin}`); x86_64 unless a toolchain overrides it.
|
||||
set(OPENRA3_SDL3_MINGW_TRIPLE "x86_64-w64-mingw32" CACHE STRING "SDL3 MinGW triple subdirectory")
|
||||
if(OPENRA3_SDL3_ROOT)
|
||||
if(EMSCRIPTEN)
|
||||
# Emscripten's SDL3 web port provides the headers and the (static) library
|
||||
# via the `-sUSE_SDL=3` flag, on every target that uses SDL.
|
||||
target_compile_options(openra3_sdl3 INTERFACE -sUSE_SDL=3)
|
||||
target_link_options(openra3_sdl3 INTERFACE -sUSE_SDL=3)
|
||||
set(OPENRA3_HAS_SDL3 ON)
|
||||
elseif(OPENRA3_SDL3_ROOT)
|
||||
target_include_directories(openra3_sdl3 INTERFACE "${OPENRA3_SDL3_ROOT}/${OPENRA3_SDL3_MINGW_TRIPLE}/include")
|
||||
target_link_libraries(openra3_sdl3 INTERFACE "${OPENRA3_SDL3_ROOT}/${OPENRA3_SDL3_MINGW_TRIPLE}/lib/libSDL3.dll.a")
|
||||
set(OPENRA3_HAS_SDL3 ON)
|
||||
@@ -78,9 +85,15 @@ endif()
|
||||
# --- Vulkan: vendored volk + headers, so no Vulkan SDK is needed ---------------
|
||||
# The loader is resolved at runtime (volk dlopen/LoadLibrary), which lets the
|
||||
# same source build the Vulkan backend on Linux and on the MinGW Windows target.
|
||||
option(OPENRA3_VULKAN "Build the Vulkan viewer" ON)
|
||||
# WebAssembly has no Vulkan, so the viewer is off there (the null backend).
|
||||
if(EMSCRIPTEN)
|
||||
set(OPENRA3_VULKAN_DEFAULT OFF)
|
||||
else()
|
||||
set(OPENRA3_VULKAN_DEFAULT ON)
|
||||
endif()
|
||||
option(OPENRA3_VULKAN "Build the Vulkan viewer" ${OPENRA3_VULKAN_DEFAULT})
|
||||
set(OPENRA3_HAS_VULKAN OFF)
|
||||
if(OPENRA3_VULKAN AND OPENRA3_HAS_SDL3)
|
||||
if(OPENRA3_VULKAN AND OPENRA3_HAS_SDL3 AND NOT EMSCRIPTEN)
|
||||
set(OPENRA3_HAS_VULKAN ON)
|
||||
endif()
|
||||
if(OPENRA3_HAS_VULKAN)
|
||||
@@ -89,9 +102,11 @@ else()
|
||||
message(STATUS "OpenRA3: Vulkan viewer disabled - offscreen image only")
|
||||
endif()
|
||||
|
||||
add_library(ra3_volk STATIC third_party/volk/volk.c)
|
||||
target_include_directories(ra3_volk PUBLIC third_party/volk third_party/vulkan/include)
|
||||
target_compile_definitions(ra3_volk PUBLIC VK_NO_PROTOTYPES)
|
||||
if(NOT EMSCRIPTEN)
|
||||
add_library(ra3_volk STATIC third_party/volk/volk.c)
|
||||
target_include_directories(ra3_volk PUBLIC third_party/volk third_party/vulkan/include)
|
||||
target_compile_definitions(ra3_volk PUBLIC VK_NO_PROTOTYPES)
|
||||
endif()
|
||||
|
||||
# Embed the committed SPIR-V blobs into a generated header at configure time.
|
||||
# Regenerate with shaders/compile.sh after editing a .vert/.frag.
|
||||
@@ -135,6 +150,25 @@ function(openra3_embed_hlsl out_header)
|
||||
file(WRITE "${out_header}" "${content}")
|
||||
endfunction()
|
||||
|
||||
# Embed the GLSL ES sources for the WebGL backend as string literals (compiled
|
||||
# at runtime by the browser's GL, like the HLSL for Direct3D).
|
||||
function(openra3_embed_glsl out_header)
|
||||
set(content "// Generated by CMake from shaders/*.glsl.\n")
|
||||
string(APPEND content "#pragma once\n\nnamespace ra3_shaders {\n")
|
||||
foreach(src IN LISTS ARGN)
|
||||
if(NOT EXISTS "${src}")
|
||||
message(FATAL_ERROR "Missing GLSL shader ${src}")
|
||||
endif()
|
||||
get_filename_component(base "${src}" NAME_WE)
|
||||
string(MAKE_C_IDENTIFIER "${base}" ident)
|
||||
string(APPEND ident "_glsl")
|
||||
file(READ "${src}" text)
|
||||
string(APPEND content "inline constexpr const char ${ident}[] = R\"RA3GLSL(${text})RA3GLSL\";\n\n")
|
||||
endforeach()
|
||||
string(APPEND content "} // namespace ra3_shaders\n")
|
||||
file(WRITE "${out_header}" "${content}")
|
||||
endfunction()
|
||||
|
||||
# --- logging (vendored libenderlog, MIT) -------------------------------------
|
||||
# A standalone C++26 module logger (`import ender.log;`) with a per-run
|
||||
# archiving file sink. libc++ has no <stacktrace>, so on this toolchain the
|
||||
@@ -275,17 +309,55 @@ else()
|
||||
endif()
|
||||
openra3_target_defaults(ra3_dx)
|
||||
|
||||
# --- WebGL viewer (Emscripten/WebAssembly, or null fallback) ------------------
|
||||
# A peer of the Vulkan/Direct3D backends; built only for the wasm target, where
|
||||
# it uses an SDL3 canvas + GLES 3.0 (WebGL2). Elsewhere it is the null stub.
|
||||
option(OPENRA3_WEBGL "Build the WebGL viewer (Emscripten)" ON)
|
||||
set(OPENRA3_HAS_WEBGL OFF)
|
||||
if(OPENRA3_WEBGL AND EMSCRIPTEN)
|
||||
set(OPENRA3_HAS_WEBGL ON)
|
||||
endif()
|
||||
if(OPENRA3_HAS_WEBGL)
|
||||
message(STATUS "OpenRA3: WebGL viewer enabled")
|
||||
else()
|
||||
message(STATUS "OpenRA3: WebGL viewer disabled - null backend")
|
||||
endif()
|
||||
add_library(ra3_webgl STATIC)
|
||||
if(OPENRA3_HAS_WEBGL)
|
||||
openra3_embed_glsl(
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/generated/glsl_embedded.hpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/webgl_scene_vert.glsl"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/webgl_scene_frag.glsl"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/webgl_terrain_vert.glsl"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/webgl_terrain_frag.glsl"
|
||||
)
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/webgl_scene_vert.glsl"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/webgl_scene_frag.glsl"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/webgl_terrain_vert.glsl"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/webgl_terrain_frag.glsl")
|
||||
target_sources(ra3_webgl PUBLIC FILE_SET CXX_MODULES FILES src/webgl/ra3.webgl.cppm)
|
||||
target_include_directories(ra3_webgl PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/generated")
|
||||
target_link_libraries(ra3_webgl PUBLIC ra3_core ra3_render ra3_terrain ra3_client ra3_enderlog openra3_sdl3)
|
||||
# Target WebGL2 so GLES3-only entry points (glTexImage3D, VAOs, ...) link.
|
||||
target_link_options(ra3_webgl PUBLIC -sMIN_WEBGL_VERSION=2 -sMAX_WEBGL_VERSION=2)
|
||||
else()
|
||||
target_sources(ra3_webgl PUBLIC FILE_SET CXX_MODULES FILES src/webgl/ra3.webgl.null.cppm)
|
||||
target_link_libraries(ra3_webgl PUBLIC ra3_core ra3_render ra3_client)
|
||||
endif()
|
||||
openra3_target_defaults(ra3_webgl)
|
||||
|
||||
# --- display backend selection ------------------------------------------------
|
||||
add_library(ra3_display STATIC)
|
||||
target_sources(ra3_display PUBLIC FILE_SET CXX_MODULES FILES src/display/ra3.display.cppm)
|
||||
target_link_libraries(ra3_display PUBLIC ra3_core ra3_render ra3_terrain ra3_client ra3_ui ra3_vulkan ra3_dx)
|
||||
target_link_libraries(ra3_display PUBLIC ra3_core ra3_render ra3_terrain ra3_client ra3_ui ra3_vulkan ra3_dx ra3_webgl)
|
||||
openra3_target_defaults(ra3_display)
|
||||
|
||||
# --- umbrella -----------------------------------------------------------------
|
||||
add_library(ra3 STATIC)
|
||||
target_sources(ra3 PUBLIC FILE_SET CXX_MODULES FILES src/ra3.cppm)
|
||||
target_link_libraries(ra3 PUBLIC ra3_core ra3_logic ra3_client ra3_data ra3_game ra3_fs ra3_map ra3_skirmish ra3_render ra3_terrain ra3_display
|
||||
ra3_ui ra3_vulkan ra3_dx ra3_enderlog)
|
||||
ra3_ui ra3_vulkan ra3_dx ra3_webgl ra3_enderlog)
|
||||
openra3_target_defaults(ra3)
|
||||
|
||||
# --- executable ---------------------------------------------------------------
|
||||
@@ -300,6 +372,24 @@ if(WIN32 AND OPENRA3_SDL3_ROOT)
|
||||
"$<TARGET_FILE_DIR:openra3>")
|
||||
endif()
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
# Emit a ready-to-serve openra3.html that loads the module and drives the
|
||||
# #canvas element SDL3 renders into.
|
||||
set_target_properties(openra3 PROPERTIES SUFFIX ".html")
|
||||
target_link_options(openra3 PRIVATE
|
||||
"--shell-file=${CMAKE_CURRENT_SOURCE_DIR}/apps/web/shell.html"
|
||||
-sFORCE_FILESYSTEM=1)
|
||||
# Preload a compact assets directory into the virtual filesystem at /assets,
|
||||
# where the runtime looks next to the module. Prefer a per-map set (one map +
|
||||
# the tiles it uses, listed by `openra3 textures --map ID`): browsers forbid
|
||||
# synchronous on-demand file reads on the main thread, so bundling a small
|
||||
# set is the portable choice.
|
||||
set(OPENRA3_WEB_ASSETS "" CACHE PATH "Assets directory preloaded into the wasm FS at /assets")
|
||||
if(OPENRA3_WEB_ASSETS AND EXISTS "${OPENRA3_WEB_ASSETS}")
|
||||
target_link_options(openra3 PRIVATE "--preload-file=${OPENRA3_WEB_ASSETS}@/assets")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# --- packaging (.deb / .msi / portable archives) -----------------------------
|
||||
# CPack produces the release artifacts in one shot:
|
||||
# Linux -> DEB (dependencies derived from the binary with dpkg-shlibdeps)
|
||||
|
||||
Reference in New Issue
Block a user