The engine now runs on a plain Web Worker (no SDL, no PROXY_TO_PTHREAD: in a pthread Emscripten proxies every filesystem syscall to the main browser thread, where synchronous XHR - and thus FS.createLazyFile - is forbidden). A page/worker pair transfers an OffscreenCanvas and forwards DOM input; assets load lazily from an embedded manifest, so entering a map fetches only that map and its tiles. Presentation: ra3.webgpu (WebGPU via Emscripten's emdawnwebgpu port, WGSL shaders, the default on wasm) and ra3.wasmgl (WebGL2, GLSL ES). The legacy SDL ra3.webgl backend is removed.
570 lines
28 KiB
CMake
570 lines
28 KiB
CMake
cmake_minimum_required(VERSION 3.30)
|
|
|
|
# --- C++26 modules + `import std;` (clang + libc++) -------------------------
|
|
# CMake gates `import std` behind an experimental value documented in
|
|
# Help/dev/experimental.rst for the CMake version in use.
|
|
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. 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()
|
|
|
|
# libc++ must be selected before the compiler is probed: CMake builds its
|
|
# internal standard-library module target during `project()`, and that target
|
|
# only sees `CMAKE_CXX_FLAGS` (not later `add_compile_options`). The standard
|
|
# and extension settings must also be in place before `project()` so the
|
|
# internal `std.pcm` is built with the same configuration as our modules.
|
|
set(CMAKE_CXX_FLAGS_INIT "-stdlib=libc++")
|
|
set(CMAKE_EXE_LINKER_FLAGS_INIT "-stdlib=libc++")
|
|
set(CMAKE_SHARED_LINKER_FLAGS_INIT "-stdlib=libc++")
|
|
set(CMAKE_CXX_STANDARD 26)
|
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
|
set(CMAKE_CXX_SCAN_FOR_MODULES ON)
|
|
|
|
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)
|
|
endif()
|
|
|
|
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
|
|
|
|
option(OPENRA3_WERROR "Treat warnings as errors" OFF)
|
|
|
|
# Windows: statically link the C++ runtime (libc++, libunwind) so the .exe only
|
|
# needs SDL3.dll next to it at runtime.
|
|
if(WIN32)
|
|
add_link_options(-static)
|
|
endif()
|
|
|
|
function(openra3_target_defaults target)
|
|
target_compile_options(${target} PRIVATE -Wall -Wextra)
|
|
set_property(TARGET ${target} PROPERTY CXX_MODULE_STD ON)
|
|
if(OPENRA3_WERROR)
|
|
target_compile_options(${target} PRIVATE -Werror)
|
|
endif()
|
|
endfunction()
|
|
|
|
# --- SDL3: pkg-config on Linux, or an explicit MinGW root for Windows --------
|
|
add_library(openra3_sdl3 INTERFACE)
|
|
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(EMSCRIPTEN)
|
|
# The wasm build runs the engine on a Web Worker and renders into an
|
|
# OffscreenCanvas (ra3.webgpu / ra3.wasmgl), so it links no SDL: SDL3's
|
|
# Emscripten port is main-thread DOM only and cannot run in a worker.
|
|
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)
|
|
else()
|
|
find_package(PkgConfig QUIET)
|
|
if(PkgConfig_FOUND)
|
|
pkg_check_modules(SDL3 QUIET IMPORTED_TARGET sdl3)
|
|
endif()
|
|
if(SDL3_FOUND)
|
|
target_link_libraries(openra3_sdl3 INTERFACE PkgConfig::SDL3)
|
|
set(OPENRA3_HAS_SDL3 ON)
|
|
endif()
|
|
endif()
|
|
if(OPENRA3_HAS_SDL3)
|
|
message(STATUS "OpenRA3: SDL3 found - windowed viewer enabled")
|
|
else()
|
|
message(STATUS "OpenRA3: SDL3 not found - offscreen BMP output only")
|
|
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.
|
|
# 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 AND NOT EMSCRIPTEN)
|
|
set(OPENRA3_HAS_VULKAN ON)
|
|
endif()
|
|
if(OPENRA3_HAS_VULKAN)
|
|
message(STATUS "OpenRA3: Vulkan viewer enabled (vendored volk)")
|
|
else()
|
|
message(STATUS "OpenRA3: Vulkan viewer disabled - offscreen image only")
|
|
endif()
|
|
|
|
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.
|
|
function(openra3_embed_spirv out_header)
|
|
set(content "// Generated by CMake from shaders/generated/*.spv.\n")
|
|
string(APPEND content "#pragma once\n#include <cstddef>\n#include <cstdint>\n\nnamespace ra3_shaders {\n")
|
|
foreach(spv IN LISTS ARGN)
|
|
if(NOT EXISTS "${spv}")
|
|
message(FATAL_ERROR "Missing precompiled shader ${spv}; run shaders/compile.sh")
|
|
endif()
|
|
get_filename_component(base "${spv}" NAME)
|
|
string(MAKE_C_IDENTIFIER "${base}" ident)
|
|
file(READ "${spv}" hex HEX)
|
|
string(LENGTH "${hex}" hexlen)
|
|
math(EXPR words "${hexlen} / 8")
|
|
string(REGEX REPLACE "(..)(..)(..)(..)" "0x\\4\\3\\2\\1," words_list "${hex}")
|
|
string(APPEND content "inline constexpr std::uint32_t ${ident}[] = {${words_list}};\n")
|
|
string(APPEND content "inline constexpr std::size_t ${ident}_words = ${words};\n\n")
|
|
endforeach()
|
|
string(APPEND content "} // namespace ra3_shaders\n")
|
|
file(WRITE "${out_header}" "${content}")
|
|
endfunction()
|
|
|
|
# Embed the HLSL sources as string literals. The Direct3D backends compile them
|
|
# at runtime with d3dcompiler_47, so no HLSL compiler is needed at build time
|
|
# (the Windows target cross-compiles from Linux).
|
|
function(openra3_embed_hlsl out_header)
|
|
set(content "// Generated by CMake from shaders/*.hlsl.\n")
|
|
string(APPEND content "#pragma once\n\nnamespace ra3_shaders {\n")
|
|
foreach(src IN LISTS ARGN)
|
|
if(NOT EXISTS "${src}")
|
|
message(FATAL_ERROR "Missing HLSL shader ${src}")
|
|
endif()
|
|
get_filename_component(base "${src}" NAME_WE)
|
|
string(MAKE_C_IDENTIFIER "${base}" ident)
|
|
string(APPEND ident "_hlsl")
|
|
file(READ "${src}" text)
|
|
string(APPEND content "inline constexpr const char ${ident}[] = R\"RA3HLSL(${text})RA3HLSL\";\n\n")
|
|
endforeach()
|
|
string(APPEND content "} // namespace ra3_shaders\n")
|
|
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()
|
|
|
|
# Embed the WGSL sources for the WebGPU backend as string literals (compiled at
|
|
# runtime by the browser's WebGPU, like the GLSL/HLSL for the other backends).
|
|
function(openra3_embed_wgsl out_header)
|
|
set(content "// Generated by CMake from shaders/*.wgsl.\n")
|
|
string(APPEND content "#pragma once\n\nnamespace ra3_shaders {\n")
|
|
foreach(src IN LISTS ARGN)
|
|
if(NOT EXISTS "${src}")
|
|
message(FATAL_ERROR "Missing WGSL shader ${src}")
|
|
endif()
|
|
get_filename_component(base "${src}" NAME_WE)
|
|
string(MAKE_C_IDENTIFIER "${base}" ident)
|
|
string(APPEND ident "_wgsl")
|
|
file(READ "${src}" text)
|
|
string(APPEND content "inline constexpr const char ${ident}[] = R\"RA3WGSL(${text})RA3WGSL\";\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
|
|
# module records call sites but no stacks; it defaults ENDERLOG_HAS_STACKTRACE
|
|
# to 0 without any define.
|
|
add_library(ra3_enderlog STATIC)
|
|
target_sources(ra3_enderlog PUBLIC FILE_SET CXX_MODULES FILES third_party/libenderlog/src/ender.log.cppm)
|
|
target_compile_features(ra3_enderlog PUBLIC cxx_std_26)
|
|
openra3_target_defaults(ra3_enderlog)
|
|
|
|
# --- engine core -------------------------------------------------------------
|
|
add_library(ra3_core STATIC)
|
|
target_sources(ra3_core PUBLIC FILE_SET CXX_MODULES FILES src/core/ra3.core.cppm)
|
|
openra3_target_defaults(ra3_core)
|
|
|
|
# --- game logic --------------------------------------------------------------
|
|
add_library(ra3_logic STATIC)
|
|
target_sources(ra3_logic PUBLIC FILE_SET CXX_MODULES FILES src/logic/ra3.logic.cppm)
|
|
target_link_libraries(ra3_logic PUBLIC ra3_core)
|
|
openra3_target_defaults(ra3_logic)
|
|
|
|
# --- RA3 gameplay data (real balance) ----------------------------------------
|
|
add_library(ra3_data STATIC)
|
|
target_sources(ra3_data PUBLIC FILE_SET CXX_MODULES FILES src/data/ra3.data.cppm)
|
|
target_link_libraries(ra3_data PUBLIC ra3_core)
|
|
openra3_target_defaults(ra3_data)
|
|
|
|
# --- client ------------------------------------------------------------------
|
|
add_library(ra3_client STATIC)
|
|
target_sources(ra3_client PUBLIC FILE_SET CXX_MODULES FILES src/client/ra3.client.cppm)
|
|
target_link_libraries(ra3_client PUBLIC ra3_core ra3_logic)
|
|
openra3_target_defaults(ra3_client)
|
|
|
|
# --- RA3 game definitions ----------------------------------------------------
|
|
add_library(ra3_game STATIC)
|
|
target_sources(ra3_game PUBLIC FILE_SET CXX_MODULES FILES src/game/ra3.game.cppm)
|
|
target_link_libraries(ra3_game PUBLIC ra3_core ra3_logic)
|
|
openra3_target_defaults(ra3_game)
|
|
|
|
# --- filesystem: BIG4 + RefPack ---------------------------------------------
|
|
add_library(ra3_fs STATIC)
|
|
target_sources(ra3_fs PUBLIC FILE_SET CXX_MODULES FILES src/fs/ra3.fs.cppm)
|
|
target_link_libraries(ra3_fs PUBLIC ra3_core)
|
|
openra3_target_defaults(ra3_fs)
|
|
|
|
# --- map discovery/loading ---------------------------------------------------
|
|
add_library(ra3_map STATIC)
|
|
target_sources(ra3_map PUBLIC FILE_SET CXX_MODULES FILES src/map/ra3.map.cppm)
|
|
target_link_libraries(ra3_map PUBLIC ra3_core ra3_fs)
|
|
openra3_target_defaults(ra3_map)
|
|
|
|
# --- minimal skirmish simulation --------------------------------------------
|
|
add_library(ra3_skirmish STATIC)
|
|
target_sources(ra3_skirmish PUBLIC FILE_SET CXX_MODULES FILES src/skirmish/ra3.skirmish.cppm)
|
|
target_link_libraries(ra3_skirmish PUBLIC ra3_core ra3_logic ra3_game ra3_data ra3_map)
|
|
openra3_target_defaults(ra3_skirmish)
|
|
|
|
# --- software renderer -------------------------------------------------------
|
|
add_library(ra3_render STATIC)
|
|
target_sources(ra3_render PUBLIC FILE_SET CXX_MODULES FILES src/render/ra3.render.cppm)
|
|
target_link_libraries(ra3_render PUBLIC ra3_core)
|
|
openra3_target_defaults(ra3_render)
|
|
|
|
# --- real map terrain (HeightMapData / BlendTileData) ------------------------
|
|
add_library(ra3_terrain STATIC)
|
|
target_sources(ra3_terrain PUBLIC FILE_SET CXX_MODULES FILES src/terrain/ra3.terrain.cppm)
|
|
target_link_libraries(ra3_terrain PUBLIC ra3_core ra3_fs ra3_render)
|
|
openra3_target_defaults(ra3_terrain)
|
|
|
|
# The presentation facade imports render + terrain (for the terrain capability).
|
|
target_link_libraries(ra3_client PUBLIC ra3_render ra3_terrain)
|
|
|
|
# --- windowed viewer (SDL3 or null) -----------------------------------------
|
|
add_library(ra3_ui STATIC)
|
|
if(OPENRA3_HAS_SDL3)
|
|
target_sources(ra3_ui PUBLIC FILE_SET CXX_MODULES FILES src/ui/ra3.ui.sdl.cppm)
|
|
target_link_libraries(ra3_ui PUBLIC ra3_core ra3_render ra3_client openra3_sdl3)
|
|
else()
|
|
target_sources(ra3_ui PUBLIC FILE_SET CXX_MODULES FILES src/ui/ra3.ui.null.cppm)
|
|
target_link_libraries(ra3_ui PUBLIC ra3_core ra3_render ra3_client)
|
|
endif()
|
|
openra3_target_defaults(ra3_ui)
|
|
|
|
# --- Vulkan viewer (SDL3 surface + Vulkan, or null fallback) ------------------
|
|
add_library(ra3_vulkan STATIC)
|
|
if(OPENRA3_HAS_VULKAN AND OPENRA3_HAS_SDL3)
|
|
openra3_embed_spirv(
|
|
"${CMAKE_CURRENT_BINARY_DIR}/generated/shaders_embedded.hpp"
|
|
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/generated/scene.vert.spv"
|
|
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/generated/scene.frag.spv"
|
|
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/generated/terrain.vert.spv"
|
|
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/generated/terrain.frag.spv"
|
|
)
|
|
# The blobs are read at configure time, so re-run CMake when they change.
|
|
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
|
|
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/generated/scene.vert.spv"
|
|
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/generated/scene.frag.spv"
|
|
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/generated/terrain.vert.spv"
|
|
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/generated/terrain.frag.spv")
|
|
target_sources(ra3_vulkan PUBLIC FILE_SET CXX_MODULES FILES src/vulkan/ra3.vulkan.sdl.cppm)
|
|
target_include_directories(ra3_vulkan PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/generated")
|
|
target_link_libraries(ra3_vulkan PUBLIC ra3_core ra3_render ra3_terrain ra3_client openra3_sdl3 ra3_volk)
|
|
else()
|
|
target_sources(ra3_vulkan PUBLIC FILE_SET CXX_MODULES FILES src/vulkan/ra3.vulkan.null.cppm)
|
|
target_link_libraries(ra3_vulkan PUBLIC ra3_core ra3_render ra3_client)
|
|
endif()
|
|
openra3_target_defaults(ra3_vulkan)
|
|
|
|
# --- Direct3D viewer (D3D11 + D3D12, or null fallback) ------------------------
|
|
# Direct3D is Windows-only, so the real backends build only for the MinGW target;
|
|
# every other platform links the `ra3.dx.null` fallback that fails `init` cleanly.
|
|
option(OPENRA3_DX "Build the Direct3D 11/12 viewer" ON)
|
|
set(OPENRA3_HAS_DX OFF)
|
|
if(OPENRA3_DX AND WIN32 AND OPENRA3_HAS_SDL3)
|
|
set(OPENRA3_HAS_DX ON)
|
|
endif()
|
|
if(OPENRA3_HAS_DX)
|
|
message(STATUS "OpenRA3: Direct3D 11/12 viewer enabled (runtime HLSL via d3dcompiler)")
|
|
else()
|
|
message(STATUS "OpenRA3: Direct3D viewer disabled - offscreen image only")
|
|
endif()
|
|
add_library(ra3_dx STATIC)
|
|
if(OPENRA3_HAS_DX)
|
|
openra3_embed_hlsl(
|
|
"${CMAKE_CURRENT_BINARY_DIR}/generated/dx_shaders_embedded.hpp"
|
|
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/dx_scene.hlsl"
|
|
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/dx_terrain.hlsl"
|
|
)
|
|
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
|
|
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/dx_scene.hlsl"
|
|
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/dx_terrain.hlsl")
|
|
target_sources(ra3_dx PUBLIC FILE_SET CXX_MODULES FILES src/dx/ra3.dx.cppm)
|
|
target_include_directories(ra3_dx PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/generated")
|
|
target_link_libraries(ra3_dx PUBLIC ra3_core ra3_render ra3_terrain ra3_client ra3_enderlog openra3_sdl3 d3d11 d3d12 dxgi d3dcompiler)
|
|
else()
|
|
target_sources(ra3_dx PUBLIC FILE_SET CXX_MODULES FILES src/dx/ra3.dx.null.cppm)
|
|
target_link_libraries(ra3_dx PUBLIC ra3_core ra3_render ra3_client)
|
|
endif()
|
|
openra3_target_defaults(ra3_dx)
|
|
|
|
# --- wasm worker viewer (SDL-free OffscreenCanvas + WebGL2, or null fallback) --
|
|
# The default wasm backend: the engine runs on a plain Web Worker and renders
|
|
# into an OffscreenCanvas transferred from the page, with no SDL. Non-wasm builds
|
|
# link the null stub (init fails, so desktop backends are unaffected).
|
|
option(OPENRA3_WASMGL "Build the SDL-free wasm worker viewer (Emscripten)" ON)
|
|
set(OPENRA3_HAS_WASMGL OFF)
|
|
if(OPENRA3_WASMGL AND EMSCRIPTEN)
|
|
set(OPENRA3_HAS_WASMGL ON)
|
|
endif()
|
|
add_library(ra3_wasmgl STATIC)
|
|
if(OPENRA3_HAS_WASMGL)
|
|
message(STATUS "OpenRA3: SDL-free wasm worker viewer enabled")
|
|
openra3_embed_glsl(
|
|
"${CMAKE_CURRENT_BINARY_DIR}/generated/wasmgl_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_wasmgl PUBLIC FILE_SET CXX_MODULES FILES src/wasmgl/ra3.wasmgl.cppm)
|
|
target_include_directories(ra3_wasmgl PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/generated")
|
|
target_link_libraries(ra3_wasmgl PUBLIC ra3_core ra3_render ra3_terrain ra3_client ra3_enderlog)
|
|
# Target WebGL2 so GLES3-only entry points (glTexImage3D, VAOs, ...) link.
|
|
target_link_options(ra3_wasmgl PUBLIC -sMIN_WEBGL_VERSION=2 -sMAX_WEBGL_VERSION=2)
|
|
else()
|
|
message(STATUS "OpenRA3: SDL-free wasm worker viewer disabled (non-wasm) - null backend")
|
|
target_sources(ra3_wasmgl PUBLIC FILE_SET CXX_MODULES FILES src/wasmgl/ra3.wasmgl.null.cppm)
|
|
target_link_libraries(ra3_wasmgl PUBLIC ra3_core ra3_render ra3_client)
|
|
endif()
|
|
openra3_target_defaults(ra3_wasmgl)
|
|
|
|
# --- wasm WebGPU viewer (Dawn emdawnwebgpu, or null fallback) ------------------
|
|
# A peer of ra3.wasmgl in the same Web Worker; web WebGPU is WGSL-only, so it has
|
|
# its own WGSL shaders (the Vulkan SPIR-V blobs cannot be used on the web). The
|
|
# `--use-port=emdawnwebgpu` port is downloaded/cached by Emscripten and linked
|
|
# into the final module.
|
|
option(OPENRA3_WEBGPU "Build the WebGPU wasm viewer (Emscripten emdawnwebgpu)" ON)
|
|
set(OPENRA3_HAS_WEBGPU OFF)
|
|
if(OPENRA3_WEBGPU AND EMSCRIPTEN)
|
|
set(OPENRA3_HAS_WEBGPU ON)
|
|
endif()
|
|
add_library(ra3_webgpu STATIC)
|
|
if(OPENRA3_HAS_WEBGPU)
|
|
message(STATUS "OpenRA3: WebGPU wasm viewer enabled (emdawnwebgpu)")
|
|
openra3_embed_wgsl(
|
|
"${CMAKE_CURRENT_BINARY_DIR}/generated/webgpu_wgsl_embedded.hpp"
|
|
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/webgpu_scene.wgsl"
|
|
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/webgpu_terrain.wgsl"
|
|
)
|
|
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
|
|
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/webgpu_scene.wgsl"
|
|
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/webgpu_terrain.wgsl")
|
|
target_sources(ra3_webgpu PUBLIC FILE_SET CXX_MODULES FILES src/webgpu/ra3.webgpu.cppm)
|
|
target_include_directories(ra3_webgpu PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/generated")
|
|
# CMake's module dependency scanner does not process `--use-port`, so point
|
|
# the include path at the (image-baked) port package explicitly.
|
|
set(OPENRA3_EMDAWNWEBGPU_DIR "$ENV{EMSDK}/upstream/emscripten/cache/ports/emdawnwebgpu/emdawnwebgpu_pkg")
|
|
target_include_directories(ra3_webgpu SYSTEM PRIVATE
|
|
"${OPENRA3_EMDAWNWEBGPU_DIR}/webgpu_cpp/include"
|
|
"${OPENRA3_EMDAWNWEBGPU_DIR}/webgpu/include")
|
|
target_compile_options(ra3_webgpu PUBLIC --use-port=emdawnwebgpu)
|
|
target_link_options(ra3_webgpu PUBLIC --use-port=emdawnwebgpu)
|
|
target_link_libraries(ra3_webgpu PUBLIC ra3_core ra3_render ra3_terrain ra3_client ra3_wasmgl ra3_enderlog)
|
|
else()
|
|
message(STATUS "OpenRA3: WebGPU wasm viewer disabled (non-wasm) - null backend")
|
|
target_sources(ra3_webgpu PUBLIC FILE_SET CXX_MODULES FILES src/webgpu/ra3.webgpu.null.cppm)
|
|
target_link_libraries(ra3_webgpu PUBLIC ra3_core ra3_render ra3_client)
|
|
endif()
|
|
openra3_target_defaults(ra3_webgpu)
|
|
|
|
# --- 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 ra3_wasmgl ra3_webgpu)
|
|
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_wasmgl ra3_webgpu ra3_enderlog)
|
|
openra3_target_defaults(ra3)
|
|
|
|
# --- executable ---------------------------------------------------------------
|
|
add_executable(openra3 apps/openra3/main.cpp)
|
|
target_link_libraries(openra3 PRIVATE ra3)
|
|
openra3_target_defaults(openra3)
|
|
|
|
if(WIN32 AND OPENRA3_SDL3_ROOT)
|
|
add_custom_command(TARGET openra3 POST_BUILD
|
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
|
"${OPENRA3_SDL3_ROOT}/${OPENRA3_SDL3_MINGW_TRIPLE}/bin/SDL3.dll"
|
|
"$<TARGET_FILE_DIR:openra3>")
|
|
endif()
|
|
|
|
if(EMSCRIPTEN)
|
|
# Emit openra3.js/.wasm. The engine runs inside apps/web/openra3.worker.js
|
|
# (a plain Web Worker), which the page (apps/web/index.html) loads and hands
|
|
# an OffscreenCanvas; so there is no Emscripten HTML shell here - both web
|
|
# files are copied next to the module.
|
|
set_target_properties(openra3 PROPERTIES SUFFIX ".js")
|
|
target_link_options(openra3 PRIVATE
|
|
-sFORCE_FILESYSTEM=1
|
|
# index.html input glue calls the exported openra3_* functions via ccall.
|
|
"-sEXPORTED_RUNTIME_METHODS=ccall,cwrap")
|
|
add_custom_command(TARGET openra3 POST_BUILD
|
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
|
"${CMAKE_CURRENT_SOURCE_DIR}/apps/web/index.html"
|
|
"${CMAKE_CURRENT_SOURCE_DIR}/apps/web/openra3.worker.js"
|
|
"$<TARGET_FILE_DIR:openra3>"
|
|
COMMENT "Copying the OpenRA3 web page and worker bootstrap")
|
|
# On-demand assets: `OPENRA3_WEB_MANIFEST` is a JSON `{"files": [...]}` listing
|
|
# every asset relative to the assets root (generate it with
|
|
# scripts/make_web_manifest.py). Only that small manifest is preloaded, to
|
|
# /assets.manifest.json; at startup the worker registers every listed file as
|
|
# an Emscripten lazy file, so the browser fetches each asset when the engine
|
|
# first opens it (serve the tree at <root>/assets, or pass serve.py --assets).
|
|
set(OPENRA3_WEB_MANIFEST "" CACHE FILEPATH "Assets manifest JSON, embedded as /assets.manifest.json for lazy loading")
|
|
if(OPENRA3_WEB_MANIFEST AND EXISTS "${OPENRA3_WEB_MANIFEST}")
|
|
target_link_options(openra3 PRIVATE "--embed-file=${OPENRA3_WEB_MANIFEST}@/assets.manifest.json")
|
|
endif()
|
|
# Legacy: preload a whole assets directory into the virtual FS at /assets (the
|
|
# monolithic path the lazy worker backend replaces). Only used when no manifest
|
|
# is given, since lazy files cannot shadow a preloaded tree.
|
|
set(OPENRA3_WEB_ASSETS "" CACHE PATH "Assets directory preloaded into the wasm FS at /assets (legacy)")
|
|
if(NOT (OPENRA3_WEB_MANIFEST AND EXISTS "${OPENRA3_WEB_MANIFEST}") AND 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)
|
|
# plus a portable .tar.gz
|
|
# Windows -> a Windows Installer .msi (built with wixl from msitools when the
|
|
# WiX tools are absent, so the Linux cross image can make it) plus a
|
|
# portable .zip with the exe and SDL3.dll
|
|
# OPENRA3_DISTRO_TAG (e.g. ubuntu26.04) is appended to the Linux file name so the
|
|
# per-distro images do not collide.
|
|
install(TARGETS openra3 RUNTIME DESTINATION bin)
|
|
if(WIN32 AND OPENRA3_SDL3_ROOT)
|
|
install(FILES "${OPENRA3_SDL3_ROOT}/${OPENRA3_SDL3_MINGW_TRIPLE}/bin/SDL3.dll" DESTINATION bin)
|
|
endif()
|
|
|
|
set(OPENRA3_DISTRO_TAG "" CACHE STRING "Distro tag appended to package file names (e.g. ubuntu26.04)")
|
|
set(CPACK_PACKAGE_NAME "openra3")
|
|
set(CPACK_PACKAGE_VENDOR "OpenRA3")
|
|
set(CPACK_PACKAGE_CONTACT "OpenRA3 <noreply@openra3.invalid>")
|
|
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "OpenRA3 - a from-scratch reimplementation of Red Alert 3")
|
|
set(CPACK_PACKAGE_HOMEPAGE_URL "https://git.ender.cool/EnderTheCoder/openra3")
|
|
set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}")
|
|
set(CPACK_PACKAGE_EXECUTABLES "openra3" "OpenRA3")
|
|
set(CPACK_STRIP_FILES ON)
|
|
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
|
|
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
|
|
endif()
|
|
|
|
if(WIN32)
|
|
if(CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64")
|
|
set(OPENRA3_WIN_ARCH "arm64")
|
|
else()
|
|
set(OPENRA3_WIN_ARCH "x86_64")
|
|
endif()
|
|
set(CPACK_PACKAGE_FILE_NAME "openra3-${PROJECT_VERSION}-windows-${OPENRA3_WIN_ARCH}")
|
|
# Portable .zip (exe + SDL3.dll).
|
|
set(CPACK_GENERATOR "ZIP")
|
|
|
|
# A Windows Installer (.msi) straight from the Linux cross build via wixl
|
|
# (msitools). wixl 0.106 has no arm64 support, so the MSI is offered for
|
|
# x86_64 and ARM64 ships the portable zip.
|
|
find_program(OPENRA3_WIXL wixl)
|
|
if(OPENRA3_WIXL AND NOT CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64")
|
|
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/packaging/openra3.wxs.in"
|
|
"${CMAKE_CURRENT_BINARY_DIR}/openra3.wxs" @ONLY)
|
|
add_custom_target(openra3_msi
|
|
COMMAND "${OPENRA3_WIXL}" --arch x64
|
|
-D "exe=$<TARGET_FILE:openra3>"
|
|
-D "dll=${OPENRA3_SDL3_ROOT}/${OPENRA3_SDL3_MINGW_TRIPLE}/bin/SDL3.dll"
|
|
-o "$<TARGET_FILE_DIR:openra3>/${CPACK_PACKAGE_FILE_NAME}.msi"
|
|
"${CMAKE_CURRENT_BINARY_DIR}/openra3.wxs"
|
|
DEPENDS openra3
|
|
COMMENT "Building Windows Installer (${CPACK_PACKAGE_FILE_NAME}.msi) with wixl"
|
|
VERBATIM)
|
|
endif()
|
|
else()
|
|
if(CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64")
|
|
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "arm64")
|
|
else()
|
|
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "amd64")
|
|
endif()
|
|
set(CPACK_PACKAGE_FILE_NAME "openra3_${PROJECT_VERSION}_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}${OPENRA3_DISTRO_TAG}")
|
|
set(CPACK_GENERATOR "TGZ;DEB")
|
|
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
|
|
set(CPACK_DEBIAN_PACKAGE_SECTION "games")
|
|
set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
|
|
endif()
|
|
include(CPack)
|
|
|
|
# --- extracted assets ---------------------------------------------------------
|
|
# At build time, extract the retail assets into <exe_dir>/assets so the
|
|
# executable is self-contained (no --game-dir at run time). This drives the
|
|
# ra3tools Python scripts (the game install + Python, no target executable), so
|
|
# it also runs for a cross-build when the install is mounted into the container
|
|
# and RA3_GAME_DIR points at it. The engine's own map/terrain reader is embedded
|
|
# in the exe (run `openra3 extract` on a native host if you prefer).
|
|
set(RA3_GAME_DIR "C:/Red Alert 3" CACHE PATH "Red Alert 3 installation used to extract assets")
|
|
set(RA3TOOLS_DIR "C:/Users/Ender/CLionProjects/ra3-headless/ra3tools" CACHE PATH "ra3tools scripts (asset extraction)")
|
|
find_program(OPENRA3_PYTHON NAMES python3 python)
|
|
set(PYTHON_EXECUTABLE "${OPENRA3_PYTHON}" CACHE FILEPATH "Python interpreter used to run ra3tools")
|
|
option(OPENRA3_EXTRACT_ALL "Extract every asset (models/textures/audio/movies) via ra3tools" ON)
|
|
|
|
if(EXISTS "${RA3_GAME_DIR}/Data" AND OPENRA3_PYTHON)
|
|
add_custom_command(
|
|
OUTPUT "${CMAKE_BINARY_DIR}/.assets_stamp"
|
|
COMMAND ${CMAKE_COMMAND}
|
|
"-DGAME_DIR=${RA3_GAME_DIR}"
|
|
"-DOUT=$<TARGET_FILE_DIR:openra3>/assets"
|
|
"-DPYTHON=${PYTHON_EXECUTABLE}"
|
|
"-DRA3TOOLS=${RA3TOOLS_DIR}"
|
|
"-DEXTRACT_ALL=$<IF:$<BOOL:${OPENRA3_EXTRACT_ALL}>,ON,OFF>"
|
|
"-DSTAMP=${CMAKE_BINARY_DIR}/.assets_stamp"
|
|
-P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/extract_assets.cmake"
|
|
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/extract_assets.cmake"
|
|
COMMENT "Extracting Red Alert 3 assets to $<TARGET_FILE_DIR:openra3>/assets"
|
|
VERBATIM)
|
|
add_custom_target(openra3_assets ALL DEPENDS "${CMAKE_BINARY_DIR}/.assets_stamp")
|
|
else()
|
|
message(STATUS "OpenRA3: asset extraction skipped (RA3_GAME_DIR '${RA3_GAME_DIR}' or Python not found)")
|
|
endif()
|
|
|
|
# --- tests --------------------------------------------------------------------
|
|
enable_testing()
|
|
add_executable(ra3_tests tests/ra3_tests.cpp)
|
|
target_link_libraries(ra3_tests PRIVATE ra3)
|
|
openra3_target_defaults(ra3_tests)
|
|
add_test(NAME ra3_tests COMMAND ra3_tests)
|