- 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.
499 lines
24 KiB
CMake
499 lines
24 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)
|
|
# 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)
|
|
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()
|
|
|
|
# --- 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)
|
|
|
|
# --- 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 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_webgl 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 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)
|
|
# 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)
|