10 Commits
Author SHA1 Message Date
EnderTheCoder 70d4beca4f 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.
2026-09-28 22:57:19 +08:00
EnderTheCoder d0ae4c76ba v0.5.0: Windows/ARM64 and Linux/ARM64 cross targets, .deb/.msi packaging
- Cross toolchains: llvm-mingw-aarch64 (Windows ARM64; SDL3 staged from MSYS2's
  clangarm64 repo since SDL ships no MinGW ARM64 dev package) and
  clang-aarch64-linux-gnu (Linux ARM64; arm64 libc++/SDL3 via dpkg multiarch).
- Distro images: Dockerfile.linux-arm64, Dockerfile.win-arm64, Dockerfile.debian
  and Dockerfile.debian-arm64 (Debian 13 with upstream CMake, whose 3.31 gate
  cannot enable import std).
- Packaging via CPack: .deb for Ubuntu 26.04 and Debian 13 on amd64 and arm64
  (dependencies derived by dpkg-shlibdeps), portable .zip/.tar.gz, and a
  Windows .msi built with wixl (msitools) straight from the Linux cross image.
  wixl 0.106 lacks arm64, so Windows/ARM64 ships the portable zip only.
- CMake: parameterize the SDL3 MinGW triple, add install() + CPack config.
- CI: build/package jobs for both arm64 targets and both Debian packages;
  existing Linux/Windows jobs now also emit the .deb / .zip + .msi.
- scripts/: build-linux-arm64, build-windows-arm64, build-debian,
  build-debian-arm64.
2026-09-28 20:38:40 +08:00
EnderTheCoder 3caf18409b v0.4.0: Direct3D 11/12 backends, in-game renderer selection, per-run logging
- ra3.dx: D3D11 and D3D12 presentation backends (runtime HLSL via d3dcompiler);
  2D image blit and the GPU heightfield terrain raymarch, with the corner
  minimap/FPS overlays. Non-Windows builds link a null fallback.
- ra3.display: preferred backend plus ordered fallback (Vulkan/D3D11/D3D12/SDL);
  menu gains a Renderer option and the CLI gains --dx11/--dx12/--sdl, which the
  render command now honours too.
- vendor libenderlog (MIT): every run writes openra3.log next to the exe and
  archives the previous run's log; records at warn and above carry a call stack
  (native fallback, since libc++ has no <stacktrace>).
- Windows crash reporter writes openra3_crash.log (faulting module + backtrace);
  D3D/DXGI diagnostics are routed through the logger.
2026-09-28 13:03:37 +08:00
EnderTheCoder 59261868e1 docs: rewrite ARCHITECTURE as a Module/Function/Feature master plan
Decompose the full Red Alert 3 feature set three levels deep (29 modules, 169 functions, ~500 features) with per-feature status and milestone tags. Fold the release roadmap into ARCHITECTURE section 5 and remove docs/ROADMAP.md.
2026-09-26 22:28:19 +08:00
EnderTheCoder a7612878e5 ci: use the dind containerd snapshotter so the docker driver can push/cache to Harbor over plain HTTP 2026-09-20 02:35:31 +08:00
EnderTheCoder bb3f8306ef ci: push toolchain images to Harbor (192.168.1.11:9090/openra3) instead of docker-save artifacts 2026-09-20 02:32:05 +08:00
EnderTheCoder 7cb4d18308 ci: fix dind anchor (services must be an array); push images to Docker Hub instead of docker-save artifacts 2026-09-20 02:30:00 +08:00
EnderTheCoder b6d619254d v0.3.0: display abstraction, map-browser menu, SAGE terrain tiling/blends
- ra3.client::display: shared interactive loops; SDL and Vulkan backends
  implement only the primitives (init/present/poll_event/window_size/
  key_down/present_terrain). ra3.display picks the backend.
- Menu: maps by localized name (gamestrings.csf), red/gold theme, hover
  highlight, mouse + keyboard, wheel scroll, fullscreen and FPS/vsync
  options, loading progress bar.
- Terrain: continuous tile sampling via a texture array (REPEAT, uv =
  cell/(2*cellSize)) removes per-cell grid seams; SAGE blend ramp for
  material transitions; FPS label + top-right minimap overlays.
- Skip the skirmish sim for map views; reuse the Vulkan texture; no idle
  terrain redraw.
2026-09-20 02:20:39 +08:00
EnderTheCoder 702b4e29aa v0.2.1: statically link the Windows C++ runtime (only SDL3.dll needed) 2026-09-12 02:18:49 +08:00
EnderTheCoder 1d12e4e099 v0.2.0: clang + import std; isolated Windows cross-build (llvm-mingw + SDL3) 2026-09-12 02:04:25 +08:00
103 changed files with 43579 additions and 743 deletions
+1
View File
@@ -30,3 +30,4 @@ compile_commands.json
# local data dumps / RE artifacts
re-data/
*.log
*.log.[0-9]*
+316 -46
View File
@@ -4,61 +4,331 @@ stages:
variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: ""
# Where to push the dev image. Leave empty to not push at all.
# Examples: "192.168.1.11:9090/<group>/openra3" or "docker.io/<user>/openra3"
IMAGE_NAME: ""
DOCKER_BUILDKIT: "1"
# apt mirror used inside both images (override per pipeline if needed)
APT_MIRROR: "http://mirrors.tuna.tsinghua.edu.cn/ubuntu"
# Images are shared through Harbor (plain HTTP), never as artifacts.
HARBOR_HOST: "192.168.1.11:9090"
HARBOR_PROJECT: "openra3"
IMAGE: "$HARBOR_HOST/$HARBOR_PROJECT/$CI_PROJECT_NAME"
image_build:
# dind: pull docker.io through the intranet Harbor proxy (plain HTTP ->
# --insecure-registry) and enable the containerd snapshotter so the *docker*
# driver can export the registry BuildKit cache (the classic overlay2 driver
# rejects it). Anchor is an array, so `services: *dind` stays an array.
.dind: &dind
- name: docker:dind
command:
- --registry-mirror=http://192.168.1.11:9090/dockerhub
- --insecure-registry=192.168.1.11:9090
- --feature=containerd-snapshotter=true
# Retry helper for transient network failures (login / build / push / pull).
.retry: &retry
- |
retry() {
n=0
until "$@"; do
n=$((n + 1))
[ "$n" -ge 5 ] && { echo "failed after $n tries: $*" >&2; return 1; }
echo "attempt $n failed, retrying: $*" >&2
sleep 15
done
}
# --- build the two isolated toolchain images and push them to Harbor ----------
linux_image:
stage: image
image: docker:latest
services:
- name: docker:dind
# Pull docker.io images through the intranet Harbor pull-through cache;
# plain HTTP registry, hence --insecure-registry.
command:
- --registry-mirror=http://192.168.1.11:9090/dockerhub
- --insecure-registry=192.168.1.11:9090
script:
- docker build --target dev -t openra3-dev:local --build-arg APT_MIRROR=$APT_MIRROR .
- docker save openra3-dev:local -o dev-image.tar
- |
if [ -n "$IMAGE_NAME" ]; then
# add `docker login` here if IMAGE_NAME points to a private registry
docker tag openra3-dev:local $IMAGE_NAME:$CI_COMMIT_SHORT_SHA
docker push $IMAGE_NAME:$CI_COMMIT_SHORT_SHA
fi
artifacts:
name: "dev-image-$CI_COMMIT_SHORT_SHA"
paths:
- dev-image.tar
expire_in: 1 day
services: *dind
tags:
- docker
before_script: *retry
script:
- retry sh -c 'echo "$HARBOR_PASSWORD" | docker login "$HARBOR_HOST" -u "$HARBOR_USERNAME" --password-stdin'
- |
retry docker build \
--cache-from type=registry,ref=$IMAGE:cache-linux,insecure=true \
--cache-to type=registry,ref=$IMAGE:cache-linux,mode=max,insecure=true \
--build-arg APT_MIRROR=$APT_MIRROR \
--target dev \
-t $IMAGE:$CI_COMMIT_SHORT_SHA-linux \
-f Dockerfile .
- retry docker push $IMAGE:$CI_COMMIT_SHORT_SHA-linux
build_test_package:
windows_image:
stage: image
image: docker:latest
services: *dind
tags:
- docker
before_script: *retry
script:
- retry sh -c 'echo "$HARBOR_PASSWORD" | docker login "$HARBOR_HOST" -u "$HARBOR_USERNAME" --password-stdin'
- |
retry docker build \
--cache-from type=registry,ref=$IMAGE:cache-windows,insecure=true \
--cache-to type=registry,ref=$IMAGE:cache-windows,mode=max,insecure=true \
--build-arg APT_MIRROR=$APT_MIRROR \
--target dev \
-t $IMAGE:$CI_COMMIT_SHORT_SHA-windows \
-f Dockerfile.win .
- retry docker push $IMAGE:$CI_COMMIT_SHORT_SHA-windows
# --- build and test each target ----------------------------------------------
build_linux:
stage: build
image: docker:latest
services:
- name: docker:dind
command:
- --registry-mirror=http://192.168.1.11:9090/dockerhub
- --insecure-registry=192.168.1.11:9090
needs:
- image_build
before_script:
- docker load -i dev-image.tar
script:
- docker run --rm -v "$(pwd):/work" -w /work openra3-dev:local cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
- docker run --rm -v "$(pwd):/work" -w /work openra3-dev:local cmake --build build -j
- docker run --rm -v "$(pwd):/work" -w /work openra3-dev:local ctest --test-dir build --output-on-failure
- docker run --rm -v "$(pwd):/work" -w /work openra3-dev:local bash -c "mkdir -p artifacts_bin && cp build/bin/openra3 artifacts_bin/"
artifacts:
name: "$CI_PROJECT_NAME-executables-$CI_COMMIT_SHORT_SHA"
paths:
- artifacts_bin/
expire_in: 7 days
services: *dind
tags:
- docker
needs:
- linux_image
before_script: *retry
script:
- retry sh -c 'echo "$HARBOR_PASSWORD" | docker login "$HARBOR_HOST" -u "$HARBOR_USERNAME" --password-stdin'
- retry docker pull $IMAGE:$CI_COMMIT_SHORT_SHA-linux
- docker run --rm -v "$(pwd):/work" -w /work $IMAGE:$CI_COMMIT_SHORT_SHA-linux cmake -S . -B build/linux -G Ninja -DCMAKE_BUILD_TYPE=Release -DOPENRA3_DISTRO_TAG=ubuntu26.04
- docker run --rm -v "$(pwd):/work" -w /work $IMAGE:$CI_COMMIT_SHORT_SHA-linux cmake --build build/linux -j
- docker run --rm -v "$(pwd):/work" -w /work $IMAGE:$CI_COMMIT_SHORT_SHA-linux ctest --test-dir build/linux --output-on-failure
- docker run --rm -v "$(pwd):/work" -w /work $IMAGE:$CI_COMMIT_SHORT_SHA-linux bash -c "cd build/linux && cpack -G DEB"
- docker run --rm -v "$(pwd):/work" -w /work $IMAGE:$CI_COMMIT_SHORT_SHA-linux bash -c "mkdir -p artifacts/linux && cp build/linux/bin/openra3 build/linux/*.deb artifacts/linux/"
artifacts:
name: "$CI_PROJECT_NAME-linux-$CI_COMMIT_SHORT_SHA"
paths:
- artifacts/linux/
expire_in: 7 days
build_windows:
stage: build
image: docker:latest
services: *dind
tags:
- docker
needs:
- windows_image
before_script: *retry
script:
- retry sh -c 'echo "$HARBOR_PASSWORD" | docker login "$HARBOR_HOST" -u "$HARBOR_USERNAME" --password-stdin'
- retry docker pull $IMAGE:$CI_COMMIT_SHORT_SHA-windows
- docker run --rm -v "$(pwd):/work" -w /work $IMAGE:$CI_COMMIT_SHORT_SHA-windows
cmake -S . -B build/windows -G Ninja
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/llvm-mingw-x86_64.cmake
-DCMAKE_BUILD_TYPE=Release
- docker run --rm -v "$(pwd):/work" -w /work $IMAGE:$CI_COMMIT_SHORT_SHA-windows cmake --build build/windows -j
- docker run --rm -v "$(pwd):/work" -w /work $IMAGE:$CI_COMMIT_SHORT_SHA-windows cmake --build build/windows --target openra3_msi
- docker run --rm -v "$(pwd):/work" -w /work $IMAGE:$CI_COMMIT_SHORT_SHA-windows bash -c "cd build/windows && cpack -G ZIP"
- docker run --rm -v "$(pwd):/work" -w /work $IMAGE:$CI_COMMIT_SHORT_SHA-windows bash -c "mkdir -p artifacts/windows && cp build/windows/bin/openra3.exe build/windows/bin/SDL3.dll build/windows/bin/*.msi build/windows/*.zip artifacts/windows/"
artifacts:
name: "$CI_PROJECT_NAME-windows-$CI_COMMIT_SHORT_SHA"
paths:
- artifacts/windows/
expire_in: 7 days
# --- arm64 + Debian toolchain images -----------------------------------------
linux_arm64_image:
stage: image
image: docker:latest
services: *dind
tags:
- docker
before_script: *retry
script:
- retry sh -c 'echo "$HARBOR_PASSWORD" | docker login "$HARBOR_HOST" -u "$HARBOR_USERNAME" --password-stdin'
- |
retry docker build \
--cache-from type=registry,ref=$IMAGE:cache-linux-arm64,insecure=true \
--cache-to type=registry,ref=$IMAGE:cache-linux-arm64,mode=max,insecure=true \
--build-arg APT_MIRROR=$APT_MIRROR \
--target dev \
-t $IMAGE:$CI_COMMIT_SHORT_SHA-linux-arm64 \
-f Dockerfile.linux-arm64 .
- retry docker push $IMAGE:$CI_COMMIT_SHORT_SHA-linux-arm64
windows_arm64_image:
stage: image
image: docker:latest
services: *dind
tags:
- docker
before_script: *retry
script:
- retry sh -c 'echo "$HARBOR_PASSWORD" | docker login "$HARBOR_HOST" -u "$HARBOR_USERNAME" --password-stdin'
- |
retry docker build \
--cache-from type=registry,ref=$IMAGE:cache-windows-arm64,insecure=true \
--cache-to type=registry,ref=$IMAGE:cache-windows-arm64,mode=max,insecure=true \
--build-arg APT_MIRROR=$APT_MIRROR \
--target dev \
-t $IMAGE:$CI_COMMIT_SHORT_SHA-windows-arm64 \
-f Dockerfile.win-arm64 .
- retry docker push $IMAGE:$CI_COMMIT_SHORT_SHA-windows-arm64
debian_image:
stage: image
image: docker:latest
services: *dind
tags:
- docker
before_script: *retry
script:
- retry sh -c 'echo "$HARBOR_PASSWORD" | docker login "$HARBOR_HOST" -u "$HARBOR_USERNAME" --password-stdin'
- |
retry docker build \
--cache-from type=registry,ref=$IMAGE:cache-debian,insecure=true \
--cache-to type=registry,ref=$IMAGE:cache-debian,mode=max,insecure=true \
--build-arg APT_MIRROR=$APT_MIRROR \
--target dev \
-t $IMAGE:$CI_COMMIT_SHORT_SHA-debian \
-f Dockerfile.debian .
- retry docker push $IMAGE:$CI_COMMIT_SHORT_SHA-debian
debian_arm64_image:
stage: image
image: docker:latest
services: *dind
tags:
- docker
before_script: *retry
script:
- retry sh -c 'echo "$HARBOR_PASSWORD" | docker login "$HARBOR_HOST" -u "$HARBOR_USERNAME" --password-stdin'
- |
retry docker build \
--cache-from type=registry,ref=$IMAGE:cache-debian-arm64,insecure=true \
--cache-to type=registry,ref=$IMAGE:cache-debian-arm64,mode=max,insecure=true \
--build-arg APT_MIRROR=$APT_MIRROR \
--target dev \
-t $IMAGE:$CI_COMMIT_SHORT_SHA-debian-arm64 \
-f Dockerfile.debian-arm64 .
- retry docker push $IMAGE:$CI_COMMIT_SHORT_SHA-debian-arm64
# --- build each target (binary + packages) -----------------------------------
build_linux_arm64:
stage: build
image: docker:latest
services: *dind
tags:
- docker
needs:
- linux_arm64_image
before_script: *retry
script:
- retry sh -c 'echo "$HARBOR_PASSWORD" | docker login "$HARBOR_HOST" -u "$HARBOR_USERNAME" --password-stdin'
- retry docker pull $IMAGE:$CI_COMMIT_SHORT_SHA-linux-arm64
- docker run --rm -v "$(pwd):/work" -w /work $IMAGE:$CI_COMMIT_SHORT_SHA-linux-arm64 cmake -S . -B build/linux-arm64 -G Ninja -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/clang-aarch64-linux-gnu.cmake -DCMAKE_BUILD_TYPE=Release -DOPENRA3_DISTRO_TAG=ubuntu26.04
- docker run --rm -v "$(pwd):/work" -w /work $IMAGE:$CI_COMMIT_SHORT_SHA-linux-arm64 cmake --build build/linux-arm64 -j
- docker run --rm -v "$(pwd):/work" -w /work $IMAGE:$CI_COMMIT_SHORT_SHA-linux-arm64 bash -c "cd build/linux-arm64 && cpack -G DEB"
- docker run --rm -v "$(pwd):/work" -w /work $IMAGE:$CI_COMMIT_SHORT_SHA-linux-arm64 bash -c "mkdir -p artifacts/linux-arm64 && cp build/linux-arm64/bin/openra3 build/linux-arm64/*.deb artifacts/linux-arm64/"
artifacts:
name: "$CI_PROJECT_NAME-linux-arm64-$CI_COMMIT_SHORT_SHA"
paths:
- artifacts/linux-arm64/
expire_in: 7 days
build_windows_arm64:
stage: build
image: docker:latest
services: *dind
tags:
- docker
needs:
- windows_arm64_image
before_script: *retry
script:
- retry sh -c 'echo "$HARBOR_PASSWORD" | docker login "$HARBOR_HOST" -u "$HARBOR_USERNAME" --password-stdin'
- retry docker pull $IMAGE:$CI_COMMIT_SHORT_SHA-windows-arm64
- docker run --rm -v "$(pwd):/work" -w /work $IMAGE:$CI_COMMIT_SHORT_SHA-windows-arm64 cmake -S . -B build/windows-arm64 -G Ninja -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/llvm-mingw-aarch64.cmake -DCMAKE_BUILD_TYPE=Release
- docker run --rm -v "$(pwd):/work" -w /work $IMAGE:$CI_COMMIT_SHORT_SHA-windows-arm64 cmake --build build/windows-arm64 -j
- docker run --rm -v "$(pwd):/work" -w /work $IMAGE:$CI_COMMIT_SHORT_SHA-windows-arm64 bash -c "cd build/windows-arm64 && cpack -G ZIP"
- docker run --rm -v "$(pwd):/work" -w /work $IMAGE:$CI_COMMIT_SHORT_SHA-windows-arm64 bash -c "mkdir -p artifacts/windows-arm64 && cp build/windows-arm64/bin/openra3.exe build/windows-arm64/bin/SDL3.dll build/windows-arm64/*.zip artifacts/windows-arm64/"
artifacts:
name: "$CI_PROJECT_NAME-windows-arm64-$CI_COMMIT_SHORT_SHA"
paths:
- artifacts/windows-arm64/
expire_in: 7 days
build_debian:
stage: build
image: docker:latest
services: *dind
tags:
- docker
needs:
- debian_image
before_script: *retry
script:
- retry sh -c 'echo "$HARBOR_PASSWORD" | docker login "$HARBOR_HOST" -u "$HARBOR_USERNAME" --password-stdin'
- retry docker pull $IMAGE:$CI_COMMIT_SHORT_SHA-debian
- docker run --rm -v "$(pwd):/work" -w /work $IMAGE:$CI_COMMIT_SHORT_SHA-debian cmake -S . -B build/debian -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STDLIB_MODULES_JSON=/usr/lib/llvm-19/lib/libc++.modules.json -DOPENRA3_DISTRO_TAG=debian13
- docker run --rm -v "$(pwd):/work" -w /work $IMAGE:$CI_COMMIT_SHORT_SHA-debian cmake --build build/debian -j
- docker run --rm -v "$(pwd):/work" -w /work $IMAGE:$CI_COMMIT_SHORT_SHA-debian ctest --test-dir build/debian --output-on-failure
- docker run --rm -v "$(pwd):/work" -w /work $IMAGE:$CI_COMMIT_SHORT_SHA-debian bash -c "cd build/debian && cpack -G DEB"
- docker run --rm -v "$(pwd):/work" -w /work $IMAGE:$CI_COMMIT_SHORT_SHA-debian bash -c "mkdir -p artifacts/debian && cp build/debian/bin/openra3 build/debian/*.deb artifacts/debian/"
artifacts:
name: "$CI_PROJECT_NAME-debian-$CI_COMMIT_SHORT_SHA"
paths:
- artifacts/debian/
expire_in: 7 days
build_debian_arm64:
stage: build
image: docker:latest
services: *dind
tags:
- docker
needs:
- debian_arm64_image
before_script: *retry
script:
- retry sh -c 'echo "$HARBOR_PASSWORD" | docker login "$HARBOR_HOST" -u "$HARBOR_USERNAME" --password-stdin'
- retry docker pull $IMAGE:$CI_COMMIT_SHORT_SHA-debian-arm64
- docker run --rm -v "$(pwd):/work" -w /work $IMAGE:$CI_COMMIT_SHORT_SHA-debian-arm64 cmake -S . -B build/debian-arm64 -G Ninja -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/clang-aarch64-linux-gnu.cmake -DCMAKE_BUILD_TYPE=Release -DOPENRA3_AARCH64_CC=clang-19 -DOPENRA3_AARCH64_CXX=clang++-19 -DOPENRA3_AARCH64_STDLIB_MODULES_JSON=/usr/lib/llvm-19/lib/libc++.modules.json -DOPENRA3_DISTRO_TAG=debian13
- docker run --rm -v "$(pwd):/work" -w /work $IMAGE:$CI_COMMIT_SHORT_SHA-debian-arm64 cmake --build build/debian-arm64 -j
- docker run --rm -v "$(pwd):/work" -w /work $IMAGE:$CI_COMMIT_SHORT_SHA-debian-arm64 bash -c "cd build/debian-arm64 && cpack -G DEB"
- docker run --rm -v "$(pwd):/work" -w /work $IMAGE:$CI_COMMIT_SHORT_SHA-debian-arm64 bash -c "mkdir -p artifacts/debian-arm64 && cp build/debian-arm64/bin/openra3 build/debian-arm64/*.deb artifacts/debian-arm64/"
artifacts:
name: "$CI_PROJECT_NAME-debian-arm64-$CI_COMMIT_SHORT_SHA"
paths:
- artifacts/debian-arm64/
expire_in: 7 days
# --- WebAssembly (Emscripten) -------------------------------------------------
wasm_image:
stage: image
image: docker:latest
services: *dind
tags:
- docker
before_script: *retry
script:
- retry sh -c 'echo "$HARBOR_PASSWORD" | docker login "$HARBOR_HOST" -u "$HARBOR_USERNAME" --password-stdin'
- |
retry docker build \
--cache-from type=registry,ref=$IMAGE:cache-wasm,insecure=true \
--cache-to type=registry,ref=$IMAGE:cache-wasm,mode=max,insecure=true \
--build-arg APT_MIRROR=$APT_MIRROR \
--target dev \
-t $IMAGE:$CI_COMMIT_SHORT_SHA-wasm \
-f Dockerfile.wasm .
- retry docker push $IMAGE:$CI_COMMIT_SHORT_SHA-wasm
build_wasm:
stage: build
image: docker:latest
services: *dind
tags:
- docker
needs:
- wasm_image
before_script: *retry
script:
- retry sh -c 'echo "$HARBOR_PASSWORD" | docker login "$HARBOR_HOST" -u "$HARBOR_USERNAME" --password-stdin'
- retry docker pull $IMAGE:$CI_COMMIT_SHORT_SHA-wasm
- docker run --rm -v "$(pwd):/work" -w /work $IMAGE:$CI_COMMIT_SHORT_SHA-wasm cmake -S . -B build/wasm -G Ninja -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/emscripten-wasm.cmake -DCMAKE_BUILD_TYPE=Release
- docker run --rm -v "$(pwd):/work" -w /work $IMAGE:$CI_COMMIT_SHORT_SHA-wasm cmake --build build/wasm -j
- docker run --rm -v "$(pwd):/work" -w /work $IMAGE:$CI_COMMIT_SHORT_SHA-wasm bash -c "mkdir -p artifacts/wasm && cp build/wasm/bin/openra3.html build/wasm/bin/openra3.js build/wasm/bin/openra3.wasm build/wasm/bin/openra3.data artifacts/wasm/ 2>/dev/null || cp build/wasm/bin/openra3.html build/wasm/bin/openra3.js build/wasm/bin/openra3.wasm artifacts/wasm/"
artifacts:
name: "$CI_PROJECT_NAME-wasm-$CI_COMMIT_SHORT_SHA"
paths:
- artifacts/wasm/
expire_in: 7 days
+456 -95
View File
@@ -1,10 +1,32 @@
cmake_minimum_required(VERSION 3.28)
cmake_minimum_required(VERSION 3.30)
project(OpenRA3 VERSION 0.0.1 LANGUAGES CXX)
# --- 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)
@@ -14,122 +36,461 @@ 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 -Wpedantic)
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()
# --- engine core: fundamental types, math, strings, random, message stream ---
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: objects, players, teams, spatial partition, game loop ---
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)
# --- client: headless display/input abstraction and the client facade ---
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: factions, player templates, science, special powers ---
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 archives, RefPack codec, local install locator ---
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 (real .big data, user's local install) ---
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 headless 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_map)
openra3_target_defaults(ra3_skirmish)
# --- software renderer: framebuffer, TGA decode, BMP encode, map compositing ---
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)
# --- windowed viewer: SDL3 when available, otherwise a null backend ---
# --- 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()
add_library(ra3_ui STATIC)
if(SDL3_FOUND)
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 PkgConfig::SDL3)
message(STATUS "OpenRA3: SDL3 ${SDL3_VERSION} found - windowed viewer enabled")
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()
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)
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)
# --- umbrella module re-exporting the whole SDK ---
add_library(ra3 STATIC)
target_sources(ra3
PUBLIC FILE_SET CXX_MODULES FILES
src/ra3.cppm
# --- 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"
)
target_link_libraries(ra3 PUBLIC ra3_core ra3_logic ra3_client ra3_game ra3_fs ra3_map ra3_skirmish ra3_render ra3_ui)
# 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)
# --- the headless game executable ---
# --- executable ---------------------------------------------------------------
add_executable(openra3 apps/openra3/main.cpp)
target_link_libraries(openra3 PRIVATE ra3)
openra3_target_defaults(openra3)
# --- unit tests ---
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)
+24 -17
View File
@@ -1,7 +1,9 @@
# syntax=docker/dockerfile:1
# dev target: full toolchain + debug tools for development and debugging.
# Base image and compiler are the latest stable (ubuntu:26.04 ships gcc/g++ 16).
# Linux build environment. Isolated from the Windows cross image (Dockerfile.win)
# so the two toolchains and their standard libraries never interfere.
#
# dev target: clang + libc++ (for `import std;`) + SDL3 + debug tools.
FROM ubuntu:26.04 AS dev
ENV DEBIAN_FRONTEND=noninteractive
@@ -23,44 +25,49 @@ RUN --mount=type=cache,target=/var/lib/apt,sharing=locked \
sleep 5; \
done \
&& apt-get -o Acquire::Retries=5 install -y --no-install-recommends \
build-essential \
ca-certificates \
clang-21 \
clang-tools-21 \
cmake \
g++-16 \
dpkg-dev \
file \
gdb \
git \
libc++-21-dev \
libc++abi-21-dev \
libsdl3-dev \
libvulkan-dev \
ninja-build \
pkg-config \
&& rm -rf /var/lib/apt/lists/*
# Pin the toolchain to GCC 16 (the distro default is still GCC 15).
ENV CC=gcc-16
ENV CXX=g++-16
RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-16 100 \
&& update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-16 100 \
&& update-alternatives --install /usr/bin/c++ c++ /usr/bin/g++-16 100
ENV CC=clang-21
ENV CXX=clang++-21
WORKDIR /work
COPY . .
# C++26 modules need CMake >= 3.28, the Ninja generator and a module-aware gcc.
RUN cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release \
&& cmake --build build -j \
&& ctest --test-dir build --output-on-failure
RUN cmake -S . -B build/linux -G Ninja -DCMAKE_BUILD_TYPE=Release -DOPENRA3_DISTRO_TAG=ubuntu26.04 \
&& cmake --build build/linux -j \
&& ctest --test-dir build/linux --output-on-failure \
&& (cd build/linux && cpack -G DEB)
# deploy target: runtime dependencies + final binary, minimal and fast.
FROM ubuntu:26.04 AS deploy
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get -o Acquire::Retries=5 update \
RUN --mount=type=cache,target=/var/lib/apt,sharing=locked \
--mount=type=cache,target=/var/cache/apt,sharing=locked \
apt-get -o Acquire::Retries=5 update \
&& apt-get -o Acquire::Retries=5 install -y --no-install-recommends \
libc++1 \
libc++abi1 \
libsdl3-0 \
libstdc++6 \
libvulkan1 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=dev /work/build/bin/openra3 /app/openra3
COPY --from=dev /work/build/linux/bin/openra3 /app/openra3
ENTRYPOINT ["/app/openra3"]
+87
View File
@@ -0,0 +1,87 @@
# syntax=docker/dockerfile:1
# Debian build environment (latest stable, Debian 13 "trixie") producing a .deb
# alongside the binary. Separate from Dockerfile (Ubuntu 26.04) because the
# distro and its libc++/SDL3 versions differ, and a .deb's dependencies must be
# generated against the distro it targets.
#
# Toolchain: clang-19 + libc++-19 (for `import std;`). Debian 13 ships CMake
# 3.31, whose experimental `import std` gate value the project does not target,
# so an upstream CMake is installed from the Kitware tarball.
FROM debian:stable AS dev
ENV DEBIAN_FRONTEND=noninteractive
ARG APT_MIRROR=""
RUN if [ -n "$APT_MIRROR" ]; then \
sed -i -e "s|http://deb.debian.org/debian|${APT_MIRROR}|g" \
-e "s|http://security.debian.org/debian-security|${APT_MIRROR}|g" \
/etc/apt/sources.list /etc/apt/sources.list.d/*.sources 2>/dev/null || true; \
fi
RUN --mount=type=cache,target=/var/lib/apt,sharing=locked \
--mount=type=cache,target=/var/cache/apt,sharing=locked \
for attempt in 1 2 3 4 5; do \
apt-get -o Acquire::Retries=5 update && break; \
echo "apt-get update failed (attempt $attempt), retrying"; \
sleep 5; \
done \
&& apt-get -o Acquire::Retries=5 install -y --no-install-recommends \
ca-certificates \
clang-19 \
clang-tools-19 \
curl \
dpkg-dev \
file \
libc++-19-dev \
libc++abi-19-dev \
libsdl3-dev \
libunwind-19-dev \
lld-19 \
ninja-build \
pkg-config \
python3 \
&& rm -rf /var/lib/apt/lists/*
# Upstream CMake, because Debian 13's 3.31 experimental gate does not enable the
# `import std;` support the project relies on.
ARG CMAKE_VERSION=4.2.3
RUN mkdir -p /opt/cmake \
&& curl -fL "https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-linux-x86_64.tar.gz" \
| tar -xz -C /opt/cmake --strip-components=1
ENV PATH="/opt/cmake/bin:${PATH}"
ENV CC=clang-19
ENV CXX=clang++-19
WORKDIR /work
COPY . .
RUN cmake -S . -B build/debian -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_CXX_STDLIB_MODULES_JSON=/usr/lib/llvm-19/lib/libc++.modules.json \
-DOPENRA3_DISTRO_TAG=debian13 \
&& cmake --build build/debian -j \
&& ctest --test-dir build/debian --output-on-failure \
&& (cd build/debian && cpack -G DEB)
# deploy target: runtime dependencies + final binary, minimal and fast.
FROM debian:stable AS deploy
ENV DEBIAN_FRONTEND=noninteractive
RUN --mount=type=cache,target=/var/lib/apt,sharing=locked \
--mount=type=cache,target=/var/cache/apt,sharing=locked \
apt-get -o Acquire::Retries=5 update \
&& apt-get -o Acquire::Retries=5 install -y --no-install-recommends \
libc++1-19 \
libc++abi1-19 \
libsdl3-0 \
libvulkan1 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=dev /work/build/debian/bin/openra3 /app/openra3
ENTRYPOINT ["/app/openra3"]
+86
View File
@@ -0,0 +1,86 @@
# syntax=docker/dockerfile:1
# Debian build environment for Linux on ARM64 (aarch64-linux-gnu), producing an
# arm64 .deb. Cross-compiled from amd64 with Debian's clang-19 + libc++-19 and
# the arm64 packages installed via dpkg multiarch.
FROM debian:stable AS dev
ENV DEBIAN_FRONTEND=noninteractive
ARG APT_MIRROR=""
RUN if [ -n "$APT_MIRROR" ]; then \
sed -i -e "s|http://deb.debian.org/debian|${APT_MIRROR}|g" \
-e "s|http://security.debian.org/debian-security|${APT_MIRROR}|g" \
/etc/apt/sources.list /etc/apt/sources.list.d/*.sources 2>/dev/null || true; \
fi
RUN dpkg --add-architecture arm64
RUN --mount=type=cache,target=/var/lib/apt,sharing=locked \
--mount=type=cache,target=/var/cache/apt,sharing=locked \
for attempt in 1 2 3 4 5; do \
apt-get -o Acquire::Retries=5 update && break; \
echo "apt-get update failed (attempt $attempt), retrying"; \
sleep 5; \
done \
&& apt-get -o Acquire::Retries=5 install -y --no-install-recommends \
ca-certificates \
clang-19 \
clang-tools-19 \
curl \
dpkg-dev \
file \
gcc-aarch64-linux-gnu \
libc++-19-dev:arm64 \
libc++abi-19-dev:arm64 \
libsdl3-dev:arm64 \
libunwind-19-dev:arm64 \
lld-19 \
ninja-build \
pkg-config \
python3 \
&& rm -rf /var/lib/apt/lists/*
ARG CMAKE_VERSION=4.2.3
RUN mkdir -p /opt/cmake \
&& curl -fL "https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-linux-x86_64.tar.gz" \
| tar -xz -C /opt/cmake --strip-components=1
ENV PATH="/opt/cmake/bin:${PATH}"
ENV CC=clang-19
ENV CXX=clang++-19
# Point pkg-config at the arm64 packages so SDL3 resolves to the aarch64 build.
ENV PKG_CONFIG_LIBDIR=/usr/lib/aarch64-linux-gnu/pkgconfig
WORKDIR /work
COPY . .
RUN cmake -S . -B build/debian-arm64 -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/clang-aarch64-linux-gnu.cmake \
-DCMAKE_BUILD_TYPE=Release \
-DOPENRA3_AARCH64_CC=clang-19 \
-DOPENRA3_AARCH64_CXX=clang++-19 \
-DOPENRA3_AARCH64_STDLIB_MODULES_JSON=/usr/lib/llvm-19/lib/libc++.modules.json \
-DOPENRA3_DISTRO_TAG=debian13 \
&& cmake --build build/debian-arm64 -j \
&& (cd build/debian-arm64 && cpack -G DEB)
FROM --platform=linux/arm64 debian:stable AS deploy
ENV DEBIAN_FRONTEND=noninteractive
RUN --mount=type=cache,target=/var/lib/apt,sharing=locked \
--mount=type=cache,target=/var/cache/apt,sharing=locked \
apt-get -o Acquire::Retries=5 update \
&& apt-get -o Acquire::Retries=5 install -y --no-install-recommends \
libc++1-19 \
libc++abi1-19 \
libsdl3-0 \
libvulkan1 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=dev /work/build/debian-arm64/bin/openra3 /app/openra3
ENTRYPOINT ["/app/openra3"]
+84
View File
@@ -0,0 +1,84 @@
# syntax=docker/dockerfile:1
# Linux on ARM64 cross-build environment (aarch64-linux-gnu), isolated from the
# x86_64 Linux image and the Windows images.
#
# Toolchain: clang-21 + libc++ with the arm64 libc++ (for `import std;`), the
# arm64 glibc from `gcc-aarch64-linux-gnu`, arm64 SDL3 from `libsdl3-dev:arm64`,
# and lld as the linker. The arm64 packages are installed with dpkg multiarch
# (`arm64` added to the sources); the native clang/cmake build tools stay amd64.
FROM ubuntu:26.04 AS dev
ENV DEBIAN_FRONTEND=noninteractive
ARG APT_MIRROR=""
RUN if [ -n "$APT_MIRROR" ]; then \
sed -i -e "s|http://archive.ubuntu.com/ubuntu|${APT_MIRROR}|g" \
-e "s|http://security.ubuntu.com/ubuntu|${APT_MIRROR}|g" \
-e "s|http://ports.ubuntu.com/ubuntu-ports|${APT_MIRROR}|g" \
/etc/apt/sources.list /etc/apt/sources.list.d/*.sources 2>/dev/null || true; \
fi
# Enable the arm64 architecture on top of the emulated amd64 userland.
RUN dpkg --add-architecture arm64
RUN --mount=type=cache,target=/var/lib/apt,sharing=locked \
--mount=type=cache,target=/var/cache/apt,sharing=locked \
for attempt in 1 2 3 4 5; do \
apt-get -o Acquire::Retries=5 update && break; \
echo "apt-get update failed (attempt $attempt), retrying"; \
sleep 5; \
done \
&& apt-get -o Acquire::Retries=5 install -y --no-install-recommends \
ca-certificates \
clang-21 \
cmake \
dpkg-dev \
file \
gcc-aarch64-linux-gnu \
libc++-21-dev:arm64 \
libc++abi-21-dev:arm64 \
libsdl3-dev:arm64 \
libunwind-21-dev:arm64 \
lld-21 \
ninja-build \
pkg-config \
python3 \
&& rm -rf /var/lib/apt/lists/*
ENV CC=clang-21
ENV CXX=clang++-21
# Point pkg-config at the arm64 packages so SDL3 resolves to the aarch64 build.
ENV PKG_CONFIG_LIBDIR=/usr/lib/aarch64-linux-gnu/pkgconfig
WORKDIR /work
COPY . .
RUN cmake -S . -B build/linux-arm64 -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/clang-aarch64-linux-gnu.cmake \
-DCMAKE_BUILD_TYPE=Release \
-DOPENRA3_DISTRO_TAG=ubuntu26.04 \
&& cmake --build build/linux-arm64 -j \
&& (cd build/linux-arm64 && cpack -G DEB)
# deploy target: runtime dependencies + final binary, minimal and fast. This is
# an arm64 image; build it on (or with emulation for) an arm64 host.
FROM --platform=linux/arm64 ubuntu:26.04 AS deploy
ENV DEBIAN_FRONTEND=noninteractive
RUN --mount=type=cache,target=/var/lib/apt,sharing=locked \
--mount=type=cache,target=/var/cache/apt,sharing=locked \
apt-get -o Acquire::Retries=5 update \
&& apt-get -o Acquire::Retries=5 install -y --no-install-recommends \
libc++1 \
libc++abi1 \
libsdl3-0 \
libvulkan1 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=dev /work/build/linux-arm64/bin/openra3 /app/openra3
ENTRYPOINT ["/app/openra3"]
+46
View File
@@ -0,0 +1,46 @@
# syntax=docker/dockerfile:1
# WebAssembly build environment (Emscripten), producing openra3.html/.js/.wasm.
#
# The emsdk image ships CMake 3.28, whose experimental `import std` support is
# too old for the project, so an upstream CMake is layered on top.
FROM emscripten/emsdk:6.0.10 AS dev
ENV DEBIAN_FRONTEND=noninteractive
ARG APT_MIRROR=""
RUN if [ -n "$APT_MIRROR" ]; then \
sed -i -e "s|http://archive.ubuntu.com/ubuntu|${APT_MIRROR}|g" \
-e "s|http://security.ubuntu.com/ubuntu|${APT_MIRROR}|g" \
-e "s|http://ports.ubuntu.com/ubuntu-ports|${APT_MIRROR}|g" \
/etc/apt/sources.list /etc/apt/sources.list.d/*.sources 2>/dev/null || true; \
fi
RUN --mount=type=cache,target=/var/lib/apt,sharing=locked \
--mount=type=cache,target=/var/cache/apt,sharing=locked \
for attempt in 1 2 3 4 5; do \
apt-get -o Acquire::Retries=5 update && break; \
echo "apt-get update failed (attempt $attempt), retrying"; \
sleep 5; \
done \
&& apt-get -o Acquire::Retries=5 install -y --no-install-recommends \
ca-certificates \
curl \
ninja-build \
&& rm -rf /var/lib/apt/lists/*
# Upstream CMake, because the emsdk image's 3.28 cannot build the project.
ARG CMAKE_VERSION=4.2.3
RUN mkdir -p /opt/cmake \
&& curl -fL "https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-linux-x86_64.tar.gz" \
| tar -xz -C /opt/cmake --strip-components=1
ENV PATH="/opt/cmake/bin:${PATH}"
WORKDIR /work
COPY . .
RUN cmake -S . -B build/wasm -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/emscripten-wasm.cmake \
-DCMAKE_BUILD_TYPE=Release \
&& cmake --build build/wasm -j
+67
View File
@@ -0,0 +1,67 @@
# syntax=docker/dockerfile:1
# Windows cross-build environment (x86_64), fully isolated from the Linux image
# in Dockerfile so the two toolchains and standard libraries never interfere.
#
# Toolchain: llvm-mingw (clang + libc++ + the libc++ `std` module, MSVCRT
# runtime) with the official SDL3 MinGW development package.
FROM ubuntu:26.04 AS dev
ENV DEBIAN_FRONTEND=noninteractive
ARG APT_MIRROR=""
RUN if [ -n "$APT_MIRROR" ]; then \
sed -i -e "s|http://archive.ubuntu.com/ubuntu|${APT_MIRROR}|g" \
-e "s|http://security.ubuntu.com/ubuntu|${APT_MIRROR}|g" \
-e "s|http://ports.ubuntu.com/ubuntu-ports|${APT_MIRROR}|g" \
/etc/apt/sources.list /etc/apt/sources.list.d/*.sources 2>/dev/null || true; \
fi
RUN --mount=type=cache,target=/var/lib/apt,sharing=locked \
--mount=type=cache,target=/var/cache/apt,sharing=locked \
for attempt in 1 2 3 4 5; do \
apt-get -o Acquire::Retries=5 update && break; \
echo "apt-get update failed (attempt $attempt), retrying"; \
sleep 5; \
done \
&& apt-get -o Acquire::Retries=5 install -y --no-install-recommends \
ca-certificates \
cmake \
curl \
ninja-build \
python3 \
wixl \
xz-utils \
&& rm -rf /var/lib/apt/lists/*
# Pinned toolchain + SDL3 versions (override with --build-arg to bump).
ARG LLVM_MINGW_VERSION=20260908
ARG LLVM_MINGW_FLAVOR=msvcrt
ARG SDL3_VERSION=3.4.16
RUN mkdir -p /opt/llvm-mingw /opt/sdl3-mingw \
&& curl -fL "https://github.com/mstorsjo/llvm-mingw/releases/download/${LLVM_MINGW_VERSION}/llvm-mingw-${LLVM_MINGW_VERSION}-${LLVM_MINGW_FLAVOR}-ubuntu-22.04-x86_64.tar.xz" \
| tar -xJ -C /opt/llvm-mingw --strip-components=1 \
&& curl -fL "https://github.com/libsdl-org/SDL/releases/download/release-${SDL3_VERSION}/SDL3-devel-${SDL3_VERSION}-mingw.tar.gz" \
| tar -xz -C /opt/sdl3-mingw --strip-components=1
ENV OPENRA3_LLVM_MINGW_ROOT=/opt/llvm-mingw
ENV OPENRA3_SDL3_ROOT=/opt/sdl3-mingw
WORKDIR /work
COPY . .
RUN cmake -S . -B build/windows -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/llvm-mingw-x86_64.cmake \
-DCMAKE_BUILD_TYPE=Release \
&& cmake --build build/windows -j \
&& cmake --build build/windows --target openra3_msi \
&& (cd build/windows && cpack -G ZIP)
# package target: the .exe plus the SDL3 runtime DLL.
FROM ubuntu:26.04 AS package
WORKDIR /app
COPY --from=dev /work/build/windows/bin/openra3.exe /app/
COPY --from=dev /work/build/windows/bin/SDL3.dll /app/
+77
View File
@@ -0,0 +1,77 @@
# syntax=docker/dockerfile:1
# Windows on ARM64 cross-build environment (aarch64-w64-mingw32), isolated from
# the x86_64 Windows image and the Linux images.
#
# Toolchain: the same multi-target llvm-mingw as Dockerfile.win (clang + libc++ +
# the libc++ `std` module, MSVCRT runtime), targeting aarch64. The official SDL
# release ships no MinGW ARM64 dev package, so SDL3 comes from MSYS2's clangarm64
# repository, staged into the `<triple>/{include,lib,bin}` layout the toolchain
# expects.
FROM ubuntu:26.04 AS dev
ENV DEBIAN_FRONTEND=noninteractive
ARG APT_MIRROR=""
RUN if [ -n "$APT_MIRROR" ]; then \
sed -i -e "s|http://archive.ubuntu.com/ubuntu|${APT_MIRROR}|g" \
-e "s|http://security.ubuntu.com/ubuntu|${APT_MIRROR}|g" \
-e "s|http://ports.ubuntu.com/ubuntu-ports|${APT_MIRROR}|g" \
/etc/apt/sources.list /etc/apt/sources.list.d/*.sources 2>/dev/null || true; \
fi
RUN --mount=type=cache,target=/var/lib/apt,sharing=locked \
--mount=type=cache,target=/var/cache/apt,sharing=locked \
for attempt in 1 2 3 4 5; do \
apt-get -o Acquire::Retries=5 update && break; \
echo "apt-get update failed (attempt $attempt), retrying"; \
sleep 5; \
done \
&& apt-get -o Acquire::Retries=5 install -y --no-install-recommends \
ca-certificates \
cmake \
curl \
ninja-build \
python3 \
xz-utils \
zstd \
&& rm -rf /var/lib/apt/lists/*
# Pinned toolchain + SDL3 versions (override with --build-arg to bump).
ARG LLVM_MINGW_VERSION=20260908
ARG LLVM_MINGW_FLAVOR=msvcrt
# SDL3 for aarch64-w64-mingw32 from MSYS2's clangarm64 repo (name includes the
# MSYS2 package release, hence the `-1`).
ARG MSYS2_SDL3_PKG=mingw-w64-clang-aarch64-sdl3-3.4.16-1-any.pkg.tar.zst
RUN mkdir -p /opt/llvm-mingw \
&& curl -fL "https://github.com/mstorsjo/llvm-mingw/releases/download/${LLVM_MINGW_VERSION}/llvm-mingw-${LLVM_MINGW_VERSION}-${LLVM_MINGW_FLAVOR}-ubuntu-22.04-x86_64.tar.xz" \
| tar -xJ -C /opt/llvm-mingw --strip-components=1
RUN mkdir -p /opt/sdl3-aarch64/aarch64-w64-mingw32/include /opt/sdl3-aarch64/aarch64-w64-mingw32/lib /opt/sdl3-aarch64/aarch64-w64-mingw32/bin \
&& curl -fL "https://repo.msys2.org/mingw/clangarm64/${MSYS2_SDL3_PKG}" -o /tmp/sdl3.pkg.tar.zst \
&& mkdir -p /tmp/sdl3 && tar --zstd -xf /tmp/sdl3.pkg.tar.zst -C /tmp/sdl3 \
&& cp -r /tmp/sdl3/clangarm64/include/SDL3 /opt/sdl3-aarch64/aarch64-w64-mingw32/include/ \
&& cp /tmp/sdl3/clangarm64/lib/libSDL3.dll.a /opt/sdl3-aarch64/aarch64-w64-mingw32/lib/ \
&& cp /tmp/sdl3/clangarm64/bin/SDL3.dll /opt/sdl3-aarch64/aarch64-w64-mingw32/bin/ \
&& rm -rf /tmp/sdl3 /tmp/sdl3.pkg.tar.zst
ENV OPENRA3_LLVM_MINGW_ROOT=/opt/llvm-mingw
ENV OPENRA3_SDL3_ROOT=/opt/sdl3-aarch64
WORKDIR /work
COPY . .
RUN cmake -S . -B build/windows-arm64 -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/llvm-mingw-aarch64.cmake \
-DCMAKE_BUILD_TYPE=Release \
&& cmake --build build/windows-arm64 -j \
&& (cd build/windows-arm64 && cpack -G ZIP)
# package target: the .exe plus the SDL3 runtime DLL.
FROM ubuntu:26.04 AS package
WORKDIR /app
COPY --from=dev /work/build/windows-arm64/bin/openra3.exe /app/
COPY --from=dev /work/build/windows-arm64/bin/SDL3.dll /app/
+223 -40
View File
@@ -1,7 +1,8 @@
# OpenRA3
A from-scratch, portable re-implementation of **Command & Conquer: Red Alert 3**
in **C++26** using **C++ modules**, built with **GCC 16**.
in **C++26** using **C++ modules** and `import std;`, built with **Clang** for
both **Linux** and **Windows**.
Red Alert 3 runs SAGE 2.0. EA never released that engine's C++ source, but it
did open-source the closely related SAGE 1.0 engine as
@@ -17,11 +18,23 @@ Ghidra.
## Status
OpenRA3 is at **v0.0.1**. The engine compiles and runs headless, and a **minimal
OpenRA3 is at **v0.6.0**. The engine compiles and runs headless, and a **minimal
skirmish** is playable: it reads a real multiplayer map out of your install,
recovers the player start waypoints, and simulates two sides earning credits,
training units and fighting until one base falls. The map and match state can be
**rendered** to a window (SDL3) or to an image.
recovers the player start waypoints, and simulates two sides building a base,
extracting ore and fighting until one side is wiped out. The balance is the
**retail Red Alert 3 balance**, pinned in `ra3.data` from EA's open RA3 XML:
damage types, `ArmorTemplate` percentages, weapon target masks, build costs and
the ore economy. Presentation offers **Vulkan** (`ra3.vulkan`), **Direct3D 11 / 12**
(`ra3.dx`) and **WebGL 2** (`ra3.webgl`, the wasm build) GPU backends with an SDL
software blit and null fallbacks; the backend is selectable from the in-game menu
and the software renderer still produces headless images. Terrain tiles
cross-fade the way the retail `Terrain.fx` does (a per-cell blend ramp plus a
gutter-padded atlas), so material boundaries are smooth instead of a grid of
hard lines. An in-window **menu** lists the maps by their localized name and
exposes every render/skirmish option for tweaking before launch. The whole tree
builds for **Linux** (clang + libc++) and cross-compiles to **Windows**
(`openra3.exe` + `SDL3.dll`) with llvm-mingw — both using C++26 modules and
`import std;`.
```text
$ openra3 skirmish --game-dir "/game" --map map_mp_2_feasel4
@@ -29,9 +42,9 @@ OpenRA3 skirmish map=map_mp_2_feasel4 source=archive:map_mp_2_feasel4 seed=1
start positions:
P0 (1338, 1940)
P1 (1291, 1404)
result: decided winner=0 frames=3397 (113.2 s)
P0 Commander Allied money= 129 units=19 kills=21 losses=3
P1 AI Soviet money= 840 units= 0 kills=3 losses=21
result: decided winner=0 frames=9293 (309.8 s)
P0 Commander Allied money= 700 units=15 kills=21 losses=15
P1 AI Soviet money= 376 units= 0 kills=15 losses=21
```
## Offline only
@@ -44,15 +57,25 @@ tested without any external service.
## Assets
The engine reads the retail game from your local install:
The runtime reads a portable `assets/` folder **next to the executable** — there
is no `--game-dir` at run time. The build extracts it from your install:
- default path `C:\Red Alert 3`, or `--game-dir DIR`, or `$RA3_GAME_DIR`;
- `Data\MapsMultiplayer.big` is read for the map catalog;
- maps are unpacked from `BIG4` + RefPack and their start waypoints recovered.
- `openra3_assets` (build target) runs `cmake/extract_assets.cmake`, which
- extracts every map (double-unwrapped `CkMp`) and terrain TGA into `assets/`
via the engine's own `openra3 extract`;
- with `-DOPENRA3_EXTRACT_ALL=ON` (default) also dumps **every** `.big` entry
plus models, textures (`.png`), sound effects/voice and movie audio via the
[`ra3-headless/ra3tools`](https://github.com/) scripts.
- CMake cache vars: `RA3_GAME_DIR` (default `C:/Red Alert 3`), `RA3TOOLS_DIR`
(the `ra3tools` scripts), `PYTHON_EXECUTABLE`.
- The step is skipped when the exe cannot run on the build host (a Windows
cross-build in a Linux container); the exe then extracts maps/terrain on first
launch, and the full dump can be run explicitly:
`cmake --build <build> --target openra3_assets` on a native host, or
`openra3 extract --game-dir DIR --out DIR`.
**No game data is committed or distributed.** `reference/` (the GPLv3 Generals
source) and any extracted assets are git-ignored; `tools/fetch_reference.sh`
fetches the former on demand. Without an install, the engine falls back to a
**No game data is committed or distributed.** `reference/`, `assets/` and the
ra3tools checkout are git-ignored. Without an install the engine falls back to a
built-in test map so the project still builds and runs in CI.
## Layout
@@ -61,70 +84,230 @@ built-in test map so the project still builds and runs in CI.
| --- | --- |
| `src/core/ra3.core.cppm` | fundamental types, math, strings, random, message stream |
| `src/logic/ra3.logic.cppm` | objects, players, teams, spatial partition, game loop |
| `src/client/ra3.client.cppm` | display abstraction + client facade / frame loop |
| `src/client/ra3.client.cppm` | `display` abstraction + shared interactive loops + client facade |
| `src/display/ra3.display.cppm` | picks the backend (Vulkan/D3D11/D3D12/WebGL, then SDL) for the app |
| `src/game/ra3.game.cppm` | RA3 sides, player templates, skirmish defaults |
| `src/data/ra3.data.cppm` | retail RA3 balance: damage types, armour, weapons, units, economy |
| `src/fs/ra3.fs.cppm` | `BIG4` archives, RefPack codec, local install locator |
| `src/map/ra3.map.cppm` | map catalog, `EAR`/RefPack unwrap, start waypoints |
| `src/skirmish/ra3.skirmish.cppm` | units, economy, AI, combat, win condition |
| `src/render/ra3.render.cppm` | ARGB framebuffer, TGA decode, BMP encode, map compositing |
| `src/ui/ra3.ui.*.cppm` | SDL3 window viewer (null backend when SDL3 is absent) |
| `src/map/ra3.map.cppm` | map catalog, `EAR`/RefPack unwrap, start waypoints, `gamestrings.csf` display names |
| `src/skirmish/ra3.skirmish.cppm` | base building, economy, AI, combat, win condition |
| `src/terrain/ra3.terrain.cppm` | `CkMp` terrain: `HeightMapData`, `BlendTileData` (tiles + blends), `Terrain.big` tiles |
| `src/render/ra3.render.cppm` | ARGB framebuffer, TGA decode, BMP encode, map compositing, bitmap-font text |
| `src/ui/ra3.ui.*.cppm` | SDL3 window viewer and menu (null backend when SDL3 is absent) |
| `src/vulkan/ra3.vulkan.*.cppm` | Vulkan presentation backend and menu (null fallback without a loader) |
| `src/dx/ra3.dx.*.cppm` | Direct3D 11/12 presentation backend (runtime HLSL; null fallback off Windows) |
| `src/webgl/ra3.webgl.*.cppm` | WebGL2 presentation backend for the wasm build (GLSL ES; null fallback off Emscripten) |
| `third_party/libenderlog/` | vendored C++26 module logger (`import ender.log;`, MIT) with a native stack fallback for libc++ |
| `src/ra3.cppm` | umbrella module re-exporting the SDK |
| `apps/openra3/main.cpp` | `maps` / `skirmish` CLI |
| `apps/openra3/main.cpp` | `menu` / `maps` / `skirmish` / `render` CLI |
| `tests/ra3_tests.cpp` | smoke tests (run via `ctest`) |
| `tools/` | reference fetch + Ghidra-driven reconstruction helpers |
| `docs/` | architecture, reverse-engineering notes, roadmap |
| `Dockerfile` | `dev` (toolchain) and `deploy` (runtime) targets |
| `.gitlab-ci.yml` | build → test → package pipeline |
| `docs/` | architecture & master plan, reverse-engineering notes |
| `Dockerfile` | Linux build image (`dev` toolchain + `deploy` runtime) |
| `Dockerfile.win` | isolated Windows cross-build image (llvm-mingw + SDL3 MinGW) |
| `cmake/toolchains/` | `llvm-mingw-x86_64.cmake` cross toolchain |
| `scripts/` | `build-linux.sh` / `build-windows.sh` one-shot builders |
| `.gitlab-ci.yml` | build both targets → test → package pipeline |
## Why Clang + `import std;`
The engine never `#include`s the standard library: every module does
`import std;`. CMake's support for that (`CXX_MODULE_STD`) works today with
Clang + libc++. Linux uses the distro clang (LLVM 21); Windows cross-compiles
with [llvm-mingw](https://github.com/mstorsjo/llvm-mingw) (clang 23 + libc++ +
the libc++ `std` module). The two toolchains live in **separate images** so
their compilers and standard libraries never interfere.
## Build
The host needs no toolchain: build inside the container.
The host needs no toolchain: each target builds inside its own image.
```bash
docker build --target dev -t openra3-dev:local .
# Linux (amd64) -> build/linux/bin/openra3 + .deb
scripts/build-linux.sh
docker run --rm -v "$PWD:/work" -w /work openra3-dev:local cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
docker run --rm -v "$PWD:/work" -w /work openra3-dev:local cmake --build build -j
docker run --rm -v "$PWD:/work" -w /work openra3-dev:local ctest --test-dir build --output-on-failure
# Linux/arm64 -> build/linux-arm64/bin/openra3 + .deb (cross)
scripts/build-linux-arm64.sh
# Debian (amd64) -> build/debian/bin/openra3 + .deb
scripts/build-debian.sh
# Debian (arm64) -> build/debian-arm64/bin/openra3 + .deb (cross)
scripts/build-debian-arm64.sh
# Windows (x86_64) -> build/windows/bin/openra3.exe + SDL3.dll, .zip, .msi
scripts/build-windows.sh
# Windows (arm64) -> build/windows-arm64/bin/openra3.exe + SDL3.dll, .zip (cross)
scripts/build-windows-arm64.sh
# WebAssembly -> build/wasm/bin/openra3.html (+ .js/.wasm)
scripts/build-wasm.sh
```
Every target is cross-built from x86_64 Linux in its own image: llvm-mingw for
the Windows targets, clang + libc++ with dpkg multiarch for the arm64 targets,
and Debian/Ubuntu-specific images for the `.deb` packages.
Or drive Docker directly:
```bash
docker build --target dev -t openra3-linux:local .
docker run --rm -v "$PWD:/work" -w /work openra3-linux:local \
cmake -S . -B build/linux -G Ninja -DCMAKE_BUILD_TYPE=Release
docker run --rm -v "$PWD:/work" -w /work openra3-linux:local cmake --build build/linux -j
docker run --rm -v "$PWD:/work" -w /work openra3-linux:local ctest --test-dir build/linux --output-on-failure
docker build -f Dockerfile.win --target dev -t openra3-windows:local .
docker run --rm -v "$PWD:/work" -w /work openra3-windows:local \
cmake -S . -B build/windows -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/llvm-mingw-x86_64.cmake \
-DCMAKE_BUILD_TYPE=Release
docker run --rm -v "$PWD:/work" -w /work openra3-windows:local cmake --build build/windows -j
```
Behind a slow or blocked mirror, pass `--build-arg APT_MIRROR=<url>`.
The two images are deliberately separate so the Linux (clang + libc++) and
Windows (llvm-mingw + MinGW SDL3) toolchains never interfere. The Windows demo
is `build/windows/bin/openra3.exe` next to `SDL3.dll`:
```powershell
# show a real map in a Vulkan window
openra3.exe render --game-dir "C:\Red Alert 3" --vulkan
```
Vulkan is provided by **vendored volk + headers** (`third_party/`), resolved at
runtime, so neither image needs a Vulkan SDK.
## WebAssembly
OpenRA3 also builds to **WebAssembly** with Emscripten (`scripts/build-wasm.sh`),
producing `openra3.html` + `.js` + `.wasm`. The browser gets the same GPU renderer
as the desktop builds: a **`ra3.webgl`** backend peers with Vulkan/Direct3D — SDL3
provides the canvas and input, and GLES 3.0 / WebGL2 runs the 2D blit and the
terrain raymarcher (GLSL ES ports of the desktop shaders).
Browsers forbid synchronous on-demand file reads on the main thread, so the
assets are **preloaded into the module's filesystem** at build time. Bundle a
compact per-map set (one map plus the tiles it uses) rather than the whole dump:
```bash
# 1. which loose TGAs does the map resolve to?
openra3 textures --map map_mp_2_feasel1 # prints paths under assets/terrain
# 2. stage one map + those tiles (+ maps/map_names.tsv) into a directory, then
OPENRA3_WEB_ASSETS=/path/to/that/set scripts/build-wasm.sh
# 3. serve the build output (a Range-capable server is included)
python3 apps/web/serve.py --root build/wasm/bin --port 8199
# open http://localhost:8199/openra3.html
```
The engine's frame loops are blocking; the wasm build yields to the browser via
Asyncify (`display::sleep_frame` calls `emscripten_sleep`), so the page repaints
and handles input. Log records go to `console.log` at their real level (the
console sink uses stdout on the web instead of stderr). Without
`OPENRA3_WEB_ASSETS` the module still loads and starts, but exits at the menu
because no maps are found.
## Packages
CPack produces the release artifacts; the Docker images and CI run it for every
target.
| Target | Portable | Installer |
| --- | --- | --- |
| Linux amd64 (Ubuntu 26.04) | `.tar.gz` | `openra3_<v>_amd64ubuntu26.04.deb` |
| Linux arm64 (Ubuntu 26.04) | `.tar.gz` | `openra3_<v>_arm64ubuntu26.04.deb` |
| Linux amd64 (Debian 13) | `.tar.gz` | `openra3_<v>_amd64debian13.deb` |
| Linux arm64 (Debian 13) | `.tar.gz` | `openra3_<v>_arm64debian13.deb` |
| Windows x86_64 | `openra3-<v>-windows-x86_64.zip` | `openra3-<v>-windows-x86_64.msi` |
| Windows arm64 | `openra3-<v>-windows-arm64.zip` | (WiX-only; see below) |
The `.deb` dependencies are derived from the binary with `dpkg-shlibdeps`. The
`.msi` is built with **wixl** (msitools) straight from the Linux cross image;
wixl 0.106 has no arm64 support, so Windows/ARM64 ships the portable `.zip`
(the MSI toolchain for ARM64 would be WiX v4 via the .NET SDK).
## Logging
Every run writes `openra3.log` next to the executable through the vendored
[`libenderlog`](third_party/libenderlog) module (`import ender.log;`). A file
sink archives the previous log to `openra3.log.<YYYYmmdd-HHMMSS>` on open, so
each run gets its own file; the active file rotates at 4 MiB and the last 10
archives are kept. Records at **`warn` and above** carry a call stack (Windows
`CaptureStackBackTrace` / POSIX `execinfo`, because libc++ has no
`<stacktrace>`). A hard crash also writes `openra3_crash.log` with the faulting
module and a raw backtrace.
## Running a skirmish
```bash
# list the maps in your install
docker run --rm -v "/path/to/Red Alert 3:/game:ro" openra3-dev:local \
./build/bin/openra3 maps --game-dir /game
docker run --rm -v "/path/to/Red Alert 3:/game:ro" openra3-linux:local \
/work/build/linux/bin/openra3 maps --game-dir /game
# play a headless skirmish on a real map
docker run --rm -v "/path/to/Red Alert 3:/game:ro" openra3-dev:local \
./build/bin/openra3 skirmish --game-dir /game --map map_mp_2_feasel4 --seed 7
docker run --rm -v "/path/to/Red Alert 3:/game:ro" openra3-linux:local \
/work/build/linux/bin/openra3 skirmish --game-dir /game --map map_mp_2_feasel4 --seed 7
```
The Windows build is a native `openra3.exe` — copy it next to `SDL3.dll` and run
it from `cmd`/PowerShell, e.g. `openra3.exe skirmish --game-dir "C:\Red Alert 3"`.
`--frames N` caps the simulation length (default 15 minutes of game time at
30 Hz). The result is deterministic for a given map and seed.
## Menu
Run `openra3` with no arguments (or `openra3 menu`) to open a window listing the
maps by their **localized display name** (read from the install's
`gamestrings.csf`, e.g. `map_mp_2_feasel4` → "Battlebase Beta") with their id
below. The `Options` column exposes every `render`/`skirmish` parameter — mode
(3D terrain / top-down / skirmish), window size, camera pitch/yaw/height, FOV,
zoom, terrain scale, terrain pitch, world size, seed, frame cap, the overview
thumbnail toggle and a BMP output path. `Up`/`Down` selects, `Left`/`Right`
changes a value (or moves the text caret on the BMP field), `Tab` switches
between the map list and the options, `Enter` starts and `Esc` quits. Starting a
3D or top-down view opens the viewer; closing it returns to the menu. When no
window backend is available the same flow falls back to a console picker, and
`openra3 menu-preview` renders one menu frame to a BMP for inspection.
## Rendering the map
The renderer draws the map's own overview art, a world grid and the match state
(start markers in yellow, player 0 in blue, player 1 in red).
`render` draws the **real terrain**: it parses the map's `HeightMapData`
(elevation grid) and `BlendTileData` (per-cell tile index plus the per-cell
`Blends`/`ThreeWayBlends` and their `BlendDescription`s), loads the tile
textures from `Data\Terrain.big` (RefPack + TGA), and rasterises the map with an
elevation shade. Each cell cross-fades into its blend neighbour with the same
linear ramp the retail `Terrain.fx` uses, so material transitions are smooth; on
the GPU path the tile atlas is padded with a replicated gutter so filtering
never bleeds between tiles. Match state is overlaid (start markers in yellow,
player 0 in blue, player 1 in red). `--thumbnail` uses the old `<map>_art.tga`
overview instead.
**Offscreen image** (works anywhere, no display needed):
```bash
docker run --rm -v "/path/to/Red Alert 3:/game:ro" -v "$PWD/out:/out" openra3-dev:local \
/work/build/bin/openra3 render --game-dir /game --map map_mp_2_feasel4 --out /out/map.bmp
docker run --rm -v "/path/to/Red Alert 3:/game:ro" -v "$PWD/out:/out" openra3-linux:local \
/work/build/linux/bin/openra3 render --game-dir /game --map map_mp_2_feasel4 --out /out/map.bmp
```
**Interactive window** (SDL3; drag to pan, wheel to zoom, Esc to quit). In a
container you need an X server on the host — on Windows run
On Windows the same command runs natively: `openra3.exe render --game-dir
"C:\Red Alert 3" --map map_mp_2_feasel4` opens an SDL3 window.
**Interactive window** (Vulkan with `--vulkan`, otherwise SDL3). The camera
follows the retail tactical view: it opens centred on the first player's start,
the wheel zooms, pushing the cursor against a screen edge scrolls, dragging with
the left button pans, and Esc quits. `--zoom Z` sets the initial zoom (1 fits
the whole map). In a container you need an X server on the host — on Windows run
[VcXsrv](https://sourceforge.net/projects/vcxsrv/) and launch it with "Disable
access control", then:
```bash
docker run --rm -v "/path/to/Red Alert 3:/game:ro" -e DISPLAY=host.docker.internal:0.0 \
openra3-dev:local /work/build/bin/openra3 render --game-dir /game --map map_mp_2_feasel4
openra3-linux:local /work/build/linux/bin/openra3 render --game-dir /game --map map_mp_2_feasel4
```
If no display is available the viewer falls back to writing `openra3_view.bmp`.
+1 -1
View File
@@ -1 +1 @@
0.1.0
0.6.0
+1199 -124
View File
File diff suppressed because it is too large Load Diff
+107
View File
@@ -0,0 +1,107 @@
#!/usr/bin/env python3
"""Static file server with HTTP Range support, for the OpenRA3 wasm build.
Emscripten's lazy MEMFS reads assets back as byte ranges, so the server must
honour the ``Range`` header -- Python's stdlib ``http.server`` does not.
python3 apps/web/serve.py --root build/wasm/bin --port 8199
# then open http://localhost:8199/openra3.html
Serve ``--root`` containing ``openra3.html/.js/.wasm``, ``assets.manifest.json``
and the extracted ``assets/`` tree (a directory or a junction/symlink to it).
"""
import argparse
import functools
import os
import re
import sys
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
_RANGE = re.compile(r"bytes=(\d*)-(\d*)$")
class RangeHandler(SimpleHTTPRequestHandler):
# SharedArrayBuffer (needed by the pthreads build) requires cross-origin
# isolation.
def end_headers(self):
self.send_header("Cross-Origin-Opener-Policy", "same-origin")
self.send_header("Cross-Origin-Embedder-Policy", "require-corp")
super().end_headers()
def send_head(self):
header = self.headers.get("Range")
if not header:
return super().send_head()
path = self.translate_path(self.path)
if os.path.isdir(path):
return super().send_head()
try:
handle = open(path, "rb")
except OSError:
self.send_error(404, "File not found")
return None
match = _RANGE.match(header.strip())
if not match:
handle.close()
return super().send_head()
size = os.fstat(handle.fileno()).st_size
start = int(match.group(1)) if match.group(1) else 0
end = int(match.group(2)) if match.group(2) else size - 1
if start > end or start >= size:
handle.close()
self.send_error(416, "Requested Range Not Satisfiable")
return None
end = min(end, size - 1)
self.send_response(206)
self.send_header("Content-Type", self.guess_type(path))
self.send_header("Accept-Ranges", "bytes")
self.send_header("Content-Range", f"bytes {start}-{end}/{size}")
self.send_header("Content-Length", str(end - start + 1))
self.send_header("Cache-Control", "no-store")
self.end_headers()
handle.seek(start)
self._remaining = end - start + 1
return handle
def copyfile(self, source, outputfile):
remaining = getattr(self, "_remaining", None)
if remaining is None:
return super().copyfile(source, outputfile)
try:
while remaining > 0:
chunk = source.read(min(65536, remaining))
if not chunk:
break
outputfile.write(chunk)
remaining -= len(chunk)
finally:
self._remaining = None
def main() -> int:
parser = argparse.ArgumentParser(description="OpenRA3 wasm dev server")
parser.add_argument("--root", default="build/wasm/bin", help="directory to serve")
parser.add_argument("--port", type=int, default=8199)
parser.add_argument("--bind", default="127.0.0.1")
args = parser.parse_args()
if not os.path.isdir(args.root):
print(f"root not found: {args.root}", file=sys.stderr)
return 1
handler = functools.partial(RangeHandler, directory=args.root)
server = ThreadingHTTPServer((args.bind, args.port), handler)
print(f"serving {args.root} at http://{args.bind}:{args.port}/openra3.html")
try:
server.serve_forever()
except KeyboardInterrupt:
pass
return 0
if __name__ == "__main__":
raise SystemExit(main())
+33
View File
@@ -0,0 +1,33 @@
<!doctype html>
<!-- Emscripten shell for OpenRA3. SDL3 (built for Emscripten) renders into the
canvas element below; the SCRIPT placeholder is replaced by the generated
loader. Note: the shell preprocessor treats a leading hash as a directive,
so do not begin a line with one. -->
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<title>OpenRA3</title>
<style>
html, body { margin: 0; height: 100%; background: #1a0a08; overflow: hidden; }
#canvas { display: block; width: 100vw; height: 100vh; outline: none; }
#loading { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
color: #ecbe50; font: 16px/1.4 monospace; pointer-events: none; }
</style>
</head>
<body>
<canvas id="canvas" tabindex="-1" oncontextmenu="event.preventDefault()"></canvas>
<div id="loading">Loading OpenRA3…</div>
<script>
// Emscripten merges this into the generated Module; drop the loading
// placeholder once the runtime is up.
var Module = {
onRuntimeInitialized: function () {
var el = document.getElementById('loading');
if (el) el.remove();
}
};
</script>
{{{ SCRIPT }}}
</body>
</html>
+54
View File
@@ -0,0 +1,54 @@
# Extract the retail assets into OUT using the ra3tools scripts. Driven by the
# openra3_assets target and run with `cmake -P`, so it needs only Python and the
# game install - no target executable, and therefore works for a cross-build too
# (mount the install into the container and pass RA3_GAME_DIR).
#
# Variables (all required unless noted):
# GAME_DIR Red Alert 3 install root
# OUT destination folder (next to the executable)
# PYTHON python interpreter
# RA3TOOLS path to ra3-headless/ra3tools
# EXTRACT_ALL ON to also dump every asset (models/textures/audio/movies)
# STAMP stamp file to write on completion
function(run)
execute_process(COMMAND ${ARGN} RESULT_VARIABLE _rc)
if(NOT _rc EQUAL 0)
message(WARNING "extract step failed (${_rc}): ${ARGN}")
endif()
endfunction()
file(MAKE_DIRECTORY "${OUT}")
if(NOT EXISTS "${RA3TOOLS}/ra3_big.py")
message(WARNING "ra3tools not found at '${RA3TOOLS}'; skipping asset extraction")
else()
# Maps + overview art: raw .big payloads; the engine unwraps EAR/RefPack.
run("${PYTHON}" "${RA3TOOLS}/ra3_big.py" extract "${GAME_DIR}/Data/MapsMultiplayer.big" --match "*.map" --out "${OUT}/maps")
run("${PYTHON}" "${RA3TOOLS}/ra3_big.py" extract "${GAME_DIR}/Data/MapsMultiplayer.big" --match "*_art.tga" --out "${OUT}/maps")
# Terrain tile textures (the engine samples these for the map).
foreach(archive Terrain.big Core11.big)
if(EXISTS "${GAME_DIR}/Data/${archive}")
run("${PYTHON}" "${RA3TOOLS}/ra3_big.py" extract "${GAME_DIR}/Data/${archive}" --match "*.tga" --out "${OUT}/terrain")
endif()
endforeach()
if(EXTRACT_ALL)
message(STATUS "extracting every .big entry to ${OUT}/raw")
run("${PYTHON}" "${RA3TOOLS}/ra3_big.py" extract-all --game-dir "${GAME_DIR}" --out "${OUT}/raw" --match "*")
if(EXISTS "${RA3TOOLS}/ra3_binary.py")
message(STATUS "extracting models + textures (static stream)")
run("${PYTHON}" "${RA3TOOLS}/ra3_binary.py" export "${GAME_DIR}/Data/StaticStream.big" --models --textures --png --by-source --out "${OUT}/art")
message(STATUS "extracting audio (sound effects + voice)")
run("${PYTHON}" "${RA3TOOLS}/ra3_binary.py" export "${GAME_DIR}/Data/EnglishAudio.big" --stream audio --audio --out "${OUT}/audio")
message(STATUS "extracting movie audio")
run("${PYTHON}" "${RA3TOOLS}/ra3_big.py" extract "${GAME_DIR}/Data/EnglishMovieAudio.big" --match "*.snd" --out "${OUT}/movies")
endif()
endif()
endif()
file(TOUCH "${STAMP}")
+39
View File
@@ -0,0 +1,39 @@
<?xml version='1.0' encoding='utf-8'?>
<!--
Windows Installer source for OpenRA3, driven by wixl (msitools) so the Linux
cross-build image can produce the .msi. `exe` and `dll` are passed with
wixl -D; the version is substituted at configure time.
-->
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>
<Product Id='*'
Name='OpenRA3'
Language='1033'
Version='@PROJECT_VERSION@'
Manufacturer='OpenRA3'
UpgradeCode='6C6B1F4E-0B9A-4C2E-9E2A-2E4D7B5C1A31'>
<Package Id='*' InstallerVersion='500' Compressed='yes' InstallScope='perMachine'/>
<Media Id='1' Cabinet='openra3.cab' EmbedCab='yes'/>
<Directory Id='TARGETDIR' Name='SourceDir'>
<Directory Id='ProgramFiles64Folder'>
<Directory Id='INSTALLFOLDER' Name='OpenRA3'>
<Component Id='MainExecutable' Guid='1F2E3D4C-5B6A-4798-8C1D-2E3F4A5B6C7D'>
<File Id='OpenRA3Exe' Name='openra3.exe' Source='$(var.exe)' KeyPath='yes'>
<Shortcut Id='StartMenuShortcut' Directory='ProgramMenuFolder' Name='OpenRA3'
WorkingDirectory='INSTALLFOLDER' Advertise='no'/>
</File>
</Component>
<Component Id='SdlRuntime' Guid='2A3B4C5D-6E7F-4809-9D2E-3F4A5B6C7D8E'>
<File Id='Sdl3Dll' Name='SDL3.dll' Source='$(var.dll)' KeyPath='yes'/>
</Component>
</Directory>
</Directory>
<Directory Id='ProgramMenuFolder'/>
</Directory>
<Feature Id='Main' Title='OpenRA3' Level='1'>
<ComponentRef Id='MainExecutable'/>
<ComponentRef Id='SdlRuntime'/>
</Feature>
</Product>
</Wix>
@@ -0,0 +1,53 @@
# Cross-compile for Linux on ARM64 (aarch64-linux-gnu) from x86_64.
#
# clang-21 + libc++ (for `import std;`) with the arm64 libc++ and SDL3 taken
# from Ubuntu's arm64 packages (installed with dpkg multiarch), and the arm64
# glibc headers/libs from `gcc-aarch64-linux-gnu`. lld links the result; the C
# multiarch include directory is added explicitly because clang does not always
# infer Debian's `bits/` layout from the target triple alone.
# The `import std` gate must be set before the compiler is probed; a toolchain
# file is processed earlier than the project's own top-level settings.
set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "d0edc3af-4c50-42ea-a356-e2862fe7a444")
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
set(OPENRA3_AARCH64_TRIPLE "aarch64-linux-gnu")
# Multiarch paths the arm64 packages install into.
set(OPENRA3_AARCH64_INCLUDE "/usr/include/${OPENRA3_AARCH64_TRIPLE}")
set(OPENRA3_AARCH64_LIB "/usr/lib/${OPENRA3_AARCH64_TRIPLE}")
# Compiler + libc++ module description are cache vars so a distro with a
# differently-versioned clang (e.g. Debian's clang-19) can override on the
# command line.
set(OPENRA3_AARCH64_CC "clang-21" CACHE STRING "aarch64 C compiler")
set(OPENRA3_AARCH64_CXX "clang++-21" CACHE STRING "aarch64 C++ compiler")
set(OPENRA3_AARCH64_STDLIB_MODULES_JSON "/usr/lib/llvm-21/lib/libc++.modules.json" CACHE FILEPATH
"libc++ std module description for the aarch64 target")
set(CMAKE_C_COMPILER ${OPENRA3_AARCH64_CC})
set(CMAKE_CXX_COMPILER ${OPENRA3_AARCH64_CXX})
set(CMAKE_C_COMPILER_TARGET ${OPENRA3_AARCH64_TRIPLE})
set(CMAKE_CXX_COMPILER_TARGET ${OPENRA3_AARCH64_TRIPLE})
# CMake's standard-library detection does not pass COMPILER_TARGET, so without
# this it probes the host standard library.
set(CMAKE_C_COMPILER_ARG1 "--target=${OPENRA3_AARCH64_TRIPLE}")
set(CMAKE_CXX_COMPILER_ARG1 "--target=${OPENRA3_AARCH64_TRIPLE}")
# libc++ standard-library module description for the aarch64 target. On a
# multiarch image only the arm64 libc++ is installed, so the standard path
# already resolves to the aarch64 module description.
set(CMAKE_CXX_STDLIB_MODULES_JSON "${OPENRA3_AARCH64_STDLIB_MODULES_JSON}")
# Strip with the cross binutils, so CPack can strip the arm64 binary.
set(CMAKE_STRIP aarch64-linux-gnu-strip)
set(CMAKE_C_FLAGS_INIT "-isystem ${OPENRA3_AARCH64_INCLUDE}")
set(CMAKE_CXX_FLAGS_INIT "-stdlib=libc++ -isystem ${OPENRA3_AARCH64_INCLUDE}")
set(CMAKE_EXE_LINKER_FLAGS_INIT "-stdlib=libc++ -fuse-ld=lld -L${OPENRA3_AARCH64_LIB}")
set(CMAKE_SHARED_LINKER_FLAGS_INIT "-stdlib=libc++ -fuse-ld=lld -L${OPENRA3_AARCH64_LIB}")
# Run arm64 test binaries through qemu when it is installed, so `ctest` works
# during the image build; harmless when the tests are only cross-compiled.
set(CMAKE_CROSSCOMPILING_EMULATOR qemu-aarch64-static)
+46
View File
@@ -0,0 +1,46 @@
# WebAssembly cross-build via Emscripten.
#
# Includes Emscripten's own platform toolchain (found through $EMSDK, set by the
# emsdk image / `emsdk_env.sh`) and then layers the project's requirements:
#
# * C++26 `import std;`. Emscripten ships the libc++ `std` module source and a
# modules.json in its sysroot; its toolchain wires that up, but only if
# `CMAKE_CXX_MODULE_STD` is on and no std-modules JSON has been set yet, so
# both are arranged before Emscripten.cmake is included and before the
# compiler is probed.
# * Global `-fexceptions`. Emscripten disables exceptions by default, and the
# libc++ `std` module must be built with the same setting as its consumers,
# or clang rejects the prebuilt module ("configuration mismatch"). The engine
# throws, so exceptions are enabled everywhere.
# * The SDL3 web port (`-sUSE_SDL=3`) is attached by CMake to the SDL interface
# target, not here, so only the targets that use SDL pay for it.
# `import std` support: the gate and the module switch must be in place before
# Emscripten.cmake runs and before the compiler is probed.
set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "d0edc3af-4c50-42ea-a356-e2862fe7a444")
set(CMAKE_CXX_MODULE_STD ON)
# Let Emscripten.cmake point CMake at its own sysroot modules.json.
unset(CMAKE_CXX_STDLIB_MODULES_JSON)
if(DEFINED ENV{EMSDK})
set(OPENRA3_EMSCRIPTEN_CMAKE "$ENV{EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake")
elseif(DEFINED ENV{EMSCRIPTEN_ROOT})
set(OPENRA3_EMSCRIPTEN_CMAKE "$ENV{EMSCRIPTEN_ROOT}/cmake/Modules/Platform/Emscripten.cmake")
endif()
if(NOT EXISTS "${OPENRA3_EMSCRIPTEN_CMAKE}")
message(FATAL_ERROR "Emscripten.cmake not found (set EMSDK); looked at '${OPENRA3_EMSCRIPTEN_CMAKE}'")
endif()
include("${OPENRA3_EMSCRIPTEN_CMAKE}")
# Exceptions are used by the engine; keep them on globally so the std module and
# its consumers agree.
#
# Asyncify turns `emscripten_sleep` (used by display::sleep_frame on Emscripten)
# into a yield to the browser: the engine's frame loops are blocking, and
# without yielding the page freezes and never paints.
set(CMAKE_CXX_FLAGS_INIT "-fexceptions -sASYNCIFY=1")
set(CMAKE_C_FLAGS_INIT "-sASYNCIFY=1")
set(CMAKE_EXE_LINKER_FLAGS_INIT
"-fexceptions -sDISABLE_EXCEPTION_CATCHING=0 -sALLOW_MEMORY_GROWTH=1 -sASYNCIFY=1")
set(CMAKE_SHARED_LINKER_FLAGS_INIT
"-fexceptions -sDISABLE_EXCEPTION_CATCHING=0 -sALLOW_MEMORY_GROWTH=1 -sASYNCIFY=1")
+50
View File
@@ -0,0 +1,50 @@
# Cross-compile for Windows on ARM64 (aarch64-w64-mingw32) with llvm-mingw:
# clang + libc++ + the libc++ `std` module, targeting the MSVCRT MinGW runtime.
# The same llvm-mingw release is multi-target, so only the target triple, the
# SDL3 SDK and the sysroot paths differ from the x86_64 toolchain.
# The `import std` gate must be set before the compiler is probed; a toolchain
# file is processed earlier than the project's own top-level settings.
set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "d0edc3af-4c50-42ea-a356-e2862fe7a444")
set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
set(OPENRA3_LLVM_MINGW_ROOT "/opt/llvm-mingw" CACHE PATH "llvm-mingw installation root")
if(NOT EXISTS "${OPENRA3_LLVM_MINGW_ROOT}/bin/clang++")
message(FATAL_ERROR "llvm-mingw not found at ${OPENRA3_LLVM_MINGW_ROOT} (expected bin/clang++)")
endif()
# llvm-mingw's per-target prefix/sysroot for the ARM64 target.
set(OPENRA3_MINGW_TRIPLE "aarch64-w64-mingw32")
set(CMAKE_C_COMPILER "${OPENRA3_LLVM_MINGW_ROOT}/bin/clang")
set(CMAKE_CXX_COMPILER "${OPENRA3_LLVM_MINGW_ROOT}/bin/clang++")
set(CMAKE_C_COMPILER_TARGET ${OPENRA3_MINGW_TRIPLE})
set(CMAKE_CXX_COMPILER_TARGET ${OPENRA3_MINGW_TRIPLE})
# CMake's standard-library detection does not pass COMPILER_TARGET, so without
# this it probes the host standard library. Putting the target in ARG1 makes
# the probe see libc++ (the MinGW target's library).
set(CMAKE_C_COMPILER_ARG1 "--target=${OPENRA3_MINGW_TRIPLE}")
set(CMAKE_CXX_COMPILER_ARG1 "--target=${OPENRA3_MINGW_TRIPLE}")
# libc++ standard-library module description for the MinGW target. Must be set
# before the compiler is probed so CMake can build `import std;`.
set(CMAKE_CXX_STDLIB_MODULES_JSON "${OPENRA3_LLVM_MINGW_ROOT}/${OPENRA3_MINGW_TRIPLE}/lib/libc++.modules.json")
# Select libc++ explicitly so CMake's `import std` detection recognises the
# standard library (llvm-mingw defaults to it, but CMake keys off -stdlib=).
set(CMAKE_CXX_FLAGS_INIT "-stdlib=libc++")
set(CMAKE_EXE_LINKER_FLAGS_INIT "-stdlib=libc++")
set(CMAKE_SHARED_LINKER_FLAGS_INIT "-stdlib=libc++")
# SDL3 for aarch64-w64-mingw32. The official SDL release ships no MinGW ARM64
# dev package, so the image stages one from MSYS2's clangarm64 repo under this
# root in the same `<triple>/{include,lib,bin}` layout.
set(OPENRA3_SDL3_ROOT "/opt/sdl3-aarch64" CACHE PATH "SDL3 aarch64 MinGW development package root")
set(OPENRA3_SDL3_MINGW_TRIPLE "${OPENRA3_MINGW_TRIPLE}" CACHE STRING "SDL3 MinGW triple subdirectory")
set(CMAKE_FIND_ROOT_PATH "${OPENRA3_LLVM_MINGW_ROOT}/${OPENRA3_MINGW_TRIPLE}")
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+45
View File
@@ -0,0 +1,45 @@
# Cross-compile for Windows x86_64 with llvm-mingw: clang + libc++ + the
# libc++ `std` module, targeting the MSVCRT MinGW runtime (which is what the
# official SDL3 MinGW package links against).
# The `import std` gate must be set before the compiler is probed; a toolchain
# file is processed earlier than the project's own top-level settings.
set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "d0edc3af-4c50-42ea-a356-e2862fe7a444")
set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_SYSTEM_PROCESSOR x86_64)
set(OPENRA3_LLVM_MINGW_ROOT "/opt/llvm-mingw" CACHE PATH "llvm-mingw installation root")
if(NOT EXISTS "${OPENRA3_LLVM_MINGW_ROOT}/bin/clang++")
message(FATAL_ERROR "llvm-mingw not found at ${OPENRA3_LLVM_MINGW_ROOT} (expected bin/clang++)")
endif()
set(CMAKE_C_COMPILER "${OPENRA3_LLVM_MINGW_ROOT}/bin/clang")
set(CMAKE_CXX_COMPILER "${OPENRA3_LLVM_MINGW_ROOT}/bin/clang++")
set(CMAKE_C_COMPILER_TARGET x86_64-w64-mingw32)
set(CMAKE_CXX_COMPILER_TARGET x86_64-w64-mingw32)
# CMake's standard-library detection does not pass COMPILER_TARGET, so without
# this it probes the host standard library. Putting the target in ARG1 makes
# the probe see libc++ (the MinGW target's library).
set(CMAKE_C_COMPILER_ARG1 "--target=x86_64-w64-mingw32")
set(CMAKE_CXX_COMPILER_ARG1 "--target=x86_64-w64-mingw32")
# libc++ standard-library module description for the MinGW target. Must be set
# before the compiler is probed so CMake can build `import std;`.
set(CMAKE_CXX_STDLIB_MODULES_JSON "${OPENRA3_LLVM_MINGW_ROOT}/x86_64-w64-mingw32/lib/libc++.modules.json")
# Select libc++ explicitly so CMake's `import std` detection recognises the
# standard library (llvm-mingw defaults to it, but CMake keys off -stdlib=).
set(CMAKE_CXX_FLAGS_INIT "-stdlib=libc++")
set(CMAKE_EXE_LINKER_FLAGS_INIT "-stdlib=libc++")
set(CMAKE_SHARED_LINKER_FLAGS_INIT "-stdlib=libc++")
# Official SDL3 MinGW development package (headers + import library + DLL).
set(OPENRA3_SDL3_ROOT "/opt/sdl3-mingw" CACHE PATH "SDL3 MinGW development package root")
# The triple-named subdirectory inside the SDL3 MinGW package.
set(OPENRA3_SDL3_MINGW_TRIPLE "x86_64-w64-mingw32" CACHE STRING "SDL3 MinGW triple subdirectory")
set(CMAKE_FIND_ROOT_PATH "${OPENRA3_LLVM_MINGW_ROOT}/x86_64-w64-mingw32")
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+778 -70
View File
@@ -1,93 +1,801 @@
# Architecture
# Architecture & Master Plan
OpenRA3 is organised as a stack of C++ modules. Each layer may import the ones
below it, never the ones above.
OpenRA3 is a from-scratch, portable re-implementation of **Command & Conquer:
Red Alert 3** (SAGE 2.0) in **pure C++26** — C++ modules, `import std;`, no
scripting language, no managed runtime, no other programming language anywhere
in the tree. It is built with Clang + libc++ on Linux and cross-compiled to
Windows with llvm-mingw.
This document is two things at once:
1. the **layer architecture** (how the modules sit on top of one another), and
2. the **master plan** — the complete Red Alert 3 feature set decomposed three
levels deep into **Module → Function → Feature**, each tagged with its
implementation status and target milestone.
The decomposition is the contract: every feature RA3 has is either implemented,
being implemented, or explicitly planned here. Nothing is silently dropped.
> **Scope.** Offline only. Single-player campaign, skirmish, Commander's
> Challenge and LAN lockstep — never an online service, matchmaking, EA account
> or GameSpy/Steam integration. The simulation is deterministic and
> self-contained. No game assets or binaries are shipped; the engine reads the
> user's own install at runtime.
---
## 1. Principles
Same five rules as the rest of the tree, restated because the plan is written
against them:
1. **Pure C++.** One language. No Lua, no JS, no C#, no Python at runtime.
Python is allowed *only* in offline build tooling (`tools/`, cmake helper
scripts), never linked into `openra3`.
2. **No raw owning pointers.** Ownership is `std::unique_ptr`; cross-references
are non-owning views (`thing *`, handles, indices).
3. **Portable simulation.** No platform API in the foundation or simulation
layers. All platform concerns live behind `display` / `audio` / `video`.
4. **Determinism.** Anything that can diverge between runs (RNG, iteration
order, float accumulation, hash order) is explicit, seeded and testable.
5. **RE-traceable.** Every structure or constant taken from the retail
`ra3_1.12.game` (image base `0x400000`) or from the GPLv3 SAGE 1.0 reference
cites its origin. No asserted fact without a source.
---
## 2. Layer architecture
Each layer may import the ones below it, never the ones above. The `ra3`
umbrella module re-exports the SDK; applications import `ra3` only.
```
┌───────────────────────────────────────┐
applications │ openra3 (apps/openra3) │
└───────────────────┬───────────────────┘
│ import ra3
┌───────────────────▼───────────────────┐
umbrella │ ra3 (re-exports everything) │
└───────────────────┬───────────────────┘
┌──────────────┬───────────────┼───────────────┬──────────────┐
▼ ▼ ▼ ▼ ▼
ra3.ui ra3.render ra3.skirmish ra3.client ra3.game
SDL3 window framebuffer match rules display/loop RA3 sides
│ │ │ │ │
└──────┬───────┘ └───────┬───────┘ │
▼ ▼ │
ra3.core ra3.logic │
simulation │
┌───────────────────────────────────────┬──────────────────────┘
▼ ▼
ra3.map ra3.fs
map catalog BIG4 + RefPack
L6 tooling / apps openra3 (CLI) tools/ (offline, Python allowed)
| import ra3
---------------------------------------------------------------------------
v
L5 meta ra3.i18n ra3.mod ra3.net (deferred)
|
L4 match services ra3.match ra3.replay ra3.save
|
L3 presentation ra3.client ── ra3.render ── ra3.audio ── ra3.video
| \ |
| \ v
| ra3.display (backend pick)
| / | \ \
v ra3.ui ra3.vulkan ra3.dx ra3.webgl
| (SDL3) (Vulkan) (D3D11/12) (WebGL2)
---------------------------------------------------------------------------
v
L2 simulation ra3.logic ra3.modules ra3.combat ra3.movement
ra3.economy ra3.ai ra3.script ra3.powers ra3.shroud
|
L1 data & assets ra3.data ── ra3.assets ── ra3.map ── ra3.terrain
| |
v v
ra3.fs (BIG4 / RefPack / install)
|
L0 foundation ra3.core (types, math, containers, RNG, message bus)
```
## Module responsibilities
The current concrete modules (`ra3.core`, `ra3.logic`, `ra3.data`,
`ra3.skirmish`, `ra3.fs`, `ra3.map`, `ra3.terrain`, `ra3.render`, `ra3.ui.*`,
`ra3.vulkan.*`, `ra3.dx.*`, `ra3.webgl.*`, `ra3.display`, `ra3.game`, `ra3.client`, and the
vendored `ender.log`) are the **seeds** of the
target modules below. `ra3.skirmish` and `ra3.game` will be absorbed into
`ra3.ai` / `ra3.match`; new modules are added as their subsystems are recovered.
### `ra3.core`
The vocabulary every other module shares, mirroring SAGE's `GameEngine/Common`:
`real`/`int32`/`uint32`, `coord3d`/`coord2d`/`rgb_color`, `ascii_string` +
`make_name_key`, the deterministic `random` stream, and the `message_stream`
command bus (node layout derived from retail `MessageStream::appendMessage`,
`0x0060c4a0`).
### Status legend
### `ra3.logic`
The deterministic simulation, mirroring SAGE's `GameLogic`: `thing` → `object`
with pluggable `update_module`s, `player`/`player_list`, the `partition_manager`
spatial grid, and the 30 Hz `game_logic` driver (`prepare_new_game` /
`start_new_game` / `update`).
| Tag | Meaning |
| --- | --- |
| `[x]` | implemented and tested on `main` |
| `[~]` | partially implemented / works for the happy path |
| `[ ]` | planned, not started |
| `(vX.Y)` | target milestone (see the roll-up in §5) |
| `!!` | needs reverse engineering before it can be built |
### `ra3.client`
The presentation boundary: an abstract `display` with a `headless_display`
implementation, and `game_client`, the seam a future W3D/D3D9 renderer plugs
into.
| Function tag | Meaning |
| --- | --- |
| `D` | **done** — the function's features are largely present |
| `P` | **partial** — some features present |
### `ra3.game`
Red Alert 3 data that SAGE keeps in `PlayerTemplate`: the three sides
(`faction` flags `Empire=2`, `Allied=4`, `Soviet=8`, `Random=7`, recovered from
the retail skirmish setup) and skirmish defaults.
---
### `ra3.fs`
Reading the user's installation. Implements the `BIG4` archive container and
EA's RefPack codec, plus `find_game_dir` (`--game-dir` / `$RA3_GAME_DIR` /
`C:\Red Alert 3`). Only the archive index is held in memory; payloads are read
on demand.
## 3. Master plan — Module → Function → Feature
### `ra3.map`
Map discovery and loading: scans `MapsMultiplayer.big` for main map entries,
unwraps the two compression layers (`BIG4` RefPack → `EAR\0` wrapper → RefPack →
`CkMp`), and recovers `Player_N_Start` waypoint coordinates. Degenerate
extractions are rejected so the caller can fall back.
### M00 `ra3.core` — foundation vocabulary `[D]`
### `ra3.skirmish`
The minimal match: two players, unit classes (harvester/infantry/tank/base),
passive + harvester income, a simple build AI, movement and combat on a fixed
30 Hz step, and a base-destruction win condition. Fully deterministic.
Mirrors SAGE `GameEngine/Common`. Everything else speaks this.
### `ra3.render`
A dependency-free software renderer: an ARGB8888 `image` framebuffer with
blit/line/circle primitives, a TGA decoder for the game's map art, a 24-bit BMP
encoder for headless output, and `compose` which overlays a world grid and
markers (start positions, live units) onto the map art.
- **F1 Types & math** `[D]`
- `[x]` `real` (float), `int32`/`uint32`/`uint16`/`uint8`, `bool` aliases
- `[x]` `coord2d` / `coord3d` vectors, dot/cross/length/normalize
- `[x]` `rgb_color` / `argb_color`, packing and lerp
- `[x]` geometry: `segment`, `triangle`, `plane`, ray/segment intersection
- `[ ]` fixed-point helpers for replay-stable accumulation `(v0.4)`
- `[ ]` matrix / quaternion (needed by W3D models) `(v0.6)`
- `[ ]` `KindOf` flag bitset (SAGE object taxonomy) `(v0.4)`
- **F2 Containers** `[D]`
- `[x]` `ascii_string` + `make_name_key` (case-folded hash for data lookup)
- `[x]` intrusive doubly linked list (message stream node layout)
- `[x]` deterministic iteration-order map (insertion-ordered)
- `[ ]` object pool / arena for per-frame allocations `(v0.4)`
- `[ ]` interned string table `(v0.5)`
- **F3 Deterministic RNG** `[D]`
- `[x]` seedable `random` stream (`game_logic` random)
- `[ ]` independent per-player / per-subsystem streams `(v0.4)`
- `[ ]` shuffle / weighted-pick primitives `(v0.4)`
- **F4 Message stream** `[D]`
- `[x]` command bus with retail node layout (`appendMessage` `0x0060c4a0`)
- `[x]` ordered per-frame command drain
- `[ ]` command argument blocks (build/target/waypoint payloads) `(v0.4)`
- `[ ]` network/replay source tagging `(v0.4)`
- **F5 Diagnostics** `[~]`
- `[x]` logging sink + assert macro
- `[ ]` scoped profiling timers, per-subsystem counters `(v0.4)`
- `[ ]` structured crash/report capture `(v0.8)`
- **F6 Serialization primitives** `[~]`
- `[x]` little-endian byte reader/writer
- `[ ]` chunked binary reader/writer with versioning `(v0.5)`
- `[ ]` stable content hashing (replay/desync checks) `(v0.4)`
- **F7 Localization primitives** `[~]` (see M26)
- `[x]` UTF-16 unit access, CSF low-byte `^0xFF` decode
- `[ ]` placeholder substitution, plural/gender rules `(v0.5)`
### `ra3.ui`
The windowed viewer. The SDL3 backend streams the rendered image to a texture
with pan/zoom and Esc-to-quit; when the build has no SDL3, a null backend
returns `false` so the caller writes an offscreen image instead.
### M01 `ra3.fs` — containers & install `[D]`
## Design rules
- **F1 BIG archive** `[D]`
- `[x]` `BIG4` header parse (`fileSize` LE, `fileCount`/offsets BE) + name index
- `[x]` payload read on demand (index-only resident memory)
- `[ ]` `BIGF` (RefPack whole-archive) variant `!!` `(v0.5)`
- `[ ]` write support (pack/repack, used by tooling) `(v0.8)`
- **F2 RefPack codec** `[D]`
- `[x]` decode: 2/3/4-byte commands, long-literal, stop opcode
- `[x]` `refpack_output_size` without full decompress
- `[ ]` encode (for tooling round-trips) `(v0.8)`
- **F3 Install locator** `[D]`
- `[x]` `--game-dir` / `$RA3_GAME_DIR` / `C:\Red Alert 3` defaults
- `[ ]` registry / Steam / EA-app discovery `(v0.5)`
- `[ ]` Uprising as an optional content source `(v0.5)`
- **F4 Virtual file system** `[~]`
- `[x]` layered archive mounts (loose files → `*.big`)
- `[ ]` patch/language precedence rules (newest `Lang-*.big` wins) `(v0.5)`
- `[ ]` case-insensitive lookup + path normalisation `(v0.5)`
- **F5 Extraction targets** `[D]`
- `[x]` `openra3 extract` dumps maps/terrain to `assets/`
- `[ ]` full asset dump via `ra3tools` integration as a build target `(v0.5)`
### M02 `ra3.data` — data schema & balance `[P]`
- **F1 Data schema** `[ ]` `!!`
- `[ ]` SAGE INI parser (`#include`, `#define`, inheritance) `(v0.5)`
- `[ ]` XML rule schema
- `[ ]` `.manifest` compiled-blob schema `!!` `(v0.5)`
- **F2 Compiled asset blobs** `[ ]` `!!`
- `[ ]` `global.bin` deserialisation `(v0.5)`
- `[ ]` `static.*.bin` deserialisation `(v0.5)`
- `[ ]` version/dependency validation `(v0.5)`
- **F3 Balance tables** `[~]`
- `[x]` damage types, `ArmorTemplate` percentages
- `[x]` weapon target masks, weapon definitions
- `[x]` unit/structure build costs, times, prerequisites
- `[x]` ore economy constants
- `[ ]` read these from the install instead of pinned constants `(v0.5)`
- **F4 Object definitions** `[ ]`
- `[ ]` `ThingTemplate`/`ObjectTemplate` inheritance graph `(v0.5)`
- `[ ]` module descriptor lists (per-object update/draw module sets)
- `[ ]` `WeaponTemplate`/`ArmorTemplate`/`LocomotorTemplate` stores
- **F5 Faction & player templates** `[~]`
- `[x]` Allied / Soviet / Empire side flags (`2/4/8`), match templates
- `[ ]` commander/sub-commander definitions `(v0.7)`
- `[ ]` build/upgrade unlock trees per faction `(v0.5)`
- **F6 Rules & settings** `[ ]`
- `[ ]` skirmish options (cash, crates, superweapons, speed, limits)
- `[ ]` bonus crate effect table
- `[ ]` AI personality tables
- `[ ]` map-specific rule overrides (`map.ini`)
- **F7 Validation** `[ ]`
- `[ ]` schema diagnostics with source location
- `[ ]` cross-reference integrity (missing templates/refs)
### M03 `ra3.assets` — runtime asset manager `[ ]`
- **F1 Textures** `[~]`
- `[x]` TGA decode (`ra3.render`); 256×256 terrain cells
- `[ ]` DDS / DXT compressed textures `(v0.5)`
- `[ ]` atlas + mip generation, gutter padding (GPU) `[~]`
- `[ ]` async upload / streaming `(v0.6)`
- **F2 Models** `[ ]` `!!`
- `[ ]` W3D container parse (chunks, hierarchy, meshes) `(v0.6)`
- `[ ]` materials, shaders, texture references
- `[ ]` LOD sets, collision meshes
- **F3 Animation** `[ ]` `!!`
- `[ ]` W3D animation chunks, bone poses `(v0.6)`
- `[ ]` blend trees / transition animations
- **F4 Audio** `[ ]` `!!`
- `[ ]` audio container + codec decode `(v0.7)`
- `[ ]` cue/event tables (unit responses, weapon foley)
- **F5 Fonts & glyphs** `[ ]`
- `[ ]` bitmap/vector font load; CJK coverage `(v0.5)`
- **F6 Asset registry** `[ ]`
- `[ ]` cache with ref counting, eviction `(v0.6)`
- `[ ]` name-key lookup into the data schema
- **F7 UI art (`.apt`)** `[ ]` `!!`
- `[ ]` RA3 interface art decode (HUD/command bar) `(v0.7)`
### M04 `ra3.map` — map catalog & loader `[D]`
- **F1 Catalog** `[D]`
- `[x]` scan `MapsMultiplayer.big` for main map entries
- `[ ]` campaign + challenge map catalog `(v0.7)`
- **F2 Compiled map (`CkMp`)** `[D]`
- `[x]` double unwrap (`BIG4` RefPack → `EAR\0` → RefPack → `CkMp`)
- `[x]` name table + chunk list (`{index,version,size,data}`)
- `[ ]` full typed chunk dispatch for all chunk kinds `(v0.5)`
- **F3 Start positions** `[~]`
- `[x]` `Player_N_Start` waypoint scan → `coord3d`
- `[ ]` `MPPositionList` layout for maps without waypoints `!!` `(v0.5)`
- **F4 Metadata** `[~]`
- `[x]` display names from `gamestrings.csf` (`MAP:<ID>`)
- `[ ]` player count, size, supported game modes `(v0.5)`
- **F5 Map rules** `[ ]`
- `[ ]` `map.ini` override application `(v0.5)`
- **F6 Preview art** `[~]`
- `[x]` `<map>_art.tga` overview decode (`--thumbnail`)
- **F7 Validation & fallback** `[D]`
- `[x]` reject degenerate extractions, fall back to built-in test map
- `[ ]` integrity/version checks with actionable errors `(v0.5)`
### M05 `ra3.terrain` — terrain `[P]`
- **F1 Heightmap** `[D]`
- `[x]` `HeightMapData` v6 (grid, border, `u16` elevations, scale)
- `[ ]` multi-resolution / LOD height sampling `(v0.6)`
- **F2 Blend tiles** `[D]`
- `[x]` `BlendTileData` v27: tile grid, blends/three-way/cliff tables
- `[x]` `BlendDescription` parse; `secondaryTile` decode
- `[x]` retail `Terrain.fx` blend ramp (`blend_factor`, axis flags)
- **F3 Terrain textures** `[D]`
- `[x]` `art\terrain\*.tga` from `Terrain.big` / `Core11.big`
- `[x]` cell atlas with replicated gutter (GPU bleeding fix)
- `[ ]` continuous 32 px / Morton layout (kill residual grid lines) `(v0.4)`
- **F4 Water** `[ ]` `!!`
- `[ ]` water height/type, sea level `(v0.6)`
- `[ ]` animated waves + shoreline blending `(v0.6)`
- `[ ]` shroud-aware water rendering `(v0.6)`
- **F5 Cliffs & roads** `[ ]` `!!`
- `[x]` `CliffTextures` table parsed
- `[ ]` cliff mesh + `CliffTextureMapping` UV remap `(v0.6)`
- `[ ]` roads and bridges (passability + render) `(v0.6)`
- **F6 Terrain lighting** `[ ]`
- `[ ]` per-vertex normals, cell lighting, global light `(v0.6)`
- **F7 Terrain queries** `[~]`
- `[x]` height lookup (render)
- `[ ]` passability grid (ground/naval/amphibious/air) `(v0.4)`
- `[ ]` buildability grid (flatness, slope, water) `(v0.4)`
### M06 `ra3.logic` — simulation core `[P]`
- **F1 Objects & things** `[~]`
- `[x]` `thing` → `object` with pluggable `update_module`s
- `[x]` global object registry with stable ids
- `[ ]` handle/reference system (survives deletion) `(v0.4)`
- `[ ]` object destruction lifecycle + death dispatch `(v0.4)`
- **F2 Players & teams** `[~]`
- `[x]` `player` / `player_list`
- `[x]` money (`std::vector<Money*>`) and power fields
- `[x]` team assignment and relations
- `[ ]` diplomacy matrix, ally vision sharing `(v0.5)`
- **F3 Partition manager** `[~]`
- `[x]` spatial grid, neighborhood queries
- `[ ]` cell-resolution + large-object multi-cell registration `(v0.4)`
- **F4 Game loop** `[~]`
- `[x]` 30 Hz deterministic step with frame counter
- `[x]` `prepare_new_game` / `start_new_game` two-phase start
- `[ ]` per-tick module scheduling with stable ordering `(v0.4)`
- **F5 Commands** `[~]`
- `[x]` `message_stream` bus
- `[ ]` typed commands (build, attack, move, ability, sell, repair) `(v0.4)`
- `[ ]` command validation + feedback (insufficient funds, etc.) `(v0.4)`
- **F6 Determinism** `[~]`
- `[x]` seeded logic random
- `[ ]` replay-hash of state per tick `(v0.4)`
- `[ ]` float determinism policy / fixed-point where required `(v0.4)`
- **F7 Victory / defeat** `[~]`
- `[x]` team-wipe / base-destruction win condition
- `[ ]` surrender, disconnection, timed, objective victories `(v0.5)`
- `[ ]` score / stats accumulation `(v0.8)`
### M07 `ra3.modules` — object update & draw modules `[P]`
- **F1 Module system** `[~]`
- `[x]` update modules attached to objects with a simple order
- `[ ]` module descriptor data-binding (from `ThingTemplate`) `(v0.5)`
- `[ ]` interface queries (get WeaponModule / ContainModule on demand) `(v0.4)`
- **F2 Locomotor** `[ ]`
- `[ ]` movement state machine (idle/moving/attacking) `(v0.4)`
- **F3 Weapon module** `[~]`
- `[x]` simple weapons on units, auto-target + fire
- `[ ]` multi-weapon slots, turret aiming/rotation `(v0.4)`
- `[ ]` reload/clip, deploy/undeploy states `(v0.6)`
- **F4 Contain** `[ ]`
- `[ ]` transport passenger slots, load/unload `(v0.4)`
- `[ ]` garrison of civilian structures `(v0.6)`
- `[ ]` paradrop / airdrop `(v0.6)`
- **F5 Production** `[~]`
- `[x]` pay-as-you-go build queue on a factory
- `[ ]` per-factory queues, rally points, queue reordering `(v0.4)`
- `[ ]` building placement → production handoff `(v0.4)`
- **F6 Power** `[~]`
- `[x]` power production/consumption fields
- `[ ]` brownout/blackout effects on radar + build speed `(v0.4)`
- **F7 Upgrades** `[ ]`
- `[ ]` upgrade research, unlock dependent modules/weapons `(v0.5)`
- **F8 Experience / veterancy** `[ ]`
- `[ ]` XP from kills, ranks (veteran/elite/heroic), bonuses `(v0.6)`
- `[ ]` chevron rendering `(v0.6)`
- **F9 Special abilities** `[~]`
- `[ ]` secondary ability slots with cooldown, target/area types `(v0.6)`
- `[ ]` toggle/instant/targeted ability kinds `(v0.6)`
- **F10 Stealth / disguise / detection** `[ ]`
- `[ ]` stealth states, detection radius, decloak on fire `(v0.6)`
- `[ ]` disguise (spy-like) and detection interaction `(v0.6)`
- **F11 Structure modules** `[~]`
- `[x]` base structures, destruction
- `[ ]` construction/assembly animation, sell, repair `(v0.4)`
- `[ ]` walls/gates if present, defensive structures `(v0.6)`
- **F12 Resource modules** `[~]`
- `[x]` harvester ↔ refinery ore cycle
- `[ ]` ore field spread/depletion, multiple miners, dock queue `(v0.4)`
- **F13 Shroud modules** `[ ]`
- `[ ]` per-object shroud reveal / clearance `(v0.4)`
- **F14 Draw modules** `[ ]`
- `[ ]` model draw, animation, particles, construction ghost, temp effects `(v0.6)`
### M08 `ra3.combat` — weapons, warheads, damage `[P]`
- **F1 Weapons** `[~]`
- `[x]` damage, range, rate of fire, target masks
- `[ ]` clip/burst, scatter, arc, continuous beam `(v0.4)`
- `[ ]` primary vs secondary weapon selection `(v0.4)`
- **F2 Warheads** `[~]`
- `[x]` damage type + armor multiplier resolution
- `[ ]` radius/falloff, affects mask, death type on kill `(v0.4)`
- **F3 Armor** `[~]`
- `[x]` `ArmorTemplate` percentage table
- `[ ]` armor upgrades and per-state armor `(v0.6)`
- **F4 Damage application** `[~]`
- `[x]` damage resolution against armor
- `[ ]` conditional modifiers (from above, in air, moving) `(v0.4)`
- `[ ]` friendly-fire policy, self-damage `(v0.4)`
- **F5 Projectiles** `[ ]`
- `[ ]` ballistic / laser / missile / beam / homing / arcing `(v0.4)`
- `[ ]` projectile draw + impact VFX hook `(v0.6)`
- **F6 Targeting** `[~]`
- `[x]` simple nearest/in-range acquisition
- `[ ]` priority scans (attack-move, guard, force-attack) `(v0.4)`
- `[ ]` re-targeting, leash, target ground `(v0.4)`
- **F7 Special effects** `[ ]`
- `[ ]` EMP/stun, flame/radiation DoT, mind-control, shrink/grow `(v0.7)`
- **F8 Death & corpses** `[ ]`
- `[ ]` death types, wrecks, gibs, salvage, rebuild `(v0.6)`
### M09 `ra3.movement` — locomotion & pathfinding `[ ]`
- **F1 Locomotors** `[ ]`
- `[ ]` ground / air / naval / amphibious / hover / teleport `!!` `(v0.4)`
- `[ ]` turn rates, acceleration, braking, banking `(v0.6)`
- **F2 Pathfinding** `[ ]`
- `[ ]` grid A* over the passability grid `(v0.4)`
- `[ ]` hierarchical / jump-point refinement `(v0.6)`
- `[ ]` dynamic obstacle integration (buildings, units) `(v0.6)`
- **F3 Steering & flocking** `[ ]`
- `[ ]` separation, avoidance, group cohesion `(v0.6)`
- `[ ]` formation slots (line/wedge/box) `(v0.6)`
- **F4 Orders & waypoints** `[~]`
- `[x]` straight-line move toward a target (skirmish)
- `[ ]` waypoint queues, queued orders with shift `(v0.4)`
- `[ ]` guard / patrol / attack-move / stop / scatter `(v0.4)`
- **F5 Collision & crush** `[ ]`
- `[ ]` unit-unit collision, pushing, crush damage `(v0.6)`
- **F6 Naval & amphibious** `[ ]`
- `[ ]` water-only movement, amphibious land↔water transition `!!` `(v0.6)`
- **F7 Transport & airdrop** `[ ]`
- `[ ]` boarding/unloading, airdrop descent `(v0.6)`
### M10 `ra3.economy` — resources, power, construction `[P]`
- **F1 Ore / resource** `[~]`
- `[x]` ore fields and harvester↔refinery cycle
- `[ ]` ore spread/regrowth, depletion, ore density `(v0.4)`
- **F2 Power** `[~]`
- `[x]` power balance fields
- `[ ]` brownout/blackout consequences `(v0.4)`
- **F3 Construction** `[~]`
- `[x]` pay-as-you-go queue, tech prerequisites, build times
- `[ ]` build-radius rules (structures must be in base vicinity) `(v0.4)`
- `[ ]` low-power build-speed penalty `(v0.4)`
- **F4 Placement** `[ ]`
- `[ ]` placement grid, footprint validation, green/red ghost `(v0.4)`
- `[ ]` adjacency bonuses / prerequisite-adjacent structures `(v0.6)`
- **F5 Repair & sell** `[ ]`
- `[ ]` structure repair over time, cost, sell refund `(v0.4)`
- `[ ]` unit repair pads if present `(v0.6)`
- **F6 Rally points** `[ ]`
- `[ ]` factory rally, rally preview, waypoint rally `(v0.6)`
- **F7 Income modifiers** `[ ]`
- `[ ]` bonus crates (cash/units/repair/heal, `random_bonus_crates`) `(v0.5)`
### M11 `ra3.ai` — computer opponents `[P]`
- **F1 Skirmish AI** `[~]`
- `[x]` simple build AI (base, ore, a couple of unit types)
- `[ ]` data-driven build orders per faction `(v0.5)`
- `[ ]` economy management (expand, defend harvesters) `(v0.5)`
- **F2 Attack management** `[~]`
- `[x]` send units at the enemy base
- `[ ]` attack force assembly, waves, retreat/regroup `(v0.5)`
- `[ ]` targeting priorities (harvesters, key structures) `(v0.6)`
- **F3 Team AI / diplomacy** `[ ]`
- `[ ]` allied coordination, shared attacks, base defense `(v0.6)`
- **F4 Difficulty & personalities** `[ ]`
- `[ ]` easy/normal/hard modifiers, AI cheating options `(v0.5)`
- `[ ]` commander personalities (aggressive/turtle/air/naval) `(v0.7)`
- **F5 Scouting** `[ ]`
- `[ ]` exploration, map awareness, threat response `(v0.6)`
- **F6 Superweapon usage** `[ ]`
- `[ ]` AI powers/power targeting `(v0.7)`
- **F7 Script hooks** `[ ]`
- `[ ]` AI cooperation with script triggers (campaign) `(v0.7)`
### M12 `ra3.script` — triggers & missions `[ ]`
- **F1 Trigger system** `[ ]` `!!`
- `[ ]` conditions (elapsed, object in region, destroyed, flag) `(v0.7)`
- `[ ]` actions (spawn, order, reveal, camera, dialog, win/lose) `(v0.7)`
- **F2 Script engine** `[ ]` `!!`
- `[ ]` SAGE script language / mission script parse `(v0.7)`
- `[ ]` timers, counters, flags, per-player state `(v0.7)`
- **F3 Campaign missions** `[ ]`
- `[ ]` mission objectives, sequential phases, briefing `(v0.8)`
- `[ ]` three faction campaigns (Allied/Soviet/Empire) `(v0.8)`
- **F4 Reinforcements & spawns** `[ ]`
- `[ ]` scripted spawns, cinematic units, capture `(v0.8)`
- **F5 Tutorials** `[ ]`
- `[ ]` tutorial message gates, camera lock/unlock actions `(v0.8)`
- **F6 Co-op** `[ ]`
- `[ ]` co-op commander missions `(v0.9)`
### M13 `ra3.powers` — superweapons & support powers `[ ]`
- **F1 Superweapons** `[ ]` `!!`
- `[ ]` Allied Chronosphere `(v0.7)`
- `[ ]` Soviet Vacuum Imploder `(v0.7)`
- `[ ]` Empire Psionic Decimator `(v0.7)`
- `[ ]` charge timer, targeting, ready state, HUD `(v0.7)`
- **F2 Support powers** `[ ]`
- `[ ]` per-faction powers (spy satellite, air support, etc.) `(v0.7)`
- `[ ]` cooldowns, targeting types, cost `(v0.7)`
- **F3 Commander's Challenge powers** `[ ]`
- `[ ]` challenge-mode power loadouts `(v0.8)`
### M14 `ra3.shroud` — fog of war & radar `[ ]`
- **F1 Shroud** `[ ]`
- `[ ]` per-player unexplored grid `(v0.4)`
- **F2 Fog of war** `[ ]`
- `[ ]` explored-but-unseen dimming `(v0.4)`
- **F3 Reveal sources** `[ ]`
- `[ ]` units/structures reveal radius, abilities, spy satellite `(v0.6)`
- **F4 Radar / minimap** `[ ]`
- `[ ]` radar texture, unit blips, radar-offline on low power `(v0.6)`
- **F5 Shroud ↔ logic** `[ ]`
- `[ ]` targetability gating, option for AI to ignore shroud `(v0.4)`
### M15 `ra3.client` — game client & shell `[P]`
- **F1 Game client** `[~]`
- `[x]` `game_client` facade owning the sim, driving the frame loop
- `[ ]` sim/present decoupling, interpolation, catch-up `(v0.6)`
- **F2 Tactical view / camera** `[~]`
- `[x]` controls: wheel zoom, edge scroll, clamped pan, open on player start
- `[x]` camera tuning table (`cameraMinHeight` …) + lock actions
- `[ ]` retail perspective camera, pitch/yaw, FOV (needs 3D terrain) `(v0.6)`
- **F3 Selection** `[ ]`
- `[ ]` click select, drag-box, double-click type select `(v0.6)`
- `[ ]` control groups (Ctrl+N), type filters, select-all-of-type `(v0.6)`
- **F4 Orders** `[ ]`
- `[ ]` contextual right-click orders, force-attack, force-move `(v0.6)`
- `[ ]` order queue with shift, formation move `(v0.6)`
- **F5 Command bar** `[ ]` `!!`
- `[ ]` build/production palettes, ability buttons, portraits `(v0.7)`
- `[ ]` tooltips, cost/time, cooldown sweep, disabled states `(v0.7)`
- **F6 HUD** `[ ]`
- `[x]` minimal match-state overlay (units, start markers)
- `[ ]` resource/power readouts, objectives, notifications `(v0.7)`
- `[ ]` EVA voice announcements hook `(v0.7)`
- **F7 Shell menus** `[~]`
- `[x]` map list by localized name + full render/skirmish options; console fallback
- `[ ]` main menu, skirmish setup, faction/team/color pickers `(v0.7)`
- `[ ]` options (video/audio/keybinds), pause, load/save, credits `(v0.8)`
- **F8 Feedback & cursors** `[ ]`
- `[ ]` action cursor, placement ghost, move/attack markers `(v0.6)`
### M16 `ra3.render` — renderer & RHI `[P]`
- **F1 RHI** `[ ]`
- `[ ]` device/queue/swapchain abstraction over Vulkan `(v0.6)`
- `[ ]` buffers, textures, samplers, descriptor sets, pipelines `(v0.6)`
- `[ ]` `present_terrain` is the seam where this lands today `[~]`
- **F2 Render graph** `[ ]`
- `[ ]` pass scheduling, barriers, transient/aliased resources `(v0.6)`
- **F3 Terrain render** `[~]`
- `[x]` top-down software + GPU heightfield with blend ramp + gutter atlas
- `[ ]` perspective terrain mesh, LOD, cliff, water `(v0.6)`
- **F4 Model render** `[ ]` `!!`
- `[ ]` W3D draw, skinning, materials, team colors `(v0.6)`
- `[ ]` shadows, decals, ground marks `(v0.6)`
- **F5 VFX** `[ ]`
- `[ ]` particle systems, beams, muzzle flashes, explosions `(v0.6)`
- `[ ]` shader effect graph (retail `.fxo` parity where feasible) `!!` `(v0.7)`
- **F6 Sky & atmosphere** `[ ]`
- `[ ]` skybox, fog, weather, time-of-day `(v0.7)`
- **F7 HUD render** `[ ]`
- `[ ]` 2D art layer, fonts, minimap/radar texture `(v0.7)`
- **F8 Software renderer** `[D]`
- `[x]` ARGB framebuffer, blit/line/circle/text, TGA decode, BMP encode
- `[x]` map compositing, grid, markers; headless output
- **F9 Post-processing** `[ ]`
- `[ ]` bloom, color grading, AA, resolution scaling `(v0.7)`
### M17 `ra3.ui` — platform layer & backends `[D]`
- **F1 Display abstraction** `[D]`
- `[x]` shared primitives + interactive loops + `ui_event` mapping in the base
- `[x]` backends implement primitives only (SDL/Vulkan cannot drift)
- **F2 SDL3 backend** `[D]`
- `[x]` window, streaming-texture blit, input polling
- `[ ]` gamepad support `(v0.8)`
- **F3 Backend selection (`ra3.display`)** `[D]`
- `[x]` preferred backend + ordered fallback (Vulkan / D3D11 / D3D12 / SDL)
- `[x]` in-game Renderer option; report failure for offscreen fallback
- **F4 Input mapping** `[~]`
- `[x]` keyboard/mouse state, modifier masks
- `[ ]` rebindable keybinds, mouse capture, scroll wheel events `(v0.6)`
- **F5 Window modes** `[ ]`
- `[ ]` windowed/fullscreen/borderless, resize, multi-monitor `(v0.6)`
- **F6 Null/headless backend** `[D]`
- `[x]` returns false so the tree builds and runs without SDL3/Vulkan
### M18 `ra3.vulkan` — Vulkan backend `[P]`
- **F1 Device & swapchain** `[~]`
- `[x]` embedded SPIR-V, SDL3 surface, present path
- `[ ]` formal RHI integration (see M16 F1) `(v0.6)`
- **F2 Terrain presentation** `[D]`
- `[x]` GPU heightfield raymarch, mipmapped atlas, gutter, retail blend ramp
- **F3 Materials & pipelines** `[ ]`
- `[ ]` model/particle/HUD pipelines `(v0.6)`
- **F4 Null fallback** `[D]`
- `[x]` report failure when no Vulkan loader is present
### M18b `ra3.dx` — Direct3D 11 / 12 backend `[P]`
- **F1 Device & swapchain** `[D]`
- `[x]` SDL3 window → HWND, DXGI flip-model swapchain, resize
- `[x]` runtime HLSL via `d3dcompiler_47` (no build-time shader compiler)
- **F2 2D image path** `[D]`
- `[x]` BGRA scene texture + fullscreen-triangle blit (D3D11/D3D12)
- **F3 Terrain presentation** `[D]`
- `[x]` GPU heightfield raymarch (HLSL port of `terrain.frag`), D3D11 and D3D12
- **F4 Root signatures / PSOs (D3D12)** `[~]`
- `[x]` root constants, descriptor tables, static samplers, barriers
- `[ ]` shared RHI with Vulkan (see M16 F1) `(v0.6)`
- **F5 Null fallback** `[D]`
- `[x]` non-Windows builds link a stub that fails `init`
### M18c `ra3.webgl` — WebGL backend `[P]`
- **F1 Device & canvas** `[D]`
- `[x]` Emscripten-only; SDL3 canvas + GLES 3.0 (WebGL2) via `-sUSE_SDL=3`
- **F2 2D image path** `[D]`
- `[x]` BGRA texture + fullscreen triangle (GLSL ES 300), `.bgra` swizzle
- **F3 Terrain presentation** `[D]`
- `[x]` GPU heightfield raymarch (GLSL ES port of `terrain.frag`)
- **F4 Wasm packaging** `[~]`
- `[x]` `openra3.html/.js/.wasm`, assets preloaded into the FS at `/assets`
- `[ ]` async/fetch streaming assets instead of a single preload blob `(v0.6)`
- **F5 Null fallback** `[D]`
- `[x]` non-Emscripten builds link a stub that fails `init`
### M19 `ra3.audio` — audio `[ ]`
- **F1 SFX** `[ ]`
- `[ ]` 3D positional sound from cues/events `(v0.7)`
- **F2 Music** `[ ]`
- `[ ]` streaming music, playlists, combat stingers `(v0.7)`
- **F3 Voice & EVA** `[ ]`
- `[ ]` unit response lines, announcer (EVA) events `(v0.7)`
- **F4 Mixer** `[ ]`
- `[ ]` buses (master/sfx/music/voice), volume, ducking, reverb `(v0.7)`
- **F5 Codecs** `[ ]` `!!`
- `[ ]` decode RA3 audio formats `(v0.7)`
### M20 `ra3.video` — movies & cutscenes `[ ]`
- **F1 Movie playback** `[ ]` `!!`
- `[ ]` intro/briefing/ending video decode + playback `(v0.8)`
- `[ ]` skip, subtitles, aspect handling `(v0.8)`
- **F2 In-engine cutscenes** `[ ]`
- `[ ]` scripted camera + unit animation sequences `(v0.8)`
### M21 `ra3.match` — game setup & match rules `[P]`
- **F1 Game setup** `[~]`
- `[x]` map + two players + seed defaults
- `[ ]` faction/color/team/start-slot selection, AI personalities `(v0.7)`
- **F2 Rules & options** `[ ]`
- `[ ]` starting cash, crates on/off, superweapons on/off, speed, limits `(v0.7)`
- **F3 Match flow** `[~]`
- `[x]` `prepare_new_game` / `start_new_game` split mirroring retail
- `[ ]` loading progress, in-game start countdown `(v0.7)`
- **F4 Factions** `[~]`
- `[x]` Allied / Soviet / Empire player templates
- `[ ]` full faction tech trees and rosters `(v0.5)`
- **F5 Victory & scoring** `[~]`
- `[x]` team-wipe victory
- `[ ]` full victory conditions, post-match score screen `(v0.8)`
### M22 `ra3.replay` — recording & playback `[ ]`
- **F1 Recorder** `[ ]`
- `[ ]` capture command stream + seed + map id per match `(v0.4)`
- **F2 Replay format** `[ ]`
- `[ ]` self-describing header, command log, checksums `(v0.4)`
- `[ ]` compatibility with retail `.RA3Replay` playback `!!` `(v0.9)`
- **F3 Playback** `[ ]`
- `[ ]` deterministic re-simulation, speed control, seek `(v0.4)`
- **F4 Golden replays** `[ ]`
- `[ ]` curated replay corpus as a regression test `(v0.4)`
- **F5 Observer / spectator** `[ ]`
- `[ ]` watch live or recorded matches, fog option `(v0.9)`
### M23 `ra3.save` — save/load & profiles `[ ]`
- **F1 Save / load** `[ ]`
- `[ ]` full simulation state serialization (objects, modules, queues) `(v0.8)`
- `[ ]` versioned saves with migration `(v0.8)`
- **F2 Profiles** `[ ]`
- `[ ]` player profile, stats, progress/unlocks `(v0.8)`
- **F3 Options persistence** `[ ]`
- `[ ]` settings, keybinds, last-used skirmish config `(v0.7)`
- **F4 Checkpoints** `[ ]`
- `[ ]` campaign checkpoint save/restore `(v0.9)`
### M24 `ra3.mod` — data packages `[ ]`
- **F1 Data packages** `[ ]`
- `[ ]` load order, override precedence, loose-file mounting `(v0.8)`
- **F2 Content discovery** `[ ]`
- `[ ]` scan user mod dirs and additional `.big` archives `(v0.8)`
- **F3 Mod validation** `[ ]`
- `[ ]` schema + reference checks, actionable errors `(v0.8)`
### M25 `ra3.net` — LAN lockstep `[ ]` *(deferred, offline-only)*
- **F1 Lockstep** `[ ]`
- `[ ]` deterministic lockstep over LAN, command-exchange only `(v1.0)`
- **F2 Lobby & sync** `[ ]`
- `[ ]` lobby, slot/team assignment, start sync `(v1.0)`
- **F3 Desync detection** `[ ]`
- `[ ]` state-hash comparison, desync report `(v1.0)`
> No online service, matchmaking, EA account or third-party network
> integration — ever. LAN only.
### M26 `ra3.i18n` — localization `[P]`
- **F1 CSF strings** `[~]`
- `[x]` parse `gamestrings.csf` (UTF-16 units, low byte `^0xFF`)
- `[x]` map display-name lookup (`MAP:<ID>`)
- `[ ]` full string-table load for all UI text `(v0.5)`
- **F2 Language selection** `[~]`
- `[x]` newest `Lang-English*.big` wins
- `[ ]` all supported languages, fallback chain `(v0.5)`
- **F3 Fonts & shaping** `[ ]`
- `[ ]` glyph coverage, CJK/RTL shaping where applicable `(v0.6)`
- **F4 Substitution** `[ ]`
- `[ ]` placeholders, numbers, plurals `(v0.5)`
### M27 `apps` — applications `[D]`
- **F1 CLI** `[D]`
- `[x]` `menu` / `menu-preview` / `maps` / `skirmish` / `render` / `extract`
- `[ ]` `replay`, `benchmark`, `validate-data` subcommands `(v0.5)`
- **F2 Extraction pipeline** `[~]`
- `[x]` `extract` → `assets/` (maps, terrain)
- `[ ]` full asset dump (models/textures/audio/movies) as a build target `(v0.5)`
- **F3 Diagnostics** `[ ]`
- `[ ]` headless render/benchmark modes for CI `(v0.4)`
### M28 `tools` — offline tooling `[~]`
*(Python is permitted here; it is never linked into `openra3`.)*
- **F1 Reference fetch** `[D]`
- `[x]` sparse-clone SAGE 1.0 reference into git-ignored `reference/`
- **F2 Ghidra workflow** `[D]`
- `[x]` MCP-driven recovery loop + recovered symbol map (see RE doc)
- `[ ]` automated structure/table extractors `(v0.5)`
- **F3 Format inspectors** `[ ]`
- `[ ]` BIG/RefPack/CkMp/W3D/APT dumpers `(v0.5)`
- **F4 CI & packaging** `[D]`
- `[x]` GitLab CI builds both targets → test → package
---
## 4. Cross-cutting invariants
These hold across **every** module above and are enforced in review:
1. **Language** — runtime code is C++ only. Build tooling may use Python.
2. **Ownership** — no raw owning pointers; `unique_ptr` for ownership, handles or
`thing *` views for references.
3. **Layering** — a module imports only lower layers. `ra3.core` imports no
engine module and no platform API.
4. **Determinism** — RNG, iteration, hashing and float use are explicit and
seedable; the logic step must be bit-reproducible for a given input stream.
5. **RE-traceable** — retail structure/constant changes cite an address in
[`REVERSE_ENGINEERING.md`](REVERSE_ENGINEERING.md).
6. **Offline** — no online service; LAN lockstep (M25) is the only networking,
and only command exchange.
7. **No bundled assets** — game data is read from the user's install; nothing
from it is committed. The tree builds and runs in CI on a built-in test map.
---
## 5. Milestone mapping
The plan rolls up to these releases:
| Milestone | Modules advanced | Delivers |
| --- | --- | --- |
| `v0.3.x` (done) | M00, M01, M04, M05(F1–F3), M06, M07(partial), M08(partial), M10(partial), M16(F3,F8), M17, M18 | minimal deterministic skirmish, real terrain, Vulkan present, menu |
| `v0.4.0` | M05(F4–F7), M06, M07, M08, M09(F1,F2,F4), M14, M22(F1–F4) | real update modules: locomotor, projectiles/warheads, placement, shroud, pathfinding, replay |
| `v0.5.0` | M02, M03, M04(F3,F4), M08, M10(F3,F4), M11(F1,F2), M26 | data-driven content: deserialise `.bin`/`.manifest`, real rosters, maps, strings |
| `v0.6.0` | M03(F1–F3), M07(F4,F6,F8,F14), M09(F2–F7), M16(F1,F2,F4,F5), M18(F3), M15(F1–F4,F8) | full renderer: perspective terrain, W3D models, in-game client + input |
| `v0.7.0` | M13, M15(F5–F7), M19, M11(F4–F6), M20, M21 | HUD/command bar, audio, superweapons, Commander's Challenge |
| `v0.8.0` | M12, M20(F1), M23, M24, M21(F5) | campaigns, cutscenes, save/load, mods |
| `v0.9.0` | M22(F2,F5), M12(F6), M25 | retail replay playback, co-op, LAN lockstep |
| `v1.0.0` | all | feature-complete offline RA3 |
---
## 6. Design rules
The five rules the codebase is held to (restated from §1, with the concrete
consequences that trip people up):
1. **No raw owning pointers.** Ownership is `std::unique_ptr`; the partition
manager holds non-owning `thing *` views only.
2. **Portable simulation.** No platform APIs in `ra3.core` / `ra3.logic`. All
platform concerns live behind `display`.
manager and object registry hold non-owning `thing *` views only.
2. **Portable simulation.** No platform APIs in `ra3.core` / simulation
modules. All platform concerns live behind `display`.
3. **Determinism.** Anything that can diverge between runs (random, iteration
order) is explicit and seedable.
4. **RE-traceable.** Where a structure or constant comes from the retail
binary, the address is cited in the comment.
5. **No bundled assets, no online.** Game data is read from the user's install
and never committed; there is no networking or online service.
---
## 7. Where to start
- New to the codebase: read this plan top-to-bottom (§3 is the feature set,
§5 the near-term order of work).
- Picking up a feature: find its **Function** above, take the lowest-numbered
unmet `[ ]` **Feature**, and cite its supporting retail address.
- Reverse engineering a subsystem: follow the loop in
[`REVERSE_ENGINEERING.md`](REVERSE_ENGINEERING.md); the `!!` tags above mark
the functions that still need a recovery pass before they can be built.
+99 -3
View File
@@ -78,6 +78,27 @@ Each holds an object pointer (0 when the subsystem is down).
cash `+0x64`, player slots `+0xfc` (stride `0x5c`, 6 slots), faction at
`slot + 0x18` (`Empire=2`, `Allied=4`, `Soviet=8`, `Random=7`).
### Tactical view (camera)
The in-game camera is the `TheTacticalView` object, held in the global at
`0x00cdb7b4`. Its vtable accessors return zoom, pitch (current/target), yaw,
world position and FOV; the debug overlay `FUN_005ef0a0` prints them through the
format string at `0x00c0b900`. Mouse state is the singleton at `0x00ce9284`
(cursor position vtable slot `+0x3c`, button down `+0x48`); the keyboard manager
is `0x00ce927c` (modifier mask `+0x38`).
Per-map camera tuning is a named-field table in `.rdata` (around
`0x00c11a54`): `cameraMinHeight`, `cameraMaxHeight`, `cameraPitchAngle`,
`cameraYawAngle`, `cameraScrollSpeedScalar`, `cameraGroundMinHeight`,
`cameraGroundMaxHeight`. The map-load chunk `CHUNK_TacticalView`
(`0x00beea14`, consumed near `0x00548a00`) seeds the view from the map; the
tutorial actions `LOCK_CAMERA_SCROLL` / `LOCK_CAMERA_ZOOM` / `LOCK_CAMERA_ROTATION`
gate the controls.
OpenRA3 has no 3D terrain yet, so `ra3::render::view_camera` reproduces the
*controls* - wheel zoom, screen-edge scroll, clamped pan and opening on the
player's start - over the 2D map overview, not the retail perspective camera.
### Match start
The BEGIN button calls `SkirmishGameOptionsMenu::start` (`0x00b28d60`), which
@@ -127,9 +148,84 @@ in `MPPositionList` instead yield no waypoints and fall back.
Verified example (`map_mp_2_feasel4`): `Player_1_Start` = `(1338.9, 1940.5, 0)`,
`Player_2_Start` = `(1290.8, 1404.9, 0)`.
### Terrain (`ra3.terrain`)
The `CkMp` tree is a flat chunk list: `"CkMp"`, `u32 assetCount`, the name
table (`{ u8 len, name, u32 index }`, index descending from `assetCount`), then
`{ u32 index, u16 version, u32 size, data }` per chunk. On
`map_mp_2_feasel4` the terrain is `HeightMapData` v6 (540 x 600, border 20,
`u16` elevations, scale `0.0390625`) and `BlendTileData` v27.
`BlendTileData` opens with `NumTiles`, the `u16` tile grid, then the
blend/three-way/cliff tables (`u16` for v27); the passability flag arrays that
follow are not needed for rendering, so the texture table is located by
scanning for its `{ cellStart, cellCount, cellSize, magic }` + `u16`-prefixed
name entries. A tile value is `(cellIndex << 2) | variant`, and `cellIndex`
indexes the global `TextureCellCount`-cell table (each texture owning
`cellSize^2` 64 px cells). The textures themselves are
`art\terrain\<stem>.tga` in `Terrain.big` / `Core11.big` (RefPack + 256x256
TGA). Rendered top-down, `map_mp_2_feasel4` correlates 0.94 with the official
`_art.tga` overview.
### Terrain blending (`BlendTileData` tail)
After the tile grid, `BlendTileData` stores three per-cell `u16` tables —
`Blends`, `ThreeWayBlends` and `CliffTextures` — then `TextureCellCount`,
`BlendsCount`, the texture table, two magic words and `BlendsCount - 1` blend
descriptions (18 bytes each: `u32 secondaryTile`, four direction bytes,
`u8 flags`, `u8 twoSided`, `u32 0xFFFFFFFF`, `u32 0x7ADA0000`). A non-zero
`Blends[cell]` is a 1-based index into the descriptions; `secondaryTile` is a
packed tile value (`secondaryTile >> 2` is its cell).
The retail `Terrain.fx` (compiled `terrain.fxo`, parameters `Terrain.BaseTexture`,
`Terrain.MacroTexture`, `MapCellSize`, `IsTerrainAtlasEnabled`; technique
`TerrainTile`) cross-fades a cell's base tile into `secondaryTile` with a linear
ramp selected by `BlendDirection`: `1` right, `2` top, `4` top-right, `8`
top-left, where `flags` bit 0 flips the axis and bit 1 marks a two-sided
diagonal. OpenSAGE's `Terrain.frag` reconstructs the exact
`CalculateBlendFactor`; `ra3::terrain::blend_factor` and `shaders/terrain.frag`
mirror it. The row axis is *not* inverted (73% of long-axis blends point at a
neighbour of the same texture, versus 25% inverted). On `map_mp_2_feasel4`,
33828 of 324000 cells carry a blend and there are 7094 descriptions.
The GPU atlas pads every 64 px tile with a 2-texel replicated gutter. Packed
edge-to-edge, bilinear/mipmap filtering averaged two unrelated tiles at every
cell border — that cross-tile bleed was the visible grid line the hardware path
drew. (`shaders/terrain.frag` samples `cell_stride = cell_texels + 2 * gutter`.)
### Open question: per-cell tile sampling
Measured on `map_mp_2_feasel4`, sampling each cell as its own 64 px block and
restarting the UV every cell leaves a 1.39x edge spike at cell boundaries
(43.4 vs 31.1 mean gradient at 8 px/cell). Two candidate mappings reduce it and
need a visual decision against the retail art:
| Mapping | Boundary/interior |
| --- | --- |
| per-cell 64 px block (current) | 1.39 |
| OpenSAGE `BlendTileTextureIndex` Morton layout, 32 px block | 1.18 |
| continuous `uv / (cellSize * 2)`, per OpenSAGE `Terrain.frag` | 1.11 |
The retail `Terrain.frag` (OpenSAGE) samples the tile texture *continuously*
(`uv / (CellSize * 2)`), so adjacent cells never restart the texture; our
per-cell restart is the remaining source of grid lines. Switching the atlas
from 64 px blocks to continuous 32 px regions (or the Morton 8x8 layout) is the
next step, pending an art correlation check.
### Map display names
The skirmish map list labels live in `Data\English.big`'s
`data\gamestrings.csf` (the newest `Lang-English*.big` wins) under
`MAP:<UPPERCASE_ID>`, e.g. `MAP:MAP_MP_2_FEASEL4` = "Battlebase Beta". CSF
values are UTF-16 code units whose low byte is XORed with `0xFF`
(`ra3::map::parse_map_names`). `openra3 extract` writes the decoded table to
`maps/map_names.tsv`; `openra3 menu` shows them instead of the raw map id.
### Still to recover
- `MPPositionList` layout (per-player starts for maps without waypoints).
- Map dimensions / `HeightMapData` / `BlendTileData`.
- Compiled asset blobs (`map.bin`, `global.bin`, `static.*.bin`) and the
`.manifest` schema used to deserialise them.
- Cliff textures and the `CliffTextureMapping` UV remap (`CliffTextures` is
parsed but not yet drawn).
- Compiled asset blobs (`global.bin`, `static.*.bin`) and the `.manifest`
schema used to deserialise them.
-45
View File
@@ -1,45 +0,0 @@
# Roadmap
OpenRA3 is a very large undertaking. This roadmap is deliberately honest about
scope: reconstructing a 2008 RTS engine from a decompiler plus a related open
engine is a multi-year, multi-person effort. The milestones below are ordered so
that each one produces something that builds and runs.
## Done
- [x] **v0.0.1 — skeleton + minimal skirmish.** C++26 modules, GCC 16,
CMake/Ninja, Docker `dev`/`deploy`, GitLab CI. Reads `BIG4`/RefPack data
from a local install, recovers map start waypoints, and runs a
deterministic headless two-player skirmish to a decision.
- [x] **v0.1.0 — map renderer + window.** Software ARGB framebuffer, TGA
decoder for the map art, BMP output, map/grid/unit compositing, and an
SDL3 window viewer (pan/zoom).
## Next
- [ ] **v0.1.1 — correct world calibration.** Parse the map heightmap
dimensions (and `MPPositionList` starts) so the unit overlay lines up with
the map art instead of using an assumed world size.
- [ ] **v0.2.0 — data & file formats.** Parse compiled gameplay assets
(`GameObject`, `WeaponTemplate`, `ArmorTemplate`, `LocomotorTemplate`) so
units use the real balance numbers instead of OpenRA3's stand-ins.
- [ ] **v0.3.0 — deterministic simulation.** Real update modules, locomotor
movement, weapons/damage/armour resolution, build queues and the tech
tree, pathfinding and shroud.
- [ ] **v0.4.0 — AI.** Skirmish AI: build states, team composition, attack
waves (the reference tree's `AI*` modules).
- [ ] **v0.5.0 — full renderer.** Terrain heightmap rendering, W3D models and a
D3D9/Vulkan/OpenGL client backend implementing `ra3::client::display`,
plus full input.
- [ ] **v0.6.0 — content.** Load real maps, units, powers and strings; play a
skirmish end-to-end with a UI.
## Cross-cutting tracks
- **RE depth** — keep recovering retail layouts (see
[`REVERSE_ENGINEERING.md`](REVERSE_ENGINEERING.md)); every structure gets an
address citation and a test.
- **Determinism & replay** — the logic random stream and frame ordering must be
reproducible; replay format and a golden-replay test suite.
- **Offline only** — no online mode. Multiplayer, if pursued, is LAN lockstep on
the message stream, never an online service.
+19
View File
@@ -0,0 +1,19 @@
#!/usr/bin/env bash
# Build the Debian (latest stable) Linux/ARM64 target (.deb + binary).
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
IMAGE="${OPENRA3_DEBIAN_ARM64_IMAGE:-openra3-debian-arm64:local}"
BUILD_DIR="${1:-build/debian-arm64}"
docker build --target dev -f "$ROOT/Dockerfile.debian-arm64" -t "$IMAGE" "$ROOT"
docker run --rm -v "$ROOT:/work" -w /work "$IMAGE" cmake -S . -B "$BUILD_DIR" -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/clang-aarch64-linux-gnu.cmake \
-DCMAKE_BUILD_TYPE=Release \
-DOPENRA3_AARCH64_CC=clang-19 -DOPENRA3_AARCH64_CXX=clang++-19 \
-DOPENRA3_AARCH64_STDLIB_MODULES_JSON=/usr/lib/llvm-19/lib/libc++.modules.json \
-DOPENRA3_DISTRO_TAG=debian13
docker run --rm -v "$ROOT:/work" -w /work "$IMAGE" cmake --build "$BUILD_DIR" -j
docker run --rm -v "$ROOT:/work" -w /work "$IMAGE" bash -c "cd $BUILD_DIR && cpack -G DEB"
echo "Debian arm64: $ROOT/$BUILD_DIR/bin/openra3 (+ .deb)"
+17
View File
@@ -0,0 +1,17 @@
#!/usr/bin/env bash
# Build the Debian (latest stable) target (.deb + binary) with the isolated image.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
IMAGE="${OPENRA3_DEBIAN_IMAGE:-openra3-debian:local}"
BUILD_DIR="${1:-build/debian}"
docker build --target dev -f "$ROOT/Dockerfile.debian" -t "$IMAGE" "$ROOT"
docker run --rm -v "$ROOT:/work" -w /work "$IMAGE" cmake -S . -B "$BUILD_DIR" -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_CXX_STDLIB_MODULES_JSON=/usr/lib/llvm-19/lib/libc++.modules.json \
-DOPENRA3_DISTRO_TAG=debian13
docker run --rm -v "$ROOT:/work" -w /work "$IMAGE" cmake --build "$BUILD_DIR" -j
docker run --rm -v "$ROOT:/work" -w /work "$IMAGE" bash -c "cd $BUILD_DIR && cpack -G DEB"
echo "Debian: $ROOT/$BUILD_DIR/bin/openra3 (+ .deb)"
+16
View File
@@ -0,0 +1,16 @@
#!/usr/bin/env bash
# Build the Linux/ARM64 target (.deb + binary) with the isolated cross image.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
IMAGE="${OPENRA3_LINUX_ARM64_IMAGE:-openra3-linux-arm64:local}"
BUILD_DIR="${1:-build/linux-arm64}"
docker build --target dev -f "$ROOT/Dockerfile.linux-arm64" -t "$IMAGE" "$ROOT"
docker run --rm -v "$ROOT:/work" -w /work "$IMAGE" cmake -S . -B "$BUILD_DIR" -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/clang-aarch64-linux-gnu.cmake \
-DCMAKE_BUILD_TYPE=Release -DOPENRA3_DISTRO_TAG=ubuntu26.04
docker run --rm -v "$ROOT:/work" -w /work "$IMAGE" cmake --build "$BUILD_DIR" -j
docker run --rm -v "$ROOT:/work" -w /work "$IMAGE" bash -c "cd $BUILD_DIR && cpack -G DEB"
echo "Linux arm64: $ROOT/$BUILD_DIR/bin/openra3 (+ .deb)"
+14
View File
@@ -0,0 +1,14 @@
#!/usr/bin/env bash
# Build the Linux target inside its own isolated image (Dockerfile).
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
IMAGE="${OPENRA3_LINUX_IMAGE:-openra3-linux:local}"
BUILD_DIR="${1:-build/linux}"
docker build --target dev -t "$IMAGE" "$ROOT"
docker run --rm -v "$ROOT:/work" -w /work "$IMAGE" cmake -S . -B "$BUILD_DIR" -G Ninja -DCMAKE_BUILD_TYPE=Release
docker run --rm -v "$ROOT:/work" -w /work "$IMAGE" cmake --build "$BUILD_DIR" -j
docker run --rm -v "$ROOT:/work" -w /work "$IMAGE" ctest --test-dir "$BUILD_DIR" --output-on-failure
echo "Linux build: $ROOT/$BUILD_DIR/bin/openra3"
+31
View File
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
# Build the WebAssembly target (openra3.html/.js/.wasm) with the Emscripten image.
#
# Browsers forbid synchronous on-demand file reads on the main thread, so assets
# are preloaded into the module's filesystem. Prefer a compact, per-map set: one
# map plus the tiles it uses:
#
# OPENRA3_WEB_ASSETS="$(mktemp -d)" \
# openra3 textures --map map_mp_2_feasel1 # copy these under the set's terrain/
#
# then point OPENRA3_WEB_ASSETS at that directory.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
IMAGE="${OPENRA3_WASM_IMAGE:-openra3-wasm:local}"
BUILD_DIR="${1:-build/wasm}"
docker build --target dev -f "$ROOT/Dockerfile.wasm" -t "$IMAGE" "$ROOT"
mounts=(-v "$ROOT:/work")
configure=(-DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/emscripten-wasm.cmake -DCMAKE_BUILD_TYPE=Release)
if [ -n "${OPENRA3_WEB_ASSETS:-}" ]; then
mounts+=(-v "$OPENRA3_WEB_ASSETS:/assets:ro")
configure+=(-DOPENRA3_WEB_ASSETS=/assets)
fi
docker run --rm "${mounts[@]}" -w /work "$IMAGE" cmake -S . -B "$BUILD_DIR" -G Ninja "${configure[@]}"
docker run --rm "${mounts[@]}" -w /work "$IMAGE" cmake --build "$BUILD_DIR" -j
echo "WebAssembly: $ROOT/$BUILD_DIR/bin/openra3.html (+ .js/.wasm/.data)"
echo "serve: python3 \"$ROOT/apps/web/serve.py\" --root \"$ROOT/$BUILD_DIR/bin\" --port 8199"
+16
View File
@@ -0,0 +1,16 @@
#!/usr/bin/env bash
# Build the Windows/ARM64 target (portable .zip) with the isolated cross image.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
IMAGE="${OPENRA3_WINDOWS_ARM64_IMAGE:-openra3-windows-arm64:local}"
BUILD_DIR="${1:-build/windows-arm64}"
docker build --target dev -f "$ROOT/Dockerfile.win-arm64" -t "$IMAGE" "$ROOT"
docker run --rm -v "$ROOT:/work" -w /work "$IMAGE" cmake -S . -B "$BUILD_DIR" -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/llvm-mingw-aarch64.cmake \
-DCMAKE_BUILD_TYPE=Release
docker run --rm -v "$ROOT:/work" -w /work "$IMAGE" cmake --build "$BUILD_DIR" -j
docker run --rm -v "$ROOT:/work" -w /work "$IMAGE" bash -c "cd $BUILD_DIR && cpack -G ZIP"
echo "Windows arm64: $ROOT/$BUILD_DIR/bin/openra3.exe (+ .zip)"
+16
View File
@@ -0,0 +1,16 @@
#!/usr/bin/env bash
# Build the Windows x86_64 target inside its own isolated image (Dockerfile.win).
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
IMAGE="${OPENRA3_WINDOWS_IMAGE:-openra3-windows:local}"
BUILD_DIR="${1:-build/windows}"
docker build --target dev -f "$ROOT/Dockerfile.win" -t "$IMAGE" "$ROOT"
docker run --rm -v "$ROOT:/work" -w /work "$IMAGE" \
cmake -S . -B "$BUILD_DIR" -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/llvm-mingw-x86_64.cmake \
-DCMAKE_BUILD_TYPE=Release
docker run --rm -v "$ROOT:/work" -w /work "$IMAGE" cmake --build "$BUILD_DIR" -j
echo "Windows build: $ROOT/$BUILD_DIR/bin/openra3.exe (+ SDL3.dll)"
+33
View File
@@ -0,0 +1,33 @@
#!/usr/bin/env sh
# Regenerate the committed SPIR-V blobs under shaders/generated/.
#
# The build embeds the .spv files directly (see CMakeLists.txt), so no shader
# compiler is needed to build OpenRA3. Run this only after editing a .vert/.frag.
#
# Needs one of: glslc (shaderc) or glslangValidator (glslang-tools). On a host
# without either, use a throwaway container:
# docker run --rm -v "$PWD/shaders:/s" ubuntu:26.04 bash -c \
# "apt-get update -qq && apt-get install -y -qq glslang-tools && \
# cd /s && sh ./compile.sh"
set -eu
DIR="$(cd "$(dirname "$0")" && pwd)"
OUT="$DIR/generated"
mkdir -p "$OUT"
compile() {
src="$1"
dst="$2"
if command -v glslc >/dev/null 2>&1; then
glslc "$src" -o "$dst"
else
glslangValidator -V "$src" -o "$dst"
fi
}
for name in scene terrain; do
compile "$DIR/$name.vert" "$OUT/$name.vert.spv"
compile "$DIR/$name.frag" "$OUT/$name.frag.spv"
done
echo "wrote $OUT/{scene,terrain}.{vert,frag}.spv"
+34
View File
@@ -0,0 +1,34 @@
// 2D image blit for the Direct3D backends (the D3D port of scene.vert/scene.frag).
//
// The scene image is drawn as a single fullscreen triangle sampling the
// software-rendered `ra3::render::image`. The constant buffer carries the
// destination rectangle in window-normalized coordinates (y down), so the map
// is letterboxed rather than stretched. D3D clip space has +Y up, so the vertex
// position flips Y relative to the Vulkan shader (which relies on Vulkan's
// +Y-down clip space); the sampled UVs and the image's top-left origin are
// unchanged.
cbuffer RectCB : register(b0) {
float4 rect; // xy = top-left (0..1), zw = size (0..1)
};
Texture2D scene_tex : register(t0);
SamplerState scene_smp : register(s0);
struct VSOut {
float4 pos : SV_Position;
float2 uv : TEXCOORD0;
};
VSOut VSMain(uint vertex_id : SV_VertexID) {
float2 p = float2((vertex_id << 1) & 2, vertex_id & 2);
VSOut o;
o.uv = (p - rect.xy) / rect.zw;
o.pos = float4(p.x * 2.0 - 1.0, 1.0 - p.y * 2.0, 0.0, 1.0);
return o;
}
float4 PSMain(VSOut input) : SV_Target {
if (input.uv.x < 0.0 || input.uv.x > 1.0 || input.uv.y < 0.0 || input.uv.y > 1.0) discard;
return scene_tex.Sample(scene_smp, input.uv);
}
+236
View File
@@ -0,0 +1,236 @@
// GPU heightfield raymarcher for the Direct3D backends (the D3D port of
// terrain.vert/terrain.frag).
//
// Textures: heightmap (R16), a per-cell blend record (R16G16B16A16_UNORM: base
// layer, blend layer, three-way layer, packed direction/flags; unpacked with
// *65535) and a texture array of the tile materials (RGBA8, REPEAT). The
// material is sampled continuously (`uv = cell / span`), as the retail
// `Terrain.fx` / OpenSAGE `Terrain.frag` do, so it never restarts at a cell
// edge; material boundaries cross-fade with the SAGE blend ramp.
//
// The Vulkan push constants (20 floats) become a constant buffer.
cbuffer TerrainCB : register(b0) {
float4 cam; // x=target_x, y=target_y, z=yaw, w=height
float4 params; // x=pitch, y=fov, z=water_z, w=has_water
float4 sun; // xyz=sun dir, w=ambient
float4 mapinfo; // x=W, y=H, z=unused, w=z_scale
float4 misc; // x=time, y=unused, z=cells per texture repeat, w=aspect
};
Texture2D<float> heightmap : register(t0);
Texture2D<float4> celldata : register(t1);
Texture2DArray<float4> atlas : register(t2);
SamplerState height_smp : register(s0);
SamplerState cell_smp : register(s1);
SamplerState atlas_smp : register(s2);
static const float CELL = 10.0; // must match ra3::terrain::cell_size
struct VSOut {
float4 pos : SV_Position;
float2 uv : TEXCOORD0;
};
VSOut VSMain(uint vertex_id : SV_VertexID) {
float2 p = float2((vertex_id << 1) & 2, vertex_id & 2);
VSOut o;
o.uv = p;
o.pos = float4(p.x * 2.0 - 1.0, 1.0 - p.y * 2.0, 0.0, 1.0);
return o;
}
float height_at(int2 c) {
c = clamp(c, int2(0, 0), int2((int) mapinfo.x - 1, (int) mapinfo.y - 1));
return heightmap.Load(int3(c, 0)) * 65535.0 * mapinfo.w;
}
float world_height(float wx, float wy) {
float world_w = mapinfo.x * CELL;
float world_h = mapinfo.y * CELL;
if (wx < 0.0 || wy < 0.0 || wx >= world_w || wy >= world_h) return -1.0e9;
int2 c = int2((int) (wx / CELL), (int) ((world_h - wy) / CELL));
return height_at(c);
}
float3 sky_color(float3 dir) {
float3 d = normalize(dir);
float3 sun_dir = normalize(sun.xyz);
float t = clamp(d.z, 0.0, 1.0);
float3 horizon = float3(0.70, 0.78, 0.85);
float3 zenith = float3(0.28, 0.48, 0.80);
float3 col = lerp(horizon, zenith, pow(t, 0.6));
float s = max(dot(d, sun_dir), 0.0);
col += float3(1.0, 0.95, 0.82) * pow(s, 300.0) * 1.6; // sun disk
col += float3(1.0, 0.90, 0.72) * pow(s, 8.0) * 0.18; // glow
return col;
}
// The retail SAGE blend ramp: 0 at one edge of the cell, 1 at the opposite.
// Direction: 1 right, 2 top, 4 top-right, 8 top-left; flag bit 0 flips,
// bit 1 marks a two-sided diagonal.
float blend_factor(uint direction, uint flags, float2 f) {
bool flipped = (flags & 1u) != 0u;
bool two_sided = (flags & 2u) != 0u;
if (flipped) {
if (direction == 1u) {
f.x = 1.0 - f.x;
} else if (direction == 2u || direction == 4u || direction == 8u) {
f.y = 1.0 - f.y;
}
}
if (direction == 1u) return f.x;
if (direction == 2u) return f.y;
if (direction == 4u) {
float s = (1.0 - f.x) + (1.0 - f.y);
return two_sided ? 1.0 - clamp(s - 1.0, 0.0, 1.0) : clamp(1.0 - s, 0.0, 1.0);
}
if (direction == 8u) {
float s = f.x + (1.0 - f.y);
return two_sided ? 1.0 - clamp(s - 1.0, 0.0, 1.0) : clamp(1.0 - s, 0.0, 1.0);
}
return 0.0;
}
// Sample one tile material layer at global cell coordinates. The texture repeats
// every `span` cells with REPEAT addressing, so it never restarts at a cell edge.
float3 sample_layer(uint layer, float wx, float wy) {
float span = max(misc.z, 1.0);
uint lw = 0;
uint lh = 0;
uint layer_count = 0;
atlas.GetDimensions(lw, lh, layer_count);
float l = (float) min(layer, layer_count > 0u ? layer_count - 1u : 0u);
return atlas.Sample(atlas_smp, float3(float2(wx, wy) / span, l)).rgb;
}
float4 PSMain(VSOut input) : SV_Target {
float4 p = cam;
float pitch = clamp(params.x, 0.15, 1.45);
float fov = clamp(params.y, 0.3, 1.4);
float world_w = mapinfo.x * CELL;
float world_h = mapinfo.y * CELL;
float cp = cos(pitch);
float3 fwd = float3(cp * sin(p.z), cp * cos(p.z), -sin(pitch));
float3 right = normalize(cross(fwd, float3(0, 0, 1)));
float3 up = cross(right, fwd);
float target_z = world_height(p.x, p.y);
if (target_z < -1.0e8) target_z = 0.0;
float dist = p.w / sin(pitch);
float3 cam_pos = float3(p.x, p.y, target_z + p.w) - fwd * dist;
float2 ndc = float2(input.uv.x * 2.0 - 1.0, 1.0 - input.uv.y * 2.0);
float aspect = misc.w;
float th = tan(fov * 0.5);
float3 dir = normalize(fwd + right * ndc.x * th * aspect + up * ndc.y * th);
if (dir.z >= -1e-4) {
return float4(sky_color(dir), 1.0);
}
// March the heightfield (bounded work: the step grows toward the horizon).
float t = CELL * 0.5;
float dt = CELL * 0.5;
float prev = t;
bool hit = false;
float hit_t = 0.0;
for (int i = 0; i < 512 && t < 60000.0; ++i) {
float3 w = cam_pos + dir * t;
if (w.x < 0.0 || w.y < 0.0 || w.x >= world_w || w.y >= world_h) {
prev = t;
dt *= 1.06;
t += dt;
continue;
}
if (params.w > 0.5 && w.z <= params.z) {
hit = true;
hit_t = t;
break;
}
if (w.z <= world_height(w.x, w.y)) {
hit = true;
hit_t = t;
break;
}
prev = t;
dt *= 1.06;
t += dt;
}
if (!hit) {
return float4(sky_color(dir), 1.0);
}
float lo = prev;
float hi = hit_t;
for (int i = 0; i < 6; ++i) {
float mid = 0.5 * (lo + hi);
float3 w = cam_pos + dir * mid;
bool water = params.w > 0.5 && w.z <= params.z;
if (water || w.z <= world_height(w.x, w.y)) {
hi = mid;
} else {
lo = mid;
}
}
float3 hitpos = cam_pos + dir * hi;
float3 sun_dir = normalize(sun.xyz);
float ambient = sun.w;
if (params.w > 0.5 && hitpos.z <= params.z + 0.01) {
// Water: animated normal from a procedural wave, sky reflection + fresnel.
float time = misc.x;
float2 q = hitpos.xy * 0.015;
float nx = sin(q.x * 1.3 + time * 1.7) + 0.5 * sin(q.x * 3.1 - time * 2.3);
float ny = sin(q.y * 1.1 - time * 1.3) + 0.5 * sin(q.y * 2.7 + time * 1.9);
float3 n = normalize(float3(nx * 0.06, ny * 0.06, 1.0));
float fres = pow(1.0 - clamp(-dir.z, 0.0, 1.0), 3.0);
float3 deep = float3(0.03, 0.16, 0.28);
float3 refl = sky_color(reflect(dir, n));
float lam = max(0.0, dot(n, sun_dir));
float3 water = lerp(deep, refl, clamp(0.25 + 0.55 * fres, 0.0, 0.9));
water += float3(1.0, 0.98, 0.9) * pow(lam, 64.0) * 0.6; // sun glint
float wfog = clamp(1.0 - exp(-hi * 0.00009), 0.0, 0.75);
water = lerp(water, sky_color(float3(dir.x, dir.y, 0.0)), wfog);
return float4(water, 1.0);
}
// Terrain: read the per-cell blend record, sample the base/blend/three-way
// material layers continuously and ramp between them across the cell.
float wx = hitpos.x / CELL;
float wy = (world_h - hitpos.y) / CELL;
int cx = clamp((int) wx, 0, (int) mapinfo.x - 1);
int cy = clamp((int) wy, 0, (int) mapinfo.y - 1);
float fx = wx - floor(wx);
float fy = wy - floor(wy);
uint4 record = (uint4) (celldata.Load(int3(cx, cy, 0)) * 65535.0 + 0.5);
uint packed = record.w;
uint dir1 = packed & 0xFu;
uint flags1 = (packed >> 4u) & 0x3u;
uint dir2 = (packed >> 8u) & 0xFu;
uint flags2 = (packed >> 12u) & 0x3u;
float2 fracUV = float2(fx, fy);
float3 c0 = sample_layer(record.x, wx, wy);
float3 c1 = sample_layer(record.y, wx, wy);
float3 c2 = sample_layer(record.z, wx, wy);
float f1 = blend_factor(dir1, flags1, fracUV);
float f2 = blend_factor(dir2, flags2, fracUV);
float3 albedo = lerp(lerp(c0, c1, f1), c2, f2);
// Per-pixel normal from the heightfield.
float hl = world_height(hitpos.x - CELL, hitpos.y);
float hr = world_height(hitpos.x + CELL, hitpos.y);
float hd = world_height(hitpos.x, hitpos.y - CELL);
float hu = world_height(hitpos.x, hitpos.y + CELL);
float3 n = normalize(float3(hl - hr, hd - hu, 2.0 * CELL));
float lambert = max(0.0, dot(n, sun_dir));
float3 lit = albedo * (ambient + (1.0 - ambient) * lambert);
// Distance haze toward the horizon so the map edge blends into the sky.
float fog = clamp(1.0 - exp(-hi * 0.00009), 0.0, 0.75);
lit = lerp(lit, sky_color(float3(dir.x, dir.y, 0.0)), fog);
return float4(lit, 1.0);
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+11
View File
@@ -0,0 +1,11 @@
#version 450
layout(binding = 0) uniform sampler2D scene;
layout(location = 0) in vec2 in_uv;
layout(location = 0) out vec4 out_color;
void main() {
if (in_uv.x < 0.0 || in_uv.x > 1.0 || in_uv.y < 0.0 || in_uv.y > 1.0) discard;
out_color = texture(scene, in_uv);
}
+18
View File
@@ -0,0 +1,18 @@
#version 450
// Fullscreen triangle. The scene image is drawn as a single textured quad; the
// push constant carries the destination rectangle (in window-normalized
// coordinates, y down), so the map is letterboxed rather than stretched.
layout(push_constant) uniform Push {
vec4 rect; // xy = top-left (0..1), zw = size (0..1)
} pc;
layout(location = 0) out vec2 out_uv;
void main() {
// Vulkan clip space has +Y pointing DOWN, so p already runs top->bottom and
// matches the image's top-left origin; do NOT flip it (OpenGL would).
vec2 p = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
out_uv = (p - pc.rect.xy) / pc.rect.zw;
gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);
}
+203
View File
@@ -0,0 +1,203 @@
#version 450
// GPU heightfield raymarcher for the real RA3 terrain.
//
// Textures: heightmap (R16), a per-cell blend record (R16G16B16A16_UNORM: base
// layer, blend layer, three-way layer, packed direction/flags; unpacked with
// *65535) and a texture array of the tile materials (RGBA8, mipmapped, REPEAT).
// The material is sampled **continuously** (`uv = cell / (2 * cellSize)`), as the
// retail `Terrain.fx` / OpenSAGE `Terrain.frag` do, so it never restarts at a
// cell edge; material boundaries cross-fade with the SAGE blend ramp.
layout(binding = 0) uniform sampler2D heightmap;
layout(binding = 1) uniform sampler2D celldata;
layout(binding = 2) uniform sampler2DArray atlas;
layout(push_constant) uniform Push {
vec4 cam; // x=target_x, y=target_y, z=yaw, w=height
vec4 params; // x=pitch, y=fov, z=water_z, w=has_water
vec4 sun; // xyz=sun dir, w=ambient
vec4 mapinfo; // x=W, y=H, z=unused, w=z_scale
vec4 misc; // x=time, y=unused, z=cells per texture repeat, w=aspect
} pc;
layout(location = 0) in vec2 in_uv;
layout(location = 0) out vec4 out_color;
const float CELL = 10.0; // must match ra3::terrain::cell_size
float height_at(ivec2 c) {
c = clamp(c, ivec2(0), ivec2(pc.mapinfo.xy) - 1);
return texelFetch(heightmap, c, 0).r * 65535.0 * pc.mapinfo.w;
}
float world_height(float wx, float wy) {
float world_w = pc.mapinfo.x * CELL;
float world_h = pc.mapinfo.y * CELL;
if (wx < 0.0 || wy < 0.0 || wx >= world_w || wy >= world_h) return -1.0e9;
ivec2 c = ivec2(int(wx / CELL), int((world_h - wy) / CELL));
return height_at(c);
}
vec3 sky_color(vec3 dir) {
vec3 d = normalize(dir);
vec3 sun_dir = normalize(pc.sun.xyz);
float t = clamp(d.z, 0.0, 1.0);
vec3 horizon = vec3(0.70, 0.78, 0.85);
vec3 zenith = vec3(0.28, 0.48, 0.80);
vec3 col = mix(horizon, zenith, pow(t, 0.6));
float sun = max(dot(d, sun_dir), 0.0);
col += vec3(1.0, 0.95, 0.82) * pow(sun, 300.0) * 1.6; // sun disk
col += vec3(1.0, 0.90, 0.72) * pow(sun, 8.0) * 0.18; // glow
return col;
}
// The retail SAGE blend ramp: 0 at one edge of the cell, 1 at the opposite.
// Direction: 1 right, 2 top, 4 top-right, 8 top-left; flag bit 0 flips,
// bit 1 marks a two-sided diagonal.
float blend_factor(uint direction, uint flags, vec2 f) {
bool flipped = (flags & 1u) != 0u;
bool two_sided = (flags & 2u) != 0u;
if (flipped) {
if (direction == 1u) {
f.x = 1.0 - f.x;
} else if (direction == 2u || direction == 4u || direction == 8u) {
f.y = 1.0 - f.y;
}
}
if (direction == 1u) return f.x;
if (direction == 2u) return f.y;
if (direction == 4u) {
float s = (1.0 - f.x) + (1.0 - f.y);
return two_sided ? 1.0 - clamp(s - 1.0, 0.0, 1.0) : clamp(1.0 - s, 0.0, 1.0);
}
if (direction == 8u) {
float s = f.x + (1.0 - f.y);
return two_sided ? 1.0 - clamp(s - 1.0, 0.0, 1.0) : clamp(1.0 - s, 0.0, 1.0);
}
return 0.0;
}
// Sample one tile material layer at global cell coordinates. The texture repeats
// every `span` cells (SAGE `uv / (cellSize * 2)`) with REPEAT addressing, so it
// never restarts at a cell edge.
vec3 sample_layer(uint layer, float wx, float wy) {
float span = max(pc.misc.z, 1.0);
int layer_count = textureSize(atlas, 0).z;
float l = float(min(layer, uint(layer_count - 1)));
return texture(atlas, vec3(vec2(wx, wy) / span, l)).rgb;
}
void main() {
vec4 p = pc.cam;
float pitch = clamp(pc.params.x, 0.15, 1.45);
float fov = clamp(pc.params.y, 0.3, 1.4);
float world_w = pc.mapinfo.x * CELL;
float world_h = pc.mapinfo.y * CELL;
float cp = cos(pitch);
vec3 fwd = vec3(cp * sin(p.z), cp * cos(p.z), -sin(pitch));
vec3 right = normalize(cross(fwd, vec3(0, 0, 1)));
vec3 up = cross(right, fwd);
float target_z = world_height(p.x, p.y);
if (target_z < -1.0e8) target_z = 0.0;
float dist = p.w / sin(pitch);
vec3 cam = vec3(p.x, p.y, target_z + p.w) - fwd * dist;
float aspect = 1.0; // set by caller implicitly via square-ish UV; corrected below
vec2 ndc = vec2(in_uv.x * 2.0 - 1.0, 1.0 - in_uv.y * 2.0);
// aspect passed in misc.w
aspect = pc.misc.w;
float th = tan(fov * 0.5);
vec3 dir = normalize(fwd + right * ndc.x * th * aspect + up * ndc.y * th);
if (dir.z >= -1e-4) {
out_color = vec4(sky_color(dir), 1.0);
return;
}
// March the heightfield (bounded work: the step grows toward the horizon).
float t = CELL * 0.5;
float dt = CELL * 0.5;
float prev = t;
bool hit = false;
float hit_t = 0.0;
for (int i = 0; i < 512 && t < 60000.0; ++i) {
vec3 w = cam + dir * t;
if (w.x < 0.0 || w.y < 0.0 || w.x >= world_w || w.y >= world_h) {
prev = t; dt *= 1.06; t += dt; continue;
}
if (pc.params.w > 0.5 && w.z <= pc.params.z) { hit = true; hit_t = t; break; }
if (w.z <= world_height(w.x, w.y)) { hit = true; hit_t = t; break; }
prev = t; dt *= 1.06; t += dt;
}
if (!hit) { out_color = vec4(sky_color(dir), 1.0); return; }
float lo = prev, hi = hit_t;
for (int i = 0; i < 6; ++i) {
float mid = 0.5 * (lo + hi);
vec3 w = cam + dir * mid;
bool water = pc.params.w > 0.5 && w.z <= pc.params.z;
if (water || w.z <= world_height(w.x, w.y)) hi = mid; else lo = mid;
}
vec3 hitpos = cam + dir * hi;
vec3 sun = normalize(pc.sun.xyz);
float ambient = pc.sun.w;
if (pc.params.w > 0.5 && hitpos.z <= pc.params.z + 0.01) {
// Water: animated normal from a procedural wave, sky reflection + fresnel.
float time = pc.misc.x;
vec2 q = hitpos.xy * 0.015;
float nx = sin(q.x * 1.3 + time * 1.7) + 0.5 * sin(q.x * 3.1 - time * 2.3);
float ny = sin(q.y * 1.1 - time * 1.3) + 0.5 * sin(q.y * 2.7 + time * 1.9);
vec3 n = normalize(vec3(nx * 0.06, ny * 0.06, 1.0));
float fres = pow(1.0 - clamp(-dir.z, 0.0, 1.0), 3.0);
vec3 deep = vec3(0.03, 0.16, 0.28);
vec3 refl = sky_color(reflect(dir, n));
float lam = max(0.0, dot(n, sun));
vec3 water = mix(deep, refl, clamp(0.25 + 0.55 * fres, 0.0, 0.9));
water += vec3(1.0, 0.98, 0.9) * pow(lam, 64.0) * 0.6; // sun glint
float wfog = clamp(1.0 - exp(-hi * 0.00009), 0.0, 0.75);
water = mix(water, sky_color(vec3(dir.x, dir.y, 0.0)), wfog);
out_color = vec4(water, 1.0);
return;
}
// Terrain: read the per-cell blend record, sample the base/blend/three-way
// material layers continuously and ramp between them across the cell.
float wx = hitpos.x / CELL;
float wy = (world_h - hitpos.y) / CELL;
int cx = clamp(int(wx), 0, int(pc.mapinfo.x) - 1);
int cy = clamp(int(wy), 0, int(pc.mapinfo.y) - 1);
float fx = wx - floor(wx);
float fy = wy - floor(wy);
uvec4 record = uvec4(texelFetch(celldata, ivec2(cx, cy), 0) * 65535.0 + 0.5);
uint packed = record.w;
uint dir1 = packed & 0xFu;
uint flags1 = (packed >> 4u) & 0x3u;
uint dir2 = (packed >> 8u) & 0xFu;
uint flags2 = (packed >> 12u) & 0x3u;
vec2 fracUV = vec2(fx, fy);
vec3 c0 = sample_layer(record.x, wx, wy);
vec3 c1 = sample_layer(record.y, wx, wy);
vec3 c2 = sample_layer(record.z, wx, wy);
float f1 = blend_factor(dir1, flags1, fracUV);
float f2 = blend_factor(dir2, flags2, fracUV);
vec3 albedo = mix(mix(c0, c1, f1), c2, f2);
// Per-pixel normal from the heightfield.
float hl = world_height(hitpos.x - CELL, hitpos.y);
float hr = world_height(hitpos.x + CELL, hitpos.y);
float hd = world_height(hitpos.x, hitpos.y - CELL);
float hu = world_height(hitpos.x, hitpos.y + CELL);
vec3 n = normalize(vec3(hl - hr, hd - hu, 2.0 * CELL));
float lambert = max(0.0, dot(n, sun));
vec3 lit = albedo * (ambient + (1.0 - ambient) * lambert);
// Distance haze toward the horizon so the map edge blends into the sky.
float fog = clamp(1.0 - exp(-hi * 0.00009), 0.0, 0.75);
lit = mix(lit, sky_color(vec3(dir.x, dir.y, 0.0)), fog);
out_color = vec4(lit, 1.0);
}
+11
View File
@@ -0,0 +1,11 @@
#version 450
// Fullscreen triangle; the terrain is ray-marched in the fragment shader
// (GPU), so the vertex stage only emits the screen UV.
layout(location = 0) out vec2 out_uv;
void main() {
vec2 p = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
out_uv = p;
gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);
}
+15
View File
@@ -0,0 +1,15 @@
#version 300 es
// Samples the software-rendered scene image (the WebGL port of scene.frag).
precision highp float;
precision highp sampler2D;
uniform sampler2D u_scene;
in vec2 v_uv;
out vec4 frag_color;
void main() {
if (v_uv.x < 0.0 || v_uv.x > 1.0 || v_uv.y < 0.0 || v_uv.y > 1.0) discard;
// The engine stores 0xAARRGGBB (BGRA in memory), uploaded as GL_RGBA.
frag_color = texture(u_scene, v_uv).bgra;
}
+15
View File
@@ -0,0 +1,15 @@
#version 300 es
// Fullscreen triangle for the 2D image path (the WebGL port of scene.vert).
// WebGL clip space is +Y up, so the vertex position flips Y relative to the
// Vulkan shader; the sampled UVs and the image's top-left origin are unchanged.
precision highp float;
uniform vec4 u_rect; // xy = top-left (0..1), zw = size (0..1)
out vec2 v_uv;
void main() {
vec2 p = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
v_uv = (p - u_rect.xy) / u_rect.zw;
gl_Position = vec4(p.x * 2.0 - 1.0, 1.0 - p.y * 2.0, 0.0, 1.0);
}
+211
View File
@@ -0,0 +1,211 @@
#version 300 es
// GPU heightfield raymarcher for the real RA3 terrain (the WebGL port of
// terrain.frag). The Vulkan push constants become a set of vec4 uniforms.
precision highp float;
precision highp int;
precision highp sampler2D;
precision highp sampler2DArray;
uniform sampler2D u_heightmap; // R16 heights
uniform sampler2D u_celldata; // per-cell blend record (RGBA16)
uniform sampler2DArray u_atlas; // tile material array (RGBA8)
// One contiguous array so the host can upload all five vec4s with a single
// glUniform4fv; the names keep the shader body identical to terrain.frag.
uniform vec4 u_data[5];
#define u_cam u_data[0] // x=target_x, y=target_y, z=yaw, w=height
#define u_params u_data[1] // x=pitch, y=fov, z=water_z, w=has_water
#define u_sun u_data[2] // xyz=sun dir, w=ambient
#define u_mapinfo u_data[3] // x=W, y=H, z=unused, w=z_scale
#define u_misc u_data[4] // x=time, y=unused, z=cells per texture repeat, w=aspect
in vec2 v_uv;
out vec4 frag_color;
const float CELL = 10.0; // must match ra3::terrain::cell_size
float height_at(ivec2 c) {
c = clamp(c, ivec2(0), ivec2(u_mapinfo.xy) - 1);
return texelFetch(u_heightmap, c, 0).r * 65535.0 * u_mapinfo.w;
}
float world_height(float wx, float wy) {
float world_w = u_mapinfo.x * CELL;
float world_h = u_mapinfo.y * CELL;
if (wx < 0.0 || wy < 0.0 || wx >= world_w || wy >= world_h) return -1.0e9;
ivec2 c = ivec2(int(wx / CELL), int((world_h - wy) / CELL));
return height_at(c);
}
vec3 sky_color(vec3 dir) {
vec3 d = normalize(dir);
vec3 sun_dir = normalize(u_sun.xyz);
float t = clamp(d.z, 0.0, 1.0);
vec3 horizon = vec3(0.70, 0.78, 0.85);
vec3 zenith = vec3(0.28, 0.48, 0.80);
vec3 col = mix(horizon, zenith, pow(t, 0.6));
float sun = max(dot(d, sun_dir), 0.0);
col += vec3(1.0, 0.95, 0.82) * pow(sun, 300.0) * 1.6; // sun disk
col += vec3(1.0, 0.90, 0.72) * pow(sun, 8.0) * 0.18; // glow
return col;
}
// The retail SAGE blend ramp (see terrain.frag).
float blend_factor(uint direction, uint flags, vec2 f) {
bool flipped = (flags & 1u) != 0u;
bool two_sided = (flags & 2u) != 0u;
if (flipped) {
if (direction == 1u) {
f.x = 1.0 - f.x;
} else if (direction == 2u || direction == 4u || direction == 8u) {
f.y = 1.0 - f.y;
}
}
if (direction == 1u) return f.x;
if (direction == 2u) return f.y;
if (direction == 4u) {
float s = (1.0 - f.x) + (1.0 - f.y);
return two_sided ? 1.0 - clamp(s - 1.0, 0.0, 1.0) : clamp(1.0 - s, 0.0, 1.0);
}
if (direction == 8u) {
float s = f.x + (1.0 - f.y);
return two_sided ? 1.0 - clamp(s - 1.0, 0.0, 1.0) : clamp(1.0 - s, 0.0, 1.0);
}
return 0.0;
}
vec3 sample_layer(uint layer, float wx, float wy) {
float span = max(u_misc.z, 1.0);
int layer_count = textureSize(u_atlas, 0).z;
float l = float(min(layer, uint(layer_count - 1)));
// The atlas is 0xAARRGGBB (BGRA in memory), uploaded as GL_RGBA.
return texture(u_atlas, vec3(vec2(wx, wy) / span, l)).bgr;
}
void main() {
vec4 p = u_cam;
float pitch = clamp(u_params.x, 0.15, 1.45);
float fov = clamp(u_params.y, 0.3, 1.4);
float world_w = u_mapinfo.x * CELL;
float world_h = u_mapinfo.y * CELL;
float cp = cos(pitch);
vec3 fwd = vec3(cp * sin(p.z), cp * cos(p.z), -sin(pitch));
vec3 right = normalize(cross(fwd, vec3(0, 0, 1)));
vec3 up = cross(right, fwd);
float target_z = world_height(p.x, p.y);
if (target_z < -1.0e8) target_z = 0.0;
float dist = p.w / sin(pitch);
vec3 cam = vec3(p.x, p.y, target_z + p.w) - fwd * dist;
vec2 ndc = vec2(v_uv.x * 2.0 - 1.0, 1.0 - v_uv.y * 2.0);
float aspect = u_misc.w;
float th = tan(fov * 0.5);
vec3 dir = normalize(fwd + right * ndc.x * th * aspect + up * ndc.y * th);
if (dir.z >= -1e-4) {
frag_color = vec4(sky_color(dir), 1.0);
return;
}
float t = CELL * 0.5;
float dt = CELL * 0.5;
float prev = t;
bool hit = false;
float hit_t = 0.0;
for (int i = 0; i < 512 && t < 60000.0; ++i) {
vec3 w = cam + dir * t;
if (w.x < 0.0 || w.y < 0.0 || w.x >= world_w || w.y >= world_h) {
prev = t;
dt *= 1.06;
t += dt;
continue;
}
if (u_params.w > 0.5 && w.z <= u_params.z) {
hit = true;
hit_t = t;
break;
}
if (w.z <= world_height(w.x, w.y)) {
hit = true;
hit_t = t;
break;
}
prev = t;
dt *= 1.06;
t += dt;
}
if (!hit) {
frag_color = vec4(sky_color(dir), 1.0);
return;
}
float lo = prev;
float hi = hit_t;
for (int i = 0; i < 6; ++i) {
float mid = 0.5 * (lo + hi);
vec3 w = cam + dir * mid;
bool water = u_params.w > 0.5 && w.z <= u_params.z;
if (water || w.z <= world_height(w.x, w.y)) {
hi = mid;
} else {
lo = mid;
}
}
vec3 hitpos = cam + dir * hi;
vec3 sun = normalize(u_sun.xyz);
float ambient = u_sun.w;
if (u_params.w > 0.5 && hitpos.z <= u_params.z + 0.01) {
float time = u_misc.x;
vec2 q = hitpos.xy * 0.015;
float nx = sin(q.x * 1.3 + time * 1.7) + 0.5 * sin(q.x * 3.1 - time * 2.3);
float ny = sin(q.y * 1.1 - time * 1.3) + 0.5 * sin(q.y * 2.7 + time * 1.9);
vec3 n = normalize(vec3(nx * 0.06, ny * 0.06, 1.0));
float fres = pow(1.0 - clamp(-dir.z, 0.0, 1.0), 3.0);
vec3 deep = vec3(0.03, 0.16, 0.28);
vec3 refl = sky_color(reflect(dir, n));
float lam = max(0.0, dot(n, sun));
vec3 water = mix(deep, refl, clamp(0.25 + 0.55 * fres, 0.0, 0.9));
water += vec3(1.0, 0.98, 0.9) * pow(lam, 64.0) * 0.6;
float wfog = clamp(1.0 - exp(-hi * 0.00009), 0.0, 0.75);
water = mix(water, sky_color(vec3(dir.x, dir.y, 0.0)), wfog);
frag_color = vec4(water, 1.0);
return;
}
float wx = hitpos.x / CELL;
float wy = (world_h - hitpos.y) / CELL;
int cx = clamp(int(wx), 0, int(u_mapinfo.x) - 1);
int cy = clamp(int(wy), 0, int(u_mapinfo.y) - 1);
float fx = wx - floor(wx);
float fy = wy - floor(wy);
uvec4 record = uvec4(texelFetch(u_celldata, ivec2(cx, cy), 0) * 65535.0 + 0.5);
uint packed = record.w;
uint dir1 = packed & 0xFu;
uint flags1 = (packed >> 4u) & 0x3u;
uint dir2 = (packed >> 8u) & 0xFu;
uint flags2 = (packed >> 12u) & 0x3u;
vec2 fracUV = vec2(fx, fy);
vec3 c0 = sample_layer(record.x, wx, wy);
vec3 c1 = sample_layer(record.y, wx, wy);
vec3 c2 = sample_layer(record.z, wx, wy);
float f1 = blend_factor(dir1, flags1, fracUV);
float f2 = blend_factor(dir2, flags2, fracUV);
vec3 albedo = mix(mix(c0, c1, f1), c2, f2);
float hl = world_height(hitpos.x - CELL, hitpos.y);
float hr = world_height(hitpos.x + CELL, hitpos.y);
float hd = world_height(hitpos.x, hitpos.y - CELL);
float hu = world_height(hitpos.x, hitpos.y + CELL);
vec3 n = normalize(vec3(hl - hr, hd - hu, 2.0 * CELL));
float lambert = max(0.0, dot(n, sun));
vec3 lit = albedo * (ambient + (1.0 - ambient) * lambert);
float fog = clamp(1.0 - exp(-hi * 0.00009), 0.0, 0.75);
lit = mix(lit, sky_color(vec3(dir.x, dir.y, 0.0)), fog);
frag_color = vec4(lit, 1.0);
}
+11
View File
@@ -0,0 +1,11 @@
#version 300 es
// Fullscreen triangle for the terrain raymarcher (the WebGL port of terrain.vert).
precision highp float;
out vec2 v_uv;
void main() {
vec2 p = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
v_uv = p;
gl_Position = vec4(p.x * 2.0 - 1.0, 1.0 - p.y * 2.0, 0.0, 1.0);
}
+507 -8
View File
@@ -1,34 +1,527 @@
module;
#include <cstdint>
#include <memory>
#include <string_view>
#if defined(__EMSCRIPTEN__)
// Yields to the browser (requires -sASYNCIFY); see display::sleep_frame.
extern "C" void emscripten_sleep(unsigned int ms);
#endif
export module ra3.client;
import std;
import ra3.core;
import ra3.logic;
import ra3.render;
import ra3.terrain;
/**
* The presentation layer: an abstract output surface plus the client facade
* that owns the simulation and drives the frame loop.
*
* A future W3D/D3D9 backend implements the same `display` interface; the
* default headless backend keeps OpenRA3 runnable on a CPU-only box.
* Backends implement only the low-level primitives (`init`, `present`,
* `poll_event`, `window_size`, `key_down`, `shutdown`) and, optionally,
* `present_terrain`; the interactive loops (menu, image viewer, camera viewer,
* GPU terrain) and the loading screen live here once, so input mapping and the
* frame loop never diverge between the SDL and Vulkan backends. The loops
* assume the display is already initialized, which lets one window host a menu,
* a loading bar and a viewer in sequence.
*/
export namespace ra3::client {
using ra3::core::uint32;
using ra3::render::image;
using ra3::render::ui_event;
using ra3::render::ui_event_type;
using ra3::render::ui_key;
using ra3::render::view_rect;
/** Window parameters for a display. */
struct display_options {
std::string title = "OpenRA3";
int width = 1280;
int height = 720;
bool fullscreen = false;
int fps_limit = 0; ///< 0 = vertical sync (present throttled to the display).
};
/** Small overlays drawn on top of the GPU terrain (top-left FPS, corner minimap). */
struct terrain_overlay {
image label; ///< top-left label (may be empty)
image minimap; ///< bottom-right overview (may be empty)
bool label_changed = true;
bool minimap_changed = true;
};
/**
* Abstract output surface presented once per logic frame.
* Abstract presentation surface.
*/
class display {
public:
virtual ~display() = default;
virtual auto begin_frame() -> void = 0;
virtual auto end_frame() -> void = 0;
virtual auto begin_frame() -> void {}
virtual auto end_frame() -> void {}
/** Create the window and rendering objects. */
[[nodiscard]] virtual auto init(const display_options &options) -> bool = 0;
/** Draw `frame` scaled into `dest` (window coordinates). `changed` is
* false when `frame` is the same image as the previous call, so the
* backend may skip re-uploading its texture. */
[[nodiscard]] virtual auto present(const image &frame, const view_rect &dest, bool changed) -> bool = 0;
/** Pop one OS event into `out`; returns false when the queue is empty. */
[[nodiscard]] virtual auto poll_event(ui_event &out) -> bool = 0;
/** Current drawable size in pixels. */
[[nodiscard]] virtual auto window_size() const -> std::pair<int, int> = 0;
/** Whether a key is currently held (for panning). */
[[nodiscard]] virtual auto key_down(ui_key key) const -> bool = 0;
virtual auto shutdown() -> void = 0;
[[nodiscard]] virtual auto name() const -> std::string_view = 0;
/**
* Optional GPU terrain path. The default reports `false`, so a low-end
* backend simply does not offer it; the Vulkan backend overrides it.
* `overlays` (FPS label, minimap) are drawn on top of the terrain.
*/
[[nodiscard]] virtual auto present_terrain(const ra3::terrain::gpu_terrain &, const ra3::render::camera3d &, float, const terrain_overlay &) -> bool {
return false;
}
/** Whether this backend can draw the GPU terrain path. */
[[nodiscard]] virtual auto supports_terrain() const -> bool { return false; }
int fps_limit_ = 0; ///< 0 = vsync, >0 = target FPS cap, <0 = uncapped.
/** Throttle an idle frame according to the configured limit. */
auto sleep_frame() const -> void {
#if defined(__EMSCRIPTEN__)
// The frame loops are blocking; on the web the only way to let the
// browser paint and handle input is to yield (Asyncify sleep).
const int fps = fps_limit_ > 0 ? fps_limit_ : 60;
emscripten_sleep(static_cast<unsigned>(std::max(1, 1000 / fps)));
#else
if (fps_limit_ > 0) {
std::this_thread::sleep_for(std::chrono::milliseconds(std::max(1, 1000 / fps_limit_)));
} else if (fps_limit_ < 0) {
std::this_thread::yield();
}
// fps_limit_ == 0: vertical sync already blocks in present().
#endif
}
// ---- shared interactive loops (assume `init` succeeded) --------------
/** Letterbox `frame` into the current window. */
[[nodiscard]] auto present_fit(const image &frame, bool changed) -> bool {
const auto [w, h] = this->window_size();
return this->present(frame, ra3::render::fit_rect(static_cast<float>(frame.width()), static_cast<float>(frame.height()), static_cast<float>(w),
static_cast<float>(h)),
changed);
}
/** Draw a warm loading screen with a progress bar (0..1). */
auto present_progress(float progress, std::string_view label) -> void {
const auto [w, h] = this->window_size();
const auto frame = ra3::render::compose_progress(static_cast<uint32>(std::max(1, w)), static_cast<uint32>(std::max(1, h)), progress, label);
this->present_fit(frame, true);
}
/**
* Run a staged loader on this display: `load` is called with a `report`
* callback that draws a progress bar and pumps events (returning false
* if the user closed the window).
*/
template<typename Loader>
[[nodiscard]] auto run_with_progress(Loader &&load) -> bool {
this->present_progress(0.0F, "Loading...");
const auto report = [&](float progress, std::string_view label) -> bool {
this->present_progress(progress, label);
ui_event event;
while (this->poll_event(event)) {
if (event.type == ui_event_type::quit) return false;
}
return true;
};
return load(report);
}
/**
* Interactive menu. `frame` returns the image to display for an input
* event, or `nullopt` when nothing changed (so the previous frame is
* kept and nothing is recomposed/re-uploaded). It sets `done` to end
* the loop. `frame` is called with the current drawable size so text
* stays crisp on HiDPI displays.
*/
[[nodiscard]] auto menu_loop(const std::function<std::optional<image>(const ui_event &, uint32, uint32, bool &)> &frame) -> bool {
auto [sw, sh] = this->window_size();
const auto w = static_cast<uint32>(std::max(1, sw));
const auto h = static_cast<uint32>(std::max(1, sh));
bool done = false;
auto first = frame({}, w, h, done);
if (!first) return false;
image current = std::move(*first);
if (!this->present_fit(current, true)) return false;
while (!done) {
ui_event event;
bool idle = true;
bool changed = false;
while (this->poll_event(event)) {
idle = false;
if (event.type == ui_event_type::quit) {
done = true;
break;
}
auto result = frame(event, w, h, done);
if (done) break;
if (result) {
current = std::move(*result);
changed = true;
}
}
if (done) break;
if (changed) {
if (!this->present_fit(current, true)) break;
} else if (idle) {
if (!this->present_fit(current, false)) break;
this->sleep_frame();
}
}
return true;
}
/**
* Pan/zoom viewer over a raster image, reproducing the retail tactical
* view controls (wheel zoom, edge scroll, drag pan).
*/
[[nodiscard]] auto image_loop(const image &scene, ra3::render::view_camera camera) -> bool {
if (scene.empty()) return false;
camera.min_zoom = 1.0F;
camera.clamp_center();
float mouse_x = 0.0F;
float mouse_y = 0.0F;
bool running = true;
auto last = std::chrono::steady_clock::now();
while (running) {
ui_event event;
float drag_x = 0.0F;
float drag_y = 0.0F;
while (this->poll_event(event)) {
if (event.type == ui_event_type::quit) {
running = false;
} else if (event.type == ui_event_type::key) {
if (event.key == ui_key::cancel) {
running = false;
} else if (event.key == ui_key::page_up) {
camera.zoom_by(camera.zoom_step);
} else if (event.key == ui_key::page_down) {
camera.zoom_by(1.0F / camera.zoom_step);
}
} else if (event.type == ui_event_type::mouse_move) {
mouse_x = event.x;
mouse_y = event.y;
if (event.left) {
drag_x += event.dx;
drag_y += event.dy;
}
} else if (event.type == ui_event_type::wheel) {
camera.zoom_by(event.wheel > 0.0F ? camera.zoom_step : (1.0F / camera.zoom_step));
}
}
if (!running) break;
const auto now = std::chrono::steady_clock::now();
const auto dt = std::min(0.1F, std::chrono::duration<float>(now - last).count());
last = now;
const auto [w, h] = this->window_size();
auto view = camera.rect(static_cast<float>(scene.width()), static_cast<float>(scene.height()), static_cast<float>(w), static_cast<float>(h));
if (view.w > 0.0F && view.h > 0.0F && (drag_x != 0.0F || drag_y != 0.0F)) {
camera.scroll(-drag_x / view.w, -drag_y / view.h);
}
camera.edge_scroll(mouse_x, mouse_y, static_cast<float>(w), static_cast<float>(h), dt);
view = camera.rect(static_cast<float>(scene.width()), static_cast<float>(scene.height()), static_cast<float>(w), static_cast<float>(h));
if (!this->present(scene, view, false)) break;
this->sleep_frame();
}
return true;
}
/**
* Interactive 3D camera over a software-rendered scene: the provider
* re-renders whenever the camera moves.
*/
[[nodiscard]] auto camera_loop(const std::function<image(const ra3::render::camera3d &, uint32, uint32)> &provider, ra3::render::camera3d camera) -> bool {
auto [window_w, window_h] = this->window_size();
image current = provider(camera, static_cast<uint32>(std::max(1, window_w)), static_cast<uint32>(std::max(1, window_h)));
if (current.empty()) return false;
if (!this->present(current, {0.0F, 0.0F, static_cast<float>(window_w), static_cast<float>(window_h)}, true)) return false;
float mouse_x = 0.5F * static_cast<float>(window_w);
float mouse_y = 0.5F * static_cast<float>(window_h);
bool running = true;
auto last = std::chrono::steady_clock::now();
while (running) {
ui_event event;
float drag_x = 0.0F;
float drag_y = 0.0F;
bool dirty = false;
while (this->poll_event(event)) {
if (event.type == ui_event_type::quit) {
running = false;
} else if (event.type == ui_event_type::key) {
switch (event.key) {
case ui_key::cancel: running = false; break;
case ui_key::page_up: camera.height = std::clamp(camera.height / 1.15F, camera.min_height, camera.max_height); dirty = true; break;
case ui_key::page_down: camera.height = std::clamp(camera.height * 1.15F, camera.min_height, camera.max_height); dirty = true; break;
default: break;
}
} else if (event.type == ui_event_type::mouse_move) {
mouse_x = event.x;
mouse_y = event.y;
if (event.left) {
drag_x += event.dx;
drag_y += event.dy;
}
} else if (event.type == ui_event_type::wheel) {
camera.height = std::clamp(camera.height * (event.wheel > 0.0F ? (1.0F / 1.15F) : 1.15F), camera.min_height, camera.max_height);
dirty = true;
}
}
if (!running) break;
const auto now = std::chrono::steady_clock::now();
const auto dt = std::min(0.1F, std::chrono::duration<float>(now - last).count());
last = now;
if (drag_x != 0.0F || drag_y != 0.0F) {
camera.yaw -= drag_x * 0.005F;
camera.pitch = std::clamp(camera.pitch + drag_y * 0.004F, 0.15F, 1.45F);
dirty = true;
}
float move_x = 0.0F;
float move_y = 0.0F;
if (this->key_down(ui_key::up)) move_y += 1.0F;
if (this->key_down(ui_key::down)) move_y -= 1.0F;
if (this->key_down(ui_key::left)) move_x -= 1.0F;
if (this->key_down(ui_key::right)) move_x += 1.0F;
if (mouse_x <= 24.0F) {
move_x -= 1.0F;
} else if (mouse_x >= static_cast<float>(window_w) - 24.0F) {
move_x += 1.0F;
}
if (mouse_y <= 24.0F) {
move_y += 1.0F;
} else if (mouse_y >= static_cast<float>(window_h) - 24.0F) {
move_y -= 1.0F;
}
if (move_x != 0.0F || move_y != 0.0F) {
const auto fwd_x = std::sin(camera.yaw);
const auto fwd_y = std::cos(camera.yaw);
const auto right_x = std::cos(camera.yaw);
const auto right_y = -std::sin(camera.yaw);
const auto step = camera.height * 0.9F * dt;
camera.target_x += (fwd_x * move_y + right_x * move_x) * step;
camera.target_y += (fwd_y * move_y + right_y * move_x) * step;
dirty = true;
}
const auto latest = this->window_size();
if (latest.first != window_w || latest.second != window_h) {
window_w = latest.first;
window_h = latest.second;
dirty = true;
}
if (dirty) {
current = provider(camera, static_cast<uint32>(std::max(1, window_w)), static_cast<uint32>(std::max(1, window_h)));
if (current.empty()) break;
dirty = false;
}
if (!this->present(current, {0.0F, 0.0F, static_cast<float>(window_w), static_cast<float>(window_h)}, true)) break;
this->sleep_frame();
}
return true;
}
/**
* GPU terrain viewer. `present_terrain` draws the heightfield; the loop
* here owns the camera controls. The top-left shows the FPS (current /
* cap) and, when `minimap_overview` is not empty, a corner minimap with
* the camera location is drawn.
*/
[[nodiscard]] auto terrain_loop(const ra3::terrain::gpu_terrain &terrain, ra3::render::camera3d camera, const image &minimap_overview) -> bool {
if (terrain.width == 0U || terrain.height == 0U || !this->supports_terrain()) return false;
const auto world_w = static_cast<float>(terrain.width) * 10.0F;
const auto world_h = static_cast<float>(terrain.height) * 10.0F;
camera.target_x = std::clamp(camera.target_x, 0.0F, world_w);
camera.target_y = std::clamp(camera.target_y, 0.0F, world_h);
auto [window_w, window_h] = this->window_size();
const auto start = std::chrono::steady_clock::now();
auto last = start;
float mouse_x = 0.5F * static_cast<float>(window_w);
float mouse_y = 0.5F * static_cast<float>(window_h);
terrain_overlay overlay;
uint32 fps = 0;
int fps_frames = 0;
auto fps_window = start;
auto minimap_time = start;
bool running = true;
bool presented = false;
while (running) {
ui_event event;
float drag_x = 0.0F;
float drag_y = 0.0F;
bool camera_moved = false;
while (this->poll_event(event)) {
if (event.type == ui_event_type::quit) {
running = false;
} else if (event.type == ui_event_type::key) {
switch (event.key) {
case ui_key::cancel: running = false; break;
case ui_key::page_up: camera.height = std::clamp(camera.height / 1.15F, camera.min_height, camera.max_height); camera_moved = true; break;
case ui_key::page_down: camera.height = std::clamp(camera.height * 1.15F, camera.min_height, camera.max_height); camera_moved = true; break;
default: break;
}
} else if (event.type == ui_event_type::mouse_move) {
mouse_x = event.x;
mouse_y = event.y;
if (event.left) {
drag_x += event.dx;
drag_y += event.dy;
}
} else if (event.type == ui_event_type::wheel) {
camera.height = std::clamp(camera.height * (event.wheel > 0.0F ? (1.0F / 1.15F) : 1.15F), camera.min_height, camera.max_height);
camera_moved = true;
}
}
if (!running) break;
const auto now = std::chrono::steady_clock::now();
const auto dt = std::min(0.1F, std::chrono::duration<float>(now - last).count());
last = now;
if (drag_x != 0.0F || drag_y != 0.0F) {
camera.yaw -= drag_x * 0.005F;
camera.pitch = std::clamp(camera.pitch + drag_y * 0.004F, 0.15F, 1.45F);
camera_moved = true;
}
float move_x = 0.0F;
float move_y = 0.0F;
if (this->key_down(ui_key::up)) move_y += 1.0F;
if (this->key_down(ui_key::down)) move_y -= 1.0F;
if (this->key_down(ui_key::left)) move_x -= 1.0F;
if (this->key_down(ui_key::right)) move_x += 1.0F;
if (mouse_x <= 24.0F) {
move_x -= 1.0F;
} else if (mouse_x >= static_cast<float>(window_w) - 24.0F) {
move_x += 1.0F;
}
if (mouse_y <= 24.0F) {
move_y += 1.0F;
} else if (mouse_y >= static_cast<float>(window_h) - 24.0F) {
move_y -= 1.0F;
}
if (move_x != 0.0F || move_y != 0.0F) {
const auto fwd_x = std::sin(camera.yaw);
const auto fwd_y = std::cos(camera.yaw);
const auto right_x = std::cos(camera.yaw);
const auto right_y = -std::sin(camera.yaw);
const auto step = camera.height * 0.9F * dt;
camera.target_x += (fwd_x * move_y + right_x * move_x) * step;
camera.target_y += (fwd_y * move_y + right_y * move_x) * step;
camera_moved = true;
}
camera.target_x = std::clamp(camera.target_x, 0.0F, world_w);
camera.target_y = std::clamp(camera.target_y, 0.0F, world_h);
const auto latest = this->window_size();
if (latest.first != window_w || latest.second != window_h) {
window_w = latest.first;
window_h = latest.second;
camera_moved = true;
}
if (camera_moved) {
++fps_frames;
const auto window_s = std::chrono::duration<float>(now - fps_window).count();
if (window_s >= 0.4F) {
fps = static_cast<uint32>(std::lround(static_cast<float>(fps_frames) / window_s));
fps_frames = 0;
fps_window = now;
overlay.label = ra3::render::compose_fps_label(fps, fps_limit_);
overlay.label_changed = true;
}
} else {
fps_frames = 0;
fps_window = now;
}
const auto since_minimap = std::chrono::duration<float>(now - minimap_time).count();
if (!minimap_overview.empty() && (overlay.minimap.empty() || (camera_moved && since_minimap >= 0.15F))) {
const auto u = camera.target_x / world_w;
const auto v = 1.0F - camera.target_y / world_h;
overlay.minimap = ra3::render::compose_minimap(minimap_overview, u, v);
overlay.minimap_changed = true;
minimap_time = now;
}
// Render only when the view changes: an idle terrain costs nothing.
if (camera_moved || !presented) {
const auto time_s = std::chrono::duration<float>(now - start).count();
if (!this->present_terrain(terrain, camera, time_s, overlay)) {
return presented;
}
overlay.label_changed = false;
overlay.minimap_changed = false;
presented = true;
}
this->sleep_frame();
}
return true;
}
// ---- one-shot helpers (own the lifecycle) ----------------------------
[[nodiscard]] auto run_menu(const display_options &options, const std::function<std::optional<image>(const ui_event &, uint32, uint32, bool &)> &frame) -> bool {
if (!this->init(options)) return false;
(void)this->menu_loop(frame);
this->shutdown();
return true;
}
[[nodiscard]] auto run_image(const display_options &options, const image &scene, ra3::render::view_camera camera) -> bool {
if (scene.empty() || !this->init(options)) return false;
(void)this->image_loop(scene, camera);
this->shutdown();
return true;
}
[[nodiscard]] auto run_camera(const display_options &options, const std::function<image(const ra3::render::camera3d &, uint32, uint32)> &provider,
ra3::render::camera3d camera) -> bool {
if (!this->init(options)) return false;
(void)this->camera_loop(provider, camera);
this->shutdown();
return true;
}
[[nodiscard]] auto run_terrain(const display_options &options, const ra3::terrain::gpu_terrain &terrain, ra3::render::camera3d camera) -> bool {
if (!this->init(options)) return false;
(void)this->terrain_loop(terrain, camera, {});
this->shutdown();
return true;
}
/** GPU terrain viewer with a corner minimap overlay. */
[[nodiscard]] auto run_terrain(const display_options &options, const ra3::terrain::gpu_terrain &terrain, ra3::render::camera3d camera,
const image &minimap) -> bool {
if (!this->init(options)) return false;
(void)this->terrain_loop(terrain, camera, minimap);
this->shutdown();
return true;
}
};
/**
@@ -38,6 +531,12 @@ export namespace ra3::client {
public:
auto begin_frame() -> void override {}
auto end_frame() -> void override {}
[[nodiscard]] auto init(const display_options &) -> bool override { return false; }
[[nodiscard]] auto present(const image &, const view_rect &, bool) -> bool override { return false; }
[[nodiscard]] auto poll_event(ui_event &) -> bool override { return false; }
[[nodiscard]] auto window_size() const -> std::pair<int, int> override { return {0, 0}; }
[[nodiscard]] auto key_down(ui_key) const -> bool override { return false; }
auto shutdown() -> void override {}
[[nodiscard]] auto name() const -> std::string_view override { return "headless"; }
};
+3 -16
View File
@@ -1,20 +1,7 @@
module;
#include <cmath>
#include <compare>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <optional>
#include <ostream>
#include <span>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
export module ra3.core;
import std;
/**
* Fundamental engine types shared by every OpenRA3 module.
*
@@ -24,7 +11,7 @@ export module ra3.core;
*/
export namespace ra3::core {
inline constexpr int version_major = 0;
inline constexpr int version_minor = 1;
inline constexpr int version_minor = 3;
inline constexpr int version_patch = 0;
using real = float;
+615
View File
@@ -0,0 +1,615 @@
export module ra3.data;
import std;
export import ra3.core;
/**
* Faithful Red Alert 3 gameplay data.
*
* Every number in this module is taken from the retail game's own asset
* definitions. Two sources back it, both auditable:
*
* 1. EA's open-sourced RA3 asset XML (`CnC_Modding_Support/Red Alert 3/Xml`,
* GPLv3) - the `*.xml` that `BinaryAssetBuilder` compiles into `*.big`.
* The `ra3_id` on each record is the originating `GameObject` id, so a
* value can be looked up in `Allied/Structures/AlliedPowerPlant.xml`,
* `Allied/Infantry/AlliedScoutInfantry.xml`, etc.
* 2. The shipped `ArmorTemplate` percentages, cross-checked against the
* compiled assets by the sibling `ra3-headless` extractor.
*
* The *semantics* of the fields (armour adjusts damage per damage type, a
* weapon's `AntiMask` decides what it may target, `UNRESISTABLE` bypasses
* armour) follow SAGE and are corroborated by the OpenSAGE re-implementation
* (GPLv3). Where a value is not yet recovered the field is documented as such.
*
* Nothing here is loaded from disk yet: the compiled `global.bin` /
* `static.*.bin` deserialiser is a later milestone, so the base-game balance
* used by `ra3.skirmish` is pinned as constants.
*/
export namespace ra3::data {
using ra3::core::int32;
using ra3::core::uint8;
using ra3::core::uint32;
using ra3::core::usize;
/**
* World units per terrain cell. RA3's `TheTerrainLogic` grid stores
* `1 / cellSize` at `grid + 0x38` and the layer reports `cellSize == 10`.
*/
inline constexpr float cell_size = 10.0F;
inline constexpr float inv_cell_size = 1.0F / cell_size;
/**
* The simulation advances on a fixed 30 Hz logic tick, matching SAGE.
*
* Note: the sibling `ra3-sim` paces its own simulator at 15 ticks/s from
* `DAT_00cdbc1c = 1000 / 15`; `ra3-headless` (which drives the real binary)
* reports a fixed 30 Hz logic rate. OpenRA3 keeps the 30 Hz SAGE cadence
* used elsewhere in this codebase until the discrepancy is settled.
*/
inline constexpr int logic_frames_per_second = ra3::core::logic_frames_per_second;
/** Convert a duration in seconds to a whole number of logic frames. */
[[nodiscard]] constexpr auto seconds_to_frames(float seconds) -> uint32 {
return static_cast<uint32>(seconds * static_cast<float>(logic_frames_per_second) + 0.5F);
}
// ---------------------------------------------------------------------
// Damage types
// ---------------------------------------------------------------------
/**
* RA3's damage types, recovered from the keys of the shipped
* `ArmorTemplate` tables and the `DamageType` of every `WeaponTemplate`.
*
* This is *not* the Generals/ZH set (`EXPLOSION`/`SMALL_ARMS`/...): RA3
* replaced it with these. `unresistable` is special - it skips the armour
* multiplier entirely.
*/
enum class damage_type : uint8 {
gun,
melee,
concussive,
auto_cannon,
rocket,
flak,
cannon,
prism,
tesla,
explosive,
impact,
sniper,
grenade,
radiation,
magic,
crush,
healing,
unresistable,
count,
};
inline constexpr usize damage_type_count = static_cast<usize>(damage_type::count);
[[nodiscard]] constexpr auto to_string(damage_type value) -> std::string_view {
switch (value) {
case damage_type::gun: return "GUN";
case damage_type::melee: return "MELEE";
case damage_type::concussive: return "CONCUSSIVE";
case damage_type::auto_cannon: return "AUTO_CANNON";
case damage_type::rocket: return "ROCKET";
case damage_type::flak: return "FLAK";
case damage_type::cannon: return "CANNON";
case damage_type::prism: return "PRISM";
case damage_type::tesla: return "TESLA";
case damage_type::explosive: return "EXPLOSIVE";
case damage_type::impact: return "IMPACT";
case damage_type::sniper: return "SNIPER";
case damage_type::grenade: return "GRENADE";
case damage_type::radiation: return "RADIATION";
case damage_type::magic: return "MAGIC";
case damage_type::crush: return "CRUSH";
case damage_type::healing: return "HEALING";
case damage_type::unresistable: return "UNRESISTABLE";
case damage_type::count: break;
}
return "UNKNOWN";
}
/** Parse an asset damage-type name (e.g. `"AUTO_CANNON"`). */
[[nodiscard]] inline auto damage_type_from_name(std::string_view name) -> damage_type {
for (usize i = 0; i < damage_type_count; ++i) {
const auto value = static_cast<damage_type>(i);
if (to_string(value) == name) return value;
}
return damage_type::gun;
}
// ---------------------------------------------------------------------
// Armour
// ---------------------------------------------------------------------
/**
* A `ArmorTemplate`: a per-damage-type damage multiplier.
*
* Values are fractions (1.0 == 100%). Unlisted types default to 100%,
* except when the template declares a `DEFAULT` (`default_fraction`), as
* `InvulnerableArmor` does with 0. Matches OpenSAGE's `ArmorTemplate`.
*/
struct armor_set {
std::string_view id;
std::array<float, damage_type_count> multipliers{};
float default_fraction = 1.0F;
/** Apply this armour. `unresistable` is never scaled. */
[[nodiscard]] constexpr auto adjust(damage_type type, float damage) const -> float {
if (type == damage_type::unresistable) return damage;
const auto scaled = damage * multipliers[static_cast<usize>(type)];
return scaled < 0.0F ? 0.0F : scaled;
}
};
/**
* Build an armour set. `vs` entries are the raw percentages from the
* shipped `ArmorTemplate` (`100.0` means normal damage).
*/
constexpr auto make_armor(std::string_view id, float default_percent, std::initializer_list<std::pair<damage_type, float>> vs) -> armor_set {
armor_set armor{};
armor.id = id;
armor.default_fraction = default_percent / 100.0F;
armor.multipliers.fill(armor.default_fraction);
for (const auto &[type, percent]: vs) armor.multipliers[static_cast<usize>(type)] = percent / 100.0F;
return armor;
}
/**
* The armour templates used by the modelled units, copied verbatim from the
* shipped `ArmorTemplate` tables (`ra3-headless/out/armors.json`).
*/
inline constexpr armor_set allied_scout_infantry_armor = make_armor(
"AlliedScoutInfantryArmor", 100.0F,
{{damage_type::gun, 1.0F}, {damage_type::melee, 50.0F}, {damage_type::concussive, 50.0F}, {damage_type::auto_cannon, 50.0F},
{damage_type::rocket, 50.0F}, {damage_type::flak, 50.0F}, {damage_type::cannon, 50.0F}, {damage_type::prism, 50.0F},
{damage_type::tesla, 50.0F}, {damage_type::explosive, 50.0F}, {damage_type::impact, 50.0F}});
inline constexpr armor_set allied_anti_infantry_infantry_armor = make_armor(
"AlliedAntiInfantryInfantryArmor", 100.0F,
{{damage_type::sniper, 100.0F}, {damage_type::cannon, 20.0F}, {damage_type::rocket, 20.0F}, {damage_type::grenade, 100.0F},
{damage_type::gun, 100.0F}, {damage_type::melee, 100.0F}, {damage_type::auto_cannon, 150.0F}, {damage_type::impact, 100.0F},
{damage_type::explosive, 100.0F}, {damage_type::flak, 10.0F}, {damage_type::prism, 100.0F}, {damage_type::tesla, 1000.0F},
{damage_type::radiation, 100.0F}});
inline constexpr armor_set soviet_anti_infantry_infantry_armor = make_armor(
"SovietAntiInfantryInfantryArmor", 100.0F,
{{damage_type::sniper, 100.0F}, {damage_type::cannon, 20.0F}, {damage_type::rocket, 20.0F}, {damage_type::grenade, 100.0F},
{damage_type::gun, 100.0F}, {damage_type::melee, 100.0F}, {damage_type::auto_cannon, 150.0F}, {damage_type::impact, 100.0F},
{damage_type::explosive, 100.0F}, {damage_type::flak, 10.0F}, {damage_type::prism, 100.0F}, {damage_type::tesla, 1000.0F},
{damage_type::radiation, 100.0F}});
inline constexpr armor_set soviet_scout_infantry_armor = make_armor(
"SovietScoutInfantryArmor", 100.0F,
{{damage_type::gun, 1.0F}, {damage_type::melee, 50.0F}, {damage_type::concussive, 50.0F}, {damage_type::auto_cannon, 50.0F},
{damage_type::rocket, 50.0F}, {damage_type::flak, 50.0F}, {damage_type::cannon, 50.0F}, {damage_type::prism, 50.0F},
{damage_type::tesla, 50.0F}, {damage_type::explosive, 50.0F}, {damage_type::impact, 50.0F}});
inline constexpr armor_set allied_miner_armor =
make_armor("AlliedMinerArmor", 100.0F,
{{damage_type::melee, 0.0F}, {damage_type::sniper, 0.0F}, {damage_type::gun, 50.0F}, {damage_type::auto_cannon, 100.0F},
{damage_type::impact, 100.0F}, {damage_type::flak, 100.0F}, {damage_type::rocket, 100.0F}, {damage_type::cannon, 150.0F},
{damage_type::prism, 150.0F}, {damage_type::tesla, 150.0F}, {damage_type::explosive, 100.0F},
{damage_type::concussive, 100.0F}, {damage_type::radiation, 5.0F}});
inline constexpr armor_set allied_anti_vehicle_vehicle_tech1_armor = make_armor(
"AlliedAntiVehicleVehicleTech1Armor", 100.0F,
{{damage_type::melee, 0.0F}, {damage_type::sniper, 0.0F}, {damage_type::gun, 25.0F}, {damage_type::auto_cannon, 100.0F},
{damage_type::impact, 75.0F}, {damage_type::rocket, 75.0F}, {damage_type::flak, 100.0F}, {damage_type::cannon, 170.0F},
{damage_type::prism, 170.0F}, {damage_type::tesla, 170.0F}, {damage_type::explosive, 100.0F}, {damage_type::radiation, 5.0F}});
inline constexpr armor_set soviet_anti_vehicle_vehicle_tech1_armor = make_armor(
"SovietAntiVehicleVehicleTech1Armor", 100.0F,
{{damage_type::melee, 0.0F}, {damage_type::sniper, 0.0F}, {damage_type::gun, 50.0F}, {damage_type::auto_cannon, 25.0F},
{damage_type::impact, 75.0F}, {damage_type::flak, 100.0F}, {damage_type::rocket, 100.0F}, {damage_type::cannon, 100.0F},
{damage_type::prism, 100.0F}, {damage_type::tesla, 100.0F}, {damage_type::explosive, 100.0F}, {damage_type::concussive, 100.0F},
{damage_type::radiation, 5.0F}});
/** The faction structure armour shared by con yard, power plant, barracks, refinery and war factory. */
inline constexpr armor_set allied_structure_armor = make_armor(
"AlliedConYardArmor", 100.0F,
{{damage_type::sniper, 0.0F}, {damage_type::cannon, 100.0F}, {damage_type::rocket, 100.0F}, {damage_type::grenade, 100.0F},
{damage_type::gun, 100.0F}, {damage_type::melee, 0.0F}, {damage_type::auto_cannon, 50.0F}, {damage_type::impact, 100.0F},
{damage_type::explosive, 125.0F}, {damage_type::flak, 100.0F}, {damage_type::prism, 100.0F}, {damage_type::tesla, 100.0F},
{damage_type::radiation, 0.0F}});
inline constexpr armor_set soviet_structure_armor = make_armor(
"SovietConYardArmor", 100.0F,
{{damage_type::sniper, 0.0F}, {damage_type::cannon, 100.0F}, {damage_type::rocket, 100.0F}, {damage_type::grenade, 100.0F},
{damage_type::gun, 100.0F}, {damage_type::melee, 0.0F}, {damage_type::auto_cannon, 50.0F}, {damage_type::impact, 100.0F},
{damage_type::explosive, 125.0F}, {damage_type::flak, 100.0F}, {damage_type::prism, 100.0F}, {damage_type::tesla, 100.0F},
{damage_type::radiation, 0.0F}});
/** Neutral armour: everything does normal damage. */
inline constexpr armor_set neutral_armor = make_armor("NoArmor", 100.0F, {});
// ---------------------------------------------------------------------
// Target classification and weapon anti-mask
// ---------------------------------------------------------------------
/** Coarse target classes, derived from a `GameObject`'s `KindOf` flags. */
namespace target_class {
inline constexpr uint32 none = 0U;
inline constexpr uint32 infantry = 1U << 0U;
inline constexpr uint32 vehicle = 1U << 1U;
inline constexpr uint32 structure = 1U << 2U;
inline constexpr uint32 aircraft = 1U << 3U;
inline constexpr uint32 ground = infantry | vehicle | structure;
inline constexpr uint32 any = 0xFFFFFFFFU;
}
/**
* A `WeaponTemplate`'s `AntiMask`. RA3's tokens are recovered from the
* shipped weapons (`ra3-headless/out/weapons.json`).
*/
namespace anti {
inline constexpr uint32 ground = 1U << 0U;
inline constexpr uint32 water = 1U << 1U;
inline constexpr uint32 structure = 1U << 2U;
inline constexpr uint32 infantry = 1U << 3U;
inline constexpr uint32 vehicle = 1U << 4U;
inline constexpr uint32 airborne_vehicle = 1U << 5U;
inline constexpr uint32 airborne_infantry = 1U << 6U;
inline constexpr uint32 submerged = 1U << 7U;
inline constexpr uint32 lifted_ground_unit = 1U << 8U;
inline constexpr uint32 mine = 1U << 9U;
inline constexpr uint32 projectile = 1U << 10U;
inline constexpr uint32 small_missile = 1U << 11U;
inline constexpr uint32 ballistic_missile = 1U << 12U;
inline constexpr uint32 parachute = 1U << 13U;
}
// ---------------------------------------------------------------------
// Weapons
// ---------------------------------------------------------------------
/** Primary weapon parameters. Timings are in seconds. */
struct weapon_data {
std::string_view ra3_id;
float attack_range = 0.0F;
float damage = 0.0F;
damage_type type = damage_type::gun;
float splash_radius = 0.0F;
float weapon_speed = 0.0F;
uint32 clip_size = 1;
float reload_seconds = 1.0F;
float firing_seconds = 0.5F;
uint32 anti_mask = 0U;
/** Classes this weapon may never target (the nugget's `SpecialObjectFilter`). */
uint32 excluded_classes = target_class::none;
/** One hit kills any valid target (the maul's `InstakillNugget`). */
bool instakill = false;
bool projectile = false;
/**
* Can this weapon target an object of `target` class?
*
* `ANTI_GROUND` covers all ground classes; the airborne flags cover
* aircraft. A weapon with no anti-air flag cannot hit aircraft, which is
* why an attack dog can never maul a plane.
*/
[[nodiscard]] constexpr auto can_target(uint32 target) const -> bool {
if ((target & excluded_classes) != 0U) return false;
if ((target & target_class::aircraft) != 0U) {
return (anti_mask & (anti::airborne_vehicle | anti::airborne_infantry)) != 0U;
}
if ((anti_mask & anti::ground) != 0U && (target & target_class::ground) != 0U) return true;
if ((anti_mask & anti::infantry) != 0U && (target & target_class::infantry) != 0U) return true;
if ((anti_mask & anti::vehicle) != 0U && (target & target_class::vehicle) != 0U) return true;
if ((anti_mask & anti::structure) != 0U && (target & target_class::structure) != 0U) return true;
return false;
}
};
/**
* The primary weapons of the modelled units, from the shipped
* `WeaponTemplate`s.
*/
inline constexpr weapon_data maul_weapon{
"AlliedScoutInfantryMaul", 30.0F, 1.0F, damage_type::unresistable, 0.0F, 125.0F, 1U, 1.5F, 0.5F, anti::ground | anti::water,
target_class::vehicle | target_class::structure | target_class::aircraft, true, false};
inline constexpr weapon_data shotgun_weapon{"AlliedAntiInfantryInfantryShotgun",
150.0F,
40.0F,
damage_type::gun,
155.0F,
750.0F,
1U,
1.0F,
0.5F,
anti::ground | anti::structure | anti::water,
target_class::none,
false,
true};
inline constexpr weapon_data ak47_weapon{"SovietAntiInfantryInfantryAK47",
150.0F,
5.0F,
damage_type::gun,
0.0F,
750.0F,
1U,
0.5F,
0.25F,
anti::water | anti::ground | anti::structure,
target_class::none,
false,
true};
inline constexpr weapon_data guardian_cannon_weapon{"AlliedAntiVehicleVehicleTech1Cannon",
150.0F,
60.0F,
damage_type::cannon,
0.0F,
999999.0F,
1U,
1.8F,
0.2F,
anti::water | anti::ground | anti::structure,
target_class::none,
false,
true};
inline constexpr weapon_data rhino_cannon_weapon{"SovietAntiVehicleVehicleTech1CannonWeapon",
150.0F,
35.0F,
damage_type::cannon,
0.0F,
999999.0F,
1U,
1.0F,
0.2F,
anti::water | anti::ground | anti::structure,
target_class::none,
false,
true};
// ---------------------------------------------------------------------
// Entities
// ---------------------------------------------------------------------
/** Everything the skirmish simulation can place. */
enum class entity_kind : uint8 {
none = 0,
attack_dog, ///< AlliedScoutInfantry
peacekeeper, ///< AlliedAntiInfantryInfantry
conscript, ///< SovietAntiInfantryInfantry
guardian_tank, ///< AlliedAntiVehicleVehicleTech1
rhino_tank, ///< SovietAntiVehicleVehicleTech1
miner, ///< AlliedMiner
construction_yard,
power_plant,
barracks,
war_factory,
refinery,
count,
};
inline constexpr usize entity_kind_count = static_cast<usize>(entity_kind::count);
[[nodiscard]] constexpr auto to_string(entity_kind value) -> std::string_view {
switch (value) {
case entity_kind::attack_dog: return "attack_dog";
case entity_kind::peacekeeper: return "peacekeeper";
case entity_kind::conscript: return "conscript";
case entity_kind::guardian_tank: return "guardian_tank";
case entity_kind::rhino_tank: return "rhino_tank";
case entity_kind::miner: return "miner";
case entity_kind::construction_yard: return "construction_yard";
case entity_kind::power_plant: return "power_plant";
case entity_kind::barracks: return "barracks";
case entity_kind::war_factory: return "war_factory";
case entity_kind::refinery: return "refinery";
case entity_kind::none: break;
case entity_kind::count: break;
}
return "none";
}
[[nodiscard]] constexpr auto is_unit(entity_kind kind) -> bool {
switch (kind) {
case entity_kind::attack_dog:
case entity_kind::peacekeeper:
case entity_kind::conscript:
case entity_kind::guardian_tank:
case entity_kind::rhino_tank:
case entity_kind::miner: return true;
default: return false;
}
}
[[nodiscard]] constexpr auto is_structure(entity_kind kind) -> bool {
switch (kind) {
case entity_kind::construction_yard:
case entity_kind::power_plant:
case entity_kind::barracks:
case entity_kind::war_factory:
case entity_kind::refinery: return true;
default: return false;
}
}
/** Target class of an entity, from its `KindOf` (`INFANTRY`/`VEHICLE`/...). */
[[nodiscard]] constexpr auto class_of(entity_kind kind) -> uint32 {
switch (kind) {
case entity_kind::attack_dog:
case entity_kind::peacekeeper:
case entity_kind::conscript: return target_class::infantry;
case entity_kind::guardian_tank:
case entity_kind::rhino_tank:
case entity_kind::miner: return target_class::vehicle;
case entity_kind::construction_yard:
case entity_kind::power_plant:
case entity_kind::barracks:
case entity_kind::war_factory:
case entity_kind::refinery: return target_class::structure;
default: return target_class::none;
}
}
/** Mobile unit parameters. */
struct unit_data {
entity_kind kind = entity_kind::none;
std::string_view ra3_id;
int32 cost = 0;
float build_seconds = 0.0F;
float max_health = 0.0F;
const armor_set *armor = &neutral_armor;
float speed = 0.0F; ///< LocomotorSet Speed, world units per second.
float vision = 0.0F; ///< VisionInfo VisionRange.
float shroud_clear = 0.0F; ///< VisionInfo ShroudClearingRange.
float radius = 0.0F; ///< Geometry MajorRadius.
const weapon_data *weapon = nullptr;
entity_kind built_by = entity_kind::none; ///< Producer structure.
};
/** Structure parameters. */
struct structure_data {
entity_kind kind = entity_kind::none;
std::string_view ra3_id;
int32 cost = 0;
float build_seconds = 0.0F;
float max_health = 0.0F;
const armor_set *armor = &neutral_armor;
int32 energy = 0; ///< `EnergyProduction`: positive supplies, negative draws.
float vision = 0.0F;
float shroud_clear = 0.0F;
float radius = 0.0F;
int size_cells = 1; ///< Footprint, cells per side.
entity_kind prerequisite = entity_kind::none; ///< Prerequisite structure.
bool is_producer = false; ///< Can build units.
bool is_refinery = false; ///< Runs an ore extraction cycle.
};
/**
* Mobile units. Health, cost, build time, speed and armour are the retail
* `GameObject`/`LocomotorSet`/`ArmorTemplate` values.
*/
inline constexpr std::array<unit_data, entity_kind_count> units{{
{}, // none
{entity_kind::attack_dog, "AlliedScoutInfantry", 200, 2.0F, 30.0F, &allied_scout_infantry_armor, 100.0F, 200.0F, 750.0F, 7.0F, &maul_weapon,
entity_kind::barracks},
{entity_kind::peacekeeper, "AlliedAntiInfantryInfantry", 200, 5.0F, 150.0F, &allied_anti_infantry_infantry_armor, 50.0F, 200.0F, 500.0F, 7.0F,
&shotgun_weapon, entity_kind::barracks},
{entity_kind::conscript, "SovietAntiInfantryInfantry", 100, 4.0F, 100.0F, &soviet_anti_infantry_infantry_armor, 50.0F, 200.0F, 500.0F, 5.0F,
&ak47_weapon, entity_kind::barracks},
{entity_kind::guardian_tank, "AlliedAntiVehicleVehicleTech1", 950, 10.0F, 480.0F, &allied_anti_vehicle_vehicle_tech1_armor, 80.0F, 200.0F, 500.0F,
20.0F, &guardian_cannon_weapon, entity_kind::war_factory},
{entity_kind::rhino_tank, "SovietAntiVehicleVehicleTech1", 1000, 10.0F, 550.0F, &soviet_anti_vehicle_vehicle_tech1_armor, 75.0F, 200.0F, 500.0F,
20.0F, &rhino_cannon_weapon, entity_kind::war_factory},
{entity_kind::miner, "AlliedMiner", 1000, 20.0F, 500.0F, &allied_miner_armor, 50.0F, 200.0F, 500.0F, 20.0F, nullptr, entity_kind::refinery},
{}, {}, {}, {}, {},
}};
/**
* Structures. `energy` is the `EnergyProduction` attribute; `build_seconds`
* is `BuildTime`. Soviet `BuildTime` is not set on the base structures (the
* extractor reads 0), so the Allied values are used for both sides and the
* discrepancy is documented rather than invented.
*/
inline constexpr std::array<structure_data, entity_kind_count> structures{{
{}, {}, {}, {}, {}, {}, {}, // none + the six units
{entity_kind::construction_yard, "AlliedConstructionYard", 5000, 25.0F, 4000.0F, &allied_structure_armor, 50, 150.0F, 1000.0F, 60.0F, 3,
entity_kind::none, false, false},
{entity_kind::power_plant, "AlliedPowerPlant", 800, 10.0F, 1000.0F, &allied_structure_armor, 100, 150.0F, 300.0F, 30.0F, 2,
entity_kind::none, false, false},
{entity_kind::barracks, "AlliedBarracks", 500, 10.0F, 1000.0F, &allied_structure_armor, -25, 150.0F, 300.0F, 45.0F, 2, entity_kind::none,
true, false},
{entity_kind::war_factory, "AlliedWarFactory", 2000, 20.0F, 2500.0F, &allied_structure_armor, -50, 150.0F, 500.0F, 60.0F, 3,
entity_kind::none, true, false},
{entity_kind::refinery, "AlliedRefinery", 2000, 20.0F, 2000.0F, &allied_structure_armor, -50, 150.0F, 500.0F, 60.0F, 3,
entity_kind::power_plant, false, true},
}};
/** Look up a mobile unit. Only valid when `is_unit(kind)`. */
[[nodiscard]] inline auto unit_of(entity_kind kind) -> const unit_data & { return units.at(static_cast<usize>(kind)); }
/** Look up a structure. Only valid when `is_structure(kind)`. */
[[nodiscard]] inline auto structure_of(entity_kind kind) -> const structure_data & { return structures.at(static_cast<usize>(kind)); }
/** Cost in credits. */
[[nodiscard]] inline auto cost_of(entity_kind kind) -> int32 {
if (is_unit(kind)) return unit_of(kind).cost;
if (is_structure(kind)) return structure_of(kind).cost;
return 0;
}
/** Build time in logic frames. */
[[nodiscard]] inline auto build_frames_of(entity_kind kind) -> uint32 {
if (is_unit(kind)) return seconds_to_frames(unit_of(kind).build_seconds);
if (is_structure(kind)) return seconds_to_frames(structure_of(kind).build_seconds);
return 0U;
}
/** Maximum health. */
[[nodiscard]] inline auto max_health_of(entity_kind kind) -> float {
if (is_unit(kind)) return unit_of(kind).max_health;
if (is_structure(kind)) return structure_of(kind).max_health;
return 0.0F;
}
/** Armour template. */
[[nodiscard]] inline auto armor_of(entity_kind kind) -> const armor_set & {
if (is_unit(kind)) return *unit_of(kind).armor;
if (is_structure(kind)) return *structure_of(kind).armor;
return neutral_armor;
}
/** Vision range in world units. */
[[nodiscard]] inline auto vision_of(entity_kind kind) -> float {
if (is_unit(kind)) return unit_of(kind).vision;
if (is_structure(kind)) return structure_of(kind).vision;
return 0.0F;
}
/** The weapon a unit fires, or `nullptr` (structures, miners). */
[[nodiscard]] inline auto weapon_of(entity_kind kind) -> const weapon_data * {
return is_unit(kind) ? unit_of(kind).weapon : nullptr;
}
// ---------------------------------------------------------------------
// Economy and world rules
// ---------------------------------------------------------------------
/**
* Ore economy, from `OreNodeBehaviour` and `MpGameRules`.
*
* A refinery runs one extraction cycle: `MOVE_TO_EXTRACT + EXTRACT +
* MOVE_TO_DELIVER + DELIVERY` = 11 s for 250 credits while its node has
* ore. The engine actually spawns a separate miner; OpenRA3 folds the cycle
* into the refinery (a documented simplification, as in `ra3-sim`).
*/
struct economy_data {
int32 starting_credits = 10000; ///< MpGameRules SkirmishStartCash LoCash.
int32 credits_step = 5000; ///< SkirmishStartCash ChoiceStepAmount.
int32 credits_max = 40000; ///< SkirmishStartCash HiCash.
int32 ore_per_delivery = 250; ///< OreNodeBehaviour DeliveryAmount.
int32 ore_per_delivery_empty = 60; ///< OreNodeBehaviour DeliveryAmountWhenEmpty.
int32 ore_node_capacity = 30000; ///< OreNodeBehaviour MaximumGatheredValue.
float extraction_seconds = 11.0F; ///< 3.5 + 2 + 3.5 + 2.
};
/** Miscellaneous world rules. */
struct world_data {
float standard_shroud_clear = 500.0F; ///< Define STANDARD_SHROUD_CLEAR.
float scout_shroud_clear = 750.0F; ///< Define SCOUT_SHROUD_CLEAR.
int low_power_production_divisor = 2; ///< Low power halves production speed.
int low_power_mining_divisor = 2; ///< Low power halves refinery income.
};
inline constexpr economy_data economy{};
inline constexpr world_data world_rules{};
/** Ore extracted per completed refinery cycle, given the remaining node ore. */
[[nodiscard]] constexpr auto ore_per_cycle(int32 node_remaining) -> int32 {
return node_remaining > 0 ? economy.ore_per_delivery : economy.ore_per_delivery_empty;
}
}
+170
View File
@@ -0,0 +1,170 @@
export module ra3.display;
import std;
export import ra3.core;
import ra3.render;
import ra3.terrain;
import ra3.client;
import ra3.ui;
import ra3.vulkan;
import ra3.dx;
import ra3.webgl;
/**
* Backend selection for the presentation layer.
*
* The app talks only to `ra3::client::display`; this module picks the concrete
* backend so no caller has to know which one is in use. The available backends
* are Vulkan, Direct3D 11 and Direct3D 12, WebGL (all GPU terrain, available
* per platform) and the SDL software blit fallback. The caller may name a
* preferred backend (the in-game menu exposes this); if it does not start, the
* others are tried in turn. The GPU terrain path reports failure so the caller
* can fall back to the software renderer.
*/
export namespace ra3::display {
using ra3::client::display_options;
using ra3::render::camera3d;
using ra3::render::image;
using ra3::render::ui_event;
using ra3::render::view_camera;
using menu_frame = std::function<std::optional<image>(const ui_event &, ra3::core::uint32, ra3::core::uint32, bool &)>;
using camera_frame = std::function<image(const camera3d &, ra3::core::uint32, ra3::core::uint32)>;
/** A concrete presentation backend, or `none`. */
enum class backend { none, vulkan, d3d11, d3d12, webgl, sdl };
/** The backend preferred on this host: WebGL under Emscripten, else Vulkan. */
[[nodiscard]] inline constexpr auto default_backend() -> backend {
#if defined(__EMSCRIPTEN__)
return backend::webgl;
#else
return backend::vulkan;
#endif
}
[[nodiscard]] inline auto backend_name(backend which) -> std::string_view {
switch (which) {
case backend::vulkan: return "vulkan";
case backend::d3d11: return "d3d11";
case backend::d3d12: return "d3d12";
case backend::webgl: return "webgl";
case backend::sdl: return "sdl";
default: return "none";
}
}
[[nodiscard]] inline auto make_backend(backend which) -> std::unique_ptr<ra3::client::display> {
switch (which) {
case backend::vulkan: return std::make_unique<ra3::vulkan::vulkan_display>();
case backend::d3d11: return std::make_unique<ra3::dx::d3d11_display>();
case backend::d3d12: return std::make_unique<ra3::dx::d3d12_display>();
case backend::webgl: return std::make_unique<ra3::webgl::webgl_display>();
case backend::sdl: return std::make_unique<ra3::ui::sdl_display>();
default: return nullptr;
}
}
/** The order in which backends are attempted for a preferred one. */
[[nodiscard]] inline auto backend_order(backend preferred) -> std::array<backend, 5> {
switch (preferred) {
case backend::d3d11: return {backend::d3d11, backend::d3d12, backend::vulkan, backend::webgl, backend::sdl};
case backend::d3d12: return {backend::d3d12, backend::d3d11, backend::vulkan, backend::webgl, backend::sdl};
case backend::webgl: return {backend::webgl, backend::vulkan, backend::d3d11, backend::d3d12, backend::sdl};
case backend::sdl: return {backend::sdl, backend::d3d11, backend::d3d12, backend::vulkan, backend::webgl};
case backend::vulkan:
default: return {backend::vulkan, backend::d3d11, backend::d3d12, backend::webgl, backend::sdl};
}
}
/**
* Create and initialize the preferred backend, falling back to the others,
* or return null when none starts. The caller owns the display and may reuse
* it for a menu, a loading bar and a viewer in sequence.
*/
[[nodiscard]] inline auto create(const display_options &options, backend preferred) -> std::unique_ptr<ra3::client::display> {
for (const auto which: backend_order(preferred)) {
auto candidate = make_backend(which);
if (candidate && candidate->init(options)) return candidate;
}
return nullptr;
}
/** Create and initialize the preferred display (Vulkan, then SDL). */
[[nodiscard]] inline auto create(const display_options &options, bool prefer_vulkan) -> std::unique_ptr<ra3::client::display> {
return create(options, prefer_vulkan ? backend::vulkan : backend::sdl);
}
/**
* Run `action` on the first backend that initializes.
*
* `action` must call one of the shared `display` loops; it returns false to
* mean "this backend did not start", so the next one is tried.
*/
template<typename Fn>
[[nodiscard]] auto with_display(backend preferred, Fn &&action) -> bool {
for (const auto which: backend_order(preferred)) {
auto candidate = make_backend(which);
if (candidate && action(*candidate)) return true;
}
return false;
}
/** Run `action` on the first display that initializes: Vulkan, then SDL. */
template<typename Fn>
[[nodiscard]] auto with_display(bool prefer_vulkan, Fn &&action) -> bool {
return with_display(prefer_vulkan ? backend::vulkan : backend::sdl, std::forward<Fn>(action));
}
/** Interactive menu on the preferred (or first available) backend. */
[[nodiscard]] inline auto run_menu(const display_options &options, const menu_frame &frame, backend preferred = default_backend()) -> bool {
return with_display(preferred, [&](ra3::client::display &d) { return d.run_menu(options, frame); });
}
/** Pan/zoom image viewer on the preferred (or first available) backend. */
[[nodiscard]] inline auto run_image(const display_options &options, const image &scene, view_camera camera, backend preferred) -> bool {
return with_display(preferred, [&](ra3::client::display &d) { return d.run_image(options, scene, camera); });
}
/** Pan/zoom image viewer on the preferred (or first available) backend. */
[[nodiscard]] inline auto run_image(const display_options &options, const image &scene, view_camera camera, bool prefer_vulkan) -> bool {
return run_image(options, scene, camera, prefer_vulkan ? backend::vulkan : backend::sdl);
}
/** Software 3D camera viewer on the preferred (or first available) backend. */
[[nodiscard]] inline auto run_camera(const display_options &options, const camera_frame &provider, camera3d camera, bool prefer_vulkan) -> bool {
return with_display(prefer_vulkan ? backend::vulkan : backend::sdl,
[&](ra3::client::display &d) { return d.run_camera(options, provider, camera); });
}
/**
* GPU terrain viewer on the preferred (or first available) backend. Returns
* false when no GPU backend that offers terrain starts, so the caller can
* render offscreen instead.
*/
[[nodiscard]] inline auto run_terrain(const display_options &options, const ra3::terrain::gpu_terrain &terrain, camera3d camera,
backend preferred) -> bool {
return with_display(preferred, [&](ra3::client::display &d) {
if (!d.supports_terrain()) return false;
return d.run_terrain(options, terrain, camera);
});
}
/** GPU terrain viewer with a corner minimap overlay. */
[[nodiscard]] inline auto run_terrain(const display_options &options, const ra3::terrain::gpu_terrain &terrain, camera3d camera,
const image &minimap, backend preferred) -> bool {
return with_display(preferred, [&](ra3::client::display &d) {
if (!d.supports_terrain()) return false;
return d.run_terrain(options, terrain, camera, minimap);
});
}
/**
* GPU terrain viewer. Vulkan/D3D only: returns false when the preferred GPU
* backend is unavailable so the caller can render offscreen instead.
*/
[[nodiscard]] inline auto run_terrain(const display_options &options, const ra3::terrain::gpu_terrain &terrain, camera3d camera) -> bool {
return run_terrain(options, terrain, camera, backend::vulkan);
}
}
+1392
View File
File diff suppressed because it is too large Load Diff
+37
View File
@@ -0,0 +1,37 @@
export module ra3.dx;
import std;
export import ra3.core;
import ra3.render;
import ra3.client;
/**
* Fallback Direct3D backends used when the build has no Windows/D3D (for
* example the Linux development build). `init` fails so the caller can select
* another display; the class names match the real `ra3.dx` module so the
* backend factory in `ra3.display` compiles unchanged.
*/
export namespace ra3::dx {
class d3d11_display final : public ra3::client::display {
public:
[[nodiscard]] auto init(const ra3::client::display_options &) -> bool override { return false; }
[[nodiscard]] auto present(const ra3::render::image &, const ra3::render::view_rect &, bool) -> bool override { return false; }
[[nodiscard]] auto poll_event(ra3::render::ui_event &) -> bool override { return false; }
[[nodiscard]] auto window_size() const -> std::pair<int, int> override { return {0, 0}; }
[[nodiscard]] auto key_down(ra3::render::ui_key) const -> bool override { return false; }
auto shutdown() -> void override {}
[[nodiscard]] auto name() const -> std::string_view override { return "d3d11(null)"; }
};
class d3d12_display final : public ra3::client::display {
public:
[[nodiscard]] auto init(const ra3::client::display_options &) -> bool override { return false; }
[[nodiscard]] auto present(const ra3::render::image &, const ra3::render::view_rect &, bool) -> bool override { return false; }
[[nodiscard]] auto poll_event(ra3::render::ui_event &) -> bool override { return false; }
[[nodiscard]] auto window_size() const -> std::pair<int, int> override { return {0, 0}; }
[[nodiscard]] auto key_down(ra3::render::ui_key) const -> bool override { return false; }
auto shutdown() -> void override {}
[[nodiscard]] auto name() const -> std::string_view override { return "d3d12(null)"; }
};
}
+2 -18
View File
@@ -1,23 +1,7 @@
module;
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <optional>
#include <span>
#include <stdexcept>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
export module ra3.fs;
import std;
export import ra3.core;
/**
+2 -7
View File
@@ -1,12 +1,7 @@
module;
#include <cstdint>
#include <string>
#include <string_view>
#include <vector>
export module ra3.game;
import std;
import ra3.core;
import ra3.logic;
+2 -12
View File
@@ -1,17 +1,7 @@
module;
#include <cstddef>
#include <cstdint>
#include <memory>
#include <span>
#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <vector>
export module ra3.logic;
import std;
import ra3.core;
/**
+219 -15
View File
@@ -1,18 +1,7 @@
module;
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstring>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
export module ra3.map;
import std;
export import ra3.core;
export import ra3.fs;
@@ -51,11 +40,12 @@ export namespace ra3::map {
uint32 unpacked_size = 0;
};
/** The result of loading a map: its metadata and recovered start positions. */
/** The result of loading a map: its metadata, recovered start positions and the raw `CkMp` tree. */
struct loaded_map {
map_info info;
std::vector<start_position> starts;
usize ckmp_size = 0;
std::vector<uint8> bytes; ///< The uncompressed `CkMp` payload (terrain/objects parsed from here).
};
namespace detail {
@@ -192,6 +182,219 @@ export namespace ra3::map {
std::vector<map_info> maps_;
};
/**
* Normalise a loose `.map` payload to bare `CkMp` bytes.
*
* Accepts either the raw `.big` payload (the `EAR\0` wrapper around a
* RefPack stream, as `ra3tools` extracts it), a bare RefPack stream, or an
* already-unwrapped `CkMp` tree (as our own `extract` writes).
*/
[[nodiscard]] inline auto to_ckmp(std::span<const uint8> raw) -> std::vector<uint8> {
std::span<const uint8> payload = raw;
if (raw.size() >= 8U && std::memcmp(raw.data(), "EAR\0", 4) == 0) payload = raw.subspan(8);
return fs::maybe_decompress(payload);
}
/** Recover `Player_N_Start` waypoints from raw, uncompressed `CkMp` bytes. */
[[nodiscard]] inline auto starts_from_ckmp(std::span<const uint8> ckmp) -> std::vector<start_position> {
auto starts = detail::extract_start_positions(ckmp);
if (detail::distinct_positions(starts) < 2U) starts.clear();
return starts;
}
/** Map id -> localized display name, keyed by lowercased id. */
struct map_name_table {
std::unordered_map<std::string, std::string> names;
[[nodiscard]] auto empty() const -> bool { return names.empty(); }
/** The display name for `id`, or `id` itself when the string table has none. */
[[nodiscard]] auto lookup(std::string_view id) const -> std::string {
auto key = std::string{id};
std::transform(key.begin(), key.end(), key.begin(), [](unsigned char ch) { return static_cast<char>(std::tolower(ch)); });
if (const auto it = names.find(key); it != names.end() && !it->second.empty()) return it->second;
return std::string{id};
}
};
namespace detail {
/**
* Decode one SAGE `.csf` string value.
*
* RA3's `gamestrings.csf` stores each UTF-16 code unit with the low byte
* XORed by `0xFF` (the high byte is the padding `0xFF`); undoing that
* yields the plain ASCII/UTF-8 text.
*/
[[nodiscard]] inline auto decode_csf_string(std::span<const uint8> raw) -> std::string {
std::string value;
for (usize i = 0; i + 1U < raw.size(); i += 2U) {
const auto ch = static_cast<char>(static_cast<uint8>(raw[i] ^ 0xFFU));
if (ch == '\0') break;
value.push_back(ch);
}
return value;
}
}
/**
* Parse `MAP:<id>` display names out of a SAGE `.csf` string table.
*
* The map list UI reads its labels from the install's `data\gamestrings.csf`
* under the key `MAP:<UPPERCASE_ID>` (e.g. `MAP:MAP_MP_2_FEASEL4` is
* "Battlebase Beta"). Values are byte-XORed with `0xFF`.
*/
[[nodiscard]] inline auto parse_map_names(std::span<const uint8> csf) -> map_name_table {
map_name_table table;
if (csf.size() < 24U || std::memcmp(csf.data(), " FSC", 4) != 0) return table;
const auto read = [&](usize p) -> uint32 {
return static_cast<uint32>(csf[p]) | (static_cast<uint32>(csf[p + 1U]) << 8U) | (static_cast<uint32>(csf[p + 2U]) << 16U) |
(static_cast<uint32>(csf[p + 3U]) << 24U);
};
usize pos = 24; // header: magic + version + label/string counts + 8 reserved bytes
while (pos + 4U <= csf.size()) {
if (std::memcmp(csf.data() + pos, " LBL", 4) != 0) {
++pos;
continue;
}
pos += 4;
if (pos + 4U > csf.size()) break;
const auto count = read(pos);
pos += 4;
std::vector<std::string> labels;
labels.reserve(count);
bool ok = true;
for (uint32 i = 0; i < count; ++i) {
if (pos + 4U > csf.size()) {
ok = false;
break;
}
const auto len = read(pos);
pos += 4;
if (pos + len > csf.size()) {
ok = false;
break;
}
labels.emplace_back(reinterpret_cast<const char *>(csf.data() + pos), len);
pos += len;
}
if (!ok) break;
for (const auto &label: labels) {
if (pos + 4U > csf.size() || std::memcmp(csf.data() + pos, " RTS", 4) != 0) {
ok = false;
break;
}
pos += 4;
if (pos + 4U > csf.size()) {
ok = false;
break;
}
const auto chars = read(pos);
pos += 4;
const auto bytes = static_cast<usize>(chars) * 2U;
if (pos + bytes > csf.size()) {
ok = false;
break;
}
auto value = detail::decode_csf_string(csf.subspan(pos, bytes));
pos += bytes;
constexpr std::string_view prefix = "MAP:";
if (label.size() > prefix.size() && label.compare(0, prefix.size(), prefix) == 0) {
auto id = label.substr(prefix.size());
std::transform(id.begin(), id.end(), id.begin(), [](unsigned char ch) { return static_cast<char>(std::tolower(ch)); });
if (!value.empty()) table.names.try_emplace(std::move(id), std::move(value));
}
}
if (!ok) break;
}
return table;
}
/** Load display names from the install's newest English string table. */
[[nodiscard]] inline auto load_map_names_from_data_dir(const std::filesystem::path &data_dir) -> map_name_table {
std::error_code ec;
std::filesystem::path chosen;
int best_version = -1;
for (const auto &entry: std::filesystem::directory_iterator(data_dir, ec)) {
if (!entry.is_regular_file()) continue;
const auto name = entry.path().filename().string();
if (name.rfind("Lang-English", 0) != 0 || !name.ends_with(".big")) continue;
const auto mid = name.substr(12, name.size() - 16U);
int version = 0;
try {
version = std::stoi(mid);
} catch (const std::exception &) {
continue;
}
if (version > best_version) {
best_version = version;
chosen = entry.path();
}
}
if (chosen.empty()) chosen = data_dir / "English.big";
if (!std::filesystem::exists(chosen, ec)) return {};
try {
const auto archive = fs::big_archive::open(chosen);
const auto matches = archive.find("gamestrings.csf");
if (matches.empty()) return {};
return parse_map_names(archive.read(matches.front()->name, true));
} catch (const std::exception &) {
return {};
}
}
/** Read `<assets>/maps/map_names.tsv`, or any `gamestrings.csf` under `assets`. */
[[nodiscard]] inline auto load_map_names(const std::filesystem::path &assets) -> map_name_table {
const auto read_file = [](const std::filesystem::path &path) {
std::ifstream in(path, std::ios::binary);
return std::vector<uint8>{std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>()};
};
std::error_code ec;
const auto tsv = assets / "maps" / "map_names.tsv";
if (std::filesystem::exists(tsv, ec)) {
const auto bytes = read_file(tsv);
map_name_table table;
std::string text{bytes.begin(), bytes.end()};
for (std::size_t start = 0; start < text.size();) {
const auto end = text.find('\n', start);
const auto line = text.substr(start, end == std::string::npos ? std::string::npos : end - start);
start = end == std::string::npos ? text.size() : end + 1U;
const auto tab = line.find('\t');
if (tab == std::string::npos || tab == 0U) continue;
auto id = line.substr(0, tab);
std::transform(id.begin(), id.end(), id.begin(), [](unsigned char ch) { return static_cast<char>(std::tolower(ch)); });
table.names.try_emplace(std::move(id), line.substr(tab + 1U));
}
if (!table.empty()) return table;
}
for (const auto &entry: std::filesystem::recursive_directory_iterator(assets, ec)) {
if (!entry.is_regular_file() || entry.path().filename() != "gamestrings.csf") continue;
return parse_map_names(read_file(entry.path()));
}
return {};
}
/** Serialize a name table as `id<TAB>name` lines (tabs/newlines stripped). */
[[nodiscard]] inline auto serialize_map_names(const map_name_table &table) -> std::string {
std::vector<std::string> ids;
ids.reserve(table.names.size());
for (const auto &[id, _]: table.names) ids.push_back(id);
std::sort(ids.begin(), ids.end());
std::string out;
for (const auto &id: ids) {
auto name = table.names.at(id);
std::replace(name.begin(), name.end(), '\t', ' ');
std::replace(name.begin(), name.end(), '\n', ' ');
out += id;
out += '\t';
out += name;
out += '\n';
}
return out;
}
/**
* Load a map: unwrap the `EAR\0` + RefPack container and recover the
* player start waypoints.
@@ -212,7 +415,8 @@ export namespace ra3::map {
// Layer 2: the wrapper body is RefPack again, yielding the `CkMp` tree.
auto ckmp = fs::maybe_decompress(payload);
result.ckmp_size = ckmp.size();
result.starts = detail::extract_start_positions(ckmp);
result.starts = starts_from_ckmp(ckmp);
result.bytes = std::move(ckmp);
// Maps that keep starts in `MPPositionList` instead of `Player_N_Start`
// waypoints yield fewer than two distinct points; signal "unknown" so
// the caller can fall back rather than spawn everyone at the origin.
+6
View File
@@ -9,9 +9,15 @@ export module ra3;
export import ra3.core;
export import ra3.logic;
export import ra3.client;
export import ra3.data;
export import ra3.game;
export import ra3.fs;
export import ra3.map;
export import ra3.skirmish;
export import ra3.render;
export import ra3.terrain;
export import ra3.ui;
export import ra3.vulkan;
export import ra3.dx;
export import ra3.webgl;
export import ra3.display;
+384 -14
View File
@@ -1,18 +1,7 @@
module;
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <span>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
export module ra3.render;
import std;
export import ra3.core;
/**
@@ -134,6 +123,264 @@ export namespace ra3::render {
std::vector<uint32> pixels_;
};
namespace detail {
// 8x12 bitmap font for ASCII 32..126 (baseline at row 9 so descenders fit);
// each row byte has bit 7 as the leftmost column, top row first.
inline constexpr std::array<std::array<uint8, 12>, 95> font8x8 = {{
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // ' '
{0x00,0x40,0x40,0x40,0x40,0x40,0x00,0x00,0x40,0x00,0x00,0x00}, // '!'
{0x00,0x6C,0x48,0x48,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // '"'
{0x00,0x14,0x14,0x28,0x7C,0x28,0x7C,0x28,0x50,0x50,0x00,0x00}, // '#'
{0x00,0x10,0x38,0x40,0x40,0x38,0x48,0x70,0x10,0x10,0x00,0x00}, // '$'
{0x00,0x20,0x50,0x20,0x0C,0x70,0x08,0x14,0x08,0x00,0x00,0x00}, // '%'
{0x00,0x00,0x00,0x18,0x20,0x20,0x54,0x48,0x34,0x00,0x00,0x00}, // '&'
{0x00,0x40,0x40,0x40,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // "'"
{0x00,0x20,0x20,0x40,0x40,0x40,0x40,0x40,0x40,0x20,0x20,0x00}, // '('
{0x00,0x40,0x40,0x20,0x20,0x20,0x20,0x20,0x20,0x40,0x40,0x00}, // ')'
{0x00,0x10,0x7C,0x10,0x28,0x08,0x00,0x00,0x00,0x00,0x00,0x00}, // '*'
{0x00,0x00,0x08,0x08,0x08,0x7F,0x08,0x08,0x08,0x00,0x00,0x00}, // '+'
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x20,0x60,0x40,0x00}, // ','
{0x00,0x00,0x00,0x00,0x00,0x7C,0x00,0x00,0x00,0x00,0x00,0x00}, // '-'
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x60,0x00,0x00,0x00}, // '.'
{0x00,0x04,0x04,0x08,0x08,0x10,0x10,0x20,0x20,0x40,0x00,0x00}, // '/'
{0x00,0x38,0x44,0x44,0x44,0x44,0x44,0x44,0x38,0x00,0x00,0x00}, // '0'
{0x00,0x30,0x10,0x10,0x10,0x10,0x10,0x10,0x7C,0x00,0x00,0x00}, // '1'
{0x00,0x38,0x44,0x04,0x08,0x10,0x20,0x44,0x7C,0x00,0x00,0x00}, // '2'
{0x00,0x38,0x44,0x04,0x18,0x04,0x04,0x44,0x38,0x00,0x00,0x00}, // '3'
{0x00,0x0C,0x14,0x14,0x24,0x44,0x7E,0x04,0x0E,0x00,0x00,0x00}, // '4'
{0x00,0x3C,0x20,0x20,0x38,0x04,0x04,0x44,0x38,0x00,0x00,0x00}, // '5'
{0x00,0x1C,0x20,0x40,0x78,0x44,0x44,0x44,0x38,0x00,0x00,0x00}, // '6'
{0x00,0x7C,0x44,0x04,0x08,0x08,0x08,0x10,0x10,0x00,0x00,0x00}, // '7'
{0x00,0x38,0x44,0x44,0x38,0x44,0x44,0x44,0x38,0x00,0x00,0x00}, // '8'
{0x00,0x38,0x44,0x44,0x44,0x3C,0x04,0x08,0x70,0x00,0x00,0x00}, // '9'
{0x00,0x00,0x00,0x60,0x60,0x00,0x00,0x60,0x60,0x00,0x00,0x00}, // ':'
{0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x30,0x60,0x40,0x00,0x00}, // ';'
{0x00,0x06,0x08,0x30,0x40,0x30,0x08,0x06,0x00,0x00,0x00,0x00}, // '<'
{0x00,0x00,0x00,0x00,0x7C,0x00,0x7C,0x00,0x00,0x00,0x00,0x00}, // '='
{0x00,0x60,0x10,0x0C,0x02,0x0C,0x10,0x60,0x00,0x00,0x00,0x00}, // '>'
{0x00,0x00,0x38,0x44,0x04,0x08,0x10,0x00,0x30,0x00,0x00,0x00}, // '?'
{0x38,0x44,0x44,0x4C,0x54,0x54,0x4C,0x40,0x44,0x38,0x00,0x00}, // '@'
{0x00,0x18,0x08,0x14,0x14,0x14,0x3E,0x22,0x77,0x00,0x00,0x00}, // 'A'
{0x00,0x7C,0x22,0x22,0x3C,0x22,0x22,0x22,0x7C,0x00,0x00,0x00}, // 'B'
{0x00,0x3C,0x44,0x40,0x40,0x40,0x40,0x44,0x38,0x00,0x00,0x00}, // 'C'
{0x00,0x78,0x24,0x22,0x22,0x22,0x22,0x24,0x78,0x00,0x00,0x00}, // 'D'
{0x00,0x7E,0x22,0x28,0x38,0x28,0x20,0x22,0x7E,0x00,0x00,0x00}, // 'E'
{0x00,0x7E,0x22,0x28,0x38,0x28,0x20,0x20,0x70,0x00,0x00,0x00}, // 'F'
{0x00,0x3C,0x44,0x40,0x40,0x4E,0x44,0x44,0x38,0x00,0x00,0x00}, // 'G'
{0x00,0x77,0x22,0x22,0x3E,0x22,0x22,0x22,0x77,0x00,0x00,0x00}, // 'H'
{0x00,0x7C,0x10,0x10,0x10,0x10,0x10,0x10,0x7C,0x00,0x00,0x00}, // 'I'
{0x00,0x3C,0x08,0x08,0x08,0x48,0x48,0x48,0x30,0x00,0x00,0x00}, // 'J'
{0x00,0x77,0x22,0x24,0x28,0x38,0x24,0x22,0x73,0x00,0x00,0x00}, // 'K'
{0x00,0x70,0x20,0x20,0x20,0x20,0x24,0x24,0x7C,0x00,0x00,0x00}, // 'L'
{0x00,0x77,0x36,0x36,0x2A,0x2A,0x22,0x22,0x77,0x00,0x00,0x00}, // 'M'
{0x00,0x77,0x32,0x32,0x2A,0x2A,0x2A,0x26,0x76,0x00,0x00,0x00}, // 'N'
{0x00,0x38,0x44,0x44,0x44,0x44,0x44,0x44,0x38,0x00,0x00,0x00}, // 'O'
{0x00,0x78,0x24,0x24,0x24,0x38,0x20,0x20,0x70,0x00,0x00,0x00}, // 'P'
{0x00,0x38,0x44,0x44,0x44,0x44,0x44,0x44,0x38,0x1C,0x00,0x00}, // 'Q'
{0x00,0x7C,0x22,0x22,0x22,0x3C,0x24,0x22,0x71,0x00,0x00,0x00}, // 'R'
{0x00,0x34,0x4C,0x40,0x38,0x04,0x04,0x64,0x58,0x00,0x00,0x00}, // 'S'
{0x00,0x7F,0x49,0x08,0x08,0x08,0x08,0x08,0x1C,0x00,0x00,0x00}, // 'T'
{0x00,0x77,0x22,0x22,0x22,0x22,0x22,0x22,0x1C,0x00,0x00,0x00}, // 'U'
{0x00,0x77,0x22,0x22,0x14,0x14,0x14,0x08,0x08,0x00,0x00,0x00}, // 'V'
{0x00,0x77,0x22,0x22,0x2A,0x2A,0x2A,0x2A,0x14,0x00,0x00,0x00}, // 'W'
{0x00,0x63,0x22,0x14,0x08,0x08,0x14,0x22,0x63,0x00,0x00,0x00}, // 'X'
{0x00,0x77,0x22,0x14,0x14,0x08,0x08,0x08,0x1C,0x00,0x00,0x00}, // 'Y'
{0x00,0x7C,0x44,0x08,0x10,0x10,0x20,0x44,0x7C,0x00,0x00,0x00}, // 'Z'
{0x00,0x70,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x70,0x00}, // '['
{0x00,0x00,0x40,0x40,0x40,0x20,0x20,0x10,0x10,0x00,0x00,0x00}, // '\\'
{0x00,0x70,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x70,0x00}, // ']'
{0x00,0x10,0x10,0x28,0x44,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // '^'
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7F}, // '_'
{0x00,0x40,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // '`'
{0x00,0x00,0x00,0x38,0x04,0x3C,0x44,0x44,0x3E,0x00,0x00,0x00}, // 'a'
{0x00,0x60,0x20,0x2C,0x32,0x22,0x22,0x22,0x7C,0x00,0x00,0x00}, // 'b'
{0x00,0x00,0x00,0x3C,0x44,0x40,0x40,0x44,0x38,0x00,0x00,0x00}, // 'c'
{0x00,0x0C,0x04,0x34,0x4C,0x44,0x44,0x44,0x3E,0x00,0x00,0x00}, // 'd'
{0x00,0x00,0x00,0x38,0x44,0x7C,0x40,0x40,0x3C,0x00,0x00,0x00}, // 'e'
{0x00,0x1C,0x20,0x7C,0x20,0x20,0x20,0x20,0x7C,0x00,0x00,0x00}, // 'f'
{0x00,0x00,0x00,0x36,0x4C,0x44,0x44,0x44,0x3C,0x04,0x38,0x00}, // 'g'
{0x00,0x60,0x20,0x2C,0x32,0x22,0x22,0x22,0x77,0x00,0x00,0x00}, // 'h'
{0x00,0x10,0x00,0x70,0x10,0x10,0x10,0x10,0x7C,0x00,0x00,0x00}, // 'i'
{0x00,0x10,0x00,0x78,0x08,0x08,0x08,0x08,0x08,0x08,0x70,0x00}, // 'j'
{0x00,0x60,0x20,0x2E,0x24,0x38,0x28,0x24,0x6E,0x00,0x00,0x00}, // 'k'
{0x00,0x30,0x10,0x10,0x10,0x10,0x10,0x10,0x7C,0x00,0x00,0x00}, // 'l'
{0x00,0x00,0x00,0x74,0x2A,0x2A,0x2A,0x2A,0x7F,0x00,0x00,0x00}, // 'm'
{0x00,0x00,0x00,0x6C,0x32,0x22,0x22,0x22,0x77,0x00,0x00,0x00}, // 'n'
{0x00,0x00,0x00,0x38,0x44,0x44,0x44,0x44,0x38,0x00,0x00,0x00}, // 'o'
{0x00,0x00,0x00,0x6C,0x32,0x22,0x22,0x22,0x3C,0x20,0x70,0x00}, // 'p'
{0x00,0x00,0x00,0x36,0x4C,0x44,0x44,0x44,0x3C,0x04,0x0E,0x00}, // 'q'
{0x00,0x00,0x00,0x6C,0x30,0x20,0x20,0x20,0x7C,0x00,0x00,0x00}, // 'r'
{0x00,0x00,0x00,0x3C,0x44,0x38,0x04,0x44,0x78,0x00,0x00,0x00}, // 's'
{0x00,0x00,0x20,0x7C,0x20,0x20,0x20,0x22,0x1C,0x00,0x00,0x00}, // 't'
{0x00,0x00,0x00,0x66,0x22,0x22,0x22,0x26,0x1B,0x00,0x00,0x00}, // 'u'
{0x00,0x00,0x00,0x77,0x22,0x22,0x14,0x14,0x08,0x00,0x00,0x00}, // 'v'
{0x00,0x00,0x00,0x77,0x22,0x2A,0x2A,0x2A,0x14,0x00,0x00,0x00}, // 'w'
{0x00,0x00,0x00,0x66,0x24,0x18,0x18,0x24,0x66,0x00,0x00,0x00}, // 'x'
{0x00,0x00,0x00,0x77,0x22,0x12,0x14,0x0C,0x08,0x08,0x3C,0x00}, // 'y'
{0x00,0x00,0x00,0x7C,0x48,0x10,0x20,0x44,0x7C,0x00,0x00,0x00}, // 'z'
{0x00,0x10,0x20,0x20,0x20,0x20,0x40,0x20,0x20,0x20,0x10,0x00}, // '{'
{0x00,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x00,0x00}, // '|'
{0x00,0x40,0x20,0x20,0x20,0x20,0x10,0x20,0x20,0x20,0x40,0x00}, // '}'
{0x00,0x00,0x00,0x00,0x00,0x24,0x58,0x00,0x00,0x00,0x00,0x00}, // '~'
}};
inline constexpr uint32 glyph_width = 8;
inline constexpr uint32 glyph_height = 12;
}
/** Pixel width of `text` drawn at `scale`. */
[[nodiscard]] inline auto text_width(std::string_view text, uint32 scale = 1U) -> uint32 {
return static_cast<uint32>(text.size()) * detail::glyph_width * std::max(1U, scale);
}
/** Draw `text` with its top-left at `(x, y)`, glyphs scaled by `scale`. */
inline auto draw_text(image &target, int x, int y, std::string_view text, uint32 color, uint32 scale = 1U) -> void {
scale = std::max(1U, scale);
int cursor = x;
for (const auto ch: text) {
const auto code = static_cast<unsigned char>(ch);
if (code >= 32U && code < 127U) {
const auto &glyph = detail::font8x8[code - 32U];
for (uint32 gy = 0; gy < detail::glyph_height; ++gy) {
const auto bits = glyph[gy];
if (bits == 0U) continue;
for (uint32 gx = 0; gx < detail::glyph_width; ++gx) {
if ((bits & (1U << (7U - gx))) == 0U) continue;
for (uint32 sy = 0; sy < scale; ++sy) {
for (uint32 sx = 0; sx < scale; ++sx) {
target.set(cursor + static_cast<int>(gx * scale + sx), y + static_cast<int>(gy * scale + sy), color);
}
}
}
}
}
cursor += static_cast<int>(detail::glyph_width * scale);
}
}
/**
* Backend-agnostic input for the interactive loops. The windowing backends
* (`ra3.vulkan` / `ra3.ui`) translate their native events into this and hand
* it to the shared `ra3::client::display` loops.
*/
enum class ui_key : uint8 { none, up, down, left, right, page_up, page_down, confirm, cancel, tab, backspace };
enum class ui_event_type : uint8 { none, quit, key, text, mouse_move, mouse_button, wheel };
struct ui_event {
ui_event_type type = ui_event_type::none;
ui_key key = ui_key::none;
char character = '\0';
float x = 0.0F;
float y = 0.0F;
float dx = 0.0F;
float dy = 0.0F;
float wheel = 0.0F;
bool left = false;
};
/** A warm red/gold loading screen with a progress bar (0..1). */
[[nodiscard]] inline auto compose_progress(uint32 width, uint32 height, float progress, std::string_view label) -> image {
const auto background = argb(34, 9, 8);
const auto panel = argb(58, 14, 12);
const auto edge = argb(120, 30, 22);
const auto gold = argb(236, 190, 80);
const auto cream = argb(246, 228, 192);
image img(width, height, background);
const auto k = static_cast<double>(height) / 720.0;
const auto S = [k](double v) { return static_cast<int>(std::lround(v * k)); };
const auto scale = static_cast<uint32>(std::max(1, static_cast<int>(std::lround(2.0 * k))));
const auto t = std::clamp(progress, 0.0F, 1.0F);
const auto title = std::string_view{"OpenRA3"};
draw_text(img, (static_cast<int>(width) - static_cast<int>(text_width(title, scale + 1U))) / 2, S(250), title, gold, scale + 1U);
draw_text(img, (static_cast<int>(width) - static_cast<int>(text_width(label, scale))) / 2, S(300), label, cream, scale);
const int bar_x = S(240);
const int bar_y = S(360);
const int bar_w = static_cast<int>(width) - 2 * bar_x;
const int bar_h = S(28);
img.draw_rect(bar_x - S(3), bar_y - S(3), bar_w + S(6), bar_h + S(6), edge);
img.draw_rect(bar_x, bar_y, bar_w, bar_h, panel);
img.draw_rect(bar_x, bar_y, static_cast<int>(static_cast<float>(bar_w) * t), bar_h, gold);
char percent[32];
std::snprintf(percent, sizeof(percent), "%d%%", static_cast<int>(t * 100.0F + 0.5F));
draw_text(img, (static_cast<int>(width) - static_cast<int>(text_width(percent, scale))) / 2, bar_y + bar_h + S(18), percent, cream, scale);
return img;
}
/**
* A small translucent label for the top-left corner, e.g. `FPS: 155/160`.
* `cap == 0` means vertical sync, `cap < 0` means uncapped.
*/
[[nodiscard]] inline auto compose_fps_label(uint32 fps, int cap) -> image {
char text[64];
if (cap == 0) {
std::snprintf(text, sizeof(text), "FPS: %u/vsync", fps);
} else if (cap < 0) {
std::snprintf(text, sizeof(text), "FPS: %u", fps);
} else {
std::snprintf(text, sizeof(text), "FPS: %u/%d", fps, cap);
}
const auto w = text_width(text, 1U) + 8U;
const auto h = detail::glyph_height + 6U;
image img(w, h, argb(30, 6, 6, 200)); // warm translucent backing
draw_text(img, 4, 3, text, argb(240, 200, 90), 1U);
return img;
}
/**
* Box-downsample `source` so its width is at most `max_width` (aspect
* preserved). Returns a copy when it is already small enough.
*/
[[nodiscard]] inline auto downscale(const image &source, uint32 max_width) -> image {
if (source.empty() || source.width() <= max_width) return source;
const auto scale = static_cast<uint32>(std::ceil(static_cast<double>(source.width()) / static_cast<double>(max_width)));
const auto w = std::max(1U, source.width() / scale);
const auto h = std::max(1U, source.height() / scale);
image out(w, h);
for (uint32 y = 0; y < h; ++y) {
for (uint32 x = 0; x < w; ++x) {
uint32 r = 0;
uint32 g = 0;
uint32 b = 0;
uint32 n = 0;
for (uint32 dy = 0; dy < scale; ++dy) {
for (uint32 dx = 0; dx < scale; ++dx) {
const auto sx = std::min(source.width() - 1U, x * scale + dx);
const auto sy = std::min(source.height() - 1U, y * scale + dy);
const auto px = source.data()[static_cast<usize>(sy) * source.width() + sx];
r += (px >> 16U) & 0xFFU;
g += (px >> 8U) & 0xFFU;
b += px & 0xFFU;
++n;
}
}
out.set(static_cast<int>(x), static_cast<int>(y), argb(static_cast<uint8>(r / n), static_cast<uint8>(g / n), static_cast<uint8>(b / n)));
}
}
return out;
}
/**
* Copy `full` (a whole-map overview) and mark the camera's location with a
* gold rectangle. `u`/`v` are the normalized map position (v is y-down).
*/
[[nodiscard]] inline auto compose_minimap(const image &full, float u, float v) -> image {
image out = full;
if (full.empty()) return out;
const int r = std::max(4, static_cast<int>(full.width()) / 48);
const int x = std::clamp(static_cast<int>(u * static_cast<float>(full.width())), r, static_cast<int>(full.width()) - r - 1);
const int y = std::clamp(static_cast<int>(v * static_cast<float>(full.height())), r, static_cast<int>(full.height()) - r - 1);
const auto gold = argb(240, 200, 90);
out.draw_rect(x - r, y - r, 2 * r, 2, gold);
out.draw_rect(x - r, y + r - 2, 2 * r, 2, gold);
out.draw_rect(x - r, y - r, 2, 2 * r, gold);
out.draw_rect(x + r - 2, y - r, 2, 2 * r, gold);
return out;
}
[[nodiscard]] inline auto read_u16(std::span<const uint8> data, usize at) -> uint32 {
return static_cast<uint32>(data[at]) | (static_cast<uint32>(data[at + 1U]) << 8U);
}
@@ -223,7 +470,7 @@ export namespace ra3::render {
out[at + 2U] = static_cast<uint8>(value >> 16U);
out[at + 3U] = static_cast<uint8>(value >> 24U);
};
auto put16 = [&out](usize at, uint16_t value) {
auto put16 = [&out](usize at, std::uint16_t value) {
out[at] = static_cast<uint8>(value);
out[at + 1U] = static_cast<uint8>(value >> 8U);
};
@@ -308,4 +555,127 @@ export namespace ra3::render {
}
return scene;
}
/**
* The destination of the map image on screen, in window coordinates.
*
* `x`/`y` is the top-left corner and `w`/`h` the size, in the same units as
* the window. The viewer draws `image * w` into this rectangle; whatever is
* left of the window stays background, so the map is letterboxed instead of
* stretched.
*/
struct view_rect {
float x = 0.0F;
float y = 0.0F;
float w = 0.0F;
float h = 0.0F;
};
/** Letterbox an `iw`x`ih` image into a `ww`x`wh` window, preserving aspect. */
[[nodiscard]] inline auto fit_rect(float iw, float ih, float ww, float wh) -> view_rect {
if (iw <= 0.0F || ih <= 0.0F || ww <= 0.0F || wh <= 0.0F) return {};
const auto scale = std::min(ww / iw, wh / ih);
const auto w = iw * scale;
const auto h = ih * scale;
return {(ww - w) * 0.5F, (wh - h) * 0.5F, w, h};
}
/**
* A 2D pan/zoom camera over a raster map, reproducing the Red Alert 3
* tactical view controls:
*
* - the wheel zooms (zoom 1 === the whole map fits the window);
* - pushing the cursor against a screen edge scrolls the view;
* - the view is clamped so it never leaves the map.
*
* `center_x`/`center_y` are the normalized image position (0..1, y down)
* held at the centre of the viewport. The camera mirrors the retail view
* object (`TheTacticalView`, retail `ra3_1.12.game` `0x00cdb7b4`), whose
* zoom is the scalar the debug overlay prints at `0x00c0b900`; the
* per-map scroll scaling is `cameraScrollSpeedScalar` (map data table at
* `0x00c11a54`).
*/
struct view_camera {
float zoom = 1.0F;
float center_x = 0.5F;
float center_y = 0.5F;
float min_zoom = 1.0F;
float max_zoom = 24.0F;
float zoom_step = 1.15F;
float edge_scroll_viewports_per_second = 0.55F; ///< speed of a full edge push
float edge_margin = 24.0F; ///< pixels from the border
/** Keep the visible window inside the image. */
auto clamp_center() -> void {
const float half = 0.5F / zoom;
center_x = std::clamp(center_x, half, 1.0F - half);
center_y = std::clamp(center_y, half, 1.0F - half);
}
/** Zoom by `factor` (wheel up > 1) about the viewport centre. */
auto zoom_by(float factor) -> void {
zoom = std::clamp(zoom * factor, min_zoom, max_zoom);
this->clamp_center();
}
/** Scroll directly by a normalized image delta. */
auto scroll(float dx, float dy) -> void {
center_x += dx;
center_y += dy;
this->clamp_center();
}
/**
* Push the camera when the cursor `(mouse_x, mouse_y)` is within
* `edge_margin` of a window edge. `dt` is the frame time in seconds.
*/
auto edge_scroll(float mouse_x, float mouse_y, float window_w, float window_h, float dt) -> void {
if (zoom <= min_zoom + 1.0e-4F || window_w <= 0.0F || window_h <= 0.0F) return;
float dir_x = 0.0F;
float dir_y = 0.0F;
if (mouse_x <= edge_margin) {
dir_x = -1.0F;
} else if (mouse_x >= window_w - edge_margin) {
dir_x = 1.0F;
}
if (mouse_y <= edge_margin) {
dir_y = -1.0F;
} else if (mouse_y >= window_h - edge_margin) {
dir_y = 1.0F;
}
if (dir_x == 0.0F && dir_y == 0.0F) return;
const auto step = (1.0F / zoom) * edge_scroll_viewports_per_second * dt;
this->scroll(dir_x * step, dir_y * step);
}
/** Resolve the image destination rectangle for a window of `window_w` x `window_h`. */
[[nodiscard]] auto rect(float image_w, float image_h, float window_w, float window_h) const -> view_rect {
if (image_w <= 0.0F || image_h <= 0.0F) return {};
const auto fit = std::min(window_w / image_w, window_h / image_h);
const auto scale = fit * zoom;
const auto w = image_w * scale;
const auto h = image_h * scale;
auto x = window_w * 0.5F - w * center_x;
auto y = window_h * 0.5F - h * center_y;
x = w <= window_w ? (window_w - w) * 0.5F : std::clamp(x, window_w - w, 0.0F);
y = h <= window_h ? (window_h - h) * 0.5F : std::clamp(y, window_h - h, 0.0F);
return {x, y, w, h};
}
};
/**
* A perspective camera aimed at a ground target, like the retail tactical
* view. The wheel changes `height` (moving the camera closer/farther), not
* an image scale; `yaw`/`pitch` orbit it.
*/
struct camera3d {
float target_x = 0.0F; ///< World position the camera looks at.
float target_y = 0.0F;
float yaw = 0.0F; ///< Radians; 0 looks toward +Y.
float pitch = 1.02F; ///< Radians above the horizon (~58 degrees down).
float height = 420.0F; ///< Camera height above the target's ground.
float fov = 0.85F; ///< Vertical field of view, radians.
float min_height = 120.0F;
float max_height = 1600.0F;
};
}
+255 -143
View File
@@ -1,29 +1,33 @@
module;
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <span>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
export module ra3.skirmish;
import std;
export import ra3.core;
export import ra3.map;
export import ra3.game;
export import ra3.data;
import ra3.logic;
/**
* A minimal, headless skirmish simulation.
* A headless skirmish simulation driven by the real Red Alert 3 balance.
*
* Two sides start on a real map's waypoint positions, earn credits, train
* units and fight until one side's base is destroyed. The simulation is fully
* deterministic: fixed 30 Hz steps, no wall-clock, and a seeded logic random
* stream. There is no networking, no online service and no EA account - this is
* offline skirmish only.
* Two sides start on a real map's waypoints, build a base, extract ore and
* fight. The mechanics mirror SAGE where it matters and use the retail numbers
* from `ra3.data`:
*
* - damage is resolved through the target's `ArmorTemplate` for the weapon's
* damage type (`UNRESISTABLE` bypasses it);
* - a weapon may only target what its `AntiMask` allows (an attack dog's maul
* can never touch a structure or vehicle);
* - structures and units are built through a pay-as-you-go queue gated on
* power, cost and the tech prerequisite;
* - refineries run the ore extraction cycle against a finite node;
* - a side is defeated when it has no structures left, and the last side
* standing wins (the engine's team-wipe victory).
*
* The simulation is fully deterministic: fixed 30 Hz steps, no wall-clock and a
* seeded logic random stream. There is no networking, no online service and no
* EA account - this is offline skirmish only.
*/
export namespace ra3::skirmish {
using ra3::core::coord3d;
@@ -32,75 +36,29 @@ export namespace ra3::skirmish {
using ra3::core::uint32;
using ra3::core::uint8;
using ra3::core::usize;
using ra3::data::entity_kind;
/** Broad unit role. */
enum class unit_class : uint8 { harvester, infantry, tank, base };
[[nodiscard]] constexpr auto to_string(unit_class value) -> std::string_view {
switch (value) {
case unit_class::harvester:
return "Harvester";
case unit_class::infantry:
return "Infantry";
case unit_class::tank:
return "Tank";
case unit_class::base:
return "Base";
}
return "Unknown";
}
/**
* Static combat/economy stats for a unit class.
*
* These are OpenRA3's own balance values (the retail numbers live in
* compiled assets that are not parsed yet), chosen so a skirmish resolves.
*/
struct unit_type {
unit_class cls = unit_class::infantry;
std::string_view name;
real max_health = 1.0F;
real speed = 0.0F;
real weapon_damage = 0.0F;
real weapon_range = 0.0F;
real weapon_cooldown = 1.0F;
int32 cost = 0;
real build_time = 1.0F;
bool mobile = false;
bool produces = false;
};
inline constexpr unit_type harvester_type{unit_class::harvester, "Harvester", 300.0F, 10.0F, 0.0F, 0.0F, 0.0F, 1400, 8.0F, true, false};
inline constexpr unit_type infantry_type{unit_class::infantry, "Infantry", 120.0F, 12.0F, 8.0F, 55.0F, 1.0F, 150, 3.0F, true, false};
inline constexpr unit_type tank_type{unit_class::tank, "Tank", 480.0F, 9.0F, 30.0F, 70.0F, 1.6F, 900, 8.0F, true, false};
inline constexpr unit_type base_type{unit_class::base, "Base", 2500.0F, 0.0F, 20.0F, 90.0F, 2.0F, 0, 0.0F, false, true};
[[nodiscard]] constexpr auto unit_stats(unit_class value) -> const unit_type & {
switch (value) {
case unit_class::harvester:
return harvester_type;
case unit_class::infantry:
return infantry_type;
case unit_class::tank:
return tank_type;
case unit_class::base:
return base_type;
}
return infantry_type;
}
/** A live unit instance. */
/** A live entity instance. */
struct unit {
uint32 id = 0;
uint32 owner = 0;
unit_class cls = unit_class::infantry;
entity_kind kind = entity_kind::none;
coord3d position{};
real health = 0.0F;
real max_health = 0.0F;
real cooldown = 0.0F;
real cooldown = 0.0F; ///< Seconds until the weapon may fire again.
real cycle_timer = 0.0F; ///< Refinery ore-extraction progress, seconds.
bool alive = true;
};
/** One entry in a player's production queue. */
struct build_job {
entity_kind kind = entity_kind::none;
uint32 progress = 0; ///< Frames completed.
uint32 total = 0; ///< Frames required.
int32 paid = 0; ///< Credits paid so far (pay-as-you-go).
};
/** Per-player state for the match. */
struct player_state {
uint32 index = 0;
@@ -111,19 +69,22 @@ export namespace ra3::skirmish {
real money_accumulator = 0.0F;
coord3d start{};
uint32 base_unit = 0;
real build_timer = 0.0F;
real think_timer = 0.0F;
int32 kills = 0;
int32 losses = 0;
int32 power_produced = 0;
int32 power_consumed = 0;
int32 ore_remaining = 0; ///< Credits left in this side's ore node(s).
std::vector<build_job> queue;
};
/** Skirmish parameters. */
struct match_config {
std::string map_id = "builtin";
int32 starting_money = 10000;
int32 starting_money = ra3::data::economy.starting_credits;
uint32 seed = 1;
uint32 max_frames = 30U * 60U * 15U;
real income_per_second = 25.0F;
real harvester_income_per_second = 15.0F;
uint32 max_units_per_player = 30U;
};
/** Outcome of a completed (or capped) match. */
@@ -165,16 +126,19 @@ export namespace ra3::skirmish {
return match;
}
/** Run until a base falls or the frame cap is reached. */
/** Run until a side is wiped out or the frame cap is reached. */
auto run() -> match_result {
while (!this->decided() && frame_ < config_.max_frames) this->step();
if (!decided_) this->decide_on_timeout();
return this->result();
}
/** Advance exactly one 30 Hz frame. */
auto step() -> void {
constexpr real dt = 1.0F / 30.0F;
this->apply_income(dt);
this->update_power();
this->update_economy(dt);
this->update_production();
this->run_ai(dt);
this->update_units(dt);
this->check_victory();
@@ -211,7 +175,7 @@ export namespace ra3::skirmish {
}
private:
static constexpr real build_interval_seconds = 4.0F;
static constexpr real ai_think_seconds = 2.0F;
auto spawn_player(uint32 index, std::string name, game::faction side, bool human, const coord3d &start) -> void {
player_state player;
@@ -221,66 +185,168 @@ export namespace ra3::skirmish {
player.human = human;
player.money = config_.starting_money;
player.start = start;
player.base_unit = this->spawn_unit(index, unit_class::base, start);
this->spawn_unit(index, unit_class::harvester, {start.x + 60.0F, start.y + 20.0F, 0.0F});
this->spawn_unit(index, unit_class::tank, {start.x + 40.0F, start.y + 50.0F, 0.0F});
this->spawn_unit(index, unit_class::tank, {start.x + 70.0F, start.y - 30.0F, 0.0F});
this->spawn_unit(index, unit_class::infantry, {start.x + 30.0F, start.y - 60.0F, 0.0F});
this->spawn_unit(index, unit_class::infantry, {start.x + 90.0F, start.y - 60.0F, 0.0F});
player.ore_remaining = ra3::data::economy.ore_node_capacity;
player.base_unit = this->spawn_unit(index, entity_kind::construction_yard, start);
players_.push_back(std::move(player));
}
auto spawn_unit(uint32 owner, unit_class cls, const coord3d &position) -> uint32 {
const auto &stats = unit_stats(cls);
auto spawn_unit(uint32 owner, entity_kind kind, const coord3d &position) -> uint32 {
unit created;
created.id = next_unit_id_++;
created.owner = owner;
created.cls = cls;
created.kind = kind;
created.position = position;
created.health = stats.max_health;
created.max_health = stats.max_health;
created.health = ra3::data::max_health_of(kind);
created.max_health = created.health;
units_.push_back(created);
return created.id;
}
auto apply_income(real dt) -> void {
/** Sum structure energy; a side is low on power when it draws more than it makes. */
auto update_power() -> void {
for (auto &player: players_) {
real rate = config_.income_per_second;
player.power_produced = 0;
player.power_consumed = 0;
for (const auto &entry: units_) {
if (entry.alive && entry.owner == player.index && entry.cls == unit_class::harvester) rate += config_.harvester_income_per_second;
if (!entry.alive || entry.owner != player.index || !ra3::data::is_structure(entry.kind)) continue;
const auto energy = ra3::data::structure_of(entry.kind).energy;
if (energy >= 0) {
player.power_produced += energy;
} else {
player.power_consumed += -energy;
}
player.money_accumulator += rate * dt;
const auto whole = static_cast<int32>(player.money_accumulator);
if (whole > 0) {
player.money += whole;
player.money_accumulator -= static_cast<real>(whole);
}
}
}
[[nodiscard]] auto low_power(uint32 owner) const -> bool {
const auto &player = players_.at(owner);
return player.power_consumed > player.power_produced;
}
/**
* Ore extraction: every refinery runs the RA3 cycle (11 s for 250
* credits) against the side's finite node. Under low power the cycle is
* half speed.
*/
auto update_economy(real dt) -> void {
for (auto &player: players_) {
const auto rate = low_power(player.index) ? (1.0F / static_cast<real>(ra3::data::world_rules.low_power_mining_divisor)) : 1.0F;
for (auto &entry: units_) {
if (!entry.alive || entry.owner != player.index || !ra3::data::is_structure(entry.kind)) continue;
if (!ra3::data::structure_of(entry.kind).is_refinery) continue;
entry.cycle_timer += dt * rate;
if (entry.cycle_timer < ra3::data::economy.extraction_seconds) continue;
entry.cycle_timer -= ra3::data::economy.extraction_seconds;
const auto yield = ra3::data::ore_per_cycle(player.ore_remaining);
player.ore_remaining = player.ore_remaining > yield ? player.ore_remaining - yield : 0;
player.money += yield;
}
}
}
/**
* Work each player's production queue. Cost is paid as the job
* progresses, a job stalls while unaffordable, and low power halves the
* production rate.
*/
auto update_production() -> void {
for (auto &player: players_) {
if (player.queue.empty()) continue;
auto &job = player.queue.front();
const auto cost = ra3::data::cost_of(job.kind);
const auto advance = low_power(player.index) ? (frame_ % static_cast<uint32>(ra3::data::world_rules.low_power_production_divisor) == 0U) : true;
if (advance && job.total > 0U) {
const auto next_progress = job.progress + 1U;
const auto target_paid = static_cast<int32>((static_cast<std::int64_t>(cost) * next_progress) / job.total);
const auto delta = target_paid - job.paid;
if (delta <= player.money) {
player.money -= delta;
job.paid = target_paid;
job.progress = next_progress;
}
}
if (job.progress < job.total) continue;
if (ra3::data::is_structure(job.kind)) {
const auto &base = this->find_unit(player.base_unit);
const coord3d at = base != nullptr && base->alive ? base->position : player.start;
const auto offset = static_cast<real>(ra3::data::structure_of(job.kind).size_cells) * ra3::data::cell_size + 40.0F;
this->spawn_unit(player.index, job.kind, {at.x + offset, at.y + offset * static_cast<real>(player.queue.size()), 0.0F});
} else {
const auto *producer = this->find_producer(player.index, ra3::data::unit_of(job.kind).built_by);
if (producer == nullptr) continue; // wait for the producer
this->spawn_unit(player.index, job.kind, {producer->position.x + 40.0F, producer->position.y + 40.0F, 0.0F});
}
player.queue.erase(player.queue.begin());
}
}
/** A simple scripted opponent: build a base, then train an army. */
auto run_ai(real dt) -> void {
for (auto &player: players_) {
player.build_timer += dt;
if (player.build_timer < build_interval_seconds) continue;
player.build_timer = 0.0F;
player.think_timer += dt;
if (player.think_timer < ai_think_seconds) continue;
player.think_timer = 0.0F;
if (!player.queue.empty()) continue;
if (this->unit_count(player.index) >= config_.max_units_per_player) continue;
const auto *base = this->find_unit(player.base_unit);
if (base == nullptr || !base->alive) continue;
if (player.money < tank_type.cost) continue;
if (this->enqueue_if_missing(player, entity_kind::power_plant)) continue;
if (this->enqueue_if_missing(player, entity_kind::refinery)) continue;
if (this->enqueue_if_missing(player, entity_kind::barracks)) continue;
if (this->enqueue_if_missing(player, entity_kind::war_factory)) continue;
player.money -= tank_type.cost;
this->spawn_unit(player.index, unit_class::tank, {base->position.x + 50.0F, base->position.y - 50.0F, 0.0F});
// Army composition: roughly half infantry, half tanks.
const bool want_infantry = (frame_ / 30U) % 2U == 0U;
const auto choice = want_infantry ? entity_kind::peacekeeper : entity_kind::guardian_tank;
if (player.money >= ra3::data::cost_of(choice) * 2) this->enqueue(player, choice);
}
}
/** Queue `kind` when the side has none and can afford it. */
auto enqueue_if_missing(player_state &player, entity_kind kind) -> bool {
for (const auto &entry: units_) {
if (entry.alive && entry.owner == player.index && entry.kind == kind) return false;
}
const auto &data = ra3::data::structure_of(kind);
if (data.prerequisite != entity_kind::none) {
bool have_prereq = false;
for (const auto &entry: units_) {
if (entry.alive && entry.owner == player.index && entry.kind == data.prerequisite) have_prereq = true;
}
if (!have_prereq) return false;
}
if (player.money < ra3::data::cost_of(kind)) return false;
return this->enqueue(player, kind);
}
auto enqueue(player_state &player, entity_kind kind) -> bool {
if (player.queue.size() >= 9U) return false;
build_job job;
job.kind = kind;
job.total = ra3::data::build_frames_of(kind);
player.queue.push_back(job);
return true;
}
/**
* Move and fight. A unit seeks the nearest enemy its weapon can legally
* target; with no such target it advances on the enemy base so a match
* always converges.
*/
auto update_units(real dt) -> void {
for (auto &entry: units_) {
if (!entry.alive) continue;
if (entry.cooldown > 0.0F) entry.cooldown -= dt;
if (entry.cls == unit_class::harvester) continue; // economy only, no combat
const auto &stats = unit_stats(entry.cls);
auto *target = this->nearest_enemy(entry);
const auto *weapon = ra3::data::weapon_of(entry.kind);
if (weapon == nullptr) continue;
auto *target = this->nearest_target(entry, *weapon);
if (target == nullptr) continue;
const auto dx = target->position.x - entry.position.x;
@@ -288,50 +354,52 @@ export namespace ra3::skirmish {
const auto dz = target->position.z - entry.position.z;
const auto distance = std::sqrt(dx * dx + dy * dy + dz * dz);
if (distance > stats.weapon_range && stats.mobile && stats.speed > 0.0F) {
if (distance > 1.0e-3F) {
const auto travel = stats.speed * dt;
if (distance > weapon->attack_range) {
const auto speed = ra3::data::unit_of(entry.kind).speed;
if (speed > 0.0F && distance > 1.0e-3F) {
const auto travel = speed * dt;
entry.position.x += dx / distance * travel;
entry.position.y += dy / distance * travel;
}
} else if (stats.weapon_damage > 0.0F && distance <= stats.weapon_range && entry.cooldown <= 0.0F) {
entry.cooldown = stats.weapon_cooldown;
target->health -= stats.weapon_damage;
if (target->health <= 0.0F) {
target->health = 0.0F;
target->alive = false;
this->player_ref(entry.owner).kills += 1;
this->player_ref(target->owner).losses += 1;
continue;
}
if (entry.cooldown > 0.0F) continue;
entry.cooldown = weapon->reload_seconds + weapon->firing_seconds;
this->apply_damage(entry, *target, *weapon);
if (weapon->splash_radius > 0.0F) {
for (auto &other: units_) {
if (!other.alive || other.owner == entry.owner || &other == target) continue;
if (!weapon->can_target(ra3::data::class_of(other.kind))) continue;
const auto ox = other.position.x - target->position.x;
const auto oy = other.position.y - target->position.y;
if (ox * ox + oy * oy <= weapon->splash_radius * weapon->splash_radius) this->apply_damage(entry, other, *weapon);
}
}
}
}
auto check_victory() -> void {
const bool first_alive = this->base_alive(0U);
const bool second_alive = this->base_alive(1U);
if (first_alive && second_alive) return;
decided_ = true;
if (first_alive) {
winner_ = 0;
} else if (second_alive) {
winner_ = 1;
} else {
winner_ = -1;
}
/** Resolve one hit through the target's armour. */
auto apply_damage(const unit &attacker, unit &target, const ra3::data::weapon_data &weapon) -> void {
if (!target.alive) return;
const auto damage = weapon.instakill ? target.max_health : ra3::data::armor_of(target.kind).adjust(weapon.type, weapon.damage);
target.health -= damage;
if (target.health > 0.0F) return;
target.health = 0.0F;
target.alive = false;
this->player_ref(attacker.owner).kills += 1;
this->player_ref(target.owner).losses += 1;
}
[[nodiscard]] auto base_alive(uint32 owner) const -> bool {
const auto &player = players_.at(owner);
const auto *base = this->find_unit(player.base_unit);
return base != nullptr && base->alive;
}
[[nodiscard]] auto nearest_enemy(const unit &self) -> unit * {
/** Nearest enemy this weapon may target, or `nullptr`. */
[[nodiscard]] auto nearest_target(const unit &self, const ra3::data::weapon_data &weapon) -> unit * {
unit *best = nullptr;
real best_distance = 1.0e30F;
for (auto &candidate: units_) {
if (!candidate.alive || candidate.owner == self.owner) continue;
if (!weapon.can_target(ra3::data::class_of(candidate.kind))) continue;
const auto dx = candidate.position.x - self.position.x;
const auto dy = candidate.position.y - self.position.y;
const auto squared = dx * dx + dy * dy;
@@ -343,6 +411,43 @@ export namespace ra3::skirmish {
return best;
}
/** Defeat is losing every structure; the last side standing wins. */
auto check_victory() -> void {
const bool first_alive = this->has_structures(0U);
const bool second_alive = this->has_structures(1U);
if (first_alive && second_alive) return;
decided_ = true;
winner_ = first_alive ? 0 : (second_alive ? 1 : -1);
}
/** Frame cap: the higher-scoring side wins, with a deterministic tie-break. */
auto decide_on_timeout() -> void {
decided_ = true;
const auto first = this->score_tuple(0U);
const auto second = this->score_tuple(1U);
winner_ = first == second ? -1 : (first > second ? 0 : 1);
}
[[nodiscard]] auto score_tuple(uint32 owner) const -> std::tuple<int32, int32, int32> {
const auto &player = players_.at(owner);
return {this->score(owner), player.kills, static_cast<int32>(this->unit_count(owner))};
}
[[nodiscard]] auto score(uint32 owner) const -> int32 {
int32 total = players_.at(owner).money;
for (const auto &entry: units_) {
if (entry.alive && entry.owner == owner) total += ra3::data::cost_of(entry.kind);
}
return total;
}
[[nodiscard]] auto has_structures(uint32 owner) const -> bool {
for (const auto &entry: units_) {
if (entry.alive && entry.owner == owner && ra3::data::is_structure(entry.kind)) return true;
}
return false;
}
[[nodiscard]] auto find_unit(uint32 id) -> unit * {
for (auto &entry: units_) {
if (entry.id == id) return &entry;
@@ -357,6 +462,13 @@ export namespace ra3::skirmish {
return nullptr;
}
[[nodiscard]] auto find_producer(uint32 owner, entity_kind kind) -> unit * {
for (auto &entry: units_) {
if (entry.alive && entry.owner == owner && entry.kind == kind) return &entry;
}
return nullptr;
}
[[nodiscard]] auto player_ref(uint32 index) -> player_state & { return players_.at(index); }
match_config config_;
File diff suppressed because it is too large Load Diff
+13 -14
View File
@@ -1,24 +1,23 @@
module;
#include <string>
export module ra3.ui;
import std;
export import ra3.core;
import ra3.render;
import ra3.client;
/**
* Fallback viewer used when the build has no SDL3. It never opens a window;
* the caller should write an offscreen image instead.
* Fallback SDL backend used when the build has no SDL3. It never opens a window;
* `init` fails so the caller can fall back to an offscreen image.
*/
export namespace ra3::ui {
/** Window parameters (mirrors the SDL backend). */
struct viewer_options {
std::string title = "OpenRA3";
int width = 1024;
int height = 768;
class sdl_display final : public ra3::client::display {
public:
[[nodiscard]] auto init(const ra3::client::display_options &) -> bool override { return false; }
[[nodiscard]] auto present(const ra3::render::image &, const ra3::render::view_rect &, bool) -> bool override { return false; }
[[nodiscard]] auto poll_event(ra3::render::ui_event &) -> bool override { return false; }
[[nodiscard]] auto window_size() const -> std::pair<int, int> override { return {0, 0}; }
[[nodiscard]] auto key_down(ra3::render::ui_key) const -> bool override { return false; }
auto shutdown() -> void override {}
[[nodiscard]] auto name() const -> std::string_view override { return "sdl(null)"; }
};
/** Always returns false: this build has no windowing backend. */
[[nodiscard]] inline auto run_viewer(const ra3::render::image &, const viewer_options &) -> bool { return false; }
}
+159 -69
View File
@@ -2,92 +2,182 @@ module;
#include <SDL3/SDL.h>
#include <algorithm>
#include <string>
export module ra3.ui;
import std;
export import ra3.core;
import ra3.render;
import ra3.client;
/**
* A minimal SDL3 window for viewing a rendered scene.
* The SDL3 presentation backend.
*
* Controls: drag with the left mouse button to pan, wheel to zoom, Esc/close
* to quit. When no display is available `run_viewer` returns false so the
* caller can fall back to an offscreen image.
* SDL3 is the platform layer: it owns the window, the input, and (here) a
* low-end `SDL_Renderer` blit path. Only the low-level display primitives live
* in this module; the interactive loops are shared in `ra3::client::display`.
* The Vulkan backend implements the same interface.
*/
export namespace ra3::ui {
/** Window parameters. */
struct viewer_options {
std::string title = "OpenRA3";
int width = 1024;
int height = 768;
};
/**
* Show `scene` in a window until the user quits.
*
* @return true if the window ran; false if video init or window creation
* failed (typically no display).
* An SDL_Renderer-backed display: cheap, dependency-light, and used as the
* fallback when Vulkan is unavailable.
*/
[[nodiscard]] inline auto run_viewer(const ra3::render::image &scene, const viewer_options &options) -> bool {
if (scene.empty()) return false;
class sdl_display final : public ra3::client::display {
public:
sdl_display() = default;
sdl_display(const sdl_display &) = delete;
auto operator=(const sdl_display &) -> sdl_display & = delete;
~sdl_display() override { this->shutdown(); }
[[nodiscard]] auto init(const ra3::client::display_options &options) -> bool override {
if (!SDL_Init(SDL_INIT_VIDEO)) return false;
SDL_Window *window = SDL_CreateWindow(options.title.c_str(), options.width, options.height, SDL_WINDOW_RESIZABLE);
if (window == nullptr) {
this->fps_limit_ = options.fps_limit;
window_ = SDL_CreateWindow(options.title.c_str(), options.width, options.height,
SDL_WINDOW_RESIZABLE | (options.fullscreen ? SDL_WINDOW_FULLSCREEN : 0));
if (window_ == nullptr) {
SDL_Quit();
return false;
}
SDL_Renderer *renderer = SDL_CreateRenderer(window, nullptr);
if (renderer == nullptr) {
SDL_DestroyWindow(window);
SDL_Quit();
SDL_RaiseWindow(window_);
renderer_ = SDL_CreateRenderer(window_, nullptr);
if (renderer_ == nullptr) {
this->shutdown();
return false;
}
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, static_cast<int>(scene.width()),
static_cast<int>(scene.height()));
if (texture == nullptr) {
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return false;
}
SDL_UpdateTexture(texture, nullptr, scene.data(), static_cast<int>(scene.width()) * 4);
SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST);
float zoom = 1.0F;
float pan_x = 0.0F;
float pan_y = 0.0F;
bool running = true;
SDL_Event event;
while (running) {
while (SDL_PollEvent(&event)) {
if (event.type == SDL_EVENT_QUIT) {
running = false;
} else if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_ESCAPE) {
running = false;
} else if (event.type == SDL_EVENT_MOUSE_WHEEL) {
zoom *= event.wheel.y > 0.0F ? 1.1F : (1.0F / 1.1F);
zoom = std::clamp(zoom, 0.1F, 20.0F);
} else if (event.type == SDL_EVENT_MOUSE_MOTION && (event.motion.state & SDL_BUTTON_LMASK) != 0U) {
pan_x += event.motion.xrel;
pan_y += event.motion.yrel;
}
}
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
const SDL_FRect dst{pan_x, pan_y, static_cast<float>(scene.width()) * zoom, static_cast<float>(scene.height()) * zoom};
SDL_RenderTexture(renderer, texture, nullptr, &dst);
SDL_RenderPresent(renderer);
}
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
SDL_SetRenderVSync(renderer_, options.fps_limit == 0 ? 1 : 0);
SDL_StartTextInput(window_);
return true;
}
[[nodiscard]] auto present(const ra3::render::image &frame, const ra3::render::view_rect &dest, bool changed) -> bool override {
if (frame.empty() || renderer_ == nullptr) return false;
if (changed || texture_ == nullptr || frame.width() != texture_w_ || frame.height() != texture_h_) {
if (texture_ != nullptr) SDL_DestroyTexture(texture_);
texture_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, static_cast<int>(frame.width()),
static_cast<int>(frame.height()));
if (texture_ == nullptr) return false;
texture_w_ = frame.width();
texture_h_ = frame.height();
SDL_SetTextureScaleMode(texture_, SDL_SCALEMODE_LINEAR);
SDL_UpdateTexture(texture_, nullptr, frame.data(), static_cast<int>(frame.width()) * 4);
}
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 255);
SDL_RenderClear(renderer_);
const SDL_FRect dst{dest.x, dest.y, dest.w, dest.h};
SDL_RenderTexture(renderer_, texture_, nullptr, &dst);
SDL_RenderPresent(renderer_);
return true;
}
[[nodiscard]] auto poll_event(ra3::render::ui_event &out) -> bool override {
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_EVENT_QUIT:
out = {};
out.type = ra3::render::ui_event_type::quit;
return true;
case SDL_EVENT_KEY_DOWN:
if (const auto key = map_key(event.key.key); key != ra3::render::ui_key::none) {
out = {};
out.type = ra3::render::ui_event_type::key;
out.key = key;
return true;
}
break;
case SDL_EVENT_TEXT_INPUT:
if (event.text.text[0] != '\0') {
out = {};
out.type = ra3::render::ui_event_type::text;
out.character = event.text.text[0];
return true;
}
break;
case SDL_EVENT_MOUSE_MOTION:
out = {};
out.type = ra3::render::ui_event_type::mouse_move;
out.x = event.motion.x;
out.y = event.motion.y;
out.dx = event.motion.xrel;
out.dy = event.motion.yrel;
out.left = (event.motion.state & SDL_BUTTON_LMASK) != 0U;
return true;
case SDL_EVENT_MOUSE_BUTTON_DOWN:
out = {};
out.type = ra3::render::ui_event_type::mouse_button;
out.x = event.button.x;
out.y = event.button.y;
out.left = event.button.button == SDL_BUTTON_LEFT;
return true;
case SDL_EVENT_MOUSE_WHEEL:
out = {};
out.type = ra3::render::ui_event_type::wheel;
out.wheel = event.wheel.y;
return true;
default:
break;
}
}
return false;
}
[[nodiscard]] auto window_size() const -> std::pair<int, int> override {
int w = 0;
int h = 0;
SDL_GetWindowSizeInPixels(window_, &w, &h);
return {w, h};
}
[[nodiscard]] auto key_down(ra3::render::ui_key key) const -> bool override {
const bool *keys = SDL_GetKeyboardState(nullptr);
if (keys == nullptr) return false;
switch (key) {
case ra3::render::ui_key::up: return keys[SDL_SCANCODE_W] || keys[SDL_SCANCODE_UP];
case ra3::render::ui_key::down: return keys[SDL_SCANCODE_S] || keys[SDL_SCANCODE_DOWN];
case ra3::render::ui_key::left: return keys[SDL_SCANCODE_A] || keys[SDL_SCANCODE_LEFT];
case ra3::render::ui_key::right: return keys[SDL_SCANCODE_D] || keys[SDL_SCANCODE_RIGHT];
default: return false;
}
}
auto shutdown() -> void override {
if (window_ != nullptr) SDL_StopTextInput(window_);
if (texture_ != nullptr) SDL_DestroyTexture(texture_);
if (renderer_ != nullptr) SDL_DestroyRenderer(renderer_);
if (window_ != nullptr) SDL_DestroyWindow(window_);
SDL_Quit();
texture_ = nullptr;
renderer_ = nullptr;
window_ = nullptr;
texture_w_ = 0;
texture_h_ = 0;
}
[[nodiscard]] auto name() const -> std::string_view override { return "sdl"; }
private:
[[nodiscard]] static auto map_key(SDL_Keycode key) -> ra3::render::ui_key {
using ra3::render::ui_key;
switch (key) {
case SDLK_UP: return ui_key::up;
case SDLK_DOWN: return ui_key::down;
case SDLK_LEFT: return ui_key::left;
case SDLK_RIGHT: return ui_key::right;
case SDLK_PAGEUP: return ui_key::page_up;
case SDLK_PAGEDOWN: return ui_key::page_down;
case SDLK_RETURN:
case SDLK_KP_ENTER: return ui_key::confirm;
case SDLK_ESCAPE: return ui_key::cancel;
case SDLK_TAB: return ui_key::tab;
case SDLK_BACKSPACE: return ui_key::backspace;
default: return ui_key::none;
}
}
SDL_Window *window_ = nullptr;
SDL_Renderer *renderer_ = nullptr;
SDL_Texture *texture_ = nullptr;
ra3::core::uint32 texture_w_ = 0;
ra3::core::uint32 texture_h_ = 0;
};
}
+25
View File
@@ -0,0 +1,25 @@
export module ra3.vulkan;
import std;
export import ra3.core;
import ra3.render;
import ra3.client;
/**
* Fallback Vulkan backend used when the build has no Vulkan loader (for example
* the Windows cross-build without a Vulkan SDK). `init` fails so the caller can
* select another display.
*/
export namespace ra3::vulkan {
class vulkan_display final : public ra3::client::display {
public:
[[nodiscard]] auto init(const ra3::client::display_options &) -> bool override { return false; }
[[nodiscard]] auto present(const ra3::render::image &, const ra3::render::view_rect &, bool) -> bool override { return false; }
[[nodiscard]] auto poll_event(ra3::render::ui_event &) -> bool override { return false; }
[[nodiscard]] auto window_size() const -> std::pair<int, int> override { return {0, 0}; }
[[nodiscard]] auto key_down(ra3::render::ui_key) const -> bool override { return false; }
auto shutdown() -> void override {}
[[nodiscard]] auto name() const -> std::string_view override { return "vulkan(null)"; }
};
}
File diff suppressed because it is too large Load Diff
+461
View File
@@ -0,0 +1,461 @@
module;
// Emscripten/GLES3 headers in the global module fragment (C headers).
#include <SDL3/SDL.h>
#include <GLES3/gl3.h>
// Sized normalized texture formats used by the terrain textures; core in
// WebGL2/GLES3 but missing from some GLES3 headers.
#ifndef GL_R16
#define GL_R16 0x822A
#endif
#ifndef GL_RGBA16
#define GL_RGBA16 0x805B
#endif
#include "glsl_embedded.hpp"
export module ra3.webgl;
import std;
export import ra3.core;
import ra3.render;
import ra3.terrain;
import ra3.client;
import ender.log;
/**
* A WebGL (GLES 3.0) presentation backend, a peer of the Vulkan and Direct3D
* backends. It owns a GL context on an SDL3 canvas and implements the same
* `ra3::client::display` primitives, including the GPU heightfield terrain, so
* the web build gets the same renderer as the desktop ones rather than the
* software SDL blit.
*
* The 2D path draws a fullscreen triangle sampling a software `ra3::render::image`;
* the terrain path ray-marches the heightfield in a fragment shader (the GLSL ES
* port of `terrain.frag`). Textures carry the engine's 0xAARRGGBB pixels, so the
* shaders swizzle `.bgra`.
*/
export namespace ra3::webgl {
namespace detail {
[[nodiscard]] inline auto key_from_sdl(SDL_Keycode key) -> ra3::render::ui_key {
using ra3::render::ui_key;
switch (key) {
case SDLK_UP: return ui_key::up;
case SDLK_DOWN: return ui_key::down;
case SDLK_LEFT: return ui_key::left;
case SDLK_RIGHT: return ui_key::right;
case SDLK_PAGEUP: return ui_key::page_up;
case SDLK_PAGEDOWN: return ui_key::page_down;
case SDLK_RETURN:
case SDLK_KP_ENTER: return ui_key::confirm;
case SDLK_ESCAPE: return ui_key::cancel;
case SDLK_TAB: return ui_key::tab;
case SDLK_BACKSPACE: return ui_key::backspace;
default: return ui_key::none;
}
}
[[nodiscard]] inline auto poll_event(SDL_Window *window, ra3::render::ui_event &out) -> bool {
(void) window;
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_EVENT_QUIT:
out = {};
out.type = ra3::render::ui_event_type::quit;
return true;
case SDL_EVENT_KEY_DOWN:
if (const auto key = key_from_sdl(event.key.key); key != ra3::render::ui_key::none) {
out = {};
out.type = ra3::render::ui_event_type::key;
out.key = key;
return true;
}
break;
case SDL_EVENT_TEXT_INPUT:
if (event.text.text[0] != '\0') {
out = {};
out.type = ra3::render::ui_event_type::text;
out.character = event.text.text[0];
return true;
}
break;
case SDL_EVENT_MOUSE_MOTION:
out = {};
out.type = ra3::render::ui_event_type::mouse_move;
out.x = event.motion.x;
out.y = event.motion.y;
out.dx = event.motion.xrel;
out.dy = event.motion.yrel;
out.left = (event.motion.state & SDL_BUTTON_LMASK) != 0U;
return true;
case SDL_EVENT_MOUSE_BUTTON_DOWN:
out = {};
out.type = ra3::render::ui_event_type::mouse_button;
out.x = event.button.x;
out.y = event.button.y;
out.left = event.button.button == SDL_BUTTON_LEFT;
return true;
case SDL_EVENT_MOUSE_WHEEL:
out = {};
out.type = ra3::render::ui_event_type::wheel;
out.wheel = event.wheel.y;
return true;
default:
break;
}
}
return false;
}
[[nodiscard]] inline auto key_down(ra3::render::ui_key key) -> bool {
const bool *keys = SDL_GetKeyboardState(nullptr);
if (keys == nullptr) return false;
switch (key) {
case ra3::render::ui_key::up: return keys[SDL_SCANCODE_W] || keys[SDL_SCANCODE_UP];
case ra3::render::ui_key::down: return keys[SDL_SCANCODE_S] || keys[SDL_SCANCODE_DOWN];
case ra3::render::ui_key::left: return keys[SDL_SCANCODE_A] || keys[SDL_SCANCODE_LEFT];
case ra3::render::ui_key::right: return keys[SDL_SCANCODE_D] || keys[SDL_SCANCODE_RIGHT];
default: return false;
}
}
/** Camera + terrain uniforms (5 x vec4), matching the other backends. */
[[nodiscard]] inline auto terrain_uniforms(const ra3::terrain::gpu_terrain &terrain, const ra3::render::camera3d &camera, float time_s,
float aspect) -> std::array<float, 20> {
return {camera.target_x,
camera.target_y,
camera.yaw,
camera.height,
camera.pitch,
camera.fov,
terrain.water_z,
terrain.has_water ? 1.0F : 0.0F,
0.45F,
0.35F,
0.82F,
0.38F,
static_cast<float>(terrain.width),
static_cast<float>(terrain.height),
0.0F,
terrain.z_scale,
time_s,
0.0F,
terrain.cell_span,
aspect};
}
} // namespace detail
class webgl_display final : public ra3::client::display {
public:
webgl_display() = default;
webgl_display(const webgl_display &) = delete;
auto operator=(const webgl_display &) -> webgl_display & = delete;
~webgl_display() override { this->shutdown(); }
[[nodiscard]] auto init(const ra3::client::display_options &options) -> bool override {
if (!SDL_Init(SDL_INIT_VIDEO)) return false;
this->fps_limit_ = options.fps_limit;
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
window_ = SDL_CreateWindow(options.title.c_str(), options.width, options.height,
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | (options.fullscreen ? SDL_WINDOW_FULLSCREEN : 0));
if (window_ == nullptr) {
SDL_Quit();
return false;
}
SDL_RaiseWindow(window_);
context_ = SDL_GL_CreateContext(window_);
if (context_ == nullptr) {
ender::log::error(std::format("ra3.webgl: SDL_GL_CreateContext failed: {}", SDL_GetError()));
this->shutdown();
return false;
}
SDL_GL_SetSwapInterval(options.fps_limit == 0 ? 1 : 0);
if (!this->create_scene_pipeline()) {
this->shutdown();
return false;
}
glGenVertexArrays(1, &vao_);
glBindVertexArray(vao_);
SDL_StartTextInput(window_);
ender::log::info(std::format("ra3.webgl: WebGL backend initialized ({})", reinterpret_cast<const char *>(glGetString(GL_VERSION))));
return true;
}
[[nodiscard]] auto present(const ra3::render::image &frame, const ra3::render::view_rect &dest, bool changed) -> bool override {
if (frame.empty()) return false;
if (!this->ensure_size()) return false;
if (changed || scene_tex_ == 0U || frame.width() != scene_w_ || frame.height() != scene_h_) {
this->upload_scene(frame);
}
const auto [window_w, window_h] = this->window_size();
if (window_w <= 0 || window_h <= 0) return true;
glViewport(0, 0, window_w, window_h);
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glClearColor(0.05F, 0.06F, 0.08F, 1.0F);
glClear(GL_COLOR_BUFFER_BIT);
this->draw_fullscreen(scene_program_, scene_rect_loc_, scene_tex_, dest, window_w, window_h);
SDL_GL_SwapWindow(window_);
return true;
}
[[nodiscard]] auto poll_event(ra3::render::ui_event &out) -> bool override { return detail::poll_event(window_, out); }
[[nodiscard]] auto window_size() const -> std::pair<int, int> override {
int w = 0;
int h = 0;
SDL_GetWindowSizeInPixels(window_, &w, &h);
return {w, h};
}
[[nodiscard]] auto key_down(ra3::render::ui_key key) const -> bool override { return detail::key_down(key); }
[[nodiscard]] auto supports_terrain() const -> bool override { return true; }
[[nodiscard]] auto present_terrain(const ra3::terrain::gpu_terrain &terrain, const ra3::render::camera3d &camera, float time_s,
const ra3::client::terrain_overlay &overlay) -> bool override {
if (!terrain_ready_) {
if (!this->create_terrain_pipeline()) return false;
if (!this->create_terrain_textures(terrain)) return false;
terrain_ready_ = true;
ender::log::info(std::format("ra3.webgl: terrain ready ({}x{}, {} layers)", terrain.width, terrain.height, terrain.layer_count));
}
if (!this->ensure_size()) return false;
if (overlay.label_changed) this->upload_overlay(label_tex_, overlay.label);
if (overlay.minimap_changed) this->upload_overlay(minimap_tex_, overlay.minimap);
const auto [window_w, window_h] = this->window_size();
if (window_w <= 0 || window_h <= 0) return true;
const auto aspect = static_cast<float>(window_w) / static_cast<float>(std::max(1, window_h));
glViewport(0, 0, window_w, window_h);
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glClearColor(0.45F, 0.55F, 0.70F, 1.0F);
glClear(GL_COLOR_BUFFER_BIT);
glBindVertexArray(vao_);
glUseProgram(terrain_program_);
const auto uniforms = detail::terrain_uniforms(terrain, camera, time_s, aspect);
glUniform4fv(terrain_cam_loc_, 5, uniforms.data());
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, height_tex_);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, cell_tex_);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D_ARRAY, atlas_tex_);
glUniform1i(terrain_height_loc_, 0);
glUniform1i(terrain_cell_loc_, 1);
glUniform1i(terrain_atlas_loc_, 2);
glDrawArrays(GL_TRIANGLES, 0, 3);
// Overlays (FPS label, corner minimap) use the image program.
const float margin = 12.0F;
this->draw_overlay(label_tex_, overlay.label.width(), overlay.label.height(), margin, margin, window_w, window_h);
this->draw_overlay(minimap_tex_, overlay.minimap.width(), overlay.minimap.height(),
static_cast<float>(window_w) - static_cast<float>(overlay.minimap.width()) - margin, margin, window_w, window_h);
SDL_GL_SwapWindow(window_);
return true;
}
auto shutdown() -> void override {
if (context_ != nullptr) {
this->destroy_textures();
if (scene_program_ != 0U) glDeleteProgram(scene_program_);
if (terrain_program_ != 0U) glDeleteProgram(terrain_program_);
if (vao_ != 0U) glDeleteVertexArrays(1, &vao_);
SDL_GL_DestroyContext(context_);
}
if (window_ != nullptr) SDL_DestroyWindow(window_);
SDL_Quit();
window_ = nullptr;
context_ = nullptr;
vao_ = 0U;
scene_program_ = terrain_program_ = 0U;
terrain_ready_ = false;
scene_w_ = scene_h_ = 0U;
swapchain_w_ = swapchain_h_ = 0;
}
[[nodiscard]] auto name() const -> std::string_view override { return "webgl"; }
private:
[[nodiscard]] auto compile(unsigned type, const char *source, std::string_view what) -> GLuint {
const GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &source, nullptr);
glCompileShader(shader);
GLint ok = GL_FALSE;
glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
if (ok == GL_FALSE) {
std::array<char, 1024> log{};
glGetShaderInfoLog(shader, static_cast<GLsizei>(log.size()), nullptr, log.data());
ender::log::error(std::format("ra3.webgl: {} shader compile failed: {}", what, log.data()));
glDeleteShader(shader);
return 0U;
}
return shader;
}
[[nodiscard]] auto link(const char *vertex, const char *fragment, std::string_view what) -> GLuint {
const GLuint vs = this->compile(GL_VERTEX_SHADER, vertex, what);
const GLuint fs = this->compile(GL_FRAGMENT_SHADER, fragment, what);
if (vs == 0U || fs == 0U) {
if (vs != 0U) glDeleteShader(vs);
if (fs != 0U) glDeleteShader(fs);
return 0U;
}
const GLuint program = glCreateProgram();
glAttachShader(program, vs);
glAttachShader(program, fs);
glLinkProgram(program);
glDeleteShader(vs);
glDeleteShader(fs);
GLint ok = GL_FALSE;
glGetProgramiv(program, GL_LINK_STATUS, &ok);
if (ok == GL_FALSE) {
std::array<char, 1024> log{};
glGetProgramInfoLog(program, static_cast<GLsizei>(log.size()), nullptr, log.data());
ender::log::error(std::format("ra3.webgl: {} program link failed: {}", what, log.data()));
glDeleteProgram(program);
return 0U;
}
return program;
}
[[nodiscard]] auto create_scene_pipeline() -> bool {
scene_program_ = this->link(ra3_shaders::webgl_scene_vert_glsl, ra3_shaders::webgl_scene_frag_glsl, "scene");
if (scene_program_ == 0U) return false;
scene_rect_loc_ = glGetUniformLocation(scene_program_, "u_rect");
glUseProgram(scene_program_);
glUniform1i(glGetUniformLocation(scene_program_, "u_scene"), 0);
return true;
}
[[nodiscard]] auto create_terrain_pipeline() -> bool {
terrain_program_ = this->link(ra3_shaders::webgl_terrain_vert_glsl, ra3_shaders::webgl_terrain_frag_glsl, "terrain");
if (terrain_program_ == 0U) return false;
terrain_cam_loc_ = glGetUniformLocation(terrain_program_, "u_data");
terrain_height_loc_ = glGetUniformLocation(terrain_program_, "u_heightmap");
terrain_cell_loc_ = glGetUniformLocation(terrain_program_, "u_celldata");
terrain_atlas_loc_ = glGetUniformLocation(terrain_program_, "u_atlas");
return true;
}
/** (Re)create the 2D texture used by the scene/overlay path. */
auto make_texture2d(GLuint &texture, GLint internal, GLenum format, GLenum type, int w, int h, const void *data, bool linear,
bool wrap) -> void {
if (texture == 0U) glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexImage2D(GL_TEXTURE_2D, 0, internal, w, h, 0, format, type, data);
if (const GLenum error = glGetError(); error != GL_NO_ERROR) {
ender::log::error(std::format("ra3.webgl: texImage2D failed: internal=0x{:X} format=0x{:X} type=0x{:X} size={}x{} err=0x{:X}",
static_cast<unsigned>(internal), static_cast<unsigned>(format), static_cast<unsigned>(type), w, h,
static_cast<unsigned>(error)));
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, linear ? GL_LINEAR : GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, linear ? GL_LINEAR : GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap ? GL_REPEAT : GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap ? GL_REPEAT : GL_CLAMP_TO_EDGE);
}
auto upload_scene(const ra3::render::image &frame) -> void {
// The image is 0xAARRGGBB (BGRA in memory); store it as RGBA and let
// the shader swizzle `.bgra`.
this->make_texture2d(scene_tex_, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, static_cast<int>(frame.width()),
static_cast<int>(frame.height()), frame.data(), true, false);
scene_w_ = frame.width();
scene_h_ = frame.height();
}
auto upload_overlay(GLuint &texture, const ra3::render::image &source) -> void {
if (source.empty()) return;
this->make_texture2d(texture, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, static_cast<int>(source.width()),
static_cast<int>(source.height()), source.data(), true, false);
}
[[nodiscard]] auto create_terrain_textures(const ra3::terrain::gpu_terrain &terrain) -> bool {
this->make_texture2d(height_tex_, GL_R16, GL_RED, GL_UNSIGNED_SHORT, static_cast<int>(terrain.width),
static_cast<int>(terrain.height), terrain.heights.data(), false, false);
this->make_texture2d(cell_tex_, GL_RGBA16, GL_RGBA, GL_UNSIGNED_SHORT, static_cast<int>(terrain.width),
static_cast<int>(terrain.height), terrain.cell_data.data(), false, false);
if (atlas_tex_ == 0U) glGenTextures(1, &atlas_tex_);
glBindTexture(GL_TEXTURE_2D_ARRAY, atlas_tex_);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, static_cast<GLsizei>(terrain.layer_size), static_cast<GLsizei>(terrain.layer_size),
static_cast<GLsizei>(terrain.layer_count), 0, GL_RGBA, GL_UNSIGNED_BYTE, terrain.layers.data());
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_REPEAT);
return true;
}
auto destroy_textures() -> void {
for (GLuint *texture: {&scene_tex_, &height_tex_, &cell_tex_, &atlas_tex_, &label_tex_, &minimap_tex_}) {
if (*texture != 0U) glDeleteTextures(1, texture);
*texture = 0U;
}
}
/** Resize the backing store when the canvas changed; false on failure. */
[[nodiscard]] auto ensure_size() -> bool {
const auto [width, height] = this->window_size();
if (width <= 0 || height <= 0) return true;
swapchain_w_ = width;
swapchain_h_ = height;
return true;
}
auto draw_fullscreen(GLuint program, GLint rect_location, GLuint texture, const ra3::render::view_rect &dest, int window_w, int window_h)
-> void {
glUseProgram(program);
glBindVertexArray(vao_);
const float rect[4] = {dest.x / static_cast<float>(window_w), dest.y / static_cast<float>(window_h),
dest.w / static_cast<float>(window_w), dest.h / static_cast<float>(window_h)};
glUniform4fv(rect_location, 1, rect);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
glDrawArrays(GL_TRIANGLES, 0, 3);
}
auto draw_overlay(GLuint texture, core::uint32 w, core::uint32 h, float x, float y, int window_w, int window_h) -> void {
if (texture == 0U || w == 0U || h == 0U) return;
const ra3::render::view_rect rect{x, y, static_cast<float>(w), static_cast<float>(h)};
this->draw_fullscreen(scene_program_, scene_rect_loc_, texture, rect, window_w, window_h);
}
SDL_Window *window_ = nullptr;
SDL_GLContext context_ = nullptr;
GLuint vao_ = 0U;
GLuint scene_program_ = 0U;
GLint scene_rect_loc_ = -1;
GLuint terrain_program_ = 0U;
GLint terrain_cam_loc_ = -1;
GLint terrain_height_loc_ = -1;
GLint terrain_cell_loc_ = -1;
GLint terrain_atlas_loc_ = -1;
GLuint scene_tex_ = 0U;
core::uint32 scene_w_ = 0U;
core::uint32 scene_h_ = 0U;
bool terrain_ready_ = false;
GLuint height_tex_ = 0U;
GLuint cell_tex_ = 0U;
GLuint atlas_tex_ = 0U;
GLuint label_tex_ = 0U;
GLuint minimap_tex_ = 0U;
int swapchain_w_ = 0;
int swapchain_h_ = 0;
};
}
+25
View File
@@ -0,0 +1,25 @@
export module ra3.webgl;
import std;
export import ra3.core;
import ra3.render;
import ra3.client;
/**
* Fallback WebGL backend for builds without Emscripten. `init` fails so the
* caller can select another display; the class name matches the real
* `ra3.webgl` module so the backend factory in `ra3.display` compiles unchanged.
*/
export namespace ra3::webgl {
class webgl_display final : public ra3::client::display {
public:
[[nodiscard]] auto init(const ra3::client::display_options &) -> bool override { return false; }
[[nodiscard]] auto present(const ra3::render::image &, const ra3::render::view_rect &, bool) -> bool override { return false; }
[[nodiscard]] auto poll_event(ra3::render::ui_event &) -> bool override { return false; }
[[nodiscard]] auto window_size() const -> std::pair<int, int> override { return {0, 0}; }
[[nodiscard]] auto key_down(ra3::render::ui_key) const -> bool override { return false; }
auto shutdown() -> void override {}
[[nodiscard]] auto name() const -> std::string_view override { return "webgl(null)"; }
};
}
+91 -9
View File
@@ -1,12 +1,4 @@
#include <cstdint>
#include <cstdio>
#include <filesystem>
#include <fstream>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
import std;
import ra3;
namespace {
@@ -89,6 +81,25 @@ auto main() -> int {
check(game::to_string(game::faction::soviet) == "Soviet", "faction names round-trip");
// RA3 data: damage types, armour resolution and weapon target masks.
check(data::to_string(data::damage_type::auto_cannon) == "AUTO_CANNON", "damage type names round-trip");
check(data::damage_type_from_name("PRISM") == data::damage_type::prism, "damage type parses from asset name");
check(data::allied_scout_infantry_armor.adjust(data::damage_type::gun, 100.0F) == 1.0F, "scout armour takes 1% from GUN");
check(data::allied_scout_infantry_armor.adjust(data::damage_type::tesla, 100.0F) == 50.0F, "scout armour takes 50% from TESLA");
check(data::allied_anti_infantry_infantry_armor.adjust(data::damage_type::tesla, 100.0F) == 1000.0F, "peacekeeper armour takes 10x from TESLA");
check(data::allied_anti_infantry_infantry_armor.adjust(data::damage_type::unresistable, 100.0F) == 100.0F, "UNRESISTABLE bypasses armour");
check(data::maul_weapon.can_target(data::target_class::infantry), "the maul can target infantry");
check(!data::maul_weapon.can_target(data::target_class::vehicle), "the maul cannot target vehicles");
check(!data::maul_weapon.can_target(data::target_class::structure), "the maul cannot target structures");
check(!data::maul_weapon.can_target(data::target_class::aircraft), "the maul cannot target aircraft");
check(data::shotgun_weapon.can_target(data::target_class::infantry) && data::shotgun_weapon.can_target(data::target_class::structure),
"the shotgun hits infantry and structures");
check(!data::shotgun_weapon.can_target(data::target_class::aircraft), "the shotgun cannot hit aircraft");
check(data::cost_of(data::entity_kind::guardian_tank) == 950, "guardian tank costs 950");
check(data::max_health_of(data::entity_kind::power_plant) == 1000.0F, "power plant has 1000 health");
check(data::structure_of(data::entity_kind::power_plant).energy == 100, "power plant supplies 100 energy");
check(data::seconds_to_frames(1.0F) == 30U, "one second is 30 logic frames");
// RefPack: a literal-only stream decodes to its payload.
const std::vector<core::uint8> refpack{0x10, 0xFB, 0x00, 0x00, 0x03, 0xFF, 'A', 'B', 'C'};
check(fs::is_refpack(refpack), "literal RefPack stream is recognised");
@@ -128,11 +139,36 @@ auto main() -> int {
const auto composed = render::compose(decoded_tga, markers, scene);
check(composed.width() == 2U && composed.height() == 2U, "compose preserves size");
// Camera: wheel zoom, clamped pan and RA3-style edge scrolling.
render::view_camera camera;
camera.zoom = 2.0F;
camera.center_x = 0.5F;
camera.center_y = 0.5F;
camera.scroll(10.0F, 10.0F);
check(camera.center_x == 0.75F && camera.center_y == 0.75F, "camera clamps the centre to the map");
camera.zoom_by(100.0F);
check(camera.zoom == camera.max_zoom, "camera clamps the maximum zoom");
camera.zoom_by(0.001F);
check(camera.zoom == camera.min_zoom, "camera clamps the minimum zoom");
const auto fit_rect = camera.rect(512.0F, 512.0F, 1024.0F, 768.0F);
check(fit_rect.w == 768.0F && fit_rect.h == 768.0F && fit_rect.x == 128.0F && fit_rect.y == 0.0F, "camera letterboxes a square map into a wide window");
render::view_camera edge;
edge.zoom = 2.0F;
edge.center_x = 0.5F;
edge.center_y = 0.5F;
edge.edge_scroll(0.0F, 0.0F, 1024.0F, 768.0F, 1.0F);
check(edge.center_x < 0.5F && edge.center_y < 0.5F, "the cursor at the top-left edge scrolls up-left");
render::view_camera whole;
whole.edge_scroll(0.0F, 0.0F, 1024.0F, 768.0F, 1.0F);
check(whole.center_x == 0.5F && whole.center_y == 0.5F, "no edge scroll while the whole map fits");
// Skirmish: deterministic, resolves within the frame cap.
skirmish::match_config config;
config.seed = 42U;
const auto starts = skirmish::builtin_start_positions();
auto first = skirmish::skirmish_match::create(config, starts);
check(first.unit_count(0U) == 1U && first.unit_count(1U) == 1U, "each side starts with a construction yard");
const auto first_result = first.run();
auto second = skirmish::skirmish_match::create(config, starts);
const auto second_result = second.run();
@@ -140,6 +176,52 @@ auto main() -> int {
check(first_result.winner == 0 || first_result.winner == 1, "skirmish has a winner");
check(first_result.frames == second_result.frames && first_result.winner == second_result.winner, "skirmish is deterministic");
// Text/font: glyphs paint, spaces are blank and widths scale.
check(render::text_width("ABC", 2U) == 48U, "text width scales with glyph size");
render::image text_image(64U, 16U, render::black);
render::draw_text(text_image, 0, 0, "A", render::white, 2U);
int lit = 0;
for (std::size_t i = 0; i < 64U * 16U; ++i) {
if (text_image.data()[i] != render::black) ++lit;
}
check(lit > 0, "draw_text paints glyph pixels");
render::image space_image(64U, 16U, render::black);
render::draw_text(space_image, 0, 0, " ", render::white, 2U);
bool space_lit = false;
for (std::size_t i = 0; i < 64U * 16U; ++i) {
if (space_image.data()[i] != render::black) space_lit = true;
}
check(!space_lit, "a space paints nothing");
const auto box = render::fit_rect(1280.0F, 720.0F, 1920.0F, 1080.0F);
check(box.x == 0.0F && box.y == 0.0F && box.w == 1920.0F && box.h == 1080.0F, "fit_rect fills a same-aspect window");
// Map display names: decode a synthetic `gamestrings.csf` `MAP:` label.
std::vector<core::uint8> csf;
const auto put_le32 = [&csf](std::uint32_t v) {
for (int i = 0; i < 4; ++i) csf.push_back(static_cast<core::uint8>(v >> (8 * i)));
};
csf.insert(csf.end(), {' ', 'F', 'S', 'C'});
put_le32(3U);
put_le32(1U);
put_le32(1U);
for (int i = 0; i < 8; ++i) csf.push_back(0U);
csf.insert(csf.end(), {' ', 'L', 'B', 'L'});
put_le32(1U);
const std::string csf_label = "MAP:MAP_MP_2_FEASEL4";
put_le32(static_cast<std::uint32_t>(csf_label.size()));
csf.insert(csf.end(), csf_label.begin(), csf_label.end());
csf.insert(csf.end(), {' ', 'R', 'T', 'S'});
const std::string csf_value = "Battlebase Beta";
put_le32(static_cast<std::uint32_t>(csf_value.size()));
for (const auto ch: csf_value) {
csf.push_back(static_cast<core::uint8>(ch ^ 0xFF));
csf.push_back(0xFFU);
}
const auto names = map::parse_map_names(csf);
check(names.lookup("map_mp_2_feasel4") == "Battlebase Beta", "CSF map names decode (byte-XOR 0xFF)");
check(names.lookup("unknown_map") == "unknown_map", "an unknown id falls back to itself");
if (failures == 0) {
std::puts("ra3_tests: OK");
}
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 EnderTheCoder
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+170
View File
@@ -0,0 +1,170 @@
# libenderlog
A standalone C++26 logging library, built as a C++20/26 **module** (`import ender.log;`)
with **`std::stacktrace`** call-stack capture on severe records.
It was extracted from the `ender-physics` engine, where it started life as
`ender.log`, and turned into a library that has no other dependency — not on the
engine, not on a logging framework.
## Features
- **Leveled records.** `trace`, `debug`, `info`, `warn`, `error`, `critical`,
filtered by an atomic `minimum` check that is cheap enough to guard expensive
message construction: `if (log::enabled(log::level::debug)) { ... }`.
- **Source location.** Every record carries the file, line and function of the
caller, taken from `std::source_location` at the call site — exact even in a
stripped release binary, because it is a compile-time constant.
- **Call stacks.** Records at or above `options::stacktrace_from` carry a
formatted `std::stacktrace`. The frames belonging to the library itself are
stripped by symbol, so the first reported frame is the caller regardless of
the optimisation level (the level wrappers get inlined away under `-O`).
- **Pluggable sinks.** A `console_sink` (stderr by default) and a `memory_sink`
(for tests and in-game consoles) ship; `sink` is a small interface.
- **File output with archiving.** `file_sink` writes to a file and, on open,
moves an existing log aside to a timestamped archive, so a run never appends
onto a previous run's log. It can also rotate by size and bound how many
archives are kept.
- **No stacktrace? No problem.** Where `<stacktrace>` is missing (libc++, and
therefore every cross target), the module still compiles and records still
carry their call site — they simply have no stack.
## Requirements
C++26 modules and `import std;` need a recent toolchain:
| Requirement | Version |
|---|---|
| Compiler | **GCC 15+** (or Clang with a standard library that provides the `std` module) |
| CMake | **3.30+** (for `CMAKE_EXPERIMENTAL_CXX_IMPORT_STD`) |
| Standard library | libstdc++ for `std::stacktrace` |
Ubuntu 26.04's default `g++` (GCC 15) and CMake 4 satisfy this, and that is the
release the CI targets and the `.deb` is built for. Ubuntu 24.04 ships GCC 13 and
CMake 3.28 and cannot build `import std;` without extra toolchains, so it is not
supported.
## Building
```sh
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
ctest --test-dir build --output-on-failure
```
Options:
| Option | Default | Description |
|---|---|---|
| `ENDERLOG_WERROR` | `OFF` | Treat warnings as errors |
| `ENDERLOG_BACKTRACE_SYMBOLS` | `ON` | Link executables with `-rdynamic` so traces can name frames |
| `ENDERLOG_BUILD_TESTS` | `ON` | Build the test suite |
| `ENDERLOG_BUILD_EXAMPLES` | `ON` | Build the example program |
Build Debug or RelWithDebInfo when you need to read a trace: frame names come
from debug information, and a release build often reports application frames as
`<unknown>`.
## Using the library
### `add_subdirectory`
```cmake
add_subdirectory(libenderlog)
target_link_libraries(my_app PRIVATE enderlog::enderlog)
```
### Installed package
```cmake
find_package(enderlog REQUIRED)
target_link_libraries(my_app PRIVATE enderlog::enderlog)
```
The static archive is installed together with the module interface source
(`ender.log.cppm`) because a module's BMI is compiler-version-specific — the
consumer rebuilds it from the source.
### In code
```cpp
import std;
import ender.log;
namespace log = ender::log;
auto main() -> int {
log::configure({.minimum = log::level::debug, .stacktrace_from = log::level::warn});
log::info(std::format("body {} moved to {:.2f}", 7, 12.35)); // formatted by the caller
log::error("a body left the world"); // carries a stack trace
}
```
Example output:
```
[11:32:18] ERROR example: a body left the world (examples/main.cpp:8)
#0 simulate_one_step (examples/main.cpp:8)
#1 main (examples/main.cpp:20)
#2 <unknown>
#3 __libc_start_main
#4 _start
```
### Configuration
```cpp
log::configure({
.minimum = log::level::debug, // drop everything below this
.stacktrace_from = log::level::error, // capture a stack at/above this
.stacktrace_depth = 16, // max frames kept
.stacktrace_skip = 2, // frames dropped before the caller is found
});
```
`log::current_options()` reads it back, `log::add_sink(...)` adds a destination,
and `log::set_sinks({...})` replaces them.
### Writing to a file
```cpp
namespace log = ender::log;
// Archive any existing enderlog.log to enderlog.log.<timestamp>, then start a
// fresh file for this run. Rotate at 64 KiB and keep the last 5 archives.
auto sink = log::add_file_sink("enderlog.log", {.max_file_size = 64 * 1024, .max_archives = 5});
```
- **No appending onto a previous run.** On open, an existing non-empty
`enderlog.log` is renamed to `enderlog.log.<YYYYmmdd-HHMMSS>` before the new
file is created, so every run gets its own file and the previous run's log is
preserved. A leftover empty file is simply replaced.
- `file_options::max_file_size` (0 disables) rotates the active file mid-run the
same way, and never archives an empty file. `file_options::max_archives`
(0 keeps all) deletes the oldest archives beyond the limit.
- `file_options::flush_each_record` (on by default) flushes after every record so
a crash keeps the tail.
- `add_file_sink` adds the sink to the global logger and returns it; `path()` and
`archives()` expose what it wrote. The `file_sink` class can also be used
directly and installed with `set_sinks`.
## Packaging
`cpack` produces a Debian package:
```sh
cmake -S . -B build -DENDERLOG_DISTRO=ubuntu26.04
cmake --build build -j
cd build
cpack
# -> libenderlog-dev_0.0.1_amd64_ubuntu26.04.deb
```
The package installs the static archive, the module interface source and the
CMake package config. CI builds it for Ubuntu 26.04 and publishes it as a job
artifact.
## License
MIT — see [LICENSE](LICENSE).
+542
View File
@@ -0,0 +1,542 @@
/**
* Logging with call-stack capture.
*
* Records carry a level, message, source location and — for severe enough
* levels — a formatted `std::stacktrace`. Capturing a trace walks the stack and
* reads debug information, so it is only done when the record's level is at or
* above `options::stacktrace_from`, and the whole call is skipped when the
* level is disabled.
*
* `std::stacktrace` is implemented by libstdc++ only. With GCC the module has
* to link `stdc++exp` (the static library that implements it); the CMake target
* takes care of that. File and line numbers in the trace come from debug
* information, so build with `-g` (Debug or RelWithDebInfo) to see them; symbol
* names work in any build.
*/
module;
// libc++ (the OpenRA3 toolchain) has no <stacktrace>, so this vendored copy adds
// a native fallback for the frames: Windows CaptureStackBackTrace and POSIX
// execinfo. These live in the global module fragment because they are C headers.
#if defined(_WIN32)
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <windows.h>
#elif (defined(__unix__) || defined(__APPLE__)) && !defined(__EMSCRIPTEN__)
#include <execinfo.h>
#endif
export module ender.log;
import std;
export namespace ender::log {
/** Severity of a record, ordered from most to least verbose. */
enum class level: std::uint8_t {
trace = 0,
debug,
info,
warn,
error,
critical,
};
/** Short upper-case name of a level, for output. */
[[nodiscard]] inline auto to_string(const level severity) -> std::string_view {
switch (severity) {
case level::trace: return "TRACE";
case level::debug: return "DEBUG";
case level::info: return "INFO";
case level::warn: return "WARN";
case level::error: return "ERROR";
case level::critical: return "CRITICAL";
}
return "?";
}
/** Logger configuration. */
struct options {
/** Records below this level are dropped before anything is built. */
level minimum{level::info};
/** Capture a stack trace for records at this level and above. */
level stacktrace_from{level::error};
/** Maximum number of frames kept in a captured trace. */
std::size_t stacktrace_depth{16};
/**
* Frames to drop from the top of a captured trace.
*
* The default drops `capture_stacktrace` and `emit`, which always exist
* as frames. The level wrappers are inlined away in optimised builds, so
* a fixed count cannot cover them; any leading frame that belongs to
* this module is therefore stripped by name instead, which keeps the
* caller visible whether or not the wrappers were inlined.
*/
std::size_t stacktrace_skip{2};
};
/** One log record. */
struct record {
level severity{level::info};
std::string message{};
std::string file{};
std::uint32_t line{0};
std::string function{};
/** Formatted call stack; empty when it was not captured. */
std::string stacktrace{};
std::chrono::system_clock::time_point time{};
std::thread::id thread{};
[[nodiscard]] auto has_stacktrace() const -> bool { return !stacktrace.empty(); }
};
namespace detail {
/**
* Render one record as a human-readable block: a header line and, when
* present, the indented stack frames. Shared by the stream sinks.
*/
[[nodiscard]] inline auto format_record(const record &entry) -> std::string {
auto text = std::format("[{:%H:%M:%S}] {:<8} {}",
std::chrono::floor<std::chrono::seconds>(entry.time),
to_string(entry.severity),
entry.message);
if (!entry.file.empty()) {
text += std::format(" ({}:{})", entry.file, entry.line);
}
text += '\n';
if (entry.has_stacktrace()) {
text += entry.stacktrace;
}
return text;
}
}
/** Where records go. */
class sink {
public:
virtual ~sink() = default;
/** Receive one record; called with the logger's mutex held. */
virtual auto write(const record &entry) -> void = 0;
/** Flush any buffering. */
virtual auto flush() -> void {}
};
/** Writes a human-readable line per record to a stream (stderr by default). */
class console_sink final: public sink {
public:
explicit console_sink(std::ostream &stream = std::cerr): stream_(&stream) {}
auto write(const record &entry) -> void override {
*stream_ << detail::format_record(entry);
stream_->flush();
}
private:
std::ostream *stream_;
};
/** Keeps every record in memory; useful for tests and in-game consoles. */
class memory_sink final: public sink {
public:
auto write(const record &entry) -> void override {
const auto lock = std::scoped_lock{mutex_};
records_.push_back(entry);
}
[[nodiscard]] auto records() const -> std::vector<record> {
const auto lock = std::scoped_lock{mutex_};
return records_;
}
[[nodiscard]] auto size() const -> std::size_t {
const auto lock = std::scoped_lock{mutex_};
return records_.size();
}
auto clear() -> void {
const auto lock = std::scoped_lock{mutex_};
records_.clear();
}
private:
mutable std::mutex mutex_;
std::vector<record> records_;
};
/** Configuration for `file_sink`. */
struct file_options {
/** Move an existing log file aside to an archive when the sink opens it. */
bool archive_on_open{true};
/** Flush after every record, so the tail survives a crash. */
bool flush_each_record{true};
/** Rotate once the active file would grow past this many bytes; 0 disables. */
std::size_t max_file_size{0};
/** Keep at most this many archives, dropping the oldest first; 0 keeps them all. */
std::size_t max_archives{0};
};
/**
* Writes records to a file, archiving the previous one on open.
*
* `path` is the active file. When the sink opens it and the file already
* holds data, that file is renamed to a timestamped archive first, so a run
* never appends onto a previous run's log: every start begins a fresh file
* and the old one is preserved as `<path>.<YYYYmmdd-HHMMSS>`. The same
* happens mid-run once the active file passes `file_options::max_file_size`.
* `file_options::max_archives` bounds how many archives are kept.
*
* As with every sink, `write` is called with the logger's mutex held, so one
* sink is safe to share; it is not safe for two processes to point at the
* same file.
*/
class file_sink final: public sink {
public:
explicit file_sink(std::filesystem::path path, const file_options options = {})
: path_(std::move(path)), options_(options) {
if (options_.archive_on_open && std::filesystem::exists(path_)) {
if (std::filesystem::file_size(path_) > 0) {
archive_current();
} else {
std::filesystem::remove(path_);
}
}
open();
}
auto write(const record &entry) -> void override {
const auto block = detail::format_record(entry);
// Rotate before writing, but never rotate an empty file: that would
// archive nothing and lose the record that is about to be written.
if (options_.max_file_size > 0 && size_ > 0 && size_ + block.size() > options_.max_file_size) {
archive_current();
open();
}
stream_ << block;
size_ += block.size();
if (options_.flush_each_record) stream_.flush();
}
auto flush() -> void override {
if (stream_.is_open()) stream_.flush();
}
/** The active log file. */
[[nodiscard]] auto path() const -> const std::filesystem::path & { return path_; }
/** Archives this sink created, oldest first. */
[[nodiscard]] auto archives() const -> const std::vector<std::filesystem::path> & { return archives_; }
private:
auto open() -> void {
stream_.clear();
stream_.open(path_, std::ios::out | std::ios::trunc | std::ios::binary);
size_ = 0;
}
auto archive_current() -> void {
if (stream_.is_open()) stream_.close();
const auto stamp = std::format("{:%Y%m%d-%H%M%S}",
std::chrono::floor<std::chrono::seconds>(std::chrono::system_clock::now()));
auto archive = path_;
archive += "." + stamp;
// Two rotations can land in the same second; disambiguate with a
// counter rather than overwrite the earlier archive.
for (auto counter = 1; std::filesystem::exists(archive); ++counter) {
archive = path_;
archive += std::format(".{}.{}", stamp, counter);
}
std::filesystem::rename(path_, archive);
archives_.push_back(archive);
prune_archives();
}
auto prune_archives() -> void {
if (options_.max_archives == 0) return;
while (archives_.size() > options_.max_archives) {
auto ignored = std::error_code{};
std::filesystem::remove(archives_.front(), ignored);
archives_.erase(archives_.begin());
}
}
std::filesystem::path path_;
file_options options_;
std::ofstream stream_;
std::size_t size_{0};
std::vector<std::filesystem::path> archives_;
};
/*
* <stacktrace> is not portable: libc++ has never implemented it, and only
* libstdc++ provides it here. Where it is missing, records still carry their
* call site through std::source_location - they simply carry no stack, and
* everything below degrades to an empty string rather than the module
* refusing to compile.
*
* CMake decides this and passes it in, rather than the module testing
* `__cpp_lib_stacktrace` itself: feature-test macros come from the standard
* library's headers, and `import std;` does not export them, so probing for
* one here silently reports "absent" even on libstdc++, which has it.
*/
#ifndef ENDERLOG_HAS_STACKTRACE
#define ENDERLOG_HAS_STACKTRACE 0
#endif
#if ENDERLOG_HAS_STACKTRACE
/** True when a frame belongs to the logging module itself. */
[[nodiscard]] inline auto is_logger_frame(const std::stacktrace_entry &entry) -> bool {
if (entry.description().find("ender::log") != std::string::npos) return true;
return entry.source_file().find("ender.log.cppm") != std::string::npos;
}
/**
* Render a trace as one indented line per frame.
*
* @param skip_logger_frames Drop leading frames belonging to this module, so
* the first reported frame is the caller. This is what makes the
* output stable across optimisation levels: in a release build the
* level wrappers are inlined into the caller, so counting frames
* alone would either over- or under-skip.
*/
[[nodiscard]] inline auto format_stacktrace(const std::stacktrace &trace,
const bool skip_logger_frames = true) -> std::string {
if (trace.empty()) return " <empty stacktrace>\n";
auto first = std::size_t{0};
if (skip_logger_frames) {
while (first < trace.size() && is_logger_frame(trace.at(first))) ++first;
if (first >= trace.size()) first = 0; // never hide the whole trace
}
auto text = std::string{};
for (auto index = first; index < trace.size(); ++index) {
const auto &entry = trace.at(index);
auto description = entry.description();
if (description.empty()) description = "<unknown>";
auto location = std::string{};
if (!entry.source_file().empty()) {
location = std::format(" ({}:{})", entry.source_file(), entry.source_line());
}
text += std::format(" #{:<3}{}{}\n", index - first, description, location);
}
return text;
}
/** Capture and render the current call stack, innermost frame first. */
[[nodiscard]] inline auto capture_stacktrace(const std::size_t skip = 2, const std::size_t depth = 16)
-> std::string {
return format_stacktrace(std::stacktrace::current(skip, depth));
}
#else
/**
* libc++ fallback: capture the current call stack with the platform's own
* backtrace API and render one indented line per frame.
*
* On Windows a frame is reported as `module.dll+0xRVA` (a MinGW release build
* has DWARF, not the PDB symbols dbghelp resolves, so a module+offset is the
* practical answer). On POSIX `backtrace_symbols` is used, which names a
* frame when the executable was linked with `-rdynamic`.
*/
#if defined(_WIN32)
[[nodiscard]] inline auto symbolicate_frame(void *address) -> std::string {
const auto value = reinterpret_cast<std::uintptr_t>(address);
HMODULE module = nullptr;
if (GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
reinterpret_cast<LPCWSTR>(address), &module)) {
wchar_t wide[260] = L"?";
GetModuleFileNameW(module, wide, 260U);
char narrow[260] = "?";
WideCharToMultiByte(CP_UTF8, 0, wide, -1, narrow, sizeof(narrow), nullptr, nullptr);
const char *base = std::strrchr(narrow, '\\');
return std::format("{}+0x{:X}", base != nullptr ? base + 1 : narrow, value - reinterpret_cast<std::uintptr_t>(module));
}
return std::format("0x{:X}", value);
}
#endif
[[nodiscard]] inline auto capture_stacktrace(const std::size_t skip = 2, const std::size_t depth = 16) -> std::string {
constexpr std::size_t max_frames = 64;
#if defined(_WIN32)
void *frames[max_frames] = {};
const auto want = static_cast<DWORD>(std::min(max_frames, skip + std::max<std::size_t>(depth, 1U)));
const USHORT count = CaptureStackBackTrace(static_cast<DWORD>(skip), want, frames, nullptr);
auto text = std::string{};
for (USHORT index = 0; index < count; ++index) {
text += std::format(" #{:<3}{}\n", index, symbolicate_frame(frames[index]));
}
return text;
#elif (defined(__unix__) || defined(__APPLE__)) && !defined(__EMSCRIPTEN__)
void *frames[max_frames] = {};
const int count = ::backtrace(frames, static_cast<int>(std::min(max_frames, skip + std::max<std::size_t>(depth, 1U))));
char **symbols = ::backtrace_symbols(frames, count);
auto text = std::string{};
for (int index = static_cast<int>(std::min<std::size_t>(skip, static_cast<std::size_t>(count))); index < count; ++index) {
text += std::format(" #{:<3}{}\n", index - static_cast<int>(skip),
symbols != nullptr ? symbols[index] : std::format("0x{:X}", reinterpret_cast<std::uintptr_t>(frames[index])));
}
if (symbols != nullptr) std::free(symbols);
return text;
#else
(void) skip;
(void) depth;
return {};
#endif
}
#endif
/**
* The process-wide logger.
*
* `enabled` is an atomic read so hot paths can guard expensive message
* construction; everything else takes the mutex.
*/
class logger {
public:
[[nodiscard]] static auto instance() -> logger & {
static logger shared;
return shared;
}
auto configure(const options &config) -> void {
const auto lock = std::scoped_lock{mutex_};
options_ = config;
minimum_.store(static_cast<std::uint8_t>(config.minimum), std::memory_order_relaxed);
}
[[nodiscard]] auto configuration() const -> options {
const auto lock = std::scoped_lock{mutex_};
return options_;
}
[[nodiscard]] auto enabled(const level severity) const -> bool {
return static_cast<std::uint8_t>(severity) >= minimum_.load(std::memory_order_relaxed);
}
auto add_sink(std::shared_ptr<sink> destination) -> void {
const auto lock = std::scoped_lock{mutex_};
sinks_.push_back(std::move(destination));
}
auto set_sinks(std::vector<std::shared_ptr<sink>> destinations) -> void {
const auto lock = std::scoped_lock{mutex_};
sinks_ = std::move(destinations);
}
auto dispatch(const record &entry) -> void {
const auto lock = std::scoped_lock{mutex_};
for (const auto &destination: sinks_) {
destination->write(entry);
}
}
private:
logger() { sinks_.push_back(std::make_shared<console_sink>()); }
mutable std::mutex mutex_;
options options_{};
std::atomic<std::uint8_t> minimum_{static_cast<std::uint8_t>(options{}.minimum)};
std::vector<std::shared_ptr<sink>> sinks_;
};
/** Apply a configuration to the process-wide logger. */
inline auto configure(const options &config) -> void { logger::instance().configure(config); }
/** Current configuration of the process-wide logger. */
[[nodiscard]] inline auto current_options() -> options { return logger::instance().configuration(); }
/** Route records to an additional sink. */
inline auto add_sink(std::shared_ptr<sink> destination) -> void {
logger::instance().add_sink(std::move(destination));
}
/**
* Create a file sink, route records to it, and hand it back.
*
* The previous log at `path` is archived on open, so this never appends onto
* an earlier run.
*
* @return The sink, so the caller can inspect the archives it creates.
*/
inline auto add_file_sink(std::filesystem::path path, const file_options &options = {})
-> std::shared_ptr<file_sink> {
auto destination = std::make_shared<file_sink>(std::move(path), options);
logger::instance().add_sink(destination);
return destination;
}
/** Replace every sink. */
inline auto set_sinks(std::vector<std::shared_ptr<sink>> destinations) -> void {
logger::instance().set_sinks(std::move(destinations));
}
/** True when a record at this level would be emitted. */
[[nodiscard]] inline auto enabled(const level severity) -> bool { return logger::instance().enabled(severity); }
namespace detail {
/** Build and dispatch one record. Not for direct use. */
inline auto emit(const level severity, std::string message, const std::source_location location) -> void {
auto &target = logger::instance();
if (!target.enabled(severity)) return;
const auto config = target.configuration();
auto entry = record{
.severity = severity,
.message = std::move(message),
.file = location.file_name(),
.line = static_cast<std::uint32_t>(location.line()),
.function = location.function_name(),
.time = std::chrono::system_clock::now(),
.thread = std::this_thread::get_id(),
};
if (severity >= config.stacktrace_from) {
entry.stacktrace = capture_stacktrace(config.stacktrace_skip, config.stacktrace_depth);
}
target.dispatch(entry);
}
}
/**
* Emit a record.
*
* The source location defaults to the call site, so this reports exactly
* where it was called from.
*/
inline auto log(const level severity,
std::string message,
const std::source_location location = std::source_location::current()) -> void {
detail::emit(severity, std::move(message), location);
}
inline auto trace(std::string message, const std::source_location location = std::source_location::current())
-> void {
detail::emit(level::trace, std::move(message), location);
}
inline auto debug(std::string message, const std::source_location location = std::source_location::current())
-> void {
detail::emit(level::debug, std::move(message), location);
}
inline auto info(std::string message, const std::source_location location = std::source_location::current())
-> void {
detail::emit(level::info, std::move(message), location);
}
inline auto warn(std::string message, const std::source_location location = std::source_location::current())
-> void {
detail::emit(level::warn, std::move(message), location);
}
/** Emits at `error`, which captures a call stack by default. */
inline auto error(std::string message, const std::source_location location = std::source_location::current())
-> void {
detail::emit(level::error, std::move(message), location);
}
inline auto critical(std::string message, const std::source_location location = std::source_location::current())
-> void {
detail::emit(level::critical, std::move(message), location);
}
}
+19
View File
@@ -0,0 +1,19 @@
Copyright (c) 2018-2024 Arseny Kapoulkine
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+3365
View File
File diff suppressed because it is too large Load Diff
+2176
View File
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,394 @@
#ifndef VULKAN_VIDEO_CODEC_AV1STD_H_
#define VULKAN_VIDEO_CODEC_AV1STD_H_ 1
/*
** Copyright 2015-2024 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
*/
/*
** This header is generated from the Khronos Vulkan XML API Registry.
**
*/
#ifdef __cplusplus
extern "C" {
#endif
// vulkan_video_codec_av1std is a preprocessor guard. Do not pass it to API calls.
#define vulkan_video_codec_av1std 1
#include "vulkan_video_codecs_common.h"
#define STD_VIDEO_AV1_NUM_REF_FRAMES 8
#define STD_VIDEO_AV1_REFS_PER_FRAME 7
#define STD_VIDEO_AV1_TOTAL_REFS_PER_FRAME 8
#define STD_VIDEO_AV1_MAX_TILE_COLS 64
#define STD_VIDEO_AV1_MAX_TILE_ROWS 64
#define STD_VIDEO_AV1_MAX_SEGMENTS 8
#define STD_VIDEO_AV1_SEG_LVL_MAX 8
#define STD_VIDEO_AV1_PRIMARY_REF_NONE 7
#define STD_VIDEO_AV1_SELECT_INTEGER_MV 2
#define STD_VIDEO_AV1_SELECT_SCREEN_CONTENT_TOOLS 2
#define STD_VIDEO_AV1_SKIP_MODE_FRAMES 2
#define STD_VIDEO_AV1_MAX_LOOP_FILTER_STRENGTHS 4
#define STD_VIDEO_AV1_LOOP_FILTER_ADJUSTMENTS 2
#define STD_VIDEO_AV1_MAX_CDEF_FILTER_STRENGTHS 8
#define STD_VIDEO_AV1_MAX_NUM_PLANES 3
#define STD_VIDEO_AV1_GLOBAL_MOTION_PARAMS 6
#define STD_VIDEO_AV1_MAX_NUM_Y_POINTS 14
#define STD_VIDEO_AV1_MAX_NUM_CB_POINTS 10
#define STD_VIDEO_AV1_MAX_NUM_CR_POINTS 10
#define STD_VIDEO_AV1_MAX_NUM_POS_LUMA 24
#define STD_VIDEO_AV1_MAX_NUM_POS_CHROMA 25
typedef enum StdVideoAV1Profile {
STD_VIDEO_AV1_PROFILE_MAIN = 0,
STD_VIDEO_AV1_PROFILE_HIGH = 1,
STD_VIDEO_AV1_PROFILE_PROFESSIONAL = 2,
STD_VIDEO_AV1_PROFILE_INVALID = 0x7FFFFFFF,
STD_VIDEO_AV1_PROFILE_MAX_ENUM = 0x7FFFFFFF
} StdVideoAV1Profile;
typedef enum StdVideoAV1Level {
STD_VIDEO_AV1_LEVEL_2_0 = 0,
STD_VIDEO_AV1_LEVEL_2_1 = 1,
STD_VIDEO_AV1_LEVEL_2_2 = 2,
STD_VIDEO_AV1_LEVEL_2_3 = 3,
STD_VIDEO_AV1_LEVEL_3_0 = 4,
STD_VIDEO_AV1_LEVEL_3_1 = 5,
STD_VIDEO_AV1_LEVEL_3_2 = 6,
STD_VIDEO_AV1_LEVEL_3_3 = 7,
STD_VIDEO_AV1_LEVEL_4_0 = 8,
STD_VIDEO_AV1_LEVEL_4_1 = 9,
STD_VIDEO_AV1_LEVEL_4_2 = 10,
STD_VIDEO_AV1_LEVEL_4_3 = 11,
STD_VIDEO_AV1_LEVEL_5_0 = 12,
STD_VIDEO_AV1_LEVEL_5_1 = 13,
STD_VIDEO_AV1_LEVEL_5_2 = 14,
STD_VIDEO_AV1_LEVEL_5_3 = 15,
STD_VIDEO_AV1_LEVEL_6_0 = 16,
STD_VIDEO_AV1_LEVEL_6_1 = 17,
STD_VIDEO_AV1_LEVEL_6_2 = 18,
STD_VIDEO_AV1_LEVEL_6_3 = 19,
STD_VIDEO_AV1_LEVEL_7_0 = 20,
STD_VIDEO_AV1_LEVEL_7_1 = 21,
STD_VIDEO_AV1_LEVEL_7_2 = 22,
STD_VIDEO_AV1_LEVEL_7_3 = 23,
STD_VIDEO_AV1_LEVEL_INVALID = 0x7FFFFFFF,
STD_VIDEO_AV1_LEVEL_MAX_ENUM = 0x7FFFFFFF
} StdVideoAV1Level;
typedef enum StdVideoAV1FrameType {
STD_VIDEO_AV1_FRAME_TYPE_KEY = 0,
STD_VIDEO_AV1_FRAME_TYPE_INTER = 1,
STD_VIDEO_AV1_FRAME_TYPE_INTRA_ONLY = 2,
STD_VIDEO_AV1_FRAME_TYPE_SWITCH = 3,
STD_VIDEO_AV1_FRAME_TYPE_INVALID = 0x7FFFFFFF,
STD_VIDEO_AV1_FRAME_TYPE_MAX_ENUM = 0x7FFFFFFF
} StdVideoAV1FrameType;
typedef enum StdVideoAV1ReferenceName {
STD_VIDEO_AV1_REFERENCE_NAME_INTRA_FRAME = 0,
STD_VIDEO_AV1_REFERENCE_NAME_LAST_FRAME = 1,
STD_VIDEO_AV1_REFERENCE_NAME_LAST2_FRAME = 2,
STD_VIDEO_AV1_REFERENCE_NAME_LAST3_FRAME = 3,
STD_VIDEO_AV1_REFERENCE_NAME_GOLDEN_FRAME = 4,
STD_VIDEO_AV1_REFERENCE_NAME_BWDREF_FRAME = 5,
STD_VIDEO_AV1_REFERENCE_NAME_ALTREF2_FRAME = 6,
STD_VIDEO_AV1_REFERENCE_NAME_ALTREF_FRAME = 7,
STD_VIDEO_AV1_REFERENCE_NAME_INVALID = 0x7FFFFFFF,
STD_VIDEO_AV1_REFERENCE_NAME_MAX_ENUM = 0x7FFFFFFF
} StdVideoAV1ReferenceName;
typedef enum StdVideoAV1InterpolationFilter {
STD_VIDEO_AV1_INTERPOLATION_FILTER_EIGHTTAP = 0,
STD_VIDEO_AV1_INTERPOLATION_FILTER_EIGHTTAP_SMOOTH = 1,
STD_VIDEO_AV1_INTERPOLATION_FILTER_EIGHTTAP_SHARP = 2,
STD_VIDEO_AV1_INTERPOLATION_FILTER_BILINEAR = 3,
STD_VIDEO_AV1_INTERPOLATION_FILTER_SWITCHABLE = 4,
STD_VIDEO_AV1_INTERPOLATION_FILTER_INVALID = 0x7FFFFFFF,
STD_VIDEO_AV1_INTERPOLATION_FILTER_MAX_ENUM = 0x7FFFFFFF
} StdVideoAV1InterpolationFilter;
typedef enum StdVideoAV1TxMode {
STD_VIDEO_AV1_TX_MODE_ONLY_4X4 = 0,
STD_VIDEO_AV1_TX_MODE_LARGEST = 1,
STD_VIDEO_AV1_TX_MODE_SELECT = 2,
STD_VIDEO_AV1_TX_MODE_INVALID = 0x7FFFFFFF,
STD_VIDEO_AV1_TX_MODE_MAX_ENUM = 0x7FFFFFFF
} StdVideoAV1TxMode;
typedef enum StdVideoAV1FrameRestorationType {
STD_VIDEO_AV1_FRAME_RESTORATION_TYPE_NONE = 0,
STD_VIDEO_AV1_FRAME_RESTORATION_TYPE_WIENER = 1,
STD_VIDEO_AV1_FRAME_RESTORATION_TYPE_SGRPROJ = 2,
STD_VIDEO_AV1_FRAME_RESTORATION_TYPE_SWITCHABLE = 3,
STD_VIDEO_AV1_FRAME_RESTORATION_TYPE_INVALID = 0x7FFFFFFF,
STD_VIDEO_AV1_FRAME_RESTORATION_TYPE_MAX_ENUM = 0x7FFFFFFF
} StdVideoAV1FrameRestorationType;
typedef enum StdVideoAV1ColorPrimaries {
STD_VIDEO_AV1_COLOR_PRIMARIES_BT_709 = 1,
STD_VIDEO_AV1_COLOR_PRIMARIES_UNSPECIFIED = 2,
STD_VIDEO_AV1_COLOR_PRIMARIES_BT_470_M = 4,
STD_VIDEO_AV1_COLOR_PRIMARIES_BT_470_B_G = 5,
STD_VIDEO_AV1_COLOR_PRIMARIES_BT_601 = 6,
STD_VIDEO_AV1_COLOR_PRIMARIES_SMPTE_240 = 7,
STD_VIDEO_AV1_COLOR_PRIMARIES_GENERIC_FILM = 8,
STD_VIDEO_AV1_COLOR_PRIMARIES_BT_2020 = 9,
STD_VIDEO_AV1_COLOR_PRIMARIES_XYZ = 10,
STD_VIDEO_AV1_COLOR_PRIMARIES_SMPTE_431 = 11,
STD_VIDEO_AV1_COLOR_PRIMARIES_SMPTE_432 = 12,
STD_VIDEO_AV1_COLOR_PRIMARIES_EBU_3213 = 22,
STD_VIDEO_AV1_COLOR_PRIMARIES_INVALID = 0x7FFFFFFF,
// STD_VIDEO_AV1_COLOR_PRIMARIES_BT_UNSPECIFIED is a deprecated alias
STD_VIDEO_AV1_COLOR_PRIMARIES_BT_UNSPECIFIED = STD_VIDEO_AV1_COLOR_PRIMARIES_UNSPECIFIED,
STD_VIDEO_AV1_COLOR_PRIMARIES_MAX_ENUM = 0x7FFFFFFF
} StdVideoAV1ColorPrimaries;
typedef enum StdVideoAV1TransferCharacteristics {
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_RESERVED_0 = 0,
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_BT_709 = 1,
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_UNSPECIFIED = 2,
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_RESERVED_3 = 3,
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_BT_470_M = 4,
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_BT_470_B_G = 5,
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_BT_601 = 6,
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_SMPTE_240 = 7,
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_LINEAR = 8,
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_LOG_100 = 9,
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_LOG_100_SQRT10 = 10,
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_IEC_61966 = 11,
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_BT_1361 = 12,
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_SRGB = 13,
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_BT_2020_10_BIT = 14,
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_BT_2020_12_BIT = 15,
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_SMPTE_2084 = 16,
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_SMPTE_428 = 17,
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_HLG = 18,
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_INVALID = 0x7FFFFFFF,
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_MAX_ENUM = 0x7FFFFFFF
} StdVideoAV1TransferCharacteristics;
typedef enum StdVideoAV1MatrixCoefficients {
STD_VIDEO_AV1_MATRIX_COEFFICIENTS_IDENTITY = 0,
STD_VIDEO_AV1_MATRIX_COEFFICIENTS_BT_709 = 1,
STD_VIDEO_AV1_MATRIX_COEFFICIENTS_UNSPECIFIED = 2,
STD_VIDEO_AV1_MATRIX_COEFFICIENTS_RESERVED_3 = 3,
STD_VIDEO_AV1_MATRIX_COEFFICIENTS_FCC = 4,
STD_VIDEO_AV1_MATRIX_COEFFICIENTS_BT_470_B_G = 5,
STD_VIDEO_AV1_MATRIX_COEFFICIENTS_BT_601 = 6,
STD_VIDEO_AV1_MATRIX_COEFFICIENTS_SMPTE_240 = 7,
STD_VIDEO_AV1_MATRIX_COEFFICIENTS_SMPTE_YCGCO = 8,
STD_VIDEO_AV1_MATRIX_COEFFICIENTS_BT_2020_NCL = 9,
STD_VIDEO_AV1_MATRIX_COEFFICIENTS_BT_2020_CL = 10,
STD_VIDEO_AV1_MATRIX_COEFFICIENTS_SMPTE_2085 = 11,
STD_VIDEO_AV1_MATRIX_COEFFICIENTS_CHROMAT_NCL = 12,
STD_VIDEO_AV1_MATRIX_COEFFICIENTS_CHROMAT_CL = 13,
STD_VIDEO_AV1_MATRIX_COEFFICIENTS_ICTCP = 14,
STD_VIDEO_AV1_MATRIX_COEFFICIENTS_INVALID = 0x7FFFFFFF,
STD_VIDEO_AV1_MATRIX_COEFFICIENTS_MAX_ENUM = 0x7FFFFFFF
} StdVideoAV1MatrixCoefficients;
typedef enum StdVideoAV1ChromaSamplePosition {
STD_VIDEO_AV1_CHROMA_SAMPLE_POSITION_UNKNOWN = 0,
STD_VIDEO_AV1_CHROMA_SAMPLE_POSITION_VERTICAL = 1,
STD_VIDEO_AV1_CHROMA_SAMPLE_POSITION_COLOCATED = 2,
STD_VIDEO_AV1_CHROMA_SAMPLE_POSITION_RESERVED = 3,
STD_VIDEO_AV1_CHROMA_SAMPLE_POSITION_INVALID = 0x7FFFFFFF,
STD_VIDEO_AV1_CHROMA_SAMPLE_POSITION_MAX_ENUM = 0x7FFFFFFF
} StdVideoAV1ChromaSamplePosition;
typedef struct StdVideoAV1ColorConfigFlags {
uint32_t mono_chrome : 1;
uint32_t color_range : 1;
uint32_t separate_uv_delta_q : 1;
uint32_t color_description_present_flag : 1;
uint32_t reserved : 28;
} StdVideoAV1ColorConfigFlags;
typedef struct StdVideoAV1ColorConfig {
StdVideoAV1ColorConfigFlags flags;
uint8_t BitDepth;
uint8_t subsampling_x;
uint8_t subsampling_y;
uint8_t reserved1;
StdVideoAV1ColorPrimaries color_primaries;
StdVideoAV1TransferCharacteristics transfer_characteristics;
StdVideoAV1MatrixCoefficients matrix_coefficients;
StdVideoAV1ChromaSamplePosition chroma_sample_position;
} StdVideoAV1ColorConfig;
typedef struct StdVideoAV1TimingInfoFlags {
uint32_t equal_picture_interval : 1;
uint32_t reserved : 31;
} StdVideoAV1TimingInfoFlags;
typedef struct StdVideoAV1TimingInfo {
StdVideoAV1TimingInfoFlags flags;
uint32_t num_units_in_display_tick;
uint32_t time_scale;
uint32_t num_ticks_per_picture_minus_1;
} StdVideoAV1TimingInfo;
typedef struct StdVideoAV1LoopFilterFlags {
uint32_t loop_filter_delta_enabled : 1;
uint32_t loop_filter_delta_update : 1;
uint32_t reserved : 30;
} StdVideoAV1LoopFilterFlags;
typedef struct StdVideoAV1LoopFilter {
StdVideoAV1LoopFilterFlags flags;
uint8_t loop_filter_level[STD_VIDEO_AV1_MAX_LOOP_FILTER_STRENGTHS];
uint8_t loop_filter_sharpness;
uint8_t update_ref_delta;
int8_t loop_filter_ref_deltas[STD_VIDEO_AV1_TOTAL_REFS_PER_FRAME];
uint8_t update_mode_delta;
int8_t loop_filter_mode_deltas[STD_VIDEO_AV1_LOOP_FILTER_ADJUSTMENTS];
} StdVideoAV1LoopFilter;
typedef struct StdVideoAV1QuantizationFlags {
uint32_t using_qmatrix : 1;
uint32_t diff_uv_delta : 1;
uint32_t reserved : 30;
} StdVideoAV1QuantizationFlags;
typedef struct StdVideoAV1Quantization {
StdVideoAV1QuantizationFlags flags;
uint8_t base_q_idx;
int8_t DeltaQYDc;
int8_t DeltaQUDc;
int8_t DeltaQUAc;
int8_t DeltaQVDc;
int8_t DeltaQVAc;
uint8_t qm_y;
uint8_t qm_u;
uint8_t qm_v;
} StdVideoAV1Quantization;
typedef struct StdVideoAV1Segmentation {
uint8_t FeatureEnabled[STD_VIDEO_AV1_MAX_SEGMENTS];
int16_t FeatureData[STD_VIDEO_AV1_MAX_SEGMENTS][STD_VIDEO_AV1_SEG_LVL_MAX];
} StdVideoAV1Segmentation;
typedef struct StdVideoAV1TileInfoFlags {
uint32_t uniform_tile_spacing_flag : 1;
uint32_t reserved : 31;
} StdVideoAV1TileInfoFlags;
typedef struct StdVideoAV1TileInfo {
StdVideoAV1TileInfoFlags flags;
uint8_t TileCols;
uint8_t TileRows;
uint16_t context_update_tile_id;
uint8_t tile_size_bytes_minus_1;
uint8_t reserved1[7];
const uint16_t* pMiColStarts;
const uint16_t* pMiRowStarts;
const uint16_t* pWidthInSbsMinus1;
const uint16_t* pHeightInSbsMinus1;
} StdVideoAV1TileInfo;
typedef struct StdVideoAV1CDEF {
uint8_t cdef_damping_minus_3;
uint8_t cdef_bits;
uint8_t cdef_y_pri_strength[STD_VIDEO_AV1_MAX_CDEF_FILTER_STRENGTHS];
uint8_t cdef_y_sec_strength[STD_VIDEO_AV1_MAX_CDEF_FILTER_STRENGTHS];
uint8_t cdef_uv_pri_strength[STD_VIDEO_AV1_MAX_CDEF_FILTER_STRENGTHS];
uint8_t cdef_uv_sec_strength[STD_VIDEO_AV1_MAX_CDEF_FILTER_STRENGTHS];
} StdVideoAV1CDEF;
typedef struct StdVideoAV1LoopRestoration {
StdVideoAV1FrameRestorationType FrameRestorationType[STD_VIDEO_AV1_MAX_NUM_PLANES];
uint16_t LoopRestorationSize[STD_VIDEO_AV1_MAX_NUM_PLANES];
} StdVideoAV1LoopRestoration;
typedef struct StdVideoAV1GlobalMotion {
uint8_t GmType[STD_VIDEO_AV1_NUM_REF_FRAMES];
int32_t gm_params[STD_VIDEO_AV1_NUM_REF_FRAMES][STD_VIDEO_AV1_GLOBAL_MOTION_PARAMS];
} StdVideoAV1GlobalMotion;
typedef struct StdVideoAV1FilmGrainFlags {
uint32_t chroma_scaling_from_luma : 1;
uint32_t overlap_flag : 1;
uint32_t clip_to_restricted_range : 1;
uint32_t update_grain : 1;
uint32_t reserved : 28;
} StdVideoAV1FilmGrainFlags;
typedef struct StdVideoAV1FilmGrain {
StdVideoAV1FilmGrainFlags flags;
uint8_t grain_scaling_minus_8;
uint8_t ar_coeff_lag;
uint8_t ar_coeff_shift_minus_6;
uint8_t grain_scale_shift;
uint16_t grain_seed;
uint8_t film_grain_params_ref_idx;
uint8_t num_y_points;
uint8_t point_y_value[STD_VIDEO_AV1_MAX_NUM_Y_POINTS];
uint8_t point_y_scaling[STD_VIDEO_AV1_MAX_NUM_Y_POINTS];
uint8_t num_cb_points;
uint8_t point_cb_value[STD_VIDEO_AV1_MAX_NUM_CB_POINTS];
uint8_t point_cb_scaling[STD_VIDEO_AV1_MAX_NUM_CB_POINTS];
uint8_t num_cr_points;
uint8_t point_cr_value[STD_VIDEO_AV1_MAX_NUM_CR_POINTS];
uint8_t point_cr_scaling[STD_VIDEO_AV1_MAX_NUM_CR_POINTS];
int8_t ar_coeffs_y_plus_128[STD_VIDEO_AV1_MAX_NUM_POS_LUMA];
int8_t ar_coeffs_cb_plus_128[STD_VIDEO_AV1_MAX_NUM_POS_CHROMA];
int8_t ar_coeffs_cr_plus_128[STD_VIDEO_AV1_MAX_NUM_POS_CHROMA];
uint8_t cb_mult;
uint8_t cb_luma_mult;
uint16_t cb_offset;
uint8_t cr_mult;
uint8_t cr_luma_mult;
uint16_t cr_offset;
} StdVideoAV1FilmGrain;
typedef struct StdVideoAV1SequenceHeaderFlags {
uint32_t still_picture : 1;
uint32_t reduced_still_picture_header : 1;
uint32_t use_128x128_superblock : 1;
uint32_t enable_filter_intra : 1;
uint32_t enable_intra_edge_filter : 1;
uint32_t enable_interintra_compound : 1;
uint32_t enable_masked_compound : 1;
uint32_t enable_warped_motion : 1;
uint32_t enable_dual_filter : 1;
uint32_t enable_order_hint : 1;
uint32_t enable_jnt_comp : 1;
uint32_t enable_ref_frame_mvs : 1;
uint32_t frame_id_numbers_present_flag : 1;
uint32_t enable_superres : 1;
uint32_t enable_cdef : 1;
uint32_t enable_restoration : 1;
uint32_t film_grain_params_present : 1;
uint32_t timing_info_present_flag : 1;
uint32_t initial_display_delay_present_flag : 1;
uint32_t reserved : 13;
} StdVideoAV1SequenceHeaderFlags;
typedef struct StdVideoAV1SequenceHeader {
StdVideoAV1SequenceHeaderFlags flags;
StdVideoAV1Profile seq_profile;
uint8_t frame_width_bits_minus_1;
uint8_t frame_height_bits_minus_1;
uint16_t max_frame_width_minus_1;
uint16_t max_frame_height_minus_1;
uint8_t delta_frame_id_length_minus_2;
uint8_t additional_frame_id_length_minus_1;
uint8_t order_hint_bits_minus_1;
uint8_t seq_force_integer_mv;
uint8_t seq_force_screen_content_tools;
uint8_t reserved1[5];
const StdVideoAV1ColorConfig* pColorConfig;
const StdVideoAV1TimingInfo* pTimingInfo;
} StdVideoAV1SequenceHeader;
#ifdef __cplusplus
}
#endif
#endif
@@ -0,0 +1,109 @@
#ifndef VULKAN_VIDEO_CODEC_AV1STD_DECODE_H_
#define VULKAN_VIDEO_CODEC_AV1STD_DECODE_H_ 1
/*
** Copyright 2015-2024 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
*/
/*
** This header is generated from the Khronos Vulkan XML API Registry.
**
*/
#ifdef __cplusplus
extern "C" {
#endif
// vulkan_video_codec_av1std_decode is a preprocessor guard. Do not pass it to API calls.
#define vulkan_video_codec_av1std_decode 1
#include "vulkan_video_codec_av1std.h"
#define VK_STD_VULKAN_VIDEO_CODEC_AV1_DECODE_API_VERSION_1_0_0 VK_MAKE_VIDEO_STD_VERSION(1, 0, 0)
#define VK_STD_VULKAN_VIDEO_CODEC_AV1_DECODE_SPEC_VERSION VK_STD_VULKAN_VIDEO_CODEC_AV1_DECODE_API_VERSION_1_0_0
#define VK_STD_VULKAN_VIDEO_CODEC_AV1_DECODE_EXTENSION_NAME "VK_STD_vulkan_video_codec_av1_decode"
typedef struct StdVideoDecodeAV1PictureInfoFlags {
uint32_t error_resilient_mode : 1;
uint32_t disable_cdf_update : 1;
uint32_t use_superres : 1;
uint32_t render_and_frame_size_different : 1;
uint32_t allow_screen_content_tools : 1;
uint32_t is_filter_switchable : 1;
uint32_t force_integer_mv : 1;
uint32_t frame_size_override_flag : 1;
uint32_t buffer_removal_time_present_flag : 1;
uint32_t allow_intrabc : 1;
uint32_t frame_refs_short_signaling : 1;
uint32_t allow_high_precision_mv : 1;
uint32_t is_motion_mode_switchable : 1;
uint32_t use_ref_frame_mvs : 1;
uint32_t disable_frame_end_update_cdf : 1;
uint32_t allow_warped_motion : 1;
uint32_t reduced_tx_set : 1;
uint32_t reference_select : 1;
uint32_t skip_mode_present : 1;
uint32_t delta_q_present : 1;
uint32_t delta_lf_present : 1;
uint32_t delta_lf_multi : 1;
uint32_t segmentation_enabled : 1;
uint32_t segmentation_update_map : 1;
uint32_t segmentation_temporal_update : 1;
uint32_t segmentation_update_data : 1;
uint32_t UsesLr : 1;
uint32_t usesChromaLr : 1;
uint32_t apply_grain : 1;
uint32_t reserved : 3;
} StdVideoDecodeAV1PictureInfoFlags;
typedef struct StdVideoDecodeAV1PictureInfo {
StdVideoDecodeAV1PictureInfoFlags flags;
StdVideoAV1FrameType frame_type;
uint32_t current_frame_id;
uint8_t OrderHint;
uint8_t primary_ref_frame;
uint8_t refresh_frame_flags;
uint8_t reserved1;
StdVideoAV1InterpolationFilter interpolation_filter;
StdVideoAV1TxMode TxMode;
uint8_t delta_q_res;
uint8_t delta_lf_res;
uint8_t SkipModeFrame[STD_VIDEO_AV1_SKIP_MODE_FRAMES];
uint8_t coded_denom;
uint8_t reserved2[3];
uint8_t OrderHints[STD_VIDEO_AV1_NUM_REF_FRAMES];
uint32_t expectedFrameId[STD_VIDEO_AV1_NUM_REF_FRAMES];
const StdVideoAV1TileInfo* pTileInfo;
const StdVideoAV1Quantization* pQuantization;
const StdVideoAV1Segmentation* pSegmentation;
const StdVideoAV1LoopFilter* pLoopFilter;
const StdVideoAV1CDEF* pCDEF;
const StdVideoAV1LoopRestoration* pLoopRestoration;
const StdVideoAV1GlobalMotion* pGlobalMotion;
const StdVideoAV1FilmGrain* pFilmGrain;
} StdVideoDecodeAV1PictureInfo;
typedef struct StdVideoDecodeAV1ReferenceInfoFlags {
uint32_t disable_frame_end_update_cdf : 1;
uint32_t segmentation_enabled : 1;
uint32_t reserved : 30;
} StdVideoDecodeAV1ReferenceInfoFlags;
typedef struct StdVideoDecodeAV1ReferenceInfo {
StdVideoDecodeAV1ReferenceInfoFlags flags;
uint8_t frame_type;
uint8_t RefFrameSignBias;
uint8_t OrderHint;
uint8_t SavedOrderHints[STD_VIDEO_AV1_NUM_REF_FRAMES];
} StdVideoDecodeAV1ReferenceInfo;
#ifdef __cplusplus
}
#endif
#endif
@@ -0,0 +1,143 @@
#ifndef VULKAN_VIDEO_CODEC_AV1STD_ENCODE_H_
#define VULKAN_VIDEO_CODEC_AV1STD_ENCODE_H_ 1
/*
** Copyright 2015-2024 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
*/
/*
** This header is generated from the Khronos Vulkan XML API Registry.
**
*/
#ifdef __cplusplus
extern "C" {
#endif
// vulkan_video_codec_av1std_encode is a preprocessor guard. Do not pass it to API calls.
#define vulkan_video_codec_av1std_encode 1
#include "vulkan_video_codec_av1std.h"
#define VK_STD_VULKAN_VIDEO_CODEC_AV1_ENCODE_API_VERSION_1_0_0 VK_MAKE_VIDEO_STD_VERSION(1, 0, 0)
#define VK_STD_VULKAN_VIDEO_CODEC_AV1_ENCODE_SPEC_VERSION VK_STD_VULKAN_VIDEO_CODEC_AV1_ENCODE_API_VERSION_1_0_0
#define VK_STD_VULKAN_VIDEO_CODEC_AV1_ENCODE_EXTENSION_NAME "VK_STD_vulkan_video_codec_av1_encode"
typedef struct StdVideoEncodeAV1DecoderModelInfo {
uint8_t buffer_delay_length_minus_1;
uint8_t buffer_removal_time_length_minus_1;
uint8_t frame_presentation_time_length_minus_1;
uint8_t reserved1;
uint32_t num_units_in_decoding_tick;
} StdVideoEncodeAV1DecoderModelInfo;
typedef struct StdVideoEncodeAV1ExtensionHeader {
uint8_t temporal_id;
uint8_t spatial_id;
} StdVideoEncodeAV1ExtensionHeader;
typedef struct StdVideoEncodeAV1OperatingPointInfoFlags {
uint32_t decoder_model_present_for_this_op : 1;
uint32_t low_delay_mode_flag : 1;
uint32_t initial_display_delay_present_for_this_op : 1;
uint32_t reserved : 29;
} StdVideoEncodeAV1OperatingPointInfoFlags;
typedef struct StdVideoEncodeAV1OperatingPointInfo {
StdVideoEncodeAV1OperatingPointInfoFlags flags;
uint16_t operating_point_idc;
uint8_t seq_level_idx;
uint8_t seq_tier;
uint32_t decoder_buffer_delay;
uint32_t encoder_buffer_delay;
uint8_t initial_display_delay_minus_1;
} StdVideoEncodeAV1OperatingPointInfo;
typedef struct StdVideoEncodeAV1PictureInfoFlags {
uint32_t error_resilient_mode : 1;
uint32_t disable_cdf_update : 1;
uint32_t use_superres : 1;
uint32_t render_and_frame_size_different : 1;
uint32_t allow_screen_content_tools : 1;
uint32_t is_filter_switchable : 1;
uint32_t force_integer_mv : 1;
uint32_t frame_size_override_flag : 1;
uint32_t buffer_removal_time_present_flag : 1;
uint32_t allow_intrabc : 1;
uint32_t frame_refs_short_signaling : 1;
uint32_t allow_high_precision_mv : 1;
uint32_t is_motion_mode_switchable : 1;
uint32_t use_ref_frame_mvs : 1;
uint32_t disable_frame_end_update_cdf : 1;
uint32_t allow_warped_motion : 1;
uint32_t reduced_tx_set : 1;
uint32_t skip_mode_present : 1;
uint32_t delta_q_present : 1;
uint32_t delta_lf_present : 1;
uint32_t delta_lf_multi : 1;
uint32_t segmentation_enabled : 1;
uint32_t segmentation_update_map : 1;
uint32_t segmentation_temporal_update : 1;
uint32_t segmentation_update_data : 1;
uint32_t UsesLr : 1;
uint32_t usesChromaLr : 1;
uint32_t show_frame : 1;
uint32_t showable_frame : 1;
uint32_t reserved : 3;
} StdVideoEncodeAV1PictureInfoFlags;
typedef struct StdVideoEncodeAV1PictureInfo {
StdVideoEncodeAV1PictureInfoFlags flags;
StdVideoAV1FrameType frame_type;
uint32_t frame_presentation_time;
uint32_t current_frame_id;
uint8_t order_hint;
uint8_t primary_ref_frame;
uint8_t refresh_frame_flags;
uint8_t coded_denom;
uint16_t render_width_minus_1;
uint16_t render_height_minus_1;
StdVideoAV1InterpolationFilter interpolation_filter;
StdVideoAV1TxMode TxMode;
uint8_t delta_q_res;
uint8_t delta_lf_res;
uint8_t ref_order_hint[STD_VIDEO_AV1_NUM_REF_FRAMES];
int8_t ref_frame_idx[STD_VIDEO_AV1_REFS_PER_FRAME];
uint8_t reserved1[3];
uint32_t delta_frame_id_minus_1[STD_VIDEO_AV1_REFS_PER_FRAME];
const StdVideoAV1TileInfo* pTileInfo;
const StdVideoAV1Quantization* pQuantization;
const StdVideoAV1Segmentation* pSegmentation;
const StdVideoAV1LoopFilter* pLoopFilter;
const StdVideoAV1CDEF* pCDEF;
const StdVideoAV1LoopRestoration* pLoopRestoration;
const StdVideoAV1GlobalMotion* pGlobalMotion;
const StdVideoEncodeAV1ExtensionHeader* pExtensionHeader;
const uint32_t* pBufferRemovalTimes;
} StdVideoEncodeAV1PictureInfo;
typedef struct StdVideoEncodeAV1ReferenceInfoFlags {
uint32_t disable_frame_end_update_cdf : 1;
uint32_t segmentation_enabled : 1;
uint32_t reserved : 30;
} StdVideoEncodeAV1ReferenceInfoFlags;
typedef struct StdVideoEncodeAV1ReferenceInfo {
StdVideoEncodeAV1ReferenceInfoFlags flags;
uint32_t RefFrameId;
StdVideoAV1FrameType frame_type;
uint8_t OrderHint;
uint8_t reserved1[3];
const StdVideoEncodeAV1ExtensionHeader* pExtensionHeader;
} StdVideoEncodeAV1ReferenceInfo;
#ifdef __cplusplus
}
#endif
#endif
@@ -0,0 +1,312 @@
#ifndef VULKAN_VIDEO_CODEC_H264STD_H_
#define VULKAN_VIDEO_CODEC_H264STD_H_ 1
/*
** Copyright 2015-2024 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
*/
/*
** This header is generated from the Khronos Vulkan XML API Registry.
**
*/
#ifdef __cplusplus
extern "C" {
#endif
// vulkan_video_codec_h264std is a preprocessor guard. Do not pass it to API calls.
#define vulkan_video_codec_h264std 1
#include "vulkan_video_codecs_common.h"
#define STD_VIDEO_H264_CPB_CNT_LIST_SIZE 32
#define STD_VIDEO_H264_SCALING_LIST_4X4_NUM_LISTS 6
#define STD_VIDEO_H264_SCALING_LIST_4X4_NUM_ELEMENTS 16
#define STD_VIDEO_H264_SCALING_LIST_8X8_NUM_LISTS 6
#define STD_VIDEO_H264_SCALING_LIST_8X8_NUM_ELEMENTS 64
#define STD_VIDEO_H264_MAX_NUM_LIST_REF 32
#define STD_VIDEO_H264_MAX_CHROMA_PLANES 2
#define STD_VIDEO_H264_NO_REFERENCE_PICTURE 0xFF
typedef enum StdVideoH264ChromaFormatIdc {
STD_VIDEO_H264_CHROMA_FORMAT_IDC_MONOCHROME = 0,
STD_VIDEO_H264_CHROMA_FORMAT_IDC_420 = 1,
STD_VIDEO_H264_CHROMA_FORMAT_IDC_422 = 2,
STD_VIDEO_H264_CHROMA_FORMAT_IDC_444 = 3,
STD_VIDEO_H264_CHROMA_FORMAT_IDC_INVALID = 0x7FFFFFFF,
STD_VIDEO_H264_CHROMA_FORMAT_IDC_MAX_ENUM = 0x7FFFFFFF
} StdVideoH264ChromaFormatIdc;
typedef enum StdVideoH264ProfileIdc {
STD_VIDEO_H264_PROFILE_IDC_BASELINE = 66,
STD_VIDEO_H264_PROFILE_IDC_MAIN = 77,
STD_VIDEO_H264_PROFILE_IDC_HIGH = 100,
STD_VIDEO_H264_PROFILE_IDC_HIGH_444_PREDICTIVE = 244,
STD_VIDEO_H264_PROFILE_IDC_INVALID = 0x7FFFFFFF,
STD_VIDEO_H264_PROFILE_IDC_MAX_ENUM = 0x7FFFFFFF
} StdVideoH264ProfileIdc;
typedef enum StdVideoH264LevelIdc {
STD_VIDEO_H264_LEVEL_IDC_1_0 = 0,
STD_VIDEO_H264_LEVEL_IDC_1_1 = 1,
STD_VIDEO_H264_LEVEL_IDC_1_2 = 2,
STD_VIDEO_H264_LEVEL_IDC_1_3 = 3,
STD_VIDEO_H264_LEVEL_IDC_2_0 = 4,
STD_VIDEO_H264_LEVEL_IDC_2_1 = 5,
STD_VIDEO_H264_LEVEL_IDC_2_2 = 6,
STD_VIDEO_H264_LEVEL_IDC_3_0 = 7,
STD_VIDEO_H264_LEVEL_IDC_3_1 = 8,
STD_VIDEO_H264_LEVEL_IDC_3_2 = 9,
STD_VIDEO_H264_LEVEL_IDC_4_0 = 10,
STD_VIDEO_H264_LEVEL_IDC_4_1 = 11,
STD_VIDEO_H264_LEVEL_IDC_4_2 = 12,
STD_VIDEO_H264_LEVEL_IDC_5_0 = 13,
STD_VIDEO_H264_LEVEL_IDC_5_1 = 14,
STD_VIDEO_H264_LEVEL_IDC_5_2 = 15,
STD_VIDEO_H264_LEVEL_IDC_6_0 = 16,
STD_VIDEO_H264_LEVEL_IDC_6_1 = 17,
STD_VIDEO_H264_LEVEL_IDC_6_2 = 18,
STD_VIDEO_H264_LEVEL_IDC_INVALID = 0x7FFFFFFF,
STD_VIDEO_H264_LEVEL_IDC_MAX_ENUM = 0x7FFFFFFF
} StdVideoH264LevelIdc;
typedef enum StdVideoH264PocType {
STD_VIDEO_H264_POC_TYPE_0 = 0,
STD_VIDEO_H264_POC_TYPE_1 = 1,
STD_VIDEO_H264_POC_TYPE_2 = 2,
STD_VIDEO_H264_POC_TYPE_INVALID = 0x7FFFFFFF,
STD_VIDEO_H264_POC_TYPE_MAX_ENUM = 0x7FFFFFFF
} StdVideoH264PocType;
typedef enum StdVideoH264AspectRatioIdc {
STD_VIDEO_H264_ASPECT_RATIO_IDC_UNSPECIFIED = 0,
STD_VIDEO_H264_ASPECT_RATIO_IDC_SQUARE = 1,
STD_VIDEO_H264_ASPECT_RATIO_IDC_12_11 = 2,
STD_VIDEO_H264_ASPECT_RATIO_IDC_10_11 = 3,
STD_VIDEO_H264_ASPECT_RATIO_IDC_16_11 = 4,
STD_VIDEO_H264_ASPECT_RATIO_IDC_40_33 = 5,
STD_VIDEO_H264_ASPECT_RATIO_IDC_24_11 = 6,
STD_VIDEO_H264_ASPECT_RATIO_IDC_20_11 = 7,
STD_VIDEO_H264_ASPECT_RATIO_IDC_32_11 = 8,
STD_VIDEO_H264_ASPECT_RATIO_IDC_80_33 = 9,
STD_VIDEO_H264_ASPECT_RATIO_IDC_18_11 = 10,
STD_VIDEO_H264_ASPECT_RATIO_IDC_15_11 = 11,
STD_VIDEO_H264_ASPECT_RATIO_IDC_64_33 = 12,
STD_VIDEO_H264_ASPECT_RATIO_IDC_160_99 = 13,
STD_VIDEO_H264_ASPECT_RATIO_IDC_4_3 = 14,
STD_VIDEO_H264_ASPECT_RATIO_IDC_3_2 = 15,
STD_VIDEO_H264_ASPECT_RATIO_IDC_2_1 = 16,
STD_VIDEO_H264_ASPECT_RATIO_IDC_EXTENDED_SAR = 255,
STD_VIDEO_H264_ASPECT_RATIO_IDC_INVALID = 0x7FFFFFFF,
STD_VIDEO_H264_ASPECT_RATIO_IDC_MAX_ENUM = 0x7FFFFFFF
} StdVideoH264AspectRatioIdc;
typedef enum StdVideoH264WeightedBipredIdc {
STD_VIDEO_H264_WEIGHTED_BIPRED_IDC_DEFAULT = 0,
STD_VIDEO_H264_WEIGHTED_BIPRED_IDC_EXPLICIT = 1,
STD_VIDEO_H264_WEIGHTED_BIPRED_IDC_IMPLICIT = 2,
STD_VIDEO_H264_WEIGHTED_BIPRED_IDC_INVALID = 0x7FFFFFFF,
STD_VIDEO_H264_WEIGHTED_BIPRED_IDC_MAX_ENUM = 0x7FFFFFFF
} StdVideoH264WeightedBipredIdc;
typedef enum StdVideoH264ModificationOfPicNumsIdc {
STD_VIDEO_H264_MODIFICATION_OF_PIC_NUMS_IDC_SHORT_TERM_SUBTRACT = 0,
STD_VIDEO_H264_MODIFICATION_OF_PIC_NUMS_IDC_SHORT_TERM_ADD = 1,
STD_VIDEO_H264_MODIFICATION_OF_PIC_NUMS_IDC_LONG_TERM = 2,
STD_VIDEO_H264_MODIFICATION_OF_PIC_NUMS_IDC_END = 3,
STD_VIDEO_H264_MODIFICATION_OF_PIC_NUMS_IDC_INVALID = 0x7FFFFFFF,
STD_VIDEO_H264_MODIFICATION_OF_PIC_NUMS_IDC_MAX_ENUM = 0x7FFFFFFF
} StdVideoH264ModificationOfPicNumsIdc;
typedef enum StdVideoH264MemMgmtControlOp {
STD_VIDEO_H264_MEM_MGMT_CONTROL_OP_END = 0,
STD_VIDEO_H264_MEM_MGMT_CONTROL_OP_UNMARK_SHORT_TERM = 1,
STD_VIDEO_H264_MEM_MGMT_CONTROL_OP_UNMARK_LONG_TERM = 2,
STD_VIDEO_H264_MEM_MGMT_CONTROL_OP_MARK_LONG_TERM = 3,
STD_VIDEO_H264_MEM_MGMT_CONTROL_OP_SET_MAX_LONG_TERM_INDEX = 4,
STD_VIDEO_H264_MEM_MGMT_CONTROL_OP_UNMARK_ALL = 5,
STD_VIDEO_H264_MEM_MGMT_CONTROL_OP_MARK_CURRENT_AS_LONG_TERM = 6,
STD_VIDEO_H264_MEM_MGMT_CONTROL_OP_INVALID = 0x7FFFFFFF,
STD_VIDEO_H264_MEM_MGMT_CONTROL_OP_MAX_ENUM = 0x7FFFFFFF
} StdVideoH264MemMgmtControlOp;
typedef enum StdVideoH264CabacInitIdc {
STD_VIDEO_H264_CABAC_INIT_IDC_0 = 0,
STD_VIDEO_H264_CABAC_INIT_IDC_1 = 1,
STD_VIDEO_H264_CABAC_INIT_IDC_2 = 2,
STD_VIDEO_H264_CABAC_INIT_IDC_INVALID = 0x7FFFFFFF,
STD_VIDEO_H264_CABAC_INIT_IDC_MAX_ENUM = 0x7FFFFFFF
} StdVideoH264CabacInitIdc;
typedef enum StdVideoH264DisableDeblockingFilterIdc {
STD_VIDEO_H264_DISABLE_DEBLOCKING_FILTER_IDC_DISABLED = 0,
STD_VIDEO_H264_DISABLE_DEBLOCKING_FILTER_IDC_ENABLED = 1,
STD_VIDEO_H264_DISABLE_DEBLOCKING_FILTER_IDC_PARTIAL = 2,
STD_VIDEO_H264_DISABLE_DEBLOCKING_FILTER_IDC_INVALID = 0x7FFFFFFF,
STD_VIDEO_H264_DISABLE_DEBLOCKING_FILTER_IDC_MAX_ENUM = 0x7FFFFFFF
} StdVideoH264DisableDeblockingFilterIdc;
typedef enum StdVideoH264SliceType {
STD_VIDEO_H264_SLICE_TYPE_P = 0,
STD_VIDEO_H264_SLICE_TYPE_B = 1,
STD_VIDEO_H264_SLICE_TYPE_I = 2,
STD_VIDEO_H264_SLICE_TYPE_INVALID = 0x7FFFFFFF,
STD_VIDEO_H264_SLICE_TYPE_MAX_ENUM = 0x7FFFFFFF
} StdVideoH264SliceType;
typedef enum StdVideoH264PictureType {
STD_VIDEO_H264_PICTURE_TYPE_P = 0,
STD_VIDEO_H264_PICTURE_TYPE_B = 1,
STD_VIDEO_H264_PICTURE_TYPE_I = 2,
STD_VIDEO_H264_PICTURE_TYPE_IDR = 5,
STD_VIDEO_H264_PICTURE_TYPE_INVALID = 0x7FFFFFFF,
STD_VIDEO_H264_PICTURE_TYPE_MAX_ENUM = 0x7FFFFFFF
} StdVideoH264PictureType;
typedef enum StdVideoH264NonVclNaluType {
STD_VIDEO_H264_NON_VCL_NALU_TYPE_SPS = 0,
STD_VIDEO_H264_NON_VCL_NALU_TYPE_PPS = 1,
STD_VIDEO_H264_NON_VCL_NALU_TYPE_AUD = 2,
STD_VIDEO_H264_NON_VCL_NALU_TYPE_PREFIX = 3,
STD_VIDEO_H264_NON_VCL_NALU_TYPE_END_OF_SEQUENCE = 4,
STD_VIDEO_H264_NON_VCL_NALU_TYPE_END_OF_STREAM = 5,
STD_VIDEO_H264_NON_VCL_NALU_TYPE_PRECODED = 6,
STD_VIDEO_H264_NON_VCL_NALU_TYPE_INVALID = 0x7FFFFFFF,
STD_VIDEO_H264_NON_VCL_NALU_TYPE_MAX_ENUM = 0x7FFFFFFF
} StdVideoH264NonVclNaluType;
typedef struct StdVideoH264SpsVuiFlags {
uint32_t aspect_ratio_info_present_flag : 1;
uint32_t overscan_info_present_flag : 1;
uint32_t overscan_appropriate_flag : 1;
uint32_t video_signal_type_present_flag : 1;
uint32_t video_full_range_flag : 1;
uint32_t color_description_present_flag : 1;
uint32_t chroma_loc_info_present_flag : 1;
uint32_t timing_info_present_flag : 1;
uint32_t fixed_frame_rate_flag : 1;
uint32_t bitstream_restriction_flag : 1;
uint32_t nal_hrd_parameters_present_flag : 1;
uint32_t vcl_hrd_parameters_present_flag : 1;
} StdVideoH264SpsVuiFlags;
typedef struct StdVideoH264HrdParameters {
uint8_t cpb_cnt_minus1;
uint8_t bit_rate_scale;
uint8_t cpb_size_scale;
uint8_t reserved1;
uint32_t bit_rate_value_minus1[STD_VIDEO_H264_CPB_CNT_LIST_SIZE];
uint32_t cpb_size_value_minus1[STD_VIDEO_H264_CPB_CNT_LIST_SIZE];
uint8_t cbr_flag[STD_VIDEO_H264_CPB_CNT_LIST_SIZE];
uint32_t initial_cpb_removal_delay_length_minus1;
uint32_t cpb_removal_delay_length_minus1;
uint32_t dpb_output_delay_length_minus1;
uint32_t time_offset_length;
} StdVideoH264HrdParameters;
typedef struct StdVideoH264SequenceParameterSetVui {
StdVideoH264SpsVuiFlags flags;
StdVideoH264AspectRatioIdc aspect_ratio_idc;
uint16_t sar_width;
uint16_t sar_height;
uint8_t video_format;
uint8_t colour_primaries;
uint8_t transfer_characteristics;
uint8_t matrix_coefficients;
uint32_t num_units_in_tick;
uint32_t time_scale;
uint8_t max_num_reorder_frames;
uint8_t max_dec_frame_buffering;
uint8_t chroma_sample_loc_type_top_field;
uint8_t chroma_sample_loc_type_bottom_field;
uint32_t reserved1;
const StdVideoH264HrdParameters* pHrdParameters;
} StdVideoH264SequenceParameterSetVui;
typedef struct StdVideoH264SpsFlags {
uint32_t constraint_set0_flag : 1;
uint32_t constraint_set1_flag : 1;
uint32_t constraint_set2_flag : 1;
uint32_t constraint_set3_flag : 1;
uint32_t constraint_set4_flag : 1;
uint32_t constraint_set5_flag : 1;
uint32_t direct_8x8_inference_flag : 1;
uint32_t mb_adaptive_frame_field_flag : 1;
uint32_t frame_mbs_only_flag : 1;
uint32_t delta_pic_order_always_zero_flag : 1;
uint32_t separate_colour_plane_flag : 1;
uint32_t gaps_in_frame_num_value_allowed_flag : 1;
uint32_t qpprime_y_zero_transform_bypass_flag : 1;
uint32_t frame_cropping_flag : 1;
uint32_t seq_scaling_matrix_present_flag : 1;
uint32_t vui_parameters_present_flag : 1;
} StdVideoH264SpsFlags;
typedef struct StdVideoH264ScalingLists {
uint16_t scaling_list_present_mask;
uint16_t use_default_scaling_matrix_mask;
uint8_t ScalingList4x4[STD_VIDEO_H264_SCALING_LIST_4X4_NUM_LISTS][STD_VIDEO_H264_SCALING_LIST_4X4_NUM_ELEMENTS];
uint8_t ScalingList8x8[STD_VIDEO_H264_SCALING_LIST_8X8_NUM_LISTS][STD_VIDEO_H264_SCALING_LIST_8X8_NUM_ELEMENTS];
} StdVideoH264ScalingLists;
typedef struct StdVideoH264SequenceParameterSet {
StdVideoH264SpsFlags flags;
StdVideoH264ProfileIdc profile_idc;
StdVideoH264LevelIdc level_idc;
StdVideoH264ChromaFormatIdc chroma_format_idc;
uint8_t seq_parameter_set_id;
uint8_t bit_depth_luma_minus8;
uint8_t bit_depth_chroma_minus8;
uint8_t log2_max_frame_num_minus4;
StdVideoH264PocType pic_order_cnt_type;
int32_t offset_for_non_ref_pic;
int32_t offset_for_top_to_bottom_field;
uint8_t log2_max_pic_order_cnt_lsb_minus4;
uint8_t num_ref_frames_in_pic_order_cnt_cycle;
uint8_t max_num_ref_frames;
uint8_t reserved1;
uint32_t pic_width_in_mbs_minus1;
uint32_t pic_height_in_map_units_minus1;
uint32_t frame_crop_left_offset;
uint32_t frame_crop_right_offset;
uint32_t frame_crop_top_offset;
uint32_t frame_crop_bottom_offset;
uint32_t reserved2;
const int32_t* pOffsetForRefFrame;
const StdVideoH264ScalingLists* pScalingLists;
const StdVideoH264SequenceParameterSetVui* pSequenceParameterSetVui;
} StdVideoH264SequenceParameterSet;
typedef struct StdVideoH264PpsFlags {
uint32_t transform_8x8_mode_flag : 1;
uint32_t redundant_pic_cnt_present_flag : 1;
uint32_t constrained_intra_pred_flag : 1;
uint32_t deblocking_filter_control_present_flag : 1;
uint32_t weighted_pred_flag : 1;
uint32_t bottom_field_pic_order_in_frame_present_flag : 1;
uint32_t entropy_coding_mode_flag : 1;
uint32_t pic_scaling_matrix_present_flag : 1;
} StdVideoH264PpsFlags;
typedef struct StdVideoH264PictureParameterSet {
StdVideoH264PpsFlags flags;
uint8_t seq_parameter_set_id;
uint8_t pic_parameter_set_id;
uint8_t num_ref_idx_l0_default_active_minus1;
uint8_t num_ref_idx_l1_default_active_minus1;
StdVideoH264WeightedBipredIdc weighted_bipred_idc;
int8_t pic_init_qp_minus26;
int8_t pic_init_qs_minus26;
int8_t chroma_qp_index_offset;
int8_t second_chroma_qp_index_offset;
const StdVideoH264ScalingLists* pScalingLists;
} StdVideoH264PictureParameterSet;
#ifdef __cplusplus
}
#endif
#endif
@@ -0,0 +1,77 @@
#ifndef VULKAN_VIDEO_CODEC_H264STD_DECODE_H_
#define VULKAN_VIDEO_CODEC_H264STD_DECODE_H_ 1
/*
** Copyright 2015-2024 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
*/
/*
** This header is generated from the Khronos Vulkan XML API Registry.
**
*/
#ifdef __cplusplus
extern "C" {
#endif
// vulkan_video_codec_h264std_decode is a preprocessor guard. Do not pass it to API calls.
#define vulkan_video_codec_h264std_decode 1
#include "vulkan_video_codec_h264std.h"
#define VK_STD_VULKAN_VIDEO_CODEC_H264_DECODE_API_VERSION_1_0_0 VK_MAKE_VIDEO_STD_VERSION(1, 0, 0)
#define VK_STD_VULKAN_VIDEO_CODEC_H264_DECODE_SPEC_VERSION VK_STD_VULKAN_VIDEO_CODEC_H264_DECODE_API_VERSION_1_0_0
#define VK_STD_VULKAN_VIDEO_CODEC_H264_DECODE_EXTENSION_NAME "VK_STD_vulkan_video_codec_h264_decode"
#define STD_VIDEO_DECODE_H264_FIELD_ORDER_COUNT_LIST_SIZE 2
typedef enum StdVideoDecodeH264FieldOrderCount {
STD_VIDEO_DECODE_H264_FIELD_ORDER_COUNT_TOP = 0,
STD_VIDEO_DECODE_H264_FIELD_ORDER_COUNT_BOTTOM = 1,
STD_VIDEO_DECODE_H264_FIELD_ORDER_COUNT_INVALID = 0x7FFFFFFF,
STD_VIDEO_DECODE_H264_FIELD_ORDER_COUNT_MAX_ENUM = 0x7FFFFFFF
} StdVideoDecodeH264FieldOrderCount;
typedef struct StdVideoDecodeH264PictureInfoFlags {
uint32_t field_pic_flag : 1;
uint32_t is_intra : 1;
uint32_t IdrPicFlag : 1;
uint32_t bottom_field_flag : 1;
uint32_t is_reference : 1;
uint32_t complementary_field_pair : 1;
} StdVideoDecodeH264PictureInfoFlags;
typedef struct StdVideoDecodeH264PictureInfo {
StdVideoDecodeH264PictureInfoFlags flags;
uint8_t seq_parameter_set_id;
uint8_t pic_parameter_set_id;
uint8_t reserved1;
uint8_t reserved2;
uint16_t frame_num;
uint16_t idr_pic_id;
int32_t PicOrderCnt[STD_VIDEO_DECODE_H264_FIELD_ORDER_COUNT_LIST_SIZE];
} StdVideoDecodeH264PictureInfo;
typedef struct StdVideoDecodeH264ReferenceInfoFlags {
uint32_t top_field_flag : 1;
uint32_t bottom_field_flag : 1;
uint32_t used_for_long_term_reference : 1;
uint32_t is_non_existing : 1;
} StdVideoDecodeH264ReferenceInfoFlags;
typedef struct StdVideoDecodeH264ReferenceInfo {
StdVideoDecodeH264ReferenceInfoFlags flags;
uint16_t FrameNum;
uint16_t reserved;
int32_t PicOrderCnt[STD_VIDEO_DECODE_H264_FIELD_ORDER_COUNT_LIST_SIZE];
} StdVideoDecodeH264ReferenceInfo;
#ifdef __cplusplus
}
#endif
#endif
@@ -0,0 +1,147 @@
#ifndef VULKAN_VIDEO_CODEC_H264STD_ENCODE_H_
#define VULKAN_VIDEO_CODEC_H264STD_ENCODE_H_ 1
/*
** Copyright 2015-2024 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
*/
/*
** This header is generated from the Khronos Vulkan XML API Registry.
**
*/
#ifdef __cplusplus
extern "C" {
#endif
// vulkan_video_codec_h264std_encode is a preprocessor guard. Do not pass it to API calls.
#define vulkan_video_codec_h264std_encode 1
#include "vulkan_video_codec_h264std.h"
#define VK_STD_VULKAN_VIDEO_CODEC_H264_ENCODE_API_VERSION_1_0_0 VK_MAKE_VIDEO_STD_VERSION(1, 0, 0)
#define VK_STD_VULKAN_VIDEO_CODEC_H264_ENCODE_SPEC_VERSION VK_STD_VULKAN_VIDEO_CODEC_H264_ENCODE_API_VERSION_1_0_0
#define VK_STD_VULKAN_VIDEO_CODEC_H264_ENCODE_EXTENSION_NAME "VK_STD_vulkan_video_codec_h264_encode"
typedef struct StdVideoEncodeH264WeightTableFlags {
uint32_t luma_weight_l0_flag;
uint32_t chroma_weight_l0_flag;
uint32_t luma_weight_l1_flag;
uint32_t chroma_weight_l1_flag;
} StdVideoEncodeH264WeightTableFlags;
typedef struct StdVideoEncodeH264WeightTable {
StdVideoEncodeH264WeightTableFlags flags;
uint8_t luma_log2_weight_denom;
uint8_t chroma_log2_weight_denom;
int8_t luma_weight_l0[STD_VIDEO_H264_MAX_NUM_LIST_REF];
int8_t luma_offset_l0[STD_VIDEO_H264_MAX_NUM_LIST_REF];
int8_t chroma_weight_l0[STD_VIDEO_H264_MAX_NUM_LIST_REF][STD_VIDEO_H264_MAX_CHROMA_PLANES];
int8_t chroma_offset_l0[STD_VIDEO_H264_MAX_NUM_LIST_REF][STD_VIDEO_H264_MAX_CHROMA_PLANES];
int8_t luma_weight_l1[STD_VIDEO_H264_MAX_NUM_LIST_REF];
int8_t luma_offset_l1[STD_VIDEO_H264_MAX_NUM_LIST_REF];
int8_t chroma_weight_l1[STD_VIDEO_H264_MAX_NUM_LIST_REF][STD_VIDEO_H264_MAX_CHROMA_PLANES];
int8_t chroma_offset_l1[STD_VIDEO_H264_MAX_NUM_LIST_REF][STD_VIDEO_H264_MAX_CHROMA_PLANES];
} StdVideoEncodeH264WeightTable;
typedef struct StdVideoEncodeH264SliceHeaderFlags {
uint32_t direct_spatial_mv_pred_flag : 1;
uint32_t num_ref_idx_active_override_flag : 1;
uint32_t reserved : 30;
} StdVideoEncodeH264SliceHeaderFlags;
typedef struct StdVideoEncodeH264PictureInfoFlags {
uint32_t IdrPicFlag : 1;
uint32_t is_reference : 1;
uint32_t no_output_of_prior_pics_flag : 1;
uint32_t long_term_reference_flag : 1;
uint32_t adaptive_ref_pic_marking_mode_flag : 1;
uint32_t reserved : 27;
} StdVideoEncodeH264PictureInfoFlags;
typedef struct StdVideoEncodeH264ReferenceInfoFlags {
uint32_t used_for_long_term_reference : 1;
uint32_t reserved : 31;
} StdVideoEncodeH264ReferenceInfoFlags;
typedef struct StdVideoEncodeH264ReferenceListsInfoFlags {
uint32_t ref_pic_list_modification_flag_l0 : 1;
uint32_t ref_pic_list_modification_flag_l1 : 1;
uint32_t reserved : 30;
} StdVideoEncodeH264ReferenceListsInfoFlags;
typedef struct StdVideoEncodeH264RefListModEntry {
StdVideoH264ModificationOfPicNumsIdc modification_of_pic_nums_idc;
uint16_t abs_diff_pic_num_minus1;
uint16_t long_term_pic_num;
} StdVideoEncodeH264RefListModEntry;
typedef struct StdVideoEncodeH264RefPicMarkingEntry {
StdVideoH264MemMgmtControlOp memory_management_control_operation;
uint16_t difference_of_pic_nums_minus1;
uint16_t long_term_pic_num;
uint16_t long_term_frame_idx;
uint16_t max_long_term_frame_idx_plus1;
} StdVideoEncodeH264RefPicMarkingEntry;
typedef struct StdVideoEncodeH264ReferenceListsInfo {
StdVideoEncodeH264ReferenceListsInfoFlags flags;
uint8_t num_ref_idx_l0_active_minus1;
uint8_t num_ref_idx_l1_active_minus1;
uint8_t RefPicList0[STD_VIDEO_H264_MAX_NUM_LIST_REF];
uint8_t RefPicList1[STD_VIDEO_H264_MAX_NUM_LIST_REF];
uint8_t refList0ModOpCount;
uint8_t refList1ModOpCount;
uint8_t refPicMarkingOpCount;
uint8_t reserved1[7];
const StdVideoEncodeH264RefListModEntry* pRefList0ModOperations;
const StdVideoEncodeH264RefListModEntry* pRefList1ModOperations;
const StdVideoEncodeH264RefPicMarkingEntry* pRefPicMarkingOperations;
} StdVideoEncodeH264ReferenceListsInfo;
typedef struct StdVideoEncodeH264PictureInfo {
StdVideoEncodeH264PictureInfoFlags flags;
uint8_t seq_parameter_set_id;
uint8_t pic_parameter_set_id;
uint16_t idr_pic_id;
StdVideoH264PictureType primary_pic_type;
uint32_t frame_num;
int32_t PicOrderCnt;
uint8_t temporal_id;
uint8_t reserved1[3];
const StdVideoEncodeH264ReferenceListsInfo* pRefLists;
} StdVideoEncodeH264PictureInfo;
typedef struct StdVideoEncodeH264ReferenceInfo {
StdVideoEncodeH264ReferenceInfoFlags flags;
StdVideoH264PictureType primary_pic_type;
uint32_t FrameNum;
int32_t PicOrderCnt;
uint16_t long_term_pic_num;
uint16_t long_term_frame_idx;
uint8_t temporal_id;
} StdVideoEncodeH264ReferenceInfo;
typedef struct StdVideoEncodeH264SliceHeader {
StdVideoEncodeH264SliceHeaderFlags flags;
uint32_t first_mb_in_slice;
StdVideoH264SliceType slice_type;
int8_t slice_alpha_c0_offset_div2;
int8_t slice_beta_offset_div2;
int8_t slice_qp_delta;
uint8_t reserved1;
StdVideoH264CabacInitIdc cabac_init_idc;
StdVideoH264DisableDeblockingFilterIdc disable_deblocking_filter_idc;
const StdVideoEncodeH264WeightTable* pWeightTable;
} StdVideoEncodeH264SliceHeader;
#ifdef __cplusplus
}
#endif
#endif
@@ -0,0 +1,446 @@
#ifndef VULKAN_VIDEO_CODEC_H265STD_H_
#define VULKAN_VIDEO_CODEC_H265STD_H_ 1
/*
** Copyright 2015-2024 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
*/
/*
** This header is generated from the Khronos Vulkan XML API Registry.
**
*/
#ifdef __cplusplus
extern "C" {
#endif
// vulkan_video_codec_h265std is a preprocessor guard. Do not pass it to API calls.
#define vulkan_video_codec_h265std 1
#include "vulkan_video_codecs_common.h"
#define STD_VIDEO_H265_CPB_CNT_LIST_SIZE 32
#define STD_VIDEO_H265_SUBLAYERS_LIST_SIZE 7
#define STD_VIDEO_H265_SCALING_LIST_4X4_NUM_LISTS 6
#define STD_VIDEO_H265_SCALING_LIST_4X4_NUM_ELEMENTS 16
#define STD_VIDEO_H265_SCALING_LIST_8X8_NUM_LISTS 6
#define STD_VIDEO_H265_SCALING_LIST_8X8_NUM_ELEMENTS 64
#define STD_VIDEO_H265_SCALING_LIST_16X16_NUM_LISTS 6
#define STD_VIDEO_H265_SCALING_LIST_16X16_NUM_ELEMENTS 64
#define STD_VIDEO_H265_SCALING_LIST_32X32_NUM_LISTS 2
#define STD_VIDEO_H265_SCALING_LIST_32X32_NUM_ELEMENTS 64
#define STD_VIDEO_H265_CHROMA_QP_OFFSET_LIST_SIZE 6
#define STD_VIDEO_H265_CHROMA_QP_OFFSET_TILE_COLS_LIST_SIZE 19
#define STD_VIDEO_H265_CHROMA_QP_OFFSET_TILE_ROWS_LIST_SIZE 21
#define STD_VIDEO_H265_PREDICTOR_PALETTE_COMPONENTS_LIST_SIZE 3
#define STD_VIDEO_H265_PREDICTOR_PALETTE_COMP_ENTRIES_LIST_SIZE 128
#define STD_VIDEO_H265_MAX_NUM_LIST_REF 15
#define STD_VIDEO_H265_MAX_CHROMA_PLANES 2
#define STD_VIDEO_H265_MAX_SHORT_TERM_REF_PIC_SETS 64
#define STD_VIDEO_H265_MAX_DPB_SIZE 16
#define STD_VIDEO_H265_MAX_LONG_TERM_REF_PICS_SPS 32
#define STD_VIDEO_H265_MAX_LONG_TERM_PICS 16
#define STD_VIDEO_H265_MAX_DELTA_POC 48
#define STD_VIDEO_H265_NO_REFERENCE_PICTURE 0xFF
typedef enum StdVideoH265ChromaFormatIdc {
STD_VIDEO_H265_CHROMA_FORMAT_IDC_MONOCHROME = 0,
STD_VIDEO_H265_CHROMA_FORMAT_IDC_420 = 1,
STD_VIDEO_H265_CHROMA_FORMAT_IDC_422 = 2,
STD_VIDEO_H265_CHROMA_FORMAT_IDC_444 = 3,
STD_VIDEO_H265_CHROMA_FORMAT_IDC_INVALID = 0x7FFFFFFF,
STD_VIDEO_H265_CHROMA_FORMAT_IDC_MAX_ENUM = 0x7FFFFFFF
} StdVideoH265ChromaFormatIdc;
typedef enum StdVideoH265ProfileIdc {
STD_VIDEO_H265_PROFILE_IDC_MAIN = 1,
STD_VIDEO_H265_PROFILE_IDC_MAIN_10 = 2,
STD_VIDEO_H265_PROFILE_IDC_MAIN_STILL_PICTURE = 3,
STD_VIDEO_H265_PROFILE_IDC_FORMAT_RANGE_EXTENSIONS = 4,
STD_VIDEO_H265_PROFILE_IDC_SCC_EXTENSIONS = 9,
STD_VIDEO_H265_PROFILE_IDC_INVALID = 0x7FFFFFFF,
STD_VIDEO_H265_PROFILE_IDC_MAX_ENUM = 0x7FFFFFFF
} StdVideoH265ProfileIdc;
typedef enum StdVideoH265LevelIdc {
STD_VIDEO_H265_LEVEL_IDC_1_0 = 0,
STD_VIDEO_H265_LEVEL_IDC_2_0 = 1,
STD_VIDEO_H265_LEVEL_IDC_2_1 = 2,
STD_VIDEO_H265_LEVEL_IDC_3_0 = 3,
STD_VIDEO_H265_LEVEL_IDC_3_1 = 4,
STD_VIDEO_H265_LEVEL_IDC_4_0 = 5,
STD_VIDEO_H265_LEVEL_IDC_4_1 = 6,
STD_VIDEO_H265_LEVEL_IDC_5_0 = 7,
STD_VIDEO_H265_LEVEL_IDC_5_1 = 8,
STD_VIDEO_H265_LEVEL_IDC_5_2 = 9,
STD_VIDEO_H265_LEVEL_IDC_6_0 = 10,
STD_VIDEO_H265_LEVEL_IDC_6_1 = 11,
STD_VIDEO_H265_LEVEL_IDC_6_2 = 12,
STD_VIDEO_H265_LEVEL_IDC_INVALID = 0x7FFFFFFF,
STD_VIDEO_H265_LEVEL_IDC_MAX_ENUM = 0x7FFFFFFF
} StdVideoH265LevelIdc;
typedef enum StdVideoH265SliceType {
STD_VIDEO_H265_SLICE_TYPE_B = 0,
STD_VIDEO_H265_SLICE_TYPE_P = 1,
STD_VIDEO_H265_SLICE_TYPE_I = 2,
STD_VIDEO_H265_SLICE_TYPE_INVALID = 0x7FFFFFFF,
STD_VIDEO_H265_SLICE_TYPE_MAX_ENUM = 0x7FFFFFFF
} StdVideoH265SliceType;
typedef enum StdVideoH265PictureType {
STD_VIDEO_H265_PICTURE_TYPE_P = 0,
STD_VIDEO_H265_PICTURE_TYPE_B = 1,
STD_VIDEO_H265_PICTURE_TYPE_I = 2,
STD_VIDEO_H265_PICTURE_TYPE_IDR = 3,
STD_VIDEO_H265_PICTURE_TYPE_INVALID = 0x7FFFFFFF,
STD_VIDEO_H265_PICTURE_TYPE_MAX_ENUM = 0x7FFFFFFF
} StdVideoH265PictureType;
typedef enum StdVideoH265AspectRatioIdc {
STD_VIDEO_H265_ASPECT_RATIO_IDC_UNSPECIFIED = 0,
STD_VIDEO_H265_ASPECT_RATIO_IDC_SQUARE = 1,
STD_VIDEO_H265_ASPECT_RATIO_IDC_12_11 = 2,
STD_VIDEO_H265_ASPECT_RATIO_IDC_10_11 = 3,
STD_VIDEO_H265_ASPECT_RATIO_IDC_16_11 = 4,
STD_VIDEO_H265_ASPECT_RATIO_IDC_40_33 = 5,
STD_VIDEO_H265_ASPECT_RATIO_IDC_24_11 = 6,
STD_VIDEO_H265_ASPECT_RATIO_IDC_20_11 = 7,
STD_VIDEO_H265_ASPECT_RATIO_IDC_32_11 = 8,
STD_VIDEO_H265_ASPECT_RATIO_IDC_80_33 = 9,
STD_VIDEO_H265_ASPECT_RATIO_IDC_18_11 = 10,
STD_VIDEO_H265_ASPECT_RATIO_IDC_15_11 = 11,
STD_VIDEO_H265_ASPECT_RATIO_IDC_64_33 = 12,
STD_VIDEO_H265_ASPECT_RATIO_IDC_160_99 = 13,
STD_VIDEO_H265_ASPECT_RATIO_IDC_4_3 = 14,
STD_VIDEO_H265_ASPECT_RATIO_IDC_3_2 = 15,
STD_VIDEO_H265_ASPECT_RATIO_IDC_2_1 = 16,
STD_VIDEO_H265_ASPECT_RATIO_IDC_EXTENDED_SAR = 255,
STD_VIDEO_H265_ASPECT_RATIO_IDC_INVALID = 0x7FFFFFFF,
STD_VIDEO_H265_ASPECT_RATIO_IDC_MAX_ENUM = 0x7FFFFFFF
} StdVideoH265AspectRatioIdc;
typedef struct StdVideoH265DecPicBufMgr {
uint32_t max_latency_increase_plus1[STD_VIDEO_H265_SUBLAYERS_LIST_SIZE];
uint8_t max_dec_pic_buffering_minus1[STD_VIDEO_H265_SUBLAYERS_LIST_SIZE];
uint8_t max_num_reorder_pics[STD_VIDEO_H265_SUBLAYERS_LIST_SIZE];
} StdVideoH265DecPicBufMgr;
typedef struct StdVideoH265SubLayerHrdParameters {
uint32_t bit_rate_value_minus1[STD_VIDEO_H265_CPB_CNT_LIST_SIZE];
uint32_t cpb_size_value_minus1[STD_VIDEO_H265_CPB_CNT_LIST_SIZE];
uint32_t cpb_size_du_value_minus1[STD_VIDEO_H265_CPB_CNT_LIST_SIZE];
uint32_t bit_rate_du_value_minus1[STD_VIDEO_H265_CPB_CNT_LIST_SIZE];
uint32_t cbr_flag;
} StdVideoH265SubLayerHrdParameters;
typedef struct StdVideoH265HrdFlags {
uint32_t nal_hrd_parameters_present_flag : 1;
uint32_t vcl_hrd_parameters_present_flag : 1;
uint32_t sub_pic_hrd_params_present_flag : 1;
uint32_t sub_pic_cpb_params_in_pic_timing_sei_flag : 1;
uint32_t fixed_pic_rate_general_flag : 8;
uint32_t fixed_pic_rate_within_cvs_flag : 8;
uint32_t low_delay_hrd_flag : 8;
} StdVideoH265HrdFlags;
typedef struct StdVideoH265HrdParameters {
StdVideoH265HrdFlags flags;
uint8_t tick_divisor_minus2;
uint8_t du_cpb_removal_delay_increment_length_minus1;
uint8_t dpb_output_delay_du_length_minus1;
uint8_t bit_rate_scale;
uint8_t cpb_size_scale;
uint8_t cpb_size_du_scale;
uint8_t initial_cpb_removal_delay_length_minus1;
uint8_t au_cpb_removal_delay_length_minus1;
uint8_t dpb_output_delay_length_minus1;
uint8_t cpb_cnt_minus1[STD_VIDEO_H265_SUBLAYERS_LIST_SIZE];
uint16_t elemental_duration_in_tc_minus1[STD_VIDEO_H265_SUBLAYERS_LIST_SIZE];
uint16_t reserved[3];
const StdVideoH265SubLayerHrdParameters* pSubLayerHrdParametersNal;
const StdVideoH265SubLayerHrdParameters* pSubLayerHrdParametersVcl;
} StdVideoH265HrdParameters;
typedef struct StdVideoH265VpsFlags {
uint32_t vps_temporal_id_nesting_flag : 1;
uint32_t vps_sub_layer_ordering_info_present_flag : 1;
uint32_t vps_timing_info_present_flag : 1;
uint32_t vps_poc_proportional_to_timing_flag : 1;
} StdVideoH265VpsFlags;
typedef struct StdVideoH265ProfileTierLevelFlags {
uint32_t general_tier_flag : 1;
uint32_t general_progressive_source_flag : 1;
uint32_t general_interlaced_source_flag : 1;
uint32_t general_non_packed_constraint_flag : 1;
uint32_t general_frame_only_constraint_flag : 1;
} StdVideoH265ProfileTierLevelFlags;
typedef struct StdVideoH265ProfileTierLevel {
StdVideoH265ProfileTierLevelFlags flags;
StdVideoH265ProfileIdc general_profile_idc;
StdVideoH265LevelIdc general_level_idc;
} StdVideoH265ProfileTierLevel;
typedef struct StdVideoH265VideoParameterSet {
StdVideoH265VpsFlags flags;
uint8_t vps_video_parameter_set_id;
uint8_t vps_max_sub_layers_minus1;
uint8_t reserved1;
uint8_t reserved2;
uint32_t vps_num_units_in_tick;
uint32_t vps_time_scale;
uint32_t vps_num_ticks_poc_diff_one_minus1;
uint32_t reserved3;
const StdVideoH265DecPicBufMgr* pDecPicBufMgr;
const StdVideoH265HrdParameters* pHrdParameters;
const StdVideoH265ProfileTierLevel* pProfileTierLevel;
} StdVideoH265VideoParameterSet;
typedef struct StdVideoH265ScalingLists {
uint8_t ScalingList4x4[STD_VIDEO_H265_SCALING_LIST_4X4_NUM_LISTS][STD_VIDEO_H265_SCALING_LIST_4X4_NUM_ELEMENTS];
uint8_t ScalingList8x8[STD_VIDEO_H265_SCALING_LIST_8X8_NUM_LISTS][STD_VIDEO_H265_SCALING_LIST_8X8_NUM_ELEMENTS];
uint8_t ScalingList16x16[STD_VIDEO_H265_SCALING_LIST_16X16_NUM_LISTS][STD_VIDEO_H265_SCALING_LIST_16X16_NUM_ELEMENTS];
uint8_t ScalingList32x32[STD_VIDEO_H265_SCALING_LIST_32X32_NUM_LISTS][STD_VIDEO_H265_SCALING_LIST_32X32_NUM_ELEMENTS];
uint8_t ScalingListDCCoef16x16[STD_VIDEO_H265_SCALING_LIST_16X16_NUM_LISTS];
uint8_t ScalingListDCCoef32x32[STD_VIDEO_H265_SCALING_LIST_32X32_NUM_LISTS];
} StdVideoH265ScalingLists;
typedef struct StdVideoH265SpsVuiFlags {
uint32_t aspect_ratio_info_present_flag : 1;
uint32_t overscan_info_present_flag : 1;
uint32_t overscan_appropriate_flag : 1;
uint32_t video_signal_type_present_flag : 1;
uint32_t video_full_range_flag : 1;
uint32_t colour_description_present_flag : 1;
uint32_t chroma_loc_info_present_flag : 1;
uint32_t neutral_chroma_indication_flag : 1;
uint32_t field_seq_flag : 1;
uint32_t frame_field_info_present_flag : 1;
uint32_t default_display_window_flag : 1;
uint32_t vui_timing_info_present_flag : 1;
uint32_t vui_poc_proportional_to_timing_flag : 1;
uint32_t vui_hrd_parameters_present_flag : 1;
uint32_t bitstream_restriction_flag : 1;
uint32_t tiles_fixed_structure_flag : 1;
uint32_t motion_vectors_over_pic_boundaries_flag : 1;
uint32_t restricted_ref_pic_lists_flag : 1;
} StdVideoH265SpsVuiFlags;
typedef struct StdVideoH265SequenceParameterSetVui {
StdVideoH265SpsVuiFlags flags;
StdVideoH265AspectRatioIdc aspect_ratio_idc;
uint16_t sar_width;
uint16_t sar_height;
uint8_t video_format;
uint8_t colour_primaries;
uint8_t transfer_characteristics;
uint8_t matrix_coeffs;
uint8_t chroma_sample_loc_type_top_field;
uint8_t chroma_sample_loc_type_bottom_field;
uint8_t reserved1;
uint8_t reserved2;
uint16_t def_disp_win_left_offset;
uint16_t def_disp_win_right_offset;
uint16_t def_disp_win_top_offset;
uint16_t def_disp_win_bottom_offset;
uint32_t vui_num_units_in_tick;
uint32_t vui_time_scale;
uint32_t vui_num_ticks_poc_diff_one_minus1;
uint16_t min_spatial_segmentation_idc;
uint16_t reserved3;
uint8_t max_bytes_per_pic_denom;
uint8_t max_bits_per_min_cu_denom;
uint8_t log2_max_mv_length_horizontal;
uint8_t log2_max_mv_length_vertical;
const StdVideoH265HrdParameters* pHrdParameters;
} StdVideoH265SequenceParameterSetVui;
typedef struct StdVideoH265PredictorPaletteEntries {
uint16_t PredictorPaletteEntries[STD_VIDEO_H265_PREDICTOR_PALETTE_COMPONENTS_LIST_SIZE][STD_VIDEO_H265_PREDICTOR_PALETTE_COMP_ENTRIES_LIST_SIZE];
} StdVideoH265PredictorPaletteEntries;
typedef struct StdVideoH265SpsFlags {
uint32_t sps_temporal_id_nesting_flag : 1;
uint32_t separate_colour_plane_flag : 1;
uint32_t conformance_window_flag : 1;
uint32_t sps_sub_layer_ordering_info_present_flag : 1;
uint32_t scaling_list_enabled_flag : 1;
uint32_t sps_scaling_list_data_present_flag : 1;
uint32_t amp_enabled_flag : 1;
uint32_t sample_adaptive_offset_enabled_flag : 1;
uint32_t pcm_enabled_flag : 1;
uint32_t pcm_loop_filter_disabled_flag : 1;
uint32_t long_term_ref_pics_present_flag : 1;
uint32_t sps_temporal_mvp_enabled_flag : 1;
uint32_t strong_intra_smoothing_enabled_flag : 1;
uint32_t vui_parameters_present_flag : 1;
uint32_t sps_extension_present_flag : 1;
uint32_t sps_range_extension_flag : 1;
uint32_t transform_skip_rotation_enabled_flag : 1;
uint32_t transform_skip_context_enabled_flag : 1;
uint32_t implicit_rdpcm_enabled_flag : 1;
uint32_t explicit_rdpcm_enabled_flag : 1;
uint32_t extended_precision_processing_flag : 1;
uint32_t intra_smoothing_disabled_flag : 1;
uint32_t high_precision_offsets_enabled_flag : 1;
uint32_t persistent_rice_adaptation_enabled_flag : 1;
uint32_t cabac_bypass_alignment_enabled_flag : 1;
uint32_t sps_scc_extension_flag : 1;
uint32_t sps_curr_pic_ref_enabled_flag : 1;
uint32_t palette_mode_enabled_flag : 1;
uint32_t sps_palette_predictor_initializers_present_flag : 1;
uint32_t intra_boundary_filtering_disabled_flag : 1;
} StdVideoH265SpsFlags;
typedef struct StdVideoH265ShortTermRefPicSetFlags {
uint32_t inter_ref_pic_set_prediction_flag : 1;
uint32_t delta_rps_sign : 1;
} StdVideoH265ShortTermRefPicSetFlags;
typedef struct StdVideoH265ShortTermRefPicSet {
StdVideoH265ShortTermRefPicSetFlags flags;
uint32_t delta_idx_minus1;
uint16_t use_delta_flag;
uint16_t abs_delta_rps_minus1;
uint16_t used_by_curr_pic_flag;
uint16_t used_by_curr_pic_s0_flag;
uint16_t used_by_curr_pic_s1_flag;
uint16_t reserved1;
uint8_t reserved2;
uint8_t reserved3;
uint8_t num_negative_pics;
uint8_t num_positive_pics;
uint16_t delta_poc_s0_minus1[STD_VIDEO_H265_MAX_DPB_SIZE];
uint16_t delta_poc_s1_minus1[STD_VIDEO_H265_MAX_DPB_SIZE];
} StdVideoH265ShortTermRefPicSet;
typedef struct StdVideoH265LongTermRefPicsSps {
uint32_t used_by_curr_pic_lt_sps_flag;
uint32_t lt_ref_pic_poc_lsb_sps[STD_VIDEO_H265_MAX_LONG_TERM_REF_PICS_SPS];
} StdVideoH265LongTermRefPicsSps;
typedef struct StdVideoH265SequenceParameterSet {
StdVideoH265SpsFlags flags;
StdVideoH265ChromaFormatIdc chroma_format_idc;
uint32_t pic_width_in_luma_samples;
uint32_t pic_height_in_luma_samples;
uint8_t sps_video_parameter_set_id;
uint8_t sps_max_sub_layers_minus1;
uint8_t sps_seq_parameter_set_id;
uint8_t bit_depth_luma_minus8;
uint8_t bit_depth_chroma_minus8;
uint8_t log2_max_pic_order_cnt_lsb_minus4;
uint8_t log2_min_luma_coding_block_size_minus3;
uint8_t log2_diff_max_min_luma_coding_block_size;
uint8_t log2_min_luma_transform_block_size_minus2;
uint8_t log2_diff_max_min_luma_transform_block_size;
uint8_t max_transform_hierarchy_depth_inter;
uint8_t max_transform_hierarchy_depth_intra;
uint8_t num_short_term_ref_pic_sets;
uint8_t num_long_term_ref_pics_sps;
uint8_t pcm_sample_bit_depth_luma_minus1;
uint8_t pcm_sample_bit_depth_chroma_minus1;
uint8_t log2_min_pcm_luma_coding_block_size_minus3;
uint8_t log2_diff_max_min_pcm_luma_coding_block_size;
uint8_t reserved1;
uint8_t reserved2;
uint8_t palette_max_size;
uint8_t delta_palette_max_predictor_size;
uint8_t motion_vector_resolution_control_idc;
uint8_t sps_num_palette_predictor_initializers_minus1;
uint32_t conf_win_left_offset;
uint32_t conf_win_right_offset;
uint32_t conf_win_top_offset;
uint32_t conf_win_bottom_offset;
const StdVideoH265ProfileTierLevel* pProfileTierLevel;
const StdVideoH265DecPicBufMgr* pDecPicBufMgr;
const StdVideoH265ScalingLists* pScalingLists;
const StdVideoH265ShortTermRefPicSet* pShortTermRefPicSet;
const StdVideoH265LongTermRefPicsSps* pLongTermRefPicsSps;
const StdVideoH265SequenceParameterSetVui* pSequenceParameterSetVui;
const StdVideoH265PredictorPaletteEntries* pPredictorPaletteEntries;
} StdVideoH265SequenceParameterSet;
typedef struct StdVideoH265PpsFlags {
uint32_t dependent_slice_segments_enabled_flag : 1;
uint32_t output_flag_present_flag : 1;
uint32_t sign_data_hiding_enabled_flag : 1;
uint32_t cabac_init_present_flag : 1;
uint32_t constrained_intra_pred_flag : 1;
uint32_t transform_skip_enabled_flag : 1;
uint32_t cu_qp_delta_enabled_flag : 1;
uint32_t pps_slice_chroma_qp_offsets_present_flag : 1;
uint32_t weighted_pred_flag : 1;
uint32_t weighted_bipred_flag : 1;
uint32_t transquant_bypass_enabled_flag : 1;
uint32_t tiles_enabled_flag : 1;
uint32_t entropy_coding_sync_enabled_flag : 1;
uint32_t uniform_spacing_flag : 1;
uint32_t loop_filter_across_tiles_enabled_flag : 1;
uint32_t pps_loop_filter_across_slices_enabled_flag : 1;
uint32_t deblocking_filter_control_present_flag : 1;
uint32_t deblocking_filter_override_enabled_flag : 1;
uint32_t pps_deblocking_filter_disabled_flag : 1;
uint32_t pps_scaling_list_data_present_flag : 1;
uint32_t lists_modification_present_flag : 1;
uint32_t slice_segment_header_extension_present_flag : 1;
uint32_t pps_extension_present_flag : 1;
uint32_t cross_component_prediction_enabled_flag : 1;
uint32_t chroma_qp_offset_list_enabled_flag : 1;
uint32_t pps_curr_pic_ref_enabled_flag : 1;
uint32_t residual_adaptive_colour_transform_enabled_flag : 1;
uint32_t pps_slice_act_qp_offsets_present_flag : 1;
uint32_t pps_palette_predictor_initializers_present_flag : 1;
uint32_t monochrome_palette_flag : 1;
uint32_t pps_range_extension_flag : 1;
} StdVideoH265PpsFlags;
typedef struct StdVideoH265PictureParameterSet {
StdVideoH265PpsFlags flags;
uint8_t pps_pic_parameter_set_id;
uint8_t pps_seq_parameter_set_id;
uint8_t sps_video_parameter_set_id;
uint8_t num_extra_slice_header_bits;
uint8_t num_ref_idx_l0_default_active_minus1;
uint8_t num_ref_idx_l1_default_active_minus1;
int8_t init_qp_minus26;
uint8_t diff_cu_qp_delta_depth;
int8_t pps_cb_qp_offset;
int8_t pps_cr_qp_offset;
int8_t pps_beta_offset_div2;
int8_t pps_tc_offset_div2;
uint8_t log2_parallel_merge_level_minus2;
uint8_t log2_max_transform_skip_block_size_minus2;
uint8_t diff_cu_chroma_qp_offset_depth;
uint8_t chroma_qp_offset_list_len_minus1;
int8_t cb_qp_offset_list[STD_VIDEO_H265_CHROMA_QP_OFFSET_LIST_SIZE];
int8_t cr_qp_offset_list[STD_VIDEO_H265_CHROMA_QP_OFFSET_LIST_SIZE];
uint8_t log2_sao_offset_scale_luma;
uint8_t log2_sao_offset_scale_chroma;
int8_t pps_act_y_qp_offset_plus5;
int8_t pps_act_cb_qp_offset_plus5;
int8_t pps_act_cr_qp_offset_plus3;
uint8_t pps_num_palette_predictor_initializers;
uint8_t luma_bit_depth_entry_minus8;
uint8_t chroma_bit_depth_entry_minus8;
uint8_t num_tile_columns_minus1;
uint8_t num_tile_rows_minus1;
uint8_t reserved1;
uint8_t reserved2;
uint16_t column_width_minus1[STD_VIDEO_H265_CHROMA_QP_OFFSET_TILE_COLS_LIST_SIZE];
uint16_t row_height_minus1[STD_VIDEO_H265_CHROMA_QP_OFFSET_TILE_ROWS_LIST_SIZE];
uint32_t reserved3;
const StdVideoH265ScalingLists* pScalingLists;
const StdVideoH265PredictorPaletteEntries* pPredictorPaletteEntries;
} StdVideoH265PictureParameterSet;
#ifdef __cplusplus
}
#endif
#endif
@@ -0,0 +1,67 @@
#ifndef VULKAN_VIDEO_CODEC_H265STD_DECODE_H_
#define VULKAN_VIDEO_CODEC_H265STD_DECODE_H_ 1
/*
** Copyright 2015-2024 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
*/
/*
** This header is generated from the Khronos Vulkan XML API Registry.
**
*/
#ifdef __cplusplus
extern "C" {
#endif
// vulkan_video_codec_h265std_decode is a preprocessor guard. Do not pass it to API calls.
#define vulkan_video_codec_h265std_decode 1
#include "vulkan_video_codec_h265std.h"
#define VK_STD_VULKAN_VIDEO_CODEC_H265_DECODE_API_VERSION_1_0_0 VK_MAKE_VIDEO_STD_VERSION(1, 0, 0)
#define VK_STD_VULKAN_VIDEO_CODEC_H265_DECODE_SPEC_VERSION VK_STD_VULKAN_VIDEO_CODEC_H265_DECODE_API_VERSION_1_0_0
#define VK_STD_VULKAN_VIDEO_CODEC_H265_DECODE_EXTENSION_NAME "VK_STD_vulkan_video_codec_h265_decode"
#define STD_VIDEO_DECODE_H265_REF_PIC_SET_LIST_SIZE 8
typedef struct StdVideoDecodeH265PictureInfoFlags {
uint32_t IrapPicFlag : 1;
uint32_t IdrPicFlag : 1;
uint32_t IsReference : 1;
uint32_t short_term_ref_pic_set_sps_flag : 1;
} StdVideoDecodeH265PictureInfoFlags;
typedef struct StdVideoDecodeH265PictureInfo {
StdVideoDecodeH265PictureInfoFlags flags;
uint8_t sps_video_parameter_set_id;
uint8_t pps_seq_parameter_set_id;
uint8_t pps_pic_parameter_set_id;
uint8_t NumDeltaPocsOfRefRpsIdx;
int32_t PicOrderCntVal;
uint16_t NumBitsForSTRefPicSetInSlice;
uint16_t reserved;
uint8_t RefPicSetStCurrBefore[STD_VIDEO_DECODE_H265_REF_PIC_SET_LIST_SIZE];
uint8_t RefPicSetStCurrAfter[STD_VIDEO_DECODE_H265_REF_PIC_SET_LIST_SIZE];
uint8_t RefPicSetLtCurr[STD_VIDEO_DECODE_H265_REF_PIC_SET_LIST_SIZE];
} StdVideoDecodeH265PictureInfo;
typedef struct StdVideoDecodeH265ReferenceInfoFlags {
uint32_t used_for_long_term_reference : 1;
uint32_t unused_for_reference : 1;
} StdVideoDecodeH265ReferenceInfoFlags;
typedef struct StdVideoDecodeH265ReferenceInfo {
StdVideoDecodeH265ReferenceInfoFlags flags;
int32_t PicOrderCntVal;
} StdVideoDecodeH265ReferenceInfo;
#ifdef __cplusplus
}
#endif
#endif
@@ -0,0 +1,157 @@
#ifndef VULKAN_VIDEO_CODEC_H265STD_ENCODE_H_
#define VULKAN_VIDEO_CODEC_H265STD_ENCODE_H_ 1
/*
** Copyright 2015-2024 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
*/
/*
** This header is generated from the Khronos Vulkan XML API Registry.
**
*/
#ifdef __cplusplus
extern "C" {
#endif
// vulkan_video_codec_h265std_encode is a preprocessor guard. Do not pass it to API calls.
#define vulkan_video_codec_h265std_encode 1
#include "vulkan_video_codec_h265std.h"
#define VK_STD_VULKAN_VIDEO_CODEC_H265_ENCODE_API_VERSION_1_0_0 VK_MAKE_VIDEO_STD_VERSION(1, 0, 0)
#define VK_STD_VULKAN_VIDEO_CODEC_H265_ENCODE_SPEC_VERSION VK_STD_VULKAN_VIDEO_CODEC_H265_ENCODE_API_VERSION_1_0_0
#define VK_STD_VULKAN_VIDEO_CODEC_H265_ENCODE_EXTENSION_NAME "VK_STD_vulkan_video_codec_h265_encode"
typedef struct StdVideoEncodeH265WeightTableFlags {
uint16_t luma_weight_l0_flag;
uint16_t chroma_weight_l0_flag;
uint16_t luma_weight_l1_flag;
uint16_t chroma_weight_l1_flag;
} StdVideoEncodeH265WeightTableFlags;
typedef struct StdVideoEncodeH265WeightTable {
StdVideoEncodeH265WeightTableFlags flags;
uint8_t luma_log2_weight_denom;
int8_t delta_chroma_log2_weight_denom;
int8_t delta_luma_weight_l0[STD_VIDEO_H265_MAX_NUM_LIST_REF];
int8_t luma_offset_l0[STD_VIDEO_H265_MAX_NUM_LIST_REF];
int8_t delta_chroma_weight_l0[STD_VIDEO_H265_MAX_NUM_LIST_REF][STD_VIDEO_H265_MAX_CHROMA_PLANES];
int8_t delta_chroma_offset_l0[STD_VIDEO_H265_MAX_NUM_LIST_REF][STD_VIDEO_H265_MAX_CHROMA_PLANES];
int8_t delta_luma_weight_l1[STD_VIDEO_H265_MAX_NUM_LIST_REF];
int8_t luma_offset_l1[STD_VIDEO_H265_MAX_NUM_LIST_REF];
int8_t delta_chroma_weight_l1[STD_VIDEO_H265_MAX_NUM_LIST_REF][STD_VIDEO_H265_MAX_CHROMA_PLANES];
int8_t delta_chroma_offset_l1[STD_VIDEO_H265_MAX_NUM_LIST_REF][STD_VIDEO_H265_MAX_CHROMA_PLANES];
} StdVideoEncodeH265WeightTable;
typedef struct StdVideoEncodeH265SliceSegmentHeaderFlags {
uint32_t first_slice_segment_in_pic_flag : 1;
uint32_t dependent_slice_segment_flag : 1;
uint32_t slice_sao_luma_flag : 1;
uint32_t slice_sao_chroma_flag : 1;
uint32_t num_ref_idx_active_override_flag : 1;
uint32_t mvd_l1_zero_flag : 1;
uint32_t cabac_init_flag : 1;
uint32_t cu_chroma_qp_offset_enabled_flag : 1;
uint32_t deblocking_filter_override_flag : 1;
uint32_t slice_deblocking_filter_disabled_flag : 1;
uint32_t collocated_from_l0_flag : 1;
uint32_t slice_loop_filter_across_slices_enabled_flag : 1;
uint32_t reserved : 20;
} StdVideoEncodeH265SliceSegmentHeaderFlags;
typedef struct StdVideoEncodeH265SliceSegmentHeader {
StdVideoEncodeH265SliceSegmentHeaderFlags flags;
StdVideoH265SliceType slice_type;
uint32_t slice_segment_address;
uint8_t collocated_ref_idx;
uint8_t MaxNumMergeCand;
int8_t slice_cb_qp_offset;
int8_t slice_cr_qp_offset;
int8_t slice_beta_offset_div2;
int8_t slice_tc_offset_div2;
int8_t slice_act_y_qp_offset;
int8_t slice_act_cb_qp_offset;
int8_t slice_act_cr_qp_offset;
int8_t slice_qp_delta;
uint16_t reserved1;
const StdVideoEncodeH265WeightTable* pWeightTable;
} StdVideoEncodeH265SliceSegmentHeader;
typedef struct StdVideoEncodeH265ReferenceListsInfoFlags {
uint32_t ref_pic_list_modification_flag_l0 : 1;
uint32_t ref_pic_list_modification_flag_l1 : 1;
uint32_t reserved : 30;
} StdVideoEncodeH265ReferenceListsInfoFlags;
typedef struct StdVideoEncodeH265ReferenceListsInfo {
StdVideoEncodeH265ReferenceListsInfoFlags flags;
uint8_t num_ref_idx_l0_active_minus1;
uint8_t num_ref_idx_l1_active_minus1;
uint8_t RefPicList0[STD_VIDEO_H265_MAX_NUM_LIST_REF];
uint8_t RefPicList1[STD_VIDEO_H265_MAX_NUM_LIST_REF];
uint8_t list_entry_l0[STD_VIDEO_H265_MAX_NUM_LIST_REF];
uint8_t list_entry_l1[STD_VIDEO_H265_MAX_NUM_LIST_REF];
} StdVideoEncodeH265ReferenceListsInfo;
typedef struct StdVideoEncodeH265PictureInfoFlags {
uint32_t is_reference : 1;
uint32_t IrapPicFlag : 1;
uint32_t used_for_long_term_reference : 1;
uint32_t discardable_flag : 1;
uint32_t cross_layer_bla_flag : 1;
uint32_t pic_output_flag : 1;
uint32_t no_output_of_prior_pics_flag : 1;
uint32_t short_term_ref_pic_set_sps_flag : 1;
uint32_t slice_temporal_mvp_enabled_flag : 1;
uint32_t reserved : 23;
} StdVideoEncodeH265PictureInfoFlags;
typedef struct StdVideoEncodeH265LongTermRefPics {
uint8_t num_long_term_sps;
uint8_t num_long_term_pics;
uint8_t lt_idx_sps[STD_VIDEO_H265_MAX_LONG_TERM_REF_PICS_SPS];
uint8_t poc_lsb_lt[STD_VIDEO_H265_MAX_LONG_TERM_PICS];
uint16_t used_by_curr_pic_lt_flag;
uint8_t delta_poc_msb_present_flag[STD_VIDEO_H265_MAX_DELTA_POC];
uint8_t delta_poc_msb_cycle_lt[STD_VIDEO_H265_MAX_DELTA_POC];
} StdVideoEncodeH265LongTermRefPics;
typedef struct StdVideoEncodeH265PictureInfo {
StdVideoEncodeH265PictureInfoFlags flags;
StdVideoH265PictureType pic_type;
uint8_t sps_video_parameter_set_id;
uint8_t pps_seq_parameter_set_id;
uint8_t pps_pic_parameter_set_id;
uint8_t short_term_ref_pic_set_idx;
int32_t PicOrderCntVal;
uint8_t TemporalId;
uint8_t reserved1[7];
const StdVideoEncodeH265ReferenceListsInfo* pRefLists;
const StdVideoH265ShortTermRefPicSet* pShortTermRefPicSet;
const StdVideoEncodeH265LongTermRefPics* pLongTermRefPics;
} StdVideoEncodeH265PictureInfo;
typedef struct StdVideoEncodeH265ReferenceInfoFlags {
uint32_t used_for_long_term_reference : 1;
uint32_t unused_for_reference : 1;
uint32_t reserved : 30;
} StdVideoEncodeH265ReferenceInfoFlags;
typedef struct StdVideoEncodeH265ReferenceInfo {
StdVideoEncodeH265ReferenceInfoFlags flags;
StdVideoH265PictureType pic_type;
int32_t PicOrderCntVal;
uint8_t TemporalId;
} StdVideoEncodeH265ReferenceInfo;
#ifdef __cplusplus
}
#endif
#endif
@@ -0,0 +1,36 @@
#ifndef VULKAN_VIDEO_CODECS_COMMON_H_
#define VULKAN_VIDEO_CODECS_COMMON_H_ 1
/*
** Copyright 2015-2024 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
*/
/*
** This header is generated from the Khronos Vulkan XML API Registry.
**
*/
#ifdef __cplusplus
extern "C" {
#endif
// vulkan_video_codecs_common is a preprocessor guard. Do not pass it to API calls.
#define vulkan_video_codecs_common 1
#if !defined(VK_NO_STDINT_H)
#include <stdint.h>
#endif
#define VK_MAKE_VIDEO_STD_VERSION(major, minor, patch) \
((((uint32_t)(major)) << 22) | (((uint32_t)(minor)) << 12) | ((uint32_t)(patch)))
#ifdef __cplusplus
}
#endif
#endif
+244
View File
@@ -0,0 +1,244 @@
/*
* Copyright 2015-2023 The Khronos Group Inc.
* Copyright 2015-2023 Valve Corporation
* Copyright 2015-2023 LunarG, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "vulkan.h"
#include <stdbool.h>
// Loader-ICD version negotiation API. Versions add the following features:
// Version 0 - Initial. Doesn't support vk_icdGetInstanceProcAddr
// or vk_icdNegotiateLoaderICDInterfaceVersion.
// Version 1 - Add support for vk_icdGetInstanceProcAddr.
// Version 2 - Add Loader/ICD Interface version negotiation
// via vk_icdNegotiateLoaderICDInterfaceVersion.
// Version 3 - Add ICD creation/destruction of KHR_surface objects.
// Version 4 - Add unknown physical device extension querying via
// vk_icdGetPhysicalDeviceProcAddr.
// Version 5 - Tells ICDs that the loader is now paying attention to the
// application version of Vulkan passed into the ApplicationInfo
// structure during vkCreateInstance. This will tell the ICD
// that if the loader is older, it should automatically fail a
// call for any API version > 1.0. Otherwise, the loader will
// manually determine if it can support the expected version.
// Version 6 - Add support for vk_icdEnumerateAdapterPhysicalDevices.
// Version 7 - If an ICD supports any of the following functions, they must be
// queryable with vk_icdGetInstanceProcAddr:
// vk_icdNegotiateLoaderICDInterfaceVersion
// vk_icdGetPhysicalDeviceProcAddr
// vk_icdEnumerateAdapterPhysicalDevices (Windows only)
// In addition, these functions no longer need to be exported directly.
// This version allows drivers provided through the extension
// VK_LUNARG_direct_driver_loading be able to support the entire
// Driver-Loader interface.
#define CURRENT_LOADER_ICD_INTERFACE_VERSION 7
#define MIN_SUPPORTED_LOADER_ICD_INTERFACE_VERSION 0
#define MIN_PHYS_DEV_EXTENSION_ICD_INTERFACE_VERSION 4
// Old typedefs that don't follow a proper naming convention but are preserved for compatibility
typedef VkResult(VKAPI_PTR *PFN_vkNegotiateLoaderICDInterfaceVersion)(uint32_t *pVersion);
// This is defined in vk_layer.h which will be found by the loader, but if an ICD is building against this
// file directly, it won't be found.
#ifndef PFN_GetPhysicalDeviceProcAddr
typedef PFN_vkVoidFunction(VKAPI_PTR *PFN_GetPhysicalDeviceProcAddr)(VkInstance instance, const char *pName);
#endif
// Typedefs for loader/ICD interface
typedef VkResult (VKAPI_PTR *PFN_vk_icdNegotiateLoaderICDInterfaceVersion)(uint32_t* pVersion);
typedef PFN_vkVoidFunction (VKAPI_PTR *PFN_vk_icdGetInstanceProcAddr)(VkInstance instance, const char* pName);
typedef PFN_vkVoidFunction (VKAPI_PTR *PFN_vk_icdGetPhysicalDeviceProcAddr)(VkInstance instance, const char* pName);
#if defined(VK_USE_PLATFORM_WIN32_KHR)
typedef VkResult (VKAPI_PTR *PFN_vk_icdEnumerateAdapterPhysicalDevices)(VkInstance instance, LUID adapterLUID,
uint32_t* pPhysicalDeviceCount, VkPhysicalDevice* pPhysicalDevices);
#endif
// Prototypes for loader/ICD interface
#if !defined(VK_NO_PROTOTYPES)
#ifdef __cplusplus
extern "C" {
#endif
VKAPI_ATTR VkResult VKAPI_CALL vk_icdNegotiateLoaderICDInterfaceVersion(uint32_t* pVersion);
VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vk_icdGetInstanceProcAddr(VkInstance instance, const char* pName);
VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vk_icdGetPhysicalDeviceProcAddr(VkInstance instance, const char* pName);
#if defined(VK_USE_PLATFORM_WIN32_KHR)
VKAPI_ATTR VkResult VKAPI_CALL vk_icdEnumerateAdapterPhysicalDevices(VkInstance instance, LUID adapterLUID,
uint32_t* pPhysicalDeviceCount, VkPhysicalDevice* pPhysicalDevices);
#endif
#ifdef __cplusplus
}
#endif
#endif
/*
* The ICD must reserve space for a pointer for the loader's dispatch
* table, at the start of <each object>.
* The ICD must initialize this variable using the SET_LOADER_MAGIC_VALUE macro.
*/
#define ICD_LOADER_MAGIC 0x01CDC0DE
typedef union {
uintptr_t loaderMagic;
void *loaderData;
} VK_LOADER_DATA;
static inline void set_loader_magic_value(void *pNewObject) {
VK_LOADER_DATA *loader_info = (VK_LOADER_DATA *)pNewObject;
loader_info->loaderMagic = ICD_LOADER_MAGIC;
}
static inline bool valid_loader_magic_value(void *pNewObject) {
const VK_LOADER_DATA *loader_info = (VK_LOADER_DATA *)pNewObject;
return (loader_info->loaderMagic & 0xffffffff) == ICD_LOADER_MAGIC;
}
/*
* Windows and Linux ICDs will treat VkSurfaceKHR as a pointer to a struct that
* contains the platform-specific connection and surface information.
*/
typedef enum {
VK_ICD_WSI_PLATFORM_MIR,
VK_ICD_WSI_PLATFORM_WAYLAND,
VK_ICD_WSI_PLATFORM_WIN32,
VK_ICD_WSI_PLATFORM_XCB,
VK_ICD_WSI_PLATFORM_XLIB,
VK_ICD_WSI_PLATFORM_ANDROID,
VK_ICD_WSI_PLATFORM_MACOS,
VK_ICD_WSI_PLATFORM_IOS,
VK_ICD_WSI_PLATFORM_DISPLAY,
VK_ICD_WSI_PLATFORM_HEADLESS,
VK_ICD_WSI_PLATFORM_METAL,
VK_ICD_WSI_PLATFORM_DIRECTFB,
VK_ICD_WSI_PLATFORM_VI,
VK_ICD_WSI_PLATFORM_GGP,
VK_ICD_WSI_PLATFORM_SCREEN,
VK_ICD_WSI_PLATFORM_FUCHSIA,
} VkIcdWsiPlatform;
typedef struct {
VkIcdWsiPlatform platform;
} VkIcdSurfaceBase;
#ifdef VK_USE_PLATFORM_MIR_KHR
typedef struct {
VkIcdSurfaceBase base;
MirConnection *connection;
MirSurface *mirSurface;
} VkIcdSurfaceMir;
#endif // VK_USE_PLATFORM_MIR_KHR
#ifdef VK_USE_PLATFORM_WAYLAND_KHR
typedef struct {
VkIcdSurfaceBase base;
struct wl_display *display;
struct wl_surface *surface;
} VkIcdSurfaceWayland;
#endif // VK_USE_PLATFORM_WAYLAND_KHR
#ifdef VK_USE_PLATFORM_WIN32_KHR
typedef struct {
VkIcdSurfaceBase base;
HINSTANCE hinstance;
HWND hwnd;
} VkIcdSurfaceWin32;
#endif // VK_USE_PLATFORM_WIN32_KHR
#ifdef VK_USE_PLATFORM_XCB_KHR
typedef struct {
VkIcdSurfaceBase base;
xcb_connection_t *connection;
xcb_window_t window;
} VkIcdSurfaceXcb;
#endif // VK_USE_PLATFORM_XCB_KHR
#ifdef VK_USE_PLATFORM_XLIB_KHR
typedef struct {
VkIcdSurfaceBase base;
Display *dpy;
Window window;
} VkIcdSurfaceXlib;
#endif // VK_USE_PLATFORM_XLIB_KHR
#ifdef VK_USE_PLATFORM_DIRECTFB_EXT
typedef struct {
VkIcdSurfaceBase base;
IDirectFB *dfb;
IDirectFBSurface *surface;
} VkIcdSurfaceDirectFB;
#endif // VK_USE_PLATFORM_DIRECTFB_EXT
#ifdef VK_USE_PLATFORM_ANDROID_KHR
typedef struct {
VkIcdSurfaceBase base;
struct ANativeWindow *window;
} VkIcdSurfaceAndroid;
#endif // VK_USE_PLATFORM_ANDROID_KHR
#ifdef VK_USE_PLATFORM_MACOS_MVK
typedef struct {
VkIcdSurfaceBase base;
const void *pView;
} VkIcdSurfaceMacOS;
#endif // VK_USE_PLATFORM_MACOS_MVK
#ifdef VK_USE_PLATFORM_IOS_MVK
typedef struct {
VkIcdSurfaceBase base;
const void *pView;
} VkIcdSurfaceIOS;
#endif // VK_USE_PLATFORM_IOS_MVK
#ifdef VK_USE_PLATFORM_GGP
typedef struct {
VkIcdSurfaceBase base;
GgpStreamDescriptor streamDescriptor;
} VkIcdSurfaceGgp;
#endif // VK_USE_PLATFORM_GGP
typedef struct {
VkIcdSurfaceBase base;
VkDisplayModeKHR displayMode;
uint32_t planeIndex;
uint32_t planeStackIndex;
VkSurfaceTransformFlagBitsKHR transform;
float globalAlpha;
VkDisplayPlaneAlphaFlagBitsKHR alphaMode;
VkExtent2D imageExtent;
} VkIcdSurfaceDisplay;
typedef struct {
VkIcdSurfaceBase base;
} VkIcdSurfaceHeadless;
#ifdef VK_USE_PLATFORM_METAL_EXT
typedef struct {
VkIcdSurfaceBase base;
const CAMetalLayer *pLayer;
} VkIcdSurfaceMetal;
#endif // VK_USE_PLATFORM_METAL_EXT
#ifdef VK_USE_PLATFORM_VI_NN
typedef struct {
VkIcdSurfaceBase base;
void *window;
} VkIcdSurfaceVi;
#endif // VK_USE_PLATFORM_VI_NN
#ifdef VK_USE_PLATFORM_SCREEN_QNX
typedef struct {
VkIcdSurfaceBase base;
struct _screen_context *context;
struct _screen_window *window;
} VkIcdSurfaceScreen;
#endif // VK_USE_PLATFORM_SCREEN_QNX
#ifdef VK_USE_PLATFORM_FUCHSIA
typedef struct {
VkIcdSurfaceBase base;
} VkIcdSurfaceImagePipe;
#endif // VK_USE_PLATFORM_FUCHSIA
+189
View File
@@ -0,0 +1,189 @@
/*
* Copyright 2015-2023 The Khronos Group Inc.
* Copyright 2015-2023 Valve Corporation
* Copyright 2015-2023 LunarG, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
/* Need to define dispatch table
* Core struct can then have ptr to dispatch table at the top
* Along with object ptrs for current and next OBJ
*/
#include "vulkan_core.h"
#define MAX_NUM_UNKNOWN_EXTS 250
// Loader-Layer version negotiation API. Versions add the following features:
// Versions 0/1 - Initial. Doesn't support vk_layerGetPhysicalDeviceProcAddr
// or vk_icdNegotiateLoaderLayerInterfaceVersion.
// Version 2 - Add support for vk_layerGetPhysicalDeviceProcAddr and
// vk_icdNegotiateLoaderLayerInterfaceVersion.
#define CURRENT_LOADER_LAYER_INTERFACE_VERSION 2
#define MIN_SUPPORTED_LOADER_LAYER_INTERFACE_VERSION 1
#define VK_CURRENT_CHAIN_VERSION 1
// Typedef for use in the interfaces below
typedef PFN_vkVoidFunction (VKAPI_PTR *PFN_GetPhysicalDeviceProcAddr)(VkInstance instance, const char* pName);
// Version negotiation values
typedef enum VkNegotiateLayerStructType {
LAYER_NEGOTIATE_UNINTIALIZED = 0,
LAYER_NEGOTIATE_INTERFACE_STRUCT = 1,
} VkNegotiateLayerStructType;
// Version negotiation structures
typedef struct VkNegotiateLayerInterface {
VkNegotiateLayerStructType sType;
void *pNext;
uint32_t loaderLayerInterfaceVersion;
PFN_vkGetInstanceProcAddr pfnGetInstanceProcAddr;
PFN_vkGetDeviceProcAddr pfnGetDeviceProcAddr;
PFN_GetPhysicalDeviceProcAddr pfnGetPhysicalDeviceProcAddr;
} VkNegotiateLayerInterface;
// Version negotiation functions
typedef VkResult (VKAPI_PTR *PFN_vkNegotiateLoaderLayerInterfaceVersion)(VkNegotiateLayerInterface *pVersionStruct);
// Function prototype for unknown physical device extension command
typedef VkResult(VKAPI_PTR *PFN_PhysDevExt)(VkPhysicalDevice phys_device);
// ------------------------------------------------------------------------------------------------
// CreateInstance and CreateDevice support structures
/* Sub type of structure for instance and device loader ext of CreateInfo.
* When sType == VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO
* or sType == VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO
* then VkLayerFunction indicates struct type pointed to by pNext
*/
typedef enum VkLayerFunction_ {
VK_LAYER_LINK_INFO = 0,
VK_LOADER_DATA_CALLBACK = 1,
VK_LOADER_LAYER_CREATE_DEVICE_CALLBACK = 2,
VK_LOADER_FEATURES = 3,
} VkLayerFunction;
typedef struct VkLayerInstanceLink_ {
struct VkLayerInstanceLink_ *pNext;
PFN_vkGetInstanceProcAddr pfnNextGetInstanceProcAddr;
PFN_GetPhysicalDeviceProcAddr pfnNextGetPhysicalDeviceProcAddr;
} VkLayerInstanceLink;
/*
* When creating the device chain the loader needs to pass
* down information about it's device structure needed at
* the end of the chain. Passing the data via the
* VkLayerDeviceInfo avoids issues with finding the
* exact instance being used.
*/
typedef struct VkLayerDeviceInfo_ {
void *device_info;
PFN_vkGetInstanceProcAddr pfnNextGetInstanceProcAddr;
} VkLayerDeviceInfo;
typedef VkResult (VKAPI_PTR *PFN_vkSetInstanceLoaderData)(VkInstance instance,
void *object);
typedef VkResult (VKAPI_PTR *PFN_vkSetDeviceLoaderData)(VkDevice device,
void *object);
typedef VkResult (VKAPI_PTR *PFN_vkLayerCreateDevice)(VkInstance instance, VkPhysicalDevice physicalDevice, const VkDeviceCreateInfo *pCreateInfo,
const VkAllocationCallbacks *pAllocator, VkDevice *pDevice, PFN_vkGetInstanceProcAddr layerGIPA, PFN_vkGetDeviceProcAddr *nextGDPA);
typedef void (VKAPI_PTR *PFN_vkLayerDestroyDevice)(VkDevice physicalDevice, const VkAllocationCallbacks *pAllocator, PFN_vkDestroyDevice destroyFunction);
typedef enum VkLoaderFeastureFlagBits {
VK_LOADER_FEATURE_PHYSICAL_DEVICE_SORTING = 0x00000001,
} VkLoaderFlagBits;
typedef VkFlags VkLoaderFeatureFlags;
typedef struct {
VkStructureType sType; // VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO
const void *pNext;
VkLayerFunction function;
union {
VkLayerInstanceLink *pLayerInfo;
PFN_vkSetInstanceLoaderData pfnSetInstanceLoaderData;
struct {
PFN_vkLayerCreateDevice pfnLayerCreateDevice;
PFN_vkLayerDestroyDevice pfnLayerDestroyDevice;
} layerDevice;
VkLoaderFeatureFlags loaderFeatures;
} u;
} VkLayerInstanceCreateInfo;
typedef struct VkLayerDeviceLink_ {
struct VkLayerDeviceLink_ *pNext;
PFN_vkGetInstanceProcAddr pfnNextGetInstanceProcAddr;
PFN_vkGetDeviceProcAddr pfnNextGetDeviceProcAddr;
} VkLayerDeviceLink;
typedef struct {
VkStructureType sType; // VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO
const void *pNext;
VkLayerFunction function;
union {
VkLayerDeviceLink *pLayerInfo;
PFN_vkSetDeviceLoaderData pfnSetDeviceLoaderData;
} u;
} VkLayerDeviceCreateInfo;
#ifdef __cplusplus
extern "C" {
#endif
VKAPI_ATTR VkResult VKAPI_CALL vkNegotiateLoaderLayerInterfaceVersion(VkNegotiateLayerInterface *pVersionStruct);
typedef enum VkChainType {
VK_CHAIN_TYPE_UNKNOWN = 0,
VK_CHAIN_TYPE_ENUMERATE_INSTANCE_EXTENSION_PROPERTIES = 1,
VK_CHAIN_TYPE_ENUMERATE_INSTANCE_LAYER_PROPERTIES = 2,
VK_CHAIN_TYPE_ENUMERATE_INSTANCE_VERSION = 3,
} VkChainType;
typedef struct VkChainHeader {
VkChainType type;
uint32_t version;
uint32_t size;
} VkChainHeader;
typedef struct VkEnumerateInstanceExtensionPropertiesChain {
VkChainHeader header;
VkResult(VKAPI_PTR *pfnNextLayer)(const struct VkEnumerateInstanceExtensionPropertiesChain *, const char *, uint32_t *,
VkExtensionProperties *);
const struct VkEnumerateInstanceExtensionPropertiesChain *pNextLink;
#if defined(__cplusplus)
inline VkResult CallDown(const char *pLayerName, uint32_t *pPropertyCount, VkExtensionProperties *pProperties) const {
return pfnNextLayer(pNextLink, pLayerName, pPropertyCount, pProperties);
}
#endif
} VkEnumerateInstanceExtensionPropertiesChain;
typedef struct VkEnumerateInstanceLayerPropertiesChain {
VkChainHeader header;
VkResult(VKAPI_PTR *pfnNextLayer)(const struct VkEnumerateInstanceLayerPropertiesChain *, uint32_t *, VkLayerProperties *);
const struct VkEnumerateInstanceLayerPropertiesChain *pNextLink;
#if defined(__cplusplus)
inline VkResult CallDown(uint32_t *pPropertyCount, VkLayerProperties *pProperties) const {
return pfnNextLayer(pNextLink, pPropertyCount, pProperties);
}
#endif
} VkEnumerateInstanceLayerPropertiesChain;
typedef struct VkEnumerateInstanceVersionChain {
VkChainHeader header;
VkResult(VKAPI_PTR *pfnNextLayer)(const struct VkEnumerateInstanceVersionChain *, uint32_t *);
const struct VkEnumerateInstanceVersionChain *pNextLink;
#if defined(__cplusplus)
inline VkResult CallDown(uint32_t *pApiVersion) const {
return pfnNextLayer(pNextLink, pApiVersion);
}
#endif
} VkEnumerateInstanceVersionChain;
#ifdef __cplusplus
}
#endif
+84
View File
@@ -0,0 +1,84 @@
//
// File: vk_platform.h
//
/*
** Copyright 2014-2024 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
*/
#ifndef VK_PLATFORM_H_
#define VK_PLATFORM_H_
#ifdef __cplusplus
extern "C"
{
#endif // __cplusplus
/*
***************************************************************************************************
* Platform-specific directives and type declarations
***************************************************************************************************
*/
/* Platform-specific calling convention macros.
*
* Platforms should define these so that Vulkan clients call Vulkan commands
* with the same calling conventions that the Vulkan implementation expects.
*
* VKAPI_ATTR - Placed before the return type in function declarations.
* Useful for C++11 and GCC/Clang-style function attribute syntax.
* VKAPI_CALL - Placed after the return type in function declarations.
* Useful for MSVC-style calling convention syntax.
* VKAPI_PTR - Placed between the '(' and '*' in function pointer types.
*
* Function declaration: VKAPI_ATTR void VKAPI_CALL vkCommand(void);
* Function pointer type: typedef void (VKAPI_PTR *PFN_vkCommand)(void);
*/
#if defined(_WIN32)
// On Windows, Vulkan commands use the stdcall convention
#define VKAPI_ATTR
#define VKAPI_CALL __stdcall
#define VKAPI_PTR VKAPI_CALL
#elif defined(__ANDROID__) && defined(__ARM_ARCH) && __ARM_ARCH < 7
#error "Vulkan is not supported for the 'armeabi' NDK ABI"
#elif defined(__ANDROID__) && defined(__ARM_ARCH) && __ARM_ARCH >= 7 && defined(__ARM_32BIT_STATE)
// On Android 32-bit ARM targets, Vulkan functions use the "hardfloat"
// calling convention, i.e. float parameters are passed in registers. This
// is true even if the rest of the application passes floats on the stack,
// as it does by default when compiling for the armeabi-v7a NDK ABI.
#define VKAPI_ATTR __attribute__((pcs("aapcs-vfp")))
#define VKAPI_CALL
#define VKAPI_PTR VKAPI_ATTR
#else
// On other platforms, use the default calling convention
#define VKAPI_ATTR
#define VKAPI_CALL
#define VKAPI_PTR
#endif
#if !defined(VK_NO_STDDEF_H)
#include <stddef.h>
#endif // !defined(VK_NO_STDDEF_H)
#if !defined(VK_NO_STDINT_H)
#if defined(_MSC_VER) && (_MSC_VER < 1600)
typedef signed __int8 int8_t;
typedef unsigned __int8 uint8_t;
typedef signed __int16 int16_t;
typedef unsigned __int16 uint16_t;
typedef signed __int32 int32_t;
typedef unsigned __int32 uint32_t;
typedef signed __int64 int64_t;
typedef unsigned __int64 uint64_t;
#else
#include <stdint.h>
#endif
#endif // !defined(VK_NO_STDINT_H)
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#endif
+99
View File
@@ -0,0 +1,99 @@
#ifndef VULKAN_H_
#define VULKAN_H_ 1
/*
** Copyright 2015-2024 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
*/
#include "vk_platform.h"
#include "vulkan_core.h"
#ifdef VK_USE_PLATFORM_ANDROID_KHR
#include "vulkan_android.h"
#endif
#ifdef VK_USE_PLATFORM_FUCHSIA
#include <zircon/types.h>
#include "vulkan_fuchsia.h"
#endif
#ifdef VK_USE_PLATFORM_IOS_MVK
#include "vulkan_ios.h"
#endif
#ifdef VK_USE_PLATFORM_MACOS_MVK
#include "vulkan_macos.h"
#endif
#ifdef VK_USE_PLATFORM_METAL_EXT
#include "vulkan_metal.h"
#endif
#ifdef VK_USE_PLATFORM_VI_NN
#include "vulkan_vi.h"
#endif
#ifdef VK_USE_PLATFORM_WAYLAND_KHR
#include "vulkan_wayland.h"
#endif
#ifdef VK_USE_PLATFORM_WIN32_KHR
#include <windows.h>
#include "vulkan_win32.h"
#endif
#ifdef VK_USE_PLATFORM_XCB_KHR
#include <xcb/xcb.h>
#include "vulkan_xcb.h"
#endif
#ifdef VK_USE_PLATFORM_XLIB_KHR
#include <X11/Xlib.h>
#include "vulkan_xlib.h"
#endif
#ifdef VK_USE_PLATFORM_DIRECTFB_EXT
#include <directfb.h>
#include "vulkan_directfb.h"
#endif
#ifdef VK_USE_PLATFORM_XLIB_XRANDR_EXT
#include <X11/Xlib.h>
#include <X11/extensions/Xrandr.h>
#include "vulkan_xlib_xrandr.h"
#endif
#ifdef VK_USE_PLATFORM_GGP
#include <ggp_c/vulkan_types.h>
#include "vulkan_ggp.h"
#endif
#ifdef VK_USE_PLATFORM_SCREEN_QNX
#include <screen/screen.h>
#include "vulkan_screen.h"
#endif
#ifdef VK_USE_PLATFORM_SCI
#include <nvscisync.h>
#include <nvscibuf.h>
#include "vulkan_sci.h"
#endif
#ifdef VK_ENABLE_BETA_EXTENSIONS
#include "vulkan_beta.h"
#endif
#endif // VULKAN_H_
+153
View File
@@ -0,0 +1,153 @@
#ifndef VULKAN_ANDROID_H_
#define VULKAN_ANDROID_H_ 1
/*
** Copyright 2015-2024 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
*/
/*
** This header is generated from the Khronos Vulkan XML API Registry.
**
*/
#ifdef __cplusplus
extern "C" {
#endif
// VK_KHR_android_surface is a preprocessor guard. Do not pass it to API calls.
#define VK_KHR_android_surface 1
struct ANativeWindow;
#define VK_KHR_ANDROID_SURFACE_SPEC_VERSION 6
#define VK_KHR_ANDROID_SURFACE_EXTENSION_NAME "VK_KHR_android_surface"
typedef VkFlags VkAndroidSurfaceCreateFlagsKHR;
typedef struct VkAndroidSurfaceCreateInfoKHR {
VkStructureType sType;
const void* pNext;
VkAndroidSurfaceCreateFlagsKHR flags;
struct ANativeWindow* window;
} VkAndroidSurfaceCreateInfoKHR;
typedef VkResult (VKAPI_PTR *PFN_vkCreateAndroidSurfaceKHR)(VkInstance instance, const VkAndroidSurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);
#ifndef VK_NO_PROTOTYPES
VKAPI_ATTR VkResult VKAPI_CALL vkCreateAndroidSurfaceKHR(
VkInstance instance,
const VkAndroidSurfaceCreateInfoKHR* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkSurfaceKHR* pSurface);
#endif
// VK_ANDROID_external_memory_android_hardware_buffer is a preprocessor guard. Do not pass it to API calls.
#define VK_ANDROID_external_memory_android_hardware_buffer 1
struct AHardwareBuffer;
#define VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_SPEC_VERSION 5
#define VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME "VK_ANDROID_external_memory_android_hardware_buffer"
typedef struct VkAndroidHardwareBufferUsageANDROID {
VkStructureType sType;
void* pNext;
uint64_t androidHardwareBufferUsage;
} VkAndroidHardwareBufferUsageANDROID;
typedef struct VkAndroidHardwareBufferPropertiesANDROID {
VkStructureType sType;
void* pNext;
VkDeviceSize allocationSize;
uint32_t memoryTypeBits;
} VkAndroidHardwareBufferPropertiesANDROID;
typedef struct VkAndroidHardwareBufferFormatPropertiesANDROID {
VkStructureType sType;
void* pNext;
VkFormat format;
uint64_t externalFormat;
VkFormatFeatureFlags formatFeatures;
VkComponentMapping samplerYcbcrConversionComponents;
VkSamplerYcbcrModelConversion suggestedYcbcrModel;
VkSamplerYcbcrRange suggestedYcbcrRange;
VkChromaLocation suggestedXChromaOffset;
VkChromaLocation suggestedYChromaOffset;
} VkAndroidHardwareBufferFormatPropertiesANDROID;
typedef struct VkImportAndroidHardwareBufferInfoANDROID {
VkStructureType sType;
const void* pNext;
struct AHardwareBuffer* buffer;
} VkImportAndroidHardwareBufferInfoANDROID;
typedef struct VkMemoryGetAndroidHardwareBufferInfoANDROID {
VkStructureType sType;
const void* pNext;
VkDeviceMemory memory;
} VkMemoryGetAndroidHardwareBufferInfoANDROID;
typedef struct VkExternalFormatANDROID {
VkStructureType sType;
void* pNext;
uint64_t externalFormat;
} VkExternalFormatANDROID;
typedef struct VkAndroidHardwareBufferFormatProperties2ANDROID {
VkStructureType sType;
void* pNext;
VkFormat format;
uint64_t externalFormat;
VkFormatFeatureFlags2 formatFeatures;
VkComponentMapping samplerYcbcrConversionComponents;
VkSamplerYcbcrModelConversion suggestedYcbcrModel;
VkSamplerYcbcrRange suggestedYcbcrRange;
VkChromaLocation suggestedXChromaOffset;
VkChromaLocation suggestedYChromaOffset;
} VkAndroidHardwareBufferFormatProperties2ANDROID;
typedef VkResult (VKAPI_PTR *PFN_vkGetAndroidHardwareBufferPropertiesANDROID)(VkDevice device, const struct AHardwareBuffer* buffer, VkAndroidHardwareBufferPropertiesANDROID* pProperties);
typedef VkResult (VKAPI_PTR *PFN_vkGetMemoryAndroidHardwareBufferANDROID)(VkDevice device, const VkMemoryGetAndroidHardwareBufferInfoANDROID* pInfo, struct AHardwareBuffer** pBuffer);
#ifndef VK_NO_PROTOTYPES
VKAPI_ATTR VkResult VKAPI_CALL vkGetAndroidHardwareBufferPropertiesANDROID(
VkDevice device,
const struct AHardwareBuffer* buffer,
VkAndroidHardwareBufferPropertiesANDROID* pProperties);
VKAPI_ATTR VkResult VKAPI_CALL vkGetMemoryAndroidHardwareBufferANDROID(
VkDevice device,
const VkMemoryGetAndroidHardwareBufferInfoANDROID* pInfo,
struct AHardwareBuffer** pBuffer);
#endif
// VK_ANDROID_external_format_resolve is a preprocessor guard. Do not pass it to API calls.
#define VK_ANDROID_external_format_resolve 1
#define VK_ANDROID_EXTERNAL_FORMAT_RESOLVE_SPEC_VERSION 1
#define VK_ANDROID_EXTERNAL_FORMAT_RESOLVE_EXTENSION_NAME "VK_ANDROID_external_format_resolve"
typedef struct VkPhysicalDeviceExternalFormatResolveFeaturesANDROID {
VkStructureType sType;
void* pNext;
VkBool32 externalFormatResolve;
} VkPhysicalDeviceExternalFormatResolveFeaturesANDROID;
typedef struct VkPhysicalDeviceExternalFormatResolvePropertiesANDROID {
VkStructureType sType;
void* pNext;
VkBool32 nullColorAttachmentWithExternalFormatResolve;
VkChromaLocation externalFormatResolveChromaOffsetX;
VkChromaLocation externalFormatResolveChromaOffsetY;
} VkPhysicalDeviceExternalFormatResolvePropertiesANDROID;
typedef struct VkAndroidHardwareBufferFormatResolvePropertiesANDROID {
VkStructureType sType;
void* pNext;
VkFormat colorAttachmentFormat;
} VkAndroidHardwareBufferFormatResolvePropertiesANDROID;
#ifdef __cplusplus
}
#endif
#endif
+226
View File
@@ -0,0 +1,226 @@
#ifndef VULKAN_BETA_H_
#define VULKAN_BETA_H_ 1
/*
** Copyright 2015-2024 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
*/
/*
** This header is generated from the Khronos Vulkan XML API Registry.
**
*/
#ifdef __cplusplus
extern "C" {
#endif
// VK_KHR_portability_subset is a preprocessor guard. Do not pass it to API calls.
#define VK_KHR_portability_subset 1
#define VK_KHR_PORTABILITY_SUBSET_SPEC_VERSION 1
#define VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME "VK_KHR_portability_subset"
typedef struct VkPhysicalDevicePortabilitySubsetFeaturesKHR {
VkStructureType sType;
void* pNext;
VkBool32 constantAlphaColorBlendFactors;
VkBool32 events;
VkBool32 imageViewFormatReinterpretation;
VkBool32 imageViewFormatSwizzle;
VkBool32 imageView2DOn3DImage;
VkBool32 multisampleArrayImage;
VkBool32 mutableComparisonSamplers;
VkBool32 pointPolygons;
VkBool32 samplerMipLodBias;
VkBool32 separateStencilMaskRef;
VkBool32 shaderSampleRateInterpolationFunctions;
VkBool32 tessellationIsolines;
VkBool32 tessellationPointMode;
VkBool32 triangleFans;
VkBool32 vertexAttributeAccessBeyondStride;
} VkPhysicalDevicePortabilitySubsetFeaturesKHR;
typedef struct VkPhysicalDevicePortabilitySubsetPropertiesKHR {
VkStructureType sType;
void* pNext;
uint32_t minVertexInputBindingStrideAlignment;
} VkPhysicalDevicePortabilitySubsetPropertiesKHR;
// VK_AMDX_shader_enqueue is a preprocessor guard. Do not pass it to API calls.
#define VK_AMDX_shader_enqueue 1
#define VK_AMDX_SHADER_ENQUEUE_SPEC_VERSION 2
#define VK_AMDX_SHADER_ENQUEUE_EXTENSION_NAME "VK_AMDX_shader_enqueue"
#define VK_SHADER_INDEX_UNUSED_AMDX (~0U)
typedef struct VkPhysicalDeviceShaderEnqueueFeaturesAMDX {
VkStructureType sType;
void* pNext;
VkBool32 shaderEnqueue;
VkBool32 shaderMeshEnqueue;
} VkPhysicalDeviceShaderEnqueueFeaturesAMDX;
typedef struct VkPhysicalDeviceShaderEnqueuePropertiesAMDX {
VkStructureType sType;
void* pNext;
uint32_t maxExecutionGraphDepth;
uint32_t maxExecutionGraphShaderOutputNodes;
uint32_t maxExecutionGraphShaderPayloadSize;
uint32_t maxExecutionGraphShaderPayloadCount;
uint32_t executionGraphDispatchAddressAlignment;
uint32_t maxExecutionGraphWorkgroupCount[3];
uint32_t maxExecutionGraphWorkgroups;
} VkPhysicalDeviceShaderEnqueuePropertiesAMDX;
typedef struct VkExecutionGraphPipelineScratchSizeAMDX {
VkStructureType sType;
void* pNext;
VkDeviceSize minSize;
VkDeviceSize maxSize;
VkDeviceSize sizeGranularity;
} VkExecutionGraphPipelineScratchSizeAMDX;
typedef struct VkExecutionGraphPipelineCreateInfoAMDX {
VkStructureType sType;
const void* pNext;
VkPipelineCreateFlags flags;
uint32_t stageCount;
const VkPipelineShaderStageCreateInfo* pStages;
const VkPipelineLibraryCreateInfoKHR* pLibraryInfo;
VkPipelineLayout layout;
VkPipeline basePipelineHandle;
int32_t basePipelineIndex;
} VkExecutionGraphPipelineCreateInfoAMDX;
typedef union VkDeviceOrHostAddressConstAMDX {
VkDeviceAddress deviceAddress;
const void* hostAddress;
} VkDeviceOrHostAddressConstAMDX;
typedef struct VkDispatchGraphInfoAMDX {
uint32_t nodeIndex;
uint32_t payloadCount;
VkDeviceOrHostAddressConstAMDX payloads;
uint64_t payloadStride;
} VkDispatchGraphInfoAMDX;
typedef struct VkDispatchGraphCountInfoAMDX {
uint32_t count;
VkDeviceOrHostAddressConstAMDX infos;
uint64_t stride;
} VkDispatchGraphCountInfoAMDX;
typedef struct VkPipelineShaderStageNodeCreateInfoAMDX {
VkStructureType sType;
const void* pNext;
const char* pName;
uint32_t index;
} VkPipelineShaderStageNodeCreateInfoAMDX;
typedef VkResult (VKAPI_PTR *PFN_vkCreateExecutionGraphPipelinesAMDX)(VkDevice device, VkPipelineCache pipelineCache, uint32_t createInfoCount, const VkExecutionGraphPipelineCreateInfoAMDX* pCreateInfos, const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines);
typedef VkResult (VKAPI_PTR *PFN_vkGetExecutionGraphPipelineScratchSizeAMDX)(VkDevice device, VkPipeline executionGraph, VkExecutionGraphPipelineScratchSizeAMDX* pSizeInfo);
typedef VkResult (VKAPI_PTR *PFN_vkGetExecutionGraphPipelineNodeIndexAMDX)(VkDevice device, VkPipeline executionGraph, const VkPipelineShaderStageNodeCreateInfoAMDX* pNodeInfo, uint32_t* pNodeIndex);
typedef void (VKAPI_PTR *PFN_vkCmdInitializeGraphScratchMemoryAMDX)(VkCommandBuffer commandBuffer, VkPipeline executionGraph, VkDeviceAddress scratch, VkDeviceSize scratchSize);
typedef void (VKAPI_PTR *PFN_vkCmdDispatchGraphAMDX)(VkCommandBuffer commandBuffer, VkDeviceAddress scratch, VkDeviceSize scratchSize, const VkDispatchGraphCountInfoAMDX* pCountInfo);
typedef void (VKAPI_PTR *PFN_vkCmdDispatchGraphIndirectAMDX)(VkCommandBuffer commandBuffer, VkDeviceAddress scratch, VkDeviceSize scratchSize, const VkDispatchGraphCountInfoAMDX* pCountInfo);
typedef void (VKAPI_PTR *PFN_vkCmdDispatchGraphIndirectCountAMDX)(VkCommandBuffer commandBuffer, VkDeviceAddress scratch, VkDeviceSize scratchSize, VkDeviceAddress countInfo);
#ifndef VK_NO_PROTOTYPES
VKAPI_ATTR VkResult VKAPI_CALL vkCreateExecutionGraphPipelinesAMDX(
VkDevice device,
VkPipelineCache pipelineCache,
uint32_t createInfoCount,
const VkExecutionGraphPipelineCreateInfoAMDX* pCreateInfos,
const VkAllocationCallbacks* pAllocator,
VkPipeline* pPipelines);
VKAPI_ATTR VkResult VKAPI_CALL vkGetExecutionGraphPipelineScratchSizeAMDX(
VkDevice device,
VkPipeline executionGraph,
VkExecutionGraphPipelineScratchSizeAMDX* pSizeInfo);
VKAPI_ATTR VkResult VKAPI_CALL vkGetExecutionGraphPipelineNodeIndexAMDX(
VkDevice device,
VkPipeline executionGraph,
const VkPipelineShaderStageNodeCreateInfoAMDX* pNodeInfo,
uint32_t* pNodeIndex);
VKAPI_ATTR void VKAPI_CALL vkCmdInitializeGraphScratchMemoryAMDX(
VkCommandBuffer commandBuffer,
VkPipeline executionGraph,
VkDeviceAddress scratch,
VkDeviceSize scratchSize);
VKAPI_ATTR void VKAPI_CALL vkCmdDispatchGraphAMDX(
VkCommandBuffer commandBuffer,
VkDeviceAddress scratch,
VkDeviceSize scratchSize,
const VkDispatchGraphCountInfoAMDX* pCountInfo);
VKAPI_ATTR void VKAPI_CALL vkCmdDispatchGraphIndirectAMDX(
VkCommandBuffer commandBuffer,
VkDeviceAddress scratch,
VkDeviceSize scratchSize,
const VkDispatchGraphCountInfoAMDX* pCountInfo);
VKAPI_ATTR void VKAPI_CALL vkCmdDispatchGraphIndirectCountAMDX(
VkCommandBuffer commandBuffer,
VkDeviceAddress scratch,
VkDeviceSize scratchSize,
VkDeviceAddress countInfo);
#endif
// VK_NV_displacement_micromap is a preprocessor guard. Do not pass it to API calls.
#define VK_NV_displacement_micromap 1
#define VK_NV_DISPLACEMENT_MICROMAP_SPEC_VERSION 2
#define VK_NV_DISPLACEMENT_MICROMAP_EXTENSION_NAME "VK_NV_displacement_micromap"
typedef enum VkDisplacementMicromapFormatNV {
VK_DISPLACEMENT_MICROMAP_FORMAT_64_TRIANGLES_64_BYTES_NV = 1,
VK_DISPLACEMENT_MICROMAP_FORMAT_256_TRIANGLES_128_BYTES_NV = 2,
VK_DISPLACEMENT_MICROMAP_FORMAT_1024_TRIANGLES_128_BYTES_NV = 3,
VK_DISPLACEMENT_MICROMAP_FORMAT_MAX_ENUM_NV = 0x7FFFFFFF
} VkDisplacementMicromapFormatNV;
typedef struct VkPhysicalDeviceDisplacementMicromapFeaturesNV {
VkStructureType sType;
void* pNext;
VkBool32 displacementMicromap;
} VkPhysicalDeviceDisplacementMicromapFeaturesNV;
typedef struct VkPhysicalDeviceDisplacementMicromapPropertiesNV {
VkStructureType sType;
void* pNext;
uint32_t maxDisplacementMicromapSubdivisionLevel;
} VkPhysicalDeviceDisplacementMicromapPropertiesNV;
typedef struct VkAccelerationStructureTrianglesDisplacementMicromapNV {
VkStructureType sType;
void* pNext;
VkFormat displacementBiasAndScaleFormat;
VkFormat displacementVectorFormat;
VkDeviceOrHostAddressConstKHR displacementBiasAndScaleBuffer;
VkDeviceSize displacementBiasAndScaleStride;
VkDeviceOrHostAddressConstKHR displacementVectorBuffer;
VkDeviceSize displacementVectorStride;
VkDeviceOrHostAddressConstKHR displacedMicromapPrimitiveFlags;
VkDeviceSize displacedMicromapPrimitiveFlagsStride;
VkIndexType indexType;
VkDeviceOrHostAddressConstKHR indexBuffer;
VkDeviceSize indexStride;
uint32_t baseTriangle;
uint32_t usageCountsCount;
const VkMicromapUsageEXT* pUsageCounts;
const VkMicromapUsageEXT* const* ppUsageCounts;
VkMicromapEXT micromap;
} VkAccelerationStructureTrianglesDisplacementMicromapNV;
#ifdef __cplusplus
}
#endif
#endif
File diff suppressed because it is too large Load Diff
+55
View File
@@ -0,0 +1,55 @@
#ifndef VULKAN_DIRECTFB_H_
#define VULKAN_DIRECTFB_H_ 1
/*
** Copyright 2015-2024 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
*/
/*
** This header is generated from the Khronos Vulkan XML API Registry.
**
*/
#ifdef __cplusplus
extern "C" {
#endif
// VK_EXT_directfb_surface is a preprocessor guard. Do not pass it to API calls.
#define VK_EXT_directfb_surface 1
#define VK_EXT_DIRECTFB_SURFACE_SPEC_VERSION 1
#define VK_EXT_DIRECTFB_SURFACE_EXTENSION_NAME "VK_EXT_directfb_surface"
typedef VkFlags VkDirectFBSurfaceCreateFlagsEXT;
typedef struct VkDirectFBSurfaceCreateInfoEXT {
VkStructureType sType;
const void* pNext;
VkDirectFBSurfaceCreateFlagsEXT flags;
IDirectFB* dfb;
IDirectFBSurface* surface;
} VkDirectFBSurfaceCreateInfoEXT;
typedef VkResult (VKAPI_PTR *PFN_vkCreateDirectFBSurfaceEXT)(VkInstance instance, const VkDirectFBSurfaceCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);
typedef VkBool32 (VKAPI_PTR *PFN_vkGetPhysicalDeviceDirectFBPresentationSupportEXT)(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex, IDirectFB* dfb);
#ifndef VK_NO_PROTOTYPES
VKAPI_ATTR VkResult VKAPI_CALL vkCreateDirectFBSurfaceEXT(
VkInstance instance,
const VkDirectFBSurfaceCreateInfoEXT* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkSurfaceKHR* pSurface);
VKAPI_ATTR VkBool32 VKAPI_CALL vkGetPhysicalDeviceDirectFBPresentationSupportEXT(
VkPhysicalDevice physicalDevice,
uint32_t queueFamilyIndex,
IDirectFB* dfb);
#endif
#ifdef __cplusplus
}
#endif
#endif
+262
View File
@@ -0,0 +1,262 @@
#ifndef VULKAN_FUCHSIA_H_
#define VULKAN_FUCHSIA_H_ 1
/*
** Copyright 2015-2024 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
*/
/*
** This header is generated from the Khronos Vulkan XML API Registry.
**
*/
#ifdef __cplusplus
extern "C" {
#endif
// VK_FUCHSIA_imagepipe_surface is a preprocessor guard. Do not pass it to API calls.
#define VK_FUCHSIA_imagepipe_surface 1
#define VK_FUCHSIA_IMAGEPIPE_SURFACE_SPEC_VERSION 1
#define VK_FUCHSIA_IMAGEPIPE_SURFACE_EXTENSION_NAME "VK_FUCHSIA_imagepipe_surface"
typedef VkFlags VkImagePipeSurfaceCreateFlagsFUCHSIA;
typedef struct VkImagePipeSurfaceCreateInfoFUCHSIA {
VkStructureType sType;
const void* pNext;
VkImagePipeSurfaceCreateFlagsFUCHSIA flags;
zx_handle_t imagePipeHandle;
} VkImagePipeSurfaceCreateInfoFUCHSIA;
typedef VkResult (VKAPI_PTR *PFN_vkCreateImagePipeSurfaceFUCHSIA)(VkInstance instance, const VkImagePipeSurfaceCreateInfoFUCHSIA* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);
#ifndef VK_NO_PROTOTYPES
VKAPI_ATTR VkResult VKAPI_CALL vkCreateImagePipeSurfaceFUCHSIA(
VkInstance instance,
const VkImagePipeSurfaceCreateInfoFUCHSIA* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkSurfaceKHR* pSurface);
#endif
// VK_FUCHSIA_external_memory is a preprocessor guard. Do not pass it to API calls.
#define VK_FUCHSIA_external_memory 1
#define VK_FUCHSIA_EXTERNAL_MEMORY_SPEC_VERSION 1
#define VK_FUCHSIA_EXTERNAL_MEMORY_EXTENSION_NAME "VK_FUCHSIA_external_memory"
typedef struct VkImportMemoryZirconHandleInfoFUCHSIA {
VkStructureType sType;
const void* pNext;
VkExternalMemoryHandleTypeFlagBits handleType;
zx_handle_t handle;
} VkImportMemoryZirconHandleInfoFUCHSIA;
typedef struct VkMemoryZirconHandlePropertiesFUCHSIA {
VkStructureType sType;
void* pNext;
uint32_t memoryTypeBits;
} VkMemoryZirconHandlePropertiesFUCHSIA;
typedef struct VkMemoryGetZirconHandleInfoFUCHSIA {
VkStructureType sType;
const void* pNext;
VkDeviceMemory memory;
VkExternalMemoryHandleTypeFlagBits handleType;
} VkMemoryGetZirconHandleInfoFUCHSIA;
typedef VkResult (VKAPI_PTR *PFN_vkGetMemoryZirconHandleFUCHSIA)(VkDevice device, const VkMemoryGetZirconHandleInfoFUCHSIA* pGetZirconHandleInfo, zx_handle_t* pZirconHandle);
typedef VkResult (VKAPI_PTR *PFN_vkGetMemoryZirconHandlePropertiesFUCHSIA)(VkDevice device, VkExternalMemoryHandleTypeFlagBits handleType, zx_handle_t zirconHandle, VkMemoryZirconHandlePropertiesFUCHSIA* pMemoryZirconHandleProperties);
#ifndef VK_NO_PROTOTYPES
VKAPI_ATTR VkResult VKAPI_CALL vkGetMemoryZirconHandleFUCHSIA(
VkDevice device,
const VkMemoryGetZirconHandleInfoFUCHSIA* pGetZirconHandleInfo,
zx_handle_t* pZirconHandle);
VKAPI_ATTR VkResult VKAPI_CALL vkGetMemoryZirconHandlePropertiesFUCHSIA(
VkDevice device,
VkExternalMemoryHandleTypeFlagBits handleType,
zx_handle_t zirconHandle,
VkMemoryZirconHandlePropertiesFUCHSIA* pMemoryZirconHandleProperties);
#endif
// VK_FUCHSIA_external_semaphore is a preprocessor guard. Do not pass it to API calls.
#define VK_FUCHSIA_external_semaphore 1
#define VK_FUCHSIA_EXTERNAL_SEMAPHORE_SPEC_VERSION 1
#define VK_FUCHSIA_EXTERNAL_SEMAPHORE_EXTENSION_NAME "VK_FUCHSIA_external_semaphore"
typedef struct VkImportSemaphoreZirconHandleInfoFUCHSIA {
VkStructureType sType;
const void* pNext;
VkSemaphore semaphore;
VkSemaphoreImportFlags flags;
VkExternalSemaphoreHandleTypeFlagBits handleType;
zx_handle_t zirconHandle;
} VkImportSemaphoreZirconHandleInfoFUCHSIA;
typedef struct VkSemaphoreGetZirconHandleInfoFUCHSIA {
VkStructureType sType;
const void* pNext;
VkSemaphore semaphore;
VkExternalSemaphoreHandleTypeFlagBits handleType;
} VkSemaphoreGetZirconHandleInfoFUCHSIA;
typedef VkResult (VKAPI_PTR *PFN_vkImportSemaphoreZirconHandleFUCHSIA)(VkDevice device, const VkImportSemaphoreZirconHandleInfoFUCHSIA* pImportSemaphoreZirconHandleInfo);
typedef VkResult (VKAPI_PTR *PFN_vkGetSemaphoreZirconHandleFUCHSIA)(VkDevice device, const VkSemaphoreGetZirconHandleInfoFUCHSIA* pGetZirconHandleInfo, zx_handle_t* pZirconHandle);
#ifndef VK_NO_PROTOTYPES
VKAPI_ATTR VkResult VKAPI_CALL vkImportSemaphoreZirconHandleFUCHSIA(
VkDevice device,
const VkImportSemaphoreZirconHandleInfoFUCHSIA* pImportSemaphoreZirconHandleInfo);
VKAPI_ATTR VkResult VKAPI_CALL vkGetSemaphoreZirconHandleFUCHSIA(
VkDevice device,
const VkSemaphoreGetZirconHandleInfoFUCHSIA* pGetZirconHandleInfo,
zx_handle_t* pZirconHandle);
#endif
// VK_FUCHSIA_buffer_collection is a preprocessor guard. Do not pass it to API calls.
#define VK_FUCHSIA_buffer_collection 1
VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkBufferCollectionFUCHSIA)
#define VK_FUCHSIA_BUFFER_COLLECTION_SPEC_VERSION 2
#define VK_FUCHSIA_BUFFER_COLLECTION_EXTENSION_NAME "VK_FUCHSIA_buffer_collection"
typedef VkFlags VkImageFormatConstraintsFlagsFUCHSIA;
typedef enum VkImageConstraintsInfoFlagBitsFUCHSIA {
VK_IMAGE_CONSTRAINTS_INFO_CPU_READ_RARELY_FUCHSIA = 0x00000001,
VK_IMAGE_CONSTRAINTS_INFO_CPU_READ_OFTEN_FUCHSIA = 0x00000002,
VK_IMAGE_CONSTRAINTS_INFO_CPU_WRITE_RARELY_FUCHSIA = 0x00000004,
VK_IMAGE_CONSTRAINTS_INFO_CPU_WRITE_OFTEN_FUCHSIA = 0x00000008,
VK_IMAGE_CONSTRAINTS_INFO_PROTECTED_OPTIONAL_FUCHSIA = 0x00000010,
VK_IMAGE_CONSTRAINTS_INFO_FLAG_BITS_MAX_ENUM_FUCHSIA = 0x7FFFFFFF
} VkImageConstraintsInfoFlagBitsFUCHSIA;
typedef VkFlags VkImageConstraintsInfoFlagsFUCHSIA;
typedef struct VkBufferCollectionCreateInfoFUCHSIA {
VkStructureType sType;
const void* pNext;
zx_handle_t collectionToken;
} VkBufferCollectionCreateInfoFUCHSIA;
typedef struct VkImportMemoryBufferCollectionFUCHSIA {
VkStructureType sType;
const void* pNext;
VkBufferCollectionFUCHSIA collection;
uint32_t index;
} VkImportMemoryBufferCollectionFUCHSIA;
typedef struct VkBufferCollectionImageCreateInfoFUCHSIA {
VkStructureType sType;
const void* pNext;
VkBufferCollectionFUCHSIA collection;
uint32_t index;
} VkBufferCollectionImageCreateInfoFUCHSIA;
typedef struct VkBufferCollectionConstraintsInfoFUCHSIA {
VkStructureType sType;
const void* pNext;
uint32_t minBufferCount;
uint32_t maxBufferCount;
uint32_t minBufferCountForCamping;
uint32_t minBufferCountForDedicatedSlack;
uint32_t minBufferCountForSharedSlack;
} VkBufferCollectionConstraintsInfoFUCHSIA;
typedef struct VkBufferConstraintsInfoFUCHSIA {
VkStructureType sType;
const void* pNext;
VkBufferCreateInfo createInfo;
VkFormatFeatureFlags requiredFormatFeatures;
VkBufferCollectionConstraintsInfoFUCHSIA bufferCollectionConstraints;
} VkBufferConstraintsInfoFUCHSIA;
typedef struct VkBufferCollectionBufferCreateInfoFUCHSIA {
VkStructureType sType;
const void* pNext;
VkBufferCollectionFUCHSIA collection;
uint32_t index;
} VkBufferCollectionBufferCreateInfoFUCHSIA;
typedef struct VkSysmemColorSpaceFUCHSIA {
VkStructureType sType;
const void* pNext;
uint32_t colorSpace;
} VkSysmemColorSpaceFUCHSIA;
typedef struct VkBufferCollectionPropertiesFUCHSIA {
VkStructureType sType;
void* pNext;
uint32_t memoryTypeBits;
uint32_t bufferCount;
uint32_t createInfoIndex;
uint64_t sysmemPixelFormat;
VkFormatFeatureFlags formatFeatures;
VkSysmemColorSpaceFUCHSIA sysmemColorSpaceIndex;
VkComponentMapping samplerYcbcrConversionComponents;
VkSamplerYcbcrModelConversion suggestedYcbcrModel;
VkSamplerYcbcrRange suggestedYcbcrRange;
VkChromaLocation suggestedXChromaOffset;
VkChromaLocation suggestedYChromaOffset;
} VkBufferCollectionPropertiesFUCHSIA;
typedef struct VkImageFormatConstraintsInfoFUCHSIA {
VkStructureType sType;
const void* pNext;
VkImageCreateInfo imageCreateInfo;
VkFormatFeatureFlags requiredFormatFeatures;
VkImageFormatConstraintsFlagsFUCHSIA flags;
uint64_t sysmemPixelFormat;
uint32_t colorSpaceCount;
const VkSysmemColorSpaceFUCHSIA* pColorSpaces;
} VkImageFormatConstraintsInfoFUCHSIA;
typedef struct VkImageConstraintsInfoFUCHSIA {
VkStructureType sType;
const void* pNext;
uint32_t formatConstraintsCount;
const VkImageFormatConstraintsInfoFUCHSIA* pFormatConstraints;
VkBufferCollectionConstraintsInfoFUCHSIA bufferCollectionConstraints;
VkImageConstraintsInfoFlagsFUCHSIA flags;
} VkImageConstraintsInfoFUCHSIA;
typedef VkResult (VKAPI_PTR *PFN_vkCreateBufferCollectionFUCHSIA)(VkDevice device, const VkBufferCollectionCreateInfoFUCHSIA* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkBufferCollectionFUCHSIA* pCollection);
typedef VkResult (VKAPI_PTR *PFN_vkSetBufferCollectionImageConstraintsFUCHSIA)(VkDevice device, VkBufferCollectionFUCHSIA collection, const VkImageConstraintsInfoFUCHSIA* pImageConstraintsInfo);
typedef VkResult (VKAPI_PTR *PFN_vkSetBufferCollectionBufferConstraintsFUCHSIA)(VkDevice device, VkBufferCollectionFUCHSIA collection, const VkBufferConstraintsInfoFUCHSIA* pBufferConstraintsInfo);
typedef void (VKAPI_PTR *PFN_vkDestroyBufferCollectionFUCHSIA)(VkDevice device, VkBufferCollectionFUCHSIA collection, const VkAllocationCallbacks* pAllocator);
typedef VkResult (VKAPI_PTR *PFN_vkGetBufferCollectionPropertiesFUCHSIA)(VkDevice device, VkBufferCollectionFUCHSIA collection, VkBufferCollectionPropertiesFUCHSIA* pProperties);
#ifndef VK_NO_PROTOTYPES
VKAPI_ATTR VkResult VKAPI_CALL vkCreateBufferCollectionFUCHSIA(
VkDevice device,
const VkBufferCollectionCreateInfoFUCHSIA* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkBufferCollectionFUCHSIA* pCollection);
VKAPI_ATTR VkResult VKAPI_CALL vkSetBufferCollectionImageConstraintsFUCHSIA(
VkDevice device,
VkBufferCollectionFUCHSIA collection,
const VkImageConstraintsInfoFUCHSIA* pImageConstraintsInfo);
VKAPI_ATTR VkResult VKAPI_CALL vkSetBufferCollectionBufferConstraintsFUCHSIA(
VkDevice device,
VkBufferCollectionFUCHSIA collection,
const VkBufferConstraintsInfoFUCHSIA* pBufferConstraintsInfo);
VKAPI_ATTR void VKAPI_CALL vkDestroyBufferCollectionFUCHSIA(
VkDevice device,
VkBufferCollectionFUCHSIA collection,
const VkAllocationCallbacks* pAllocator);
VKAPI_ATTR VkResult VKAPI_CALL vkGetBufferCollectionPropertiesFUCHSIA(
VkDevice device,
VkBufferCollectionFUCHSIA collection,
VkBufferCollectionPropertiesFUCHSIA* pProperties);
#endif
#ifdef __cplusplus
}
#endif
#endif
+60
View File
@@ -0,0 +1,60 @@
#ifndef VULKAN_GGP_H_
#define VULKAN_GGP_H_ 1
/*
** Copyright 2015-2024 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
*/
/*
** This header is generated from the Khronos Vulkan XML API Registry.
**
*/
#ifdef __cplusplus
extern "C" {
#endif
// VK_GGP_stream_descriptor_surface is a preprocessor guard. Do not pass it to API calls.
#define VK_GGP_stream_descriptor_surface 1
#define VK_GGP_STREAM_DESCRIPTOR_SURFACE_SPEC_VERSION 1
#define VK_GGP_STREAM_DESCRIPTOR_SURFACE_EXTENSION_NAME "VK_GGP_stream_descriptor_surface"
typedef VkFlags VkStreamDescriptorSurfaceCreateFlagsGGP;
typedef struct VkStreamDescriptorSurfaceCreateInfoGGP {
VkStructureType sType;
const void* pNext;
VkStreamDescriptorSurfaceCreateFlagsGGP flags;
GgpStreamDescriptor streamDescriptor;
} VkStreamDescriptorSurfaceCreateInfoGGP;
typedef VkResult (VKAPI_PTR *PFN_vkCreateStreamDescriptorSurfaceGGP)(VkInstance instance, const VkStreamDescriptorSurfaceCreateInfoGGP* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);
#ifndef VK_NO_PROTOTYPES
VKAPI_ATTR VkResult VKAPI_CALL vkCreateStreamDescriptorSurfaceGGP(
VkInstance instance,
const VkStreamDescriptorSurfaceCreateInfoGGP* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkSurfaceKHR* pSurface);
#endif
// VK_GGP_frame_token is a preprocessor guard. Do not pass it to API calls.
#define VK_GGP_frame_token 1
#define VK_GGP_FRAME_TOKEN_SPEC_VERSION 1
#define VK_GGP_FRAME_TOKEN_EXTENSION_NAME "VK_GGP_frame_token"
typedef struct VkPresentFrameTokenGGP {
VkStructureType sType;
const void* pNext;
GgpFrameToken frameToken;
} VkPresentFrameTokenGGP;
#ifdef __cplusplus
}
#endif
#endif
+48
View File
@@ -0,0 +1,48 @@
#ifndef VULKAN_IOS_H_
#define VULKAN_IOS_H_ 1
/*
** Copyright 2015-2024 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
*/
/*
** This header is generated from the Khronos Vulkan XML API Registry.
**
*/
#ifdef __cplusplus
extern "C" {
#endif
// VK_MVK_ios_surface is a preprocessor guard. Do not pass it to API calls.
#define VK_MVK_ios_surface 1
#define VK_MVK_IOS_SURFACE_SPEC_VERSION 3
#define VK_MVK_IOS_SURFACE_EXTENSION_NAME "VK_MVK_ios_surface"
typedef VkFlags VkIOSSurfaceCreateFlagsMVK;
typedef struct VkIOSSurfaceCreateInfoMVK {
VkStructureType sType;
const void* pNext;
VkIOSSurfaceCreateFlagsMVK flags;
const void* pView;
} VkIOSSurfaceCreateInfoMVK;
typedef VkResult (VKAPI_PTR *PFN_vkCreateIOSSurfaceMVK)(VkInstance instance, const VkIOSSurfaceCreateInfoMVK* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);
#ifndef VK_NO_PROTOTYPES
VKAPI_ATTR VkResult VKAPI_CALL vkCreateIOSSurfaceMVK(
VkInstance instance,
const VkIOSSurfaceCreateInfoMVK* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkSurfaceKHR* pSurface);
#endif
#ifdef __cplusplus
}
#endif
#endif
+48
View File
@@ -0,0 +1,48 @@
#ifndef VULKAN_MACOS_H_
#define VULKAN_MACOS_H_ 1
/*
** Copyright 2015-2024 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
*/
/*
** This header is generated from the Khronos Vulkan XML API Registry.
**
*/
#ifdef __cplusplus
extern "C" {
#endif
// VK_MVK_macos_surface is a preprocessor guard. Do not pass it to API calls.
#define VK_MVK_macos_surface 1
#define VK_MVK_MACOS_SURFACE_SPEC_VERSION 3
#define VK_MVK_MACOS_SURFACE_EXTENSION_NAME "VK_MVK_macos_surface"
typedef VkFlags VkMacOSSurfaceCreateFlagsMVK;
typedef struct VkMacOSSurfaceCreateInfoMVK {
VkStructureType sType;
const void* pNext;
VkMacOSSurfaceCreateFlagsMVK flags;
const void* pView;
} VkMacOSSurfaceCreateInfoMVK;
typedef VkResult (VKAPI_PTR *PFN_vkCreateMacOSSurfaceMVK)(VkInstance instance, const VkMacOSSurfaceCreateInfoMVK* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);
#ifndef VK_NO_PROTOTYPES
VKAPI_ATTR VkResult VKAPI_CALL vkCreateMacOSSurfaceMVK(
VkInstance instance,
const VkMacOSSurfaceCreateInfoMVK* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkSurfaceKHR* pSurface);
#endif
#ifdef __cplusplus
}
#endif
#endif
+195
View File
@@ -0,0 +1,195 @@
#ifndef VULKAN_METAL_H_
#define VULKAN_METAL_H_ 1
/*
** Copyright 2015-2024 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
*/
/*
** This header is generated from the Khronos Vulkan XML API Registry.
**
*/
#ifdef __cplusplus
extern "C" {
#endif
// VK_EXT_metal_surface is a preprocessor guard. Do not pass it to API calls.
#define VK_EXT_metal_surface 1
#ifdef __OBJC__
@class CAMetalLayer;
#else
typedef void CAMetalLayer;
#endif
#define VK_EXT_METAL_SURFACE_SPEC_VERSION 1
#define VK_EXT_METAL_SURFACE_EXTENSION_NAME "VK_EXT_metal_surface"
typedef VkFlags VkMetalSurfaceCreateFlagsEXT;
typedef struct VkMetalSurfaceCreateInfoEXT {
VkStructureType sType;
const void* pNext;
VkMetalSurfaceCreateFlagsEXT flags;
const CAMetalLayer* pLayer;
} VkMetalSurfaceCreateInfoEXT;
typedef VkResult (VKAPI_PTR *PFN_vkCreateMetalSurfaceEXT)(VkInstance instance, const VkMetalSurfaceCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);
#ifndef VK_NO_PROTOTYPES
VKAPI_ATTR VkResult VKAPI_CALL vkCreateMetalSurfaceEXT(
VkInstance instance,
const VkMetalSurfaceCreateInfoEXT* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkSurfaceKHR* pSurface);
#endif
// VK_EXT_metal_objects is a preprocessor guard. Do not pass it to API calls.
#define VK_EXT_metal_objects 1
#ifdef __OBJC__
@protocol MTLDevice;
typedef __unsafe_unretained id<MTLDevice> MTLDevice_id;
#else
typedef void* MTLDevice_id;
#endif
#ifdef __OBJC__
@protocol MTLCommandQueue;
typedef __unsafe_unretained id<MTLCommandQueue> MTLCommandQueue_id;
#else
typedef void* MTLCommandQueue_id;
#endif
#ifdef __OBJC__
@protocol MTLBuffer;
typedef __unsafe_unretained id<MTLBuffer> MTLBuffer_id;
#else
typedef void* MTLBuffer_id;
#endif
#ifdef __OBJC__
@protocol MTLTexture;
typedef __unsafe_unretained id<MTLTexture> MTLTexture_id;
#else
typedef void* MTLTexture_id;
#endif
typedef struct __IOSurface* IOSurfaceRef;
#ifdef __OBJC__
@protocol MTLSharedEvent;
typedef __unsafe_unretained id<MTLSharedEvent> MTLSharedEvent_id;
#else
typedef void* MTLSharedEvent_id;
#endif
#define VK_EXT_METAL_OBJECTS_SPEC_VERSION 2
#define VK_EXT_METAL_OBJECTS_EXTENSION_NAME "VK_EXT_metal_objects"
typedef enum VkExportMetalObjectTypeFlagBitsEXT {
VK_EXPORT_METAL_OBJECT_TYPE_METAL_DEVICE_BIT_EXT = 0x00000001,
VK_EXPORT_METAL_OBJECT_TYPE_METAL_COMMAND_QUEUE_BIT_EXT = 0x00000002,
VK_EXPORT_METAL_OBJECT_TYPE_METAL_BUFFER_BIT_EXT = 0x00000004,
VK_EXPORT_METAL_OBJECT_TYPE_METAL_TEXTURE_BIT_EXT = 0x00000008,
VK_EXPORT_METAL_OBJECT_TYPE_METAL_IOSURFACE_BIT_EXT = 0x00000010,
VK_EXPORT_METAL_OBJECT_TYPE_METAL_SHARED_EVENT_BIT_EXT = 0x00000020,
VK_EXPORT_METAL_OBJECT_TYPE_FLAG_BITS_MAX_ENUM_EXT = 0x7FFFFFFF
} VkExportMetalObjectTypeFlagBitsEXT;
typedef VkFlags VkExportMetalObjectTypeFlagsEXT;
typedef struct VkExportMetalObjectCreateInfoEXT {
VkStructureType sType;
const void* pNext;
VkExportMetalObjectTypeFlagBitsEXT exportObjectType;
} VkExportMetalObjectCreateInfoEXT;
typedef struct VkExportMetalObjectsInfoEXT {
VkStructureType sType;
const void* pNext;
} VkExportMetalObjectsInfoEXT;
typedef struct VkExportMetalDeviceInfoEXT {
VkStructureType sType;
const void* pNext;
MTLDevice_id mtlDevice;
} VkExportMetalDeviceInfoEXT;
typedef struct VkExportMetalCommandQueueInfoEXT {
VkStructureType sType;
const void* pNext;
VkQueue queue;
MTLCommandQueue_id mtlCommandQueue;
} VkExportMetalCommandQueueInfoEXT;
typedef struct VkExportMetalBufferInfoEXT {
VkStructureType sType;
const void* pNext;
VkDeviceMemory memory;
MTLBuffer_id mtlBuffer;
} VkExportMetalBufferInfoEXT;
typedef struct VkImportMetalBufferInfoEXT {
VkStructureType sType;
const void* pNext;
MTLBuffer_id mtlBuffer;
} VkImportMetalBufferInfoEXT;
typedef struct VkExportMetalTextureInfoEXT {
VkStructureType sType;
const void* pNext;
VkImage image;
VkImageView imageView;
VkBufferView bufferView;
VkImageAspectFlagBits plane;
MTLTexture_id mtlTexture;
} VkExportMetalTextureInfoEXT;
typedef struct VkImportMetalTextureInfoEXT {
VkStructureType sType;
const void* pNext;
VkImageAspectFlagBits plane;
MTLTexture_id mtlTexture;
} VkImportMetalTextureInfoEXT;
typedef struct VkExportMetalIOSurfaceInfoEXT {
VkStructureType sType;
const void* pNext;
VkImage image;
IOSurfaceRef ioSurface;
} VkExportMetalIOSurfaceInfoEXT;
typedef struct VkImportMetalIOSurfaceInfoEXT {
VkStructureType sType;
const void* pNext;
IOSurfaceRef ioSurface;
} VkImportMetalIOSurfaceInfoEXT;
typedef struct VkExportMetalSharedEventInfoEXT {
VkStructureType sType;
const void* pNext;
VkSemaphore semaphore;
VkEvent event;
MTLSharedEvent_id mtlSharedEvent;
} VkExportMetalSharedEventInfoEXT;
typedef struct VkImportMetalSharedEventInfoEXT {
VkStructureType sType;
const void* pNext;
MTLSharedEvent_id mtlSharedEvent;
} VkImportMetalSharedEventInfoEXT;
typedef void (VKAPI_PTR *PFN_vkExportMetalObjectsEXT)(VkDevice device, VkExportMetalObjectsInfoEXT* pMetalObjectsInfo);
#ifndef VK_NO_PROTOTYPES
VKAPI_ATTR void VKAPI_CALL vkExportMetalObjectsEXT(
VkDevice device,
VkExportMetalObjectsInfoEXT* pMetalObjectsInfo);
#endif
#ifdef __cplusplus
}
#endif
#endif
+108
View File
@@ -0,0 +1,108 @@
#ifndef VULKAN_SCREEN_H_
#define VULKAN_SCREEN_H_ 1
/*
** Copyright 2015-2024 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
*/
/*
** This header is generated from the Khronos Vulkan XML API Registry.
**
*/
#ifdef __cplusplus
extern "C" {
#endif
// VK_QNX_screen_surface is a preprocessor guard. Do not pass it to API calls.
#define VK_QNX_screen_surface 1
#define VK_QNX_SCREEN_SURFACE_SPEC_VERSION 1
#define VK_QNX_SCREEN_SURFACE_EXTENSION_NAME "VK_QNX_screen_surface"
typedef VkFlags VkScreenSurfaceCreateFlagsQNX;
typedef struct VkScreenSurfaceCreateInfoQNX {
VkStructureType sType;
const void* pNext;
VkScreenSurfaceCreateFlagsQNX flags;
struct _screen_context* context;
struct _screen_window* window;
} VkScreenSurfaceCreateInfoQNX;
typedef VkResult (VKAPI_PTR *PFN_vkCreateScreenSurfaceQNX)(VkInstance instance, const VkScreenSurfaceCreateInfoQNX* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);
typedef VkBool32 (VKAPI_PTR *PFN_vkGetPhysicalDeviceScreenPresentationSupportQNX)(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex, struct _screen_window* window);
#ifndef VK_NO_PROTOTYPES
VKAPI_ATTR VkResult VKAPI_CALL vkCreateScreenSurfaceQNX(
VkInstance instance,
const VkScreenSurfaceCreateInfoQNX* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkSurfaceKHR* pSurface);
VKAPI_ATTR VkBool32 VKAPI_CALL vkGetPhysicalDeviceScreenPresentationSupportQNX(
VkPhysicalDevice physicalDevice,
uint32_t queueFamilyIndex,
struct _screen_window* window);
#endif
// VK_QNX_external_memory_screen_buffer is a preprocessor guard. Do not pass it to API calls.
#define VK_QNX_external_memory_screen_buffer 1
#define VK_QNX_EXTERNAL_MEMORY_SCREEN_BUFFER_SPEC_VERSION 1
#define VK_QNX_EXTERNAL_MEMORY_SCREEN_BUFFER_EXTENSION_NAME "VK_QNX_external_memory_screen_buffer"
typedef struct VkScreenBufferPropertiesQNX {
VkStructureType sType;
void* pNext;
VkDeviceSize allocationSize;
uint32_t memoryTypeBits;
} VkScreenBufferPropertiesQNX;
typedef struct VkScreenBufferFormatPropertiesQNX {
VkStructureType sType;
void* pNext;
VkFormat format;
uint64_t externalFormat;
uint64_t screenUsage;
VkFormatFeatureFlags formatFeatures;
VkComponentMapping samplerYcbcrConversionComponents;
VkSamplerYcbcrModelConversion suggestedYcbcrModel;
VkSamplerYcbcrRange suggestedYcbcrRange;
VkChromaLocation suggestedXChromaOffset;
VkChromaLocation suggestedYChromaOffset;
} VkScreenBufferFormatPropertiesQNX;
typedef struct VkImportScreenBufferInfoQNX {
VkStructureType sType;
const void* pNext;
struct _screen_buffer* buffer;
} VkImportScreenBufferInfoQNX;
typedef struct VkExternalFormatQNX {
VkStructureType sType;
void* pNext;
uint64_t externalFormat;
} VkExternalFormatQNX;
typedef struct VkPhysicalDeviceExternalMemoryScreenBufferFeaturesQNX {
VkStructureType sType;
void* pNext;
VkBool32 screenBufferImport;
} VkPhysicalDeviceExternalMemoryScreenBufferFeaturesQNX;
typedef VkResult (VKAPI_PTR *PFN_vkGetScreenBufferPropertiesQNX)(VkDevice device, const struct _screen_buffer* buffer, VkScreenBufferPropertiesQNX* pProperties);
#ifndef VK_NO_PROTOTYPES
VKAPI_ATTR VkResult VKAPI_CALL vkGetScreenBufferPropertiesQNX(
VkDevice device,
const struct _screen_buffer* buffer,
VkScreenBufferPropertiesQNX* pProperties);
#endif
#ifdef __cplusplus
}
#endif
#endif
+48
View File
@@ -0,0 +1,48 @@
#ifndef VULKAN_VI_H_
#define VULKAN_VI_H_ 1
/*
** Copyright 2015-2024 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
*/
/*
** This header is generated from the Khronos Vulkan XML API Registry.
**
*/
#ifdef __cplusplus
extern "C" {
#endif
// VK_NN_vi_surface is a preprocessor guard. Do not pass it to API calls.
#define VK_NN_vi_surface 1
#define VK_NN_VI_SURFACE_SPEC_VERSION 1
#define VK_NN_VI_SURFACE_EXTENSION_NAME "VK_NN_vi_surface"
typedef VkFlags VkViSurfaceCreateFlagsNN;
typedef struct VkViSurfaceCreateInfoNN {
VkStructureType sType;
const void* pNext;
VkViSurfaceCreateFlagsNN flags;
void* window;
} VkViSurfaceCreateInfoNN;
typedef VkResult (VKAPI_PTR *PFN_vkCreateViSurfaceNN)(VkInstance instance, const VkViSurfaceCreateInfoNN* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);
#ifndef VK_NO_PROTOTYPES
VKAPI_ATTR VkResult VKAPI_CALL vkCreateViSurfaceNN(
VkInstance instance,
const VkViSurfaceCreateInfoNN* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkSurfaceKHR* pSurface);
#endif
#ifdef __cplusplus
}
#endif
#endif
+55
View File
@@ -0,0 +1,55 @@
#ifndef VULKAN_WAYLAND_H_
#define VULKAN_WAYLAND_H_ 1
/*
** Copyright 2015-2024 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
*/
/*
** This header is generated from the Khronos Vulkan XML API Registry.
**
*/
#ifdef __cplusplus
extern "C" {
#endif
// VK_KHR_wayland_surface is a preprocessor guard. Do not pass it to API calls.
#define VK_KHR_wayland_surface 1
#define VK_KHR_WAYLAND_SURFACE_SPEC_VERSION 6
#define VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME "VK_KHR_wayland_surface"
typedef VkFlags VkWaylandSurfaceCreateFlagsKHR;
typedef struct VkWaylandSurfaceCreateInfoKHR {
VkStructureType sType;
const void* pNext;
VkWaylandSurfaceCreateFlagsKHR flags;
struct wl_display* display;
struct wl_surface* surface;
} VkWaylandSurfaceCreateInfoKHR;
typedef VkResult (VKAPI_PTR *PFN_vkCreateWaylandSurfaceKHR)(VkInstance instance, const VkWaylandSurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);
typedef VkBool32 (VKAPI_PTR *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR)(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex, struct wl_display* display);
#ifndef VK_NO_PROTOTYPES
VKAPI_ATTR VkResult VKAPI_CALL vkCreateWaylandSurfaceKHR(
VkInstance instance,
const VkWaylandSurfaceCreateInfoKHR* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkSurfaceKHR* pSurface);
VKAPI_ATTR VkBool32 VKAPI_CALL vkGetPhysicalDeviceWaylandPresentationSupportKHR(
VkPhysicalDevice physicalDevice,
uint32_t queueFamilyIndex,
struct wl_display* display);
#endif
#ifdef __cplusplus
}
#endif
#endif
+342
View File
@@ -0,0 +1,342 @@
#ifndef VULKAN_WIN32_H_
#define VULKAN_WIN32_H_ 1
/*
** Copyright 2015-2024 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
*/
/*
** This header is generated from the Khronos Vulkan XML API Registry.
**
*/
#ifdef __cplusplus
extern "C" {
#endif
// VK_KHR_win32_surface is a preprocessor guard. Do not pass it to API calls.
#define VK_KHR_win32_surface 1
#define VK_KHR_WIN32_SURFACE_SPEC_VERSION 6
#define VK_KHR_WIN32_SURFACE_EXTENSION_NAME "VK_KHR_win32_surface"
typedef VkFlags VkWin32SurfaceCreateFlagsKHR;
typedef struct VkWin32SurfaceCreateInfoKHR {
VkStructureType sType;
const void* pNext;
VkWin32SurfaceCreateFlagsKHR flags;
HINSTANCE hinstance;
HWND hwnd;
} VkWin32SurfaceCreateInfoKHR;
typedef VkResult (VKAPI_PTR *PFN_vkCreateWin32SurfaceKHR)(VkInstance instance, const VkWin32SurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);
typedef VkBool32 (VKAPI_PTR *PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR)(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex);
#ifndef VK_NO_PROTOTYPES
VKAPI_ATTR VkResult VKAPI_CALL vkCreateWin32SurfaceKHR(
VkInstance instance,
const VkWin32SurfaceCreateInfoKHR* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkSurfaceKHR* pSurface);
VKAPI_ATTR VkBool32 VKAPI_CALL vkGetPhysicalDeviceWin32PresentationSupportKHR(
VkPhysicalDevice physicalDevice,
uint32_t queueFamilyIndex);
#endif
// VK_KHR_external_memory_win32 is a preprocessor guard. Do not pass it to API calls.
#define VK_KHR_external_memory_win32 1
#define VK_KHR_EXTERNAL_MEMORY_WIN32_SPEC_VERSION 1
#define VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME "VK_KHR_external_memory_win32"
typedef struct VkImportMemoryWin32HandleInfoKHR {
VkStructureType sType;
const void* pNext;
VkExternalMemoryHandleTypeFlagBits handleType;
HANDLE handle;
LPCWSTR name;
} VkImportMemoryWin32HandleInfoKHR;
typedef struct VkExportMemoryWin32HandleInfoKHR {
VkStructureType sType;
const void* pNext;
const SECURITY_ATTRIBUTES* pAttributes;
DWORD dwAccess;
LPCWSTR name;
} VkExportMemoryWin32HandleInfoKHR;
typedef struct VkMemoryWin32HandlePropertiesKHR {
VkStructureType sType;
void* pNext;
uint32_t memoryTypeBits;
} VkMemoryWin32HandlePropertiesKHR;
typedef struct VkMemoryGetWin32HandleInfoKHR {
VkStructureType sType;
const void* pNext;
VkDeviceMemory memory;
VkExternalMemoryHandleTypeFlagBits handleType;
} VkMemoryGetWin32HandleInfoKHR;
typedef VkResult (VKAPI_PTR *PFN_vkGetMemoryWin32HandleKHR)(VkDevice device, const VkMemoryGetWin32HandleInfoKHR* pGetWin32HandleInfo, HANDLE* pHandle);
typedef VkResult (VKAPI_PTR *PFN_vkGetMemoryWin32HandlePropertiesKHR)(VkDevice device, VkExternalMemoryHandleTypeFlagBits handleType, HANDLE handle, VkMemoryWin32HandlePropertiesKHR* pMemoryWin32HandleProperties);
#ifndef VK_NO_PROTOTYPES
VKAPI_ATTR VkResult VKAPI_CALL vkGetMemoryWin32HandleKHR(
VkDevice device,
const VkMemoryGetWin32HandleInfoKHR* pGetWin32HandleInfo,
HANDLE* pHandle);
VKAPI_ATTR VkResult VKAPI_CALL vkGetMemoryWin32HandlePropertiesKHR(
VkDevice device,
VkExternalMemoryHandleTypeFlagBits handleType,
HANDLE handle,
VkMemoryWin32HandlePropertiesKHR* pMemoryWin32HandleProperties);
#endif
// VK_KHR_win32_keyed_mutex is a preprocessor guard. Do not pass it to API calls.
#define VK_KHR_win32_keyed_mutex 1
#define VK_KHR_WIN32_KEYED_MUTEX_SPEC_VERSION 1
#define VK_KHR_WIN32_KEYED_MUTEX_EXTENSION_NAME "VK_KHR_win32_keyed_mutex"
typedef struct VkWin32KeyedMutexAcquireReleaseInfoKHR {
VkStructureType sType;
const void* pNext;
uint32_t acquireCount;
const VkDeviceMemory* pAcquireSyncs;
const uint64_t* pAcquireKeys;
const uint32_t* pAcquireTimeouts;
uint32_t releaseCount;
const VkDeviceMemory* pReleaseSyncs;
const uint64_t* pReleaseKeys;
} VkWin32KeyedMutexAcquireReleaseInfoKHR;
// VK_KHR_external_semaphore_win32 is a preprocessor guard. Do not pass it to API calls.
#define VK_KHR_external_semaphore_win32 1
#define VK_KHR_EXTERNAL_SEMAPHORE_WIN32_SPEC_VERSION 1
#define VK_KHR_EXTERNAL_SEMAPHORE_WIN32_EXTENSION_NAME "VK_KHR_external_semaphore_win32"
typedef struct VkImportSemaphoreWin32HandleInfoKHR {
VkStructureType sType;
const void* pNext;
VkSemaphore semaphore;
VkSemaphoreImportFlags flags;
VkExternalSemaphoreHandleTypeFlagBits handleType;
HANDLE handle;
LPCWSTR name;
} VkImportSemaphoreWin32HandleInfoKHR;
typedef struct VkExportSemaphoreWin32HandleInfoKHR {
VkStructureType sType;
const void* pNext;
const SECURITY_ATTRIBUTES* pAttributes;
DWORD dwAccess;
LPCWSTR name;
} VkExportSemaphoreWin32HandleInfoKHR;
typedef struct VkD3D12FenceSubmitInfoKHR {
VkStructureType sType;
const void* pNext;
uint32_t waitSemaphoreValuesCount;
const uint64_t* pWaitSemaphoreValues;
uint32_t signalSemaphoreValuesCount;
const uint64_t* pSignalSemaphoreValues;
} VkD3D12FenceSubmitInfoKHR;
typedef struct VkSemaphoreGetWin32HandleInfoKHR {
VkStructureType sType;
const void* pNext;
VkSemaphore semaphore;
VkExternalSemaphoreHandleTypeFlagBits handleType;
} VkSemaphoreGetWin32HandleInfoKHR;
typedef VkResult (VKAPI_PTR *PFN_vkImportSemaphoreWin32HandleKHR)(VkDevice device, const VkImportSemaphoreWin32HandleInfoKHR* pImportSemaphoreWin32HandleInfo);
typedef VkResult (VKAPI_PTR *PFN_vkGetSemaphoreWin32HandleKHR)(VkDevice device, const VkSemaphoreGetWin32HandleInfoKHR* pGetWin32HandleInfo, HANDLE* pHandle);
#ifndef VK_NO_PROTOTYPES
VKAPI_ATTR VkResult VKAPI_CALL vkImportSemaphoreWin32HandleKHR(
VkDevice device,
const VkImportSemaphoreWin32HandleInfoKHR* pImportSemaphoreWin32HandleInfo);
VKAPI_ATTR VkResult VKAPI_CALL vkGetSemaphoreWin32HandleKHR(
VkDevice device,
const VkSemaphoreGetWin32HandleInfoKHR* pGetWin32HandleInfo,
HANDLE* pHandle);
#endif
// VK_KHR_external_fence_win32 is a preprocessor guard. Do not pass it to API calls.
#define VK_KHR_external_fence_win32 1
#define VK_KHR_EXTERNAL_FENCE_WIN32_SPEC_VERSION 1
#define VK_KHR_EXTERNAL_FENCE_WIN32_EXTENSION_NAME "VK_KHR_external_fence_win32"
typedef struct VkImportFenceWin32HandleInfoKHR {
VkStructureType sType;
const void* pNext;
VkFence fence;
VkFenceImportFlags flags;
VkExternalFenceHandleTypeFlagBits handleType;
HANDLE handle;
LPCWSTR name;
} VkImportFenceWin32HandleInfoKHR;
typedef struct VkExportFenceWin32HandleInfoKHR {
VkStructureType sType;
const void* pNext;
const SECURITY_ATTRIBUTES* pAttributes;
DWORD dwAccess;
LPCWSTR name;
} VkExportFenceWin32HandleInfoKHR;
typedef struct VkFenceGetWin32HandleInfoKHR {
VkStructureType sType;
const void* pNext;
VkFence fence;
VkExternalFenceHandleTypeFlagBits handleType;
} VkFenceGetWin32HandleInfoKHR;
typedef VkResult (VKAPI_PTR *PFN_vkImportFenceWin32HandleKHR)(VkDevice device, const VkImportFenceWin32HandleInfoKHR* pImportFenceWin32HandleInfo);
typedef VkResult (VKAPI_PTR *PFN_vkGetFenceWin32HandleKHR)(VkDevice device, const VkFenceGetWin32HandleInfoKHR* pGetWin32HandleInfo, HANDLE* pHandle);
#ifndef VK_NO_PROTOTYPES
VKAPI_ATTR VkResult VKAPI_CALL vkImportFenceWin32HandleKHR(
VkDevice device,
const VkImportFenceWin32HandleInfoKHR* pImportFenceWin32HandleInfo);
VKAPI_ATTR VkResult VKAPI_CALL vkGetFenceWin32HandleKHR(
VkDevice device,
const VkFenceGetWin32HandleInfoKHR* pGetWin32HandleInfo,
HANDLE* pHandle);
#endif
// VK_NV_external_memory_win32 is a preprocessor guard. Do not pass it to API calls.
#define VK_NV_external_memory_win32 1
#define VK_NV_EXTERNAL_MEMORY_WIN32_SPEC_VERSION 1
#define VK_NV_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME "VK_NV_external_memory_win32"
typedef struct VkImportMemoryWin32HandleInfoNV {
VkStructureType sType;
const void* pNext;
VkExternalMemoryHandleTypeFlagsNV handleType;
HANDLE handle;
} VkImportMemoryWin32HandleInfoNV;
typedef struct VkExportMemoryWin32HandleInfoNV {
VkStructureType sType;
const void* pNext;
const SECURITY_ATTRIBUTES* pAttributes;
DWORD dwAccess;
} VkExportMemoryWin32HandleInfoNV;
typedef VkResult (VKAPI_PTR *PFN_vkGetMemoryWin32HandleNV)(VkDevice device, VkDeviceMemory memory, VkExternalMemoryHandleTypeFlagsNV handleType, HANDLE* pHandle);
#ifndef VK_NO_PROTOTYPES
VKAPI_ATTR VkResult VKAPI_CALL vkGetMemoryWin32HandleNV(
VkDevice device,
VkDeviceMemory memory,
VkExternalMemoryHandleTypeFlagsNV handleType,
HANDLE* pHandle);
#endif
// VK_NV_win32_keyed_mutex is a preprocessor guard. Do not pass it to API calls.
#define VK_NV_win32_keyed_mutex 1
#define VK_NV_WIN32_KEYED_MUTEX_SPEC_VERSION 2
#define VK_NV_WIN32_KEYED_MUTEX_EXTENSION_NAME "VK_NV_win32_keyed_mutex"
typedef struct VkWin32KeyedMutexAcquireReleaseInfoNV {
VkStructureType sType;
const void* pNext;
uint32_t acquireCount;
const VkDeviceMemory* pAcquireSyncs;
const uint64_t* pAcquireKeys;
const uint32_t* pAcquireTimeoutMilliseconds;
uint32_t releaseCount;
const VkDeviceMemory* pReleaseSyncs;
const uint64_t* pReleaseKeys;
} VkWin32KeyedMutexAcquireReleaseInfoNV;
// VK_EXT_full_screen_exclusive is a preprocessor guard. Do not pass it to API calls.
#define VK_EXT_full_screen_exclusive 1
#define VK_EXT_FULL_SCREEN_EXCLUSIVE_SPEC_VERSION 4
#define VK_EXT_FULL_SCREEN_EXCLUSIVE_EXTENSION_NAME "VK_EXT_full_screen_exclusive"
typedef enum VkFullScreenExclusiveEXT {
VK_FULL_SCREEN_EXCLUSIVE_DEFAULT_EXT = 0,
VK_FULL_SCREEN_EXCLUSIVE_ALLOWED_EXT = 1,
VK_FULL_SCREEN_EXCLUSIVE_DISALLOWED_EXT = 2,
VK_FULL_SCREEN_EXCLUSIVE_APPLICATION_CONTROLLED_EXT = 3,
VK_FULL_SCREEN_EXCLUSIVE_MAX_ENUM_EXT = 0x7FFFFFFF
} VkFullScreenExclusiveEXT;
typedef struct VkSurfaceFullScreenExclusiveInfoEXT {
VkStructureType sType;
void* pNext;
VkFullScreenExclusiveEXT fullScreenExclusive;
} VkSurfaceFullScreenExclusiveInfoEXT;
typedef struct VkSurfaceCapabilitiesFullScreenExclusiveEXT {
VkStructureType sType;
void* pNext;
VkBool32 fullScreenExclusiveSupported;
} VkSurfaceCapabilitiesFullScreenExclusiveEXT;
typedef struct VkSurfaceFullScreenExclusiveWin32InfoEXT {
VkStructureType sType;
const void* pNext;
HMONITOR hmonitor;
} VkSurfaceFullScreenExclusiveWin32InfoEXT;
typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceSurfacePresentModes2EXT)(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceSurfaceInfo2KHR* pSurfaceInfo, uint32_t* pPresentModeCount, VkPresentModeKHR* pPresentModes);
typedef VkResult (VKAPI_PTR *PFN_vkAcquireFullScreenExclusiveModeEXT)(VkDevice device, VkSwapchainKHR swapchain);
typedef VkResult (VKAPI_PTR *PFN_vkReleaseFullScreenExclusiveModeEXT)(VkDevice device, VkSwapchainKHR swapchain);
typedef VkResult (VKAPI_PTR *PFN_vkGetDeviceGroupSurfacePresentModes2EXT)(VkDevice device, const VkPhysicalDeviceSurfaceInfo2KHR* pSurfaceInfo, VkDeviceGroupPresentModeFlagsKHR* pModes);
#ifndef VK_NO_PROTOTYPES
VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfacePresentModes2EXT(
VkPhysicalDevice physicalDevice,
const VkPhysicalDeviceSurfaceInfo2KHR* pSurfaceInfo,
uint32_t* pPresentModeCount,
VkPresentModeKHR* pPresentModes);
VKAPI_ATTR VkResult VKAPI_CALL vkAcquireFullScreenExclusiveModeEXT(
VkDevice device,
VkSwapchainKHR swapchain);
VKAPI_ATTR VkResult VKAPI_CALL vkReleaseFullScreenExclusiveModeEXT(
VkDevice device,
VkSwapchainKHR swapchain);
VKAPI_ATTR VkResult VKAPI_CALL vkGetDeviceGroupSurfacePresentModes2EXT(
VkDevice device,
const VkPhysicalDeviceSurfaceInfo2KHR* pSurfaceInfo,
VkDeviceGroupPresentModeFlagsKHR* pModes);
#endif
// VK_NV_acquire_winrt_display is a preprocessor guard. Do not pass it to API calls.
#define VK_NV_acquire_winrt_display 1
#define VK_NV_ACQUIRE_WINRT_DISPLAY_SPEC_VERSION 1
#define VK_NV_ACQUIRE_WINRT_DISPLAY_EXTENSION_NAME "VK_NV_acquire_winrt_display"
typedef VkResult (VKAPI_PTR *PFN_vkAcquireWinrtDisplayNV)(VkPhysicalDevice physicalDevice, VkDisplayKHR display);
typedef VkResult (VKAPI_PTR *PFN_vkGetWinrtDisplayNV)(VkPhysicalDevice physicalDevice, uint32_t deviceRelativeId, VkDisplayKHR* pDisplay);
#ifndef VK_NO_PROTOTYPES
VKAPI_ATTR VkResult VKAPI_CALL vkAcquireWinrtDisplayNV(
VkPhysicalDevice physicalDevice,
VkDisplayKHR display);
VKAPI_ATTR VkResult VKAPI_CALL vkGetWinrtDisplayNV(
VkPhysicalDevice physicalDevice,
uint32_t deviceRelativeId,
VkDisplayKHR* pDisplay);
#endif
#ifdef __cplusplus
}
#endif
#endif

Some files were not shown because too many files have changed in this diff Show More