diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..d2160131 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,50 @@ +# .dockerignore — Exclude files from the Docker build context. +# Keeps the context small and prevents leaking build artifacts or secrets. + +# Build outputs +build/ +cache/ + +# Git history +.git/ +.gitignore +.github/ + +# Large external directories (fetched at build time inside the container) +extern/FidelityFX-FSR2/ +extern/FidelityFX-SDK/ + +# IDE / editor files +.vscode/ +.idea/ +*.swp +*.swo +*~ +.DS_Store + +# Documentation (not needed for build) +docs/ +*.md +!container/*.md + +# Test / tool outputs +logs/ + +# Host build scripts that run outside the container (not needed inside) +build.sh +build.bat +build.ps1 +rebuild.sh +rebuild.bat +rebuild.ps1 +clean.sh +debug_texture.* +extract_assets.* +extract_warden_rsa.py +restart-worldserver.sh +test.sh + +# macOS SDK tarballs that may be temporarily placed here +*.tar.xz +*.tar.gz +*.tar.bz2 diff --git a/CMakeLists.txt b/CMakeLists.txt index 5489cbe3..c13547b1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -248,28 +248,98 @@ endif() find_package(SDL2 REQUIRED) find_package(Vulkan QUIET) if(NOT Vulkan_FOUND) - # Fallback: some distros / CMake versions need pkg-config to locate Vulkan. - find_package(PkgConfig QUIET) - if(PkgConfig_FOUND) - pkg_check_modules(VULKAN_PKG vulkan) - if(VULKAN_PKG_FOUND) - add_library(Vulkan::Vulkan INTERFACE IMPORTED) - set_target_properties(Vulkan::Vulkan PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${VULKAN_PKG_INCLUDE_DIRS}" - INTERFACE_LINK_LIBRARIES "${VULKAN_PKG_LIBRARIES}" - ) - if(VULKAN_PKG_LIBRARY_DIRS) + # For Windows cross-compilation the host pkg-config finds the Linux libvulkan-dev + # and injects /usr/include as an INTERFACE_INCLUDE_DIRECTORY, which causes + # MinGW clang to pull in glibc headers (bits/libc-header-start.h) instead of + # the MinGW sysroot headers. Skip the host pkg-config path entirely and instead + # locate Vulkan via vcpkg-installed vulkan-headers or the MinGW toolchain. + if(CMAKE_CROSSCOMPILING AND WIN32) + # The cross-compile build script generates a Vulkan import library + # (libvulkan-1.a) in ${CMAKE_BINARY_DIR}/vulkan-import from the headers. + set(_VULKAN_IMPORT_DIR "${CMAKE_BINARY_DIR}/vulkan-import") + + find_package(VulkanHeaders CONFIG QUIET) + if(VulkanHeaders_FOUND) + if(NOT TARGET Vulkan::Vulkan) + add_library(Vulkan::Vulkan INTERFACE IMPORTED) + endif() + # Vulkan::Headers is provided by vcpkg's vulkan-headers port and carries + # the correct MinGW include path — no Linux system headers involved. + set_property(TARGET Vulkan::Vulkan APPEND PROPERTY + INTERFACE_LINK_LIBRARIES Vulkan::Headers) + # Link against the Vulkan loader import library (vulkan-1.dll). + if(EXISTS "${_VULKAN_IMPORT_DIR}/libvulkan-1.a") set_property(TARGET Vulkan::Vulkan APPEND PROPERTY - INTERFACE_LINK_DIRECTORIES "${VULKAN_PKG_LIBRARY_DIRS}") + INTERFACE_LINK_DIRECTORIES "${_VULKAN_IMPORT_DIR}") + set_property(TARGET Vulkan::Vulkan APPEND PROPERTY + INTERFACE_LINK_LIBRARIES vulkan-1) endif() set(Vulkan_FOUND TRUE) - message(STATUS "Found Vulkan via pkg-config: ${VULKAN_PKG_LIBRARIES}") + message(STATUS "Found Vulkan headers for Windows cross-compile via vcpkg VulkanHeaders") + else() + # Last-resort: check the LLVM-MinGW toolchain sysroot directly. + find_path(_VULKAN_MINGW_INCLUDE NAMES vulkan/vulkan.h + PATHS /opt/llvm-mingw/x86_64-w64-mingw32/include NO_DEFAULT_PATH) + if(_VULKAN_MINGW_INCLUDE) + if(NOT TARGET Vulkan::Vulkan) + add_library(Vulkan::Vulkan INTERFACE IMPORTED) + endif() + set_target_properties(Vulkan::Vulkan PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${_VULKAN_MINGW_INCLUDE}") + # Link against the Vulkan loader import library (vulkan-1.dll). + if(EXISTS "${_VULKAN_IMPORT_DIR}/libvulkan-1.a") + set_property(TARGET Vulkan::Vulkan APPEND PROPERTY + INTERFACE_LINK_DIRECTORIES "${_VULKAN_IMPORT_DIR}") + set_property(TARGET Vulkan::Vulkan APPEND PROPERTY + INTERFACE_LINK_LIBRARIES vulkan-1) + endif() + set(Vulkan_FOUND TRUE) + message(STATUS "Found Vulkan headers in LLVM-MinGW sysroot: ${_VULKAN_MINGW_INCLUDE}") + endif() + endif() + elseif(CMAKE_CROSSCOMPILING AND CMAKE_SYSTEM_NAME STREQUAL "Darwin") + # macOS cross-compilation: use vcpkg-installed vulkan-headers. + # The host pkg-config would find Linux libvulkan-dev headers which the + # macOS cross-compiler cannot use (different sysroot). + find_package(VulkanHeaders CONFIG QUIET) + if(VulkanHeaders_FOUND) + if(NOT TARGET Vulkan::Vulkan) + add_library(Vulkan::Vulkan INTERFACE IMPORTED) + endif() + set_property(TARGET Vulkan::Vulkan APPEND PROPERTY + INTERFACE_LINK_LIBRARIES Vulkan::Headers) + set(Vulkan_FOUND TRUE) + message(STATUS "Found Vulkan headers for macOS cross-compile via vcpkg VulkanHeaders") + endif() + else() + # Fallback: some distros / CMake versions need pkg-config to locate Vulkan. + find_package(PkgConfig QUIET) + if(PkgConfig_FOUND) + pkg_check_modules(VULKAN_PKG vulkan) + if(VULKAN_PKG_FOUND) + add_library(Vulkan::Vulkan INTERFACE IMPORTED) + set_target_properties(Vulkan::Vulkan PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${VULKAN_PKG_INCLUDE_DIRS}" + INTERFACE_LINK_LIBRARIES "${VULKAN_PKG_LIBRARIES}" + ) + if(VULKAN_PKG_LIBRARY_DIRS) + set_property(TARGET Vulkan::Vulkan APPEND PROPERTY + INTERFACE_LINK_DIRECTORIES "${VULKAN_PKG_LIBRARY_DIRS}") + endif() + set(Vulkan_FOUND TRUE) + message(STATUS "Found Vulkan via pkg-config: ${VULKAN_PKG_LIBRARIES}") + endif() endif() endif() if(NOT Vulkan_FOUND) message(FATAL_ERROR "Could not find Vulkan. Install libvulkan-dev (Linux), vulkan-loader (macOS), or the Vulkan SDK (Windows).") endif() endif() +# macOS cross-compilation: the Vulkan loader (MoltenVK) is not available at link +# time. Allow unresolved Vulkan symbols — they are resolved at runtime. +if(CMAKE_CROSSCOMPILING AND CMAKE_SYSTEM_NAME STREQUAL "Darwin") + add_link_options("-undefined" "dynamic_lookup") +endif() # GL/GLEW kept temporarily for unconverted sub-renderers during Vulkan migration. # These files compile against GL types but their code is never called — the Vulkan # path is the only active rendering backend. Remove in Phase 7 when all renderers @@ -674,12 +744,16 @@ set(WOWEE_HEADERS set(WOWEE_PLATFORM_SOURCES) if(WIN32) - # Copy icon into build tree so llvm-rc can find it via the relative path in wowee.rc + # Copy icon into build tree so windres can find it via the relative path + # in wowee.rc ("assets\\wowee.ico"). Tell the RC compiler to also search + # the build directory — GNU windres uses cwd (already the build dir) but + # llvm-windres resolves relative to the .rc file, so it needs the hint. configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/assets/Wowee.ico ${CMAKE_CURRENT_BINARY_DIR}/assets/wowee.ico COPYONLY ) + set(CMAKE_RC_FLAGS "${CMAKE_RC_FLAGS} -I ${CMAKE_CURRENT_BINARY_DIR} -I ${CMAKE_CURRENT_SOURCE_DIR}") list(APPEND WOWEE_PLATFORM_SOURCES resources/wowee.rc) endif() diff --git a/container/FLOW.md b/container/FLOW.md new file mode 100644 index 00000000..619328c0 --- /dev/null +++ b/container/FLOW.md @@ -0,0 +1,283 @@ +# Container Build Flow + +Comprehensive documentation of the Docker-based build pipeline for each target platform. + +--- + +## Architecture Overview + +Each platform follows the same two-phase pattern: + +1. **Image Build** (one-time, cached by Docker) — installs compilers, toolchains, and pre-builds vcpkg dependencies. +2. **Container Run** (each build) — copies source into the container, runs CMake configure + build, outputs artifacts to the host. + +``` +Host Docker +───────────────────────────────────────────────────────────── +run-{platform}.sh/.ps1 + │ + ├─ docker build builder-{platform}.Dockerfile + │ (cached after first run) ├─ install compilers + │ ├─ install vcpkg + packages + │ └─ COPY build-{platform}.sh + │ + └─ docker run build-{platform}.sh (entrypoint) + ├─ bind /src (readonly) ├─ tar copy source → /wowee-build-src + └─ bind /out (writable) ├─ git clone FidelityFX SDKs + ├─ cmake -S . -B /out + ├─ cmake --build /out + └─ artifacts appear in /out +``` + +--- + +## Linux Build Flow + +**Image:** `wowee-builder-linux` +**Dockerfile:** `builder-linux.Dockerfile` +**Toolchain:** GCC + Ninja (native amd64) +**Base:** Ubuntu 24.04 + +### Docker Image Build Steps + +| Step | What | Why | +|------|------|-----| +| 1 | `apt-get install` cmake, ninja-build, build-essential, pkg-config, git, python3 | Core build tools | +| 2 | `apt-get install` glslang-tools, spirv-tools | Vulkan shader compilation | +| 3 | `apt-get install` libsdl2-dev, libglew-dev, libglm-dev, libssl-dev, zlib1g-dev | Runtime dependencies (system packages) | +| 4 | `apt-get install` libavformat-dev, libavcodec-dev, libswscale-dev, libavutil-dev | FFmpeg libraries | +| 5 | `apt-get install` libvulkan-dev, vulkan-tools | Vulkan SDK | +| 6 | `apt-get install` libstorm-dev, libunicorn-dev | MPQ archive + CPU emulation | +| 7 | COPY `build-linux.sh` → `/build-platform.sh` | Container entrypoint | + +### Container Run Steps (build-linux.sh) + +``` +1. tar copy /src → /wowee-build-src (excludes build/, .git/, large Data/ dirs) +2. git clone FidelityFX-FSR2 (if missing) +3. git clone FidelityFX-SDK (if missing) +4. cmake configure: + -G Ninja + -DCMAKE_BUILD_TYPE=Release + -DCMAKE_C_COMPILER=gcc + -DCMAKE_CXX_COMPILER=g++ + -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON +5. cmake --build (parallel) +6. Create Data symlink: build/linux/bin/Data → ../../../Data +``` + +### Output +- `build/linux/bin/wowee` — ELF 64-bit x86-64 executable +- `build/linux/bin/Data` — symlink to project Data/ directory + +--- + +## macOS Build Flow + +**Image:** `wowee-builder-macos` +**Dockerfile:** `builder-macos.Dockerfile` (multi-stage) +**Toolchain:** osxcross (Clang 18 + Apple ld64) +**Base:** Ubuntu 24.04 +**Targets:** arm64-apple-darwin24.5 (default), x86_64-apple-darwin24.5 + +### Docker Image Build — Stage 1: SDK Fetcher + +The macOS SDK is fetched automatically from Apple's public software update catalog. +No manual download required. + +| Step | What | Why | +|------|------|-----| +| 1 | `FROM ubuntu:24.04 AS sdk-fetcher` | Lightweight stage for SDK download | +| 2 | `apt-get install` ca-certificates, python3, cpio, tar, gzip, xz-utils | SDK extraction tools | +| 3 | COPY `macos/sdk-fetcher.py` → `/opt/sdk-fetcher.py` | Python script that scrapes Apple's SUCATALOG | +| 4 | `python3 /opt/sdk-fetcher.py /opt/sdk` | Downloads, extracts, and packages MacOSX15.5.sdk.tar.gz | + +**SDK Fetcher internals** (`macos/sdk-fetcher.py`): +1. Queries Apple SUCATALOG URLs for the latest macOS package +2. Downloads the `CLTools_macOSNMOS_SDK.pkg` package +3. Extracts the XAR archive (using `bsdtar` or pure-Python fallback) +4. Decompresses the PBZX payload stream +5. Extracts via `cpio` to get the SDK directory +6. Packages as `MacOSX.sdk.tar.gz` + +### Docker Image Build — Stage 2: Builder + +| Step | What | Why | +|------|------|-----| +| 1 | `FROM ubuntu:24.04 AS builder` | Full build environment | +| 2 | `apt-get install` cmake, ninja-build, git, python3, curl, wget, xz-utils, zip, unzip, tar, make, patch, libssl-dev, zlib1g-dev, pkg-config, libbz2-dev, libxml2-dev, uuid-dev | Build tools + osxcross build deps | +| 3 | Install Clang 18 from LLVM apt repo (`llvm-toolchain-jammy-18`) | Cross-compiler backend | +| 4 | Symlink clang-18 → clang, clang++-18 → clang++, etc. | osxcross expects unversioned names | +| 5 | `git clone osxcross` → `/opt/osxcross` | Apple cross-compile toolchain wrapper | +| 6 | `COPY --from=sdk-fetcher /opt/sdk/ → /opt/osxcross/tarballs/` | SDK from stage 1 | +| 7 | `UNATTENDED=1 ./build.sh` | Builds osxcross (LLVM wrappers + cctools + ld64) | +| 8 | Create unprefixed symlinks (install_name_tool, otool, lipo, codesign) | vcpkg/CMake need these without arch prefix | +| 9 | COPY `macos/osxcross-toolchain.cmake` → `/opt/osxcross-toolchain.cmake` | Auto-detecting CMake toolchain | +| 10 | COPY `macos/triplets/` → `/opt/vcpkg-triplets/` | vcpkg cross-compile triplet definitions | +| 11 | `apt-get install` file, nasm | Mach-O detection + ffmpeg x86 asm | +| 12 | Bootstrap vcpkg → `/opt/vcpkg` | Package manager | +| 13 | `vcpkg install` sdl2, openssl, glew, glm, zlib, ffmpeg `--triplet arm64-osx-cross` | arm64 dependencies | +| 14 | `vcpkg install` same packages `--triplet x64-osx-cross` | x86_64 dependencies | +| 15 | `apt-get install` libvulkan-dev, glslang-tools | Vulkan headers (for compilation, not runtime) | +| 16 | COPY `build-macos.sh` → `/build-platform.sh` | Container entrypoint | + +### Custom Toolchain Files + +**`macos/osxcross-toolchain.cmake`** — Auto-detecting CMake toolchain: +- Detects SDK path via `file(GLOB)` in `/opt/osxcross/target/SDK/MacOSX*.sdk` +- Detects darwin version from compiler binary names (e.g., `arm64-apple-darwin24.5-clang`) +- Picks architecture from `CMAKE_OSX_ARCHITECTURES` +- Sets `CMAKE_C_COMPILER`, `CMAKE_CXX_COMPILER`, `CMAKE_AR`, `CMAKE_RANLIB`, `CMAKE_STRIP` + +**`macos/triplets/arm64-osx-cross.cmake`**: +```cmake +set(VCPKG_TARGET_ARCHITECTURE arm64) +set(VCPKG_LIBRARY_LINKAGE static) +set(VCPKG_CMAKE_SYSTEM_NAME Darwin) +set(VCPKG_CHAINLOAD_TOOLCHAIN_FILE /opt/osxcross-toolchain.cmake) +``` + +### Container Run Steps (build-macos.sh) + +``` +1. Determine arch from MACOS_ARCH env (default: arm64) +2. Pick vcpkg triplet: arm64-osx-cross or x64-osx-cross +3. Auto-detect darwin target from osxcross binaries +4. tar copy /src → /wowee-build-src +5. git clone FidelityFX-FSR2 + FidelityFX-SDK (if missing) +6. cmake configure: + -G Ninja + -DCMAKE_BUILD_TYPE=Release + -DCMAKE_SYSTEM_NAME=Darwin + -DCMAKE_OSX_ARCHITECTURES=${ARCH} + -DCMAKE_C_COMPILER=osxcross clang + -DCMAKE_CXX_COMPILER=osxcross clang++ + -DCMAKE_TOOLCHAIN_FILE=vcpkg.cmake + -DVCPKG_TARGET_TRIPLET=arm64-osx-cross + -DVCPKG_OVERLAY_TRIPLETS=/opt/vcpkg-triplets +7. cmake --build (parallel) +``` + +### CMakeLists.txt Integration + +The main CMakeLists.txt has a macOS cross-compile branch that: +- Finds Vulkan headers via vcpkg (`VulkanHeaders` package) instead of the Vulkan SDK +- Adds `-undefined dynamic_lookup` linker flag for Vulkan loader symbols (resolved at runtime via MoltenVK) + +### Output +- `build/macos/bin/wowee` — Mach-O 64-bit arm64 (or x86_64) executable (~40 MB) + +--- + +## Windows Build Flow + +**Image:** `wowee-builder-windows` +**Dockerfile:** `builder-windows.Dockerfile` +**Toolchain:** LLVM-MinGW (Clang + LLD) targeting x86_64-w64-mingw32-ucrt +**Base:** Ubuntu 24.04 + +### Docker Image Build Steps + +| Step | What | Why | +|------|------|-----| +| 1 | `apt-get install` ca-certificates, build-essential, cmake, ninja-build, git, python3, curl, zip, unzip, tar, xz-utils, pkg-config, nasm, libssl-dev, zlib1g-dev | Build tools | +| 2 | Download + extract LLVM-MinGW (v20240619 ucrt) → `/opt/llvm-mingw` | Clang/LLD cross-compiler for Windows | +| 3 | Add `/opt/llvm-mingw/bin` to PATH | Makes `x86_64-w64-mingw32-clang` etc. available | +| 4 | Bootstrap vcpkg → `/opt/vcpkg` | Package manager | +| 5 | `vcpkg install` sdl2, openssl, glew, glm, zlib, ffmpeg `--triplet x64-mingw-static` | Static Windows dependencies | +| 6 | `apt-get install` libvulkan-dev, glslang-tools | Vulkan headers + shader tools | +| 7 | Create no-op `powershell.exe` stub | vcpkg MinGW post-build hook needs it | +| 8 | COPY `build-windows.sh` → `/build-platform.sh` | Container entrypoint | + +### Container Run Steps (build-windows.sh) + +``` +1. Set up no-op powershell.exe (if not already present) +2. tar copy /src → /wowee-build-src +3. git clone FidelityFX-FSR2 + FidelityFX-SDK (if missing) +4. Generate Vulkan import library: + a. Extract vk* symbols from vulkan_core.h + b. Create vulkan-1.def file + c. Run dlltool to create libvulkan-1.a +5. Lock PKG_CONFIG_LIBDIR to vcpkg packages only +6. cmake configure: + -G Ninja + -DCMAKE_BUILD_TYPE=Release + -DCMAKE_SYSTEM_NAME=Windows + -DCMAKE_C_COMPILER=x86_64-w64-mingw32-clang + -DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-clang++ + -DCMAKE_RC_COMPILER=x86_64-w64-mingw32-windres + -DCMAKE_EXE_LINKER_FLAGS=-fuse-ld=lld + -DCMAKE_TOOLCHAIN_FILE=vcpkg.cmake + -DVCPKG_TARGET_TRIPLET=x64-mingw-static + -DVCPKG_APPLOCAL_DEPS=OFF +7. cmake --build (parallel) +``` + +### Vulkan Import Library Generation + +Windows applications link against `vulkan-1.dll` (the Khronos Vulkan loader). Since the LLVM-MinGW toolchain doesn't ship a Vulkan import library, the build script generates one: + +1. Parses `vulkan_core.h` for `VKAPI_CALL vk*` function names +2. Creates a `.def` file mapping symbols to `vulkan-1.dll` +3. Uses `dlltool` to produce `libvulkan-1.a` (PE import library) + +This allows the linker to resolve Vulkan symbols at build time, while deferring actual loading to the runtime DLL. + +### Output +- `build/windows/bin/wowee.exe` — PE32+ x86-64 executable (~135 MB) + +--- + +## Shared Patterns + +### Source Tree Copy + +All three platforms use the same tar-based copy with exclusions: +```bash +tar -C /src \ + --exclude='./build' --exclude='./logs' --exclude='./cache' \ + --exclude='./container' --exclude='./.git' \ + --exclude='./Data/character' --exclude='./Data/creature' \ + --exclude='./Data/db' --exclude='./Data/environment' \ + --exclude='./Data/interface' --exclude='./Data/item' \ + --exclude='./Data/misc' --exclude='./Data/sound' \ + --exclude='./Data/spell' --exclude='./Data/terrain' \ + --exclude='./Data/world' \ + -cf - . | tar -C /wowee-build-src -xf - +``` + +**Kept:** `Data/opcodes/`, `Data/expansions/` (small, needed at build time for configuration). +**Excluded:** Large game asset directories (character, creature, environment, etc.) not needed for compilation. + +### FidelityFX SDK Fetch + +All platforms clone the same two repos at build time: +1. **FidelityFX-FSR2** — FSR 2.0 upscaling +2. **FidelityFX-SDK** — FSR 3.0 frame generation (repo URL/ref configurable via env vars) + +### .dockerignore + +The `.dockerignore` at the project root minimizes the Docker build context by excluding: +- `build/`, `cache/`, `logs/`, `.git/` +- Large external dirs (`extern/FidelityFX-*`) +- IDE files, documentation, host-only scripts +- SDK tarballs (`*.tar.xz`, `*.tar.gz`, etc.) + +--- + +## Timing Estimates + +These are approximate times on a 4-core machine with 16 GB RAM: + +| Phase | Linux | macOS | Windows | +|-------|-------|-------|---------| +| Docker image build (first time) | ~5 min | ~25 min | ~15 min | +| Docker image build (cached) | seconds | seconds | seconds | +| Source copy + SDK fetch | ~10 sec | ~10 sec | ~10 sec | +| CMake configure | ~20 sec | ~30 sec | ~30 sec | +| Compilation | ~8 min | ~8 min | ~8 min | +| **Total (first build)** | **~14 min** | **~34 min** | **~24 min** | +| **Total (subsequent)** | **~9 min** | **~9 min** | **~9 min** | + +macOS image is slowest because osxcross builds a subset of LLVM + cctools, and vcpkg packages are compiled for two architectures (arm64 + x64). diff --git a/container/README.md b/container/README.md new file mode 100644 index 00000000..da911f2c --- /dev/null +++ b/container/README.md @@ -0,0 +1,119 @@ +# Container Builds + +Build WoWee for **Linux**, **macOS**, or **Windows** with a single command. +All builds run inside Docker — no toolchains to install on your host. + +## Prerequisites + +- [Docker](https://docs.docker.com/get-docker/) (Docker Desktop on Windows/macOS, or Docker Engine on Linux) +- ~20 GB free disk space (toolchains + vcpkg packages are cached in the Docker image) + +## Quick Start + +Run **from the project root directory**. + +### Linux (native amd64) + +```bash +# Bash / Linux / macOS terminal +./container/run-linux.sh +``` +```powershell +# PowerShell (Windows) +.\container\run-linux.ps1 +``` + +Output: `build/linux/bin/wowee` + +### macOS (cross-compile, arm64 default) + +```bash +./container/run-macos.sh +``` +```powershell +.\container\run-macos.ps1 +``` + +Output: `build/macos/bin/wowee` + +For Intel (x86_64): +```bash +MACOS_ARCH=x86_64 ./container/run-macos.sh +``` +```powershell +.\container\run-macos.ps1 -Arch x86_64 +``` + +### Windows (cross-compile, x86_64) + +```bash +./container/run-windows.sh +``` +```powershell +.\container\run-windows.ps1 +``` + +Output: `build/windows/bin/wowee.exe` + +## Options + +| Option | Bash | PowerShell | Description | +|--------|------|------------|-------------| +| Rebuild image | `--rebuild-image` | `-RebuildImage` | Force a fresh Docker image build | +| macOS arch | `MACOS_ARCH=x86_64` | `-Arch x86_64` | Build for Intel instead of Apple Silicon | +| FidelityFX SDK repo | `WOWEE_FFX_SDK_REPO=` | `$env:WOWEE_FFX_SDK_REPO=""` | Custom FidelityFX SDK git URL | +| FidelityFX SDK ref | `WOWEE_FFX_SDK_REF=` | `$env:WOWEE_FFX_SDK_REF=""` | Custom FidelityFX SDK git ref/tag | + +## Docker Image Caching + +The first build takes longer because Docker builds the toolchain image (installing compilers, vcpkg packages, etc.). Subsequent builds reuse the cached image and only run the compilation step. + +To force a full image rebuild: +```bash +./container/run-linux.sh --rebuild-image +``` + +## Output Locations + +| Target | Binary | Size | +|--------|--------|------| +| Linux | `build/linux/bin/wowee` | ~135 MB | +| macOS | `build/macos/bin/wowee` | ~40 MB | +| Windows | `build/windows/bin/wowee.exe` | ~135 MB | + +## File Structure + +``` +container/ +├── run-linux.sh / .ps1 # Host launchers (bash / PowerShell) +├── run-macos.sh / .ps1 +├── run-windows.sh / .ps1 +├── build-linux.sh # Container entrypoints (run inside Docker) +├── build-macos.sh +├── build-windows.sh +├── builder-linux.Dockerfile # Docker image definitions +├── builder-macos.Dockerfile +├── builder-windows.Dockerfile +├── macos/ +│ ├── sdk-fetcher.py # Auto-fetches macOS SDK from Apple's catalog +│ ├── osxcross-toolchain.cmake # CMake toolchain for osxcross +│ └── triplets/ # vcpkg cross-compile triplets +│ ├── arm64-osx-cross.cmake +│ └── x64-osx-cross.cmake +├── README.md # This file +└── FLOW.md # Detailed build flow documentation +``` + +## Troubleshooting + +**"docker is not installed or not in PATH"** +Install Docker and ensure the `docker` command is available in your terminal. + +**Build fails on first run** +Some vcpkg packages (ffmpeg, SDL2) take a while to compile. Ensure you have enough RAM (4 GB+) and disk space. + +**macOS build: "could not find osxcross compiler"** +The Docker image may not have built correctly. Run with `--rebuild-image` to rebuild from scratch. + +**Windows build: linker errors about vulkan-1.dll** +The build script auto-generates a Vulkan import library. If this fails, ensure the Docker image has `libvulkan-dev` installed (it should, by default). diff --git a/container/build-in-container.sh b/container/build-in-container.sh deleted file mode 100755 index cc0822b4..00000000 --- a/container/build-in-container.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash - -set -eu -set -o pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" - -podman build \ - -f "${SCRIPT_DIR}/builder-ubuntu.Dockerfile" \ - -t wowee-builder-ubuntu - -BUILD_DIR="$(mktemp --tmpdir -d wowee.XXXXX \ - --suffix=".$(cd "${PROJECT_ROOT}"; git rev-parse --short HEAD)")" -podman run \ - --mount "type=bind,src=${PROJECT_ROOT},dst=/WoWee-src,ro=true" \ - --mount "type=bind,src=${BUILD_DIR},dst=/build" \ - localhost/wowee-builder-ubuntu \ - ./build-wowee.sh diff --git a/container/build-linux.sh b/container/build-linux.sh new file mode 100755 index 00000000..e16cae11 --- /dev/null +++ b/container/build-linux.sh @@ -0,0 +1,62 @@ +#!/bin/bash +# Linux amd64 build entrypoint — runs INSIDE the linux container. +# Bind-mounts: +# /src (ro) — project source +# /out (rw) — host ./build/linux + +set -euo pipefail + +SRC=/src +OUT=/out +NPROC=$(nproc) + +echo "==> [linux] Copying source tree..." +tar -C "${SRC}" \ + --exclude='./build' --exclude='./logs' --exclude='./cache' \ + --exclude='./container' --exclude='./.git' \ + --exclude='./Data/character' --exclude='./Data/creature' \ + --exclude='./Data/db' --exclude='./Data/environment' \ + --exclude='./Data/interface' --exclude='./Data/item' \ + --exclude='./Data/misc' --exclude='./Data/sound' \ + --exclude='./Data/spell' --exclude='./Data/terrain' \ + --exclude='./Data/world' \ + -cf - . | tar -C /wowee-build-src -xf - + +cd /wowee-build-src + +echo "==> [linux] Fetching external SDKs (if needed)..." +if [ ! -f extern/FidelityFX-FSR2/src/ffx-fsr2-api/ffx_fsr2.h ]; then + git clone --depth 1 \ + https://github.com/GPUOpen-Effects/FidelityFX-FSR2.git \ + extern/FidelityFX-FSR2 || echo "Warning: FSR2 clone failed — continuing without FSR2" +fi + +SDK_REPO="${WOWEE_FFX_SDK_REPO:-https://github.com/Kelsidavis/FidelityFX-SDK.git}" +SDK_REF="${WOWEE_FFX_SDK_REF:-main}" +if [ ! -f "extern/FidelityFX-SDK/sdk/include/FidelityFX/host/ffx_frameinterpolation.h" ]; then + git clone --depth 1 --branch "${SDK_REF}" "${SDK_REPO}" extern/FidelityFX-SDK \ + || echo "Warning: FidelityFX-SDK clone failed — continuing without FSR3" +fi + +echo "==> [linux] Configuring with CMake (Release, Ninja, amd64)..." +cmake -S . -B "${OUT}" \ + -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_C_COMPILER=gcc \ + -DCMAKE_CXX_COMPILER=g++ \ + -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON + +echo "==> [linux] Building with ${NPROC} cores..." +cmake --build "${OUT}" --parallel "${NPROC}" + +echo "==> [linux] Creating Data symlink..." +mkdir -p "${OUT}/bin" +if [ ! -e "${OUT}/bin/Data" ]; then + # Relative symlink so it resolves correctly on the host: + # build/linux/bin/Data -> ../../../Data (project root) + ln -s ../../../Data "${OUT}/bin/Data" +fi + +echo "" +echo "==> [linux] Build complete. Artifacts in: ./build/linux/" +echo " Binary: ./build/linux/bin/wowee" diff --git a/container/build-macos.sh b/container/build-macos.sh new file mode 100755 index 00000000..3ead48e1 --- /dev/null +++ b/container/build-macos.sh @@ -0,0 +1,83 @@ +#!/bin/bash +# macOS cross-compile entrypoint — runs INSIDE the macos container. +# Toolchain: osxcross + Apple Clang, target: arm64-apple-darwin (default) or +# x86_64-apple-darwin when MACOS_ARCH=x86_64. +# Bind-mounts: +# /src (ro) — project source +# /out (rw) — host ./build/macos + +set -euo pipefail + +SRC=/src +OUT=/out +NPROC=$(nproc) + +# Arch selection: arm64 (Apple Silicon) is the default primary target. +ARCH="${MACOS_ARCH:-arm64}" +case "${ARCH}" in + arm64) VCPKG_TRIPLET=arm64-osx-cross ;; + x86_64) VCPKG_TRIPLET=x64-osx-cross ;; + *) echo "ERROR: unsupported MACOS_ARCH '${ARCH}'. Use arm64 or x86_64." ; exit 1 ;; +esac + +# Auto-detect darwin target from osxcross binaries (e.g. arm64-apple-darwin24.5). +OSXCROSS_BIN=/opt/osxcross/target/bin +TARGET=$(basename "$(ls "${OSXCROSS_BIN}/${ARCH}-apple-darwin"*-clang 2>/dev/null | head -1)" | sed 's/-clang$//') +if [[ -z "${TARGET}" ]]; then + echo "ERROR: could not find osxcross ${ARCH} compiler in ${OSXCROSS_BIN}" >&2 + exit 1 +fi +echo "==> Detected osxcross target: ${TARGET}" + +echo "==> [macos/${ARCH}] Copying source tree..." +mkdir -p /wowee-build-src +tar -C "${SRC}" \ + --exclude='./build' --exclude='./logs' --exclude='./cache' \ + --exclude='./container' --exclude='./.git' \ + --exclude='./Data/character' --exclude='./Data/creature' \ + --exclude='./Data/db' --exclude='./Data/environment' \ + --exclude='./Data/interface' --exclude='./Data/item' \ + --exclude='./Data/misc' --exclude='./Data/sound' \ + --exclude='./Data/spell' --exclude='./Data/terrain' \ + --exclude='./Data/world' \ + -cf - . | tar -C /wowee-build-src -xf - + +cd /wowee-build-src + +echo "==> [macos/${ARCH}] Fetching external SDKs (if needed)..." +if [ ! -f extern/FidelityFX-FSR2/src/ffx-fsr2-api/ffx_fsr2.h ]; then + git clone --depth 1 \ + https://github.com/GPUOpen-Effects/FidelityFX-FSR2.git \ + extern/FidelityFX-FSR2 || echo "Warning: FSR2 clone failed" +fi + +SDK_REPO="${WOWEE_FFX_SDK_REPO:-https://github.com/Kelsidavis/FidelityFX-SDK.git}" +SDK_REF="${WOWEE_FFX_SDK_REF:-main}" +if [ ! -f "extern/FidelityFX-SDK/sdk/include/FidelityFX/host/ffx_frameinterpolation.h" ]; then + git clone --depth 1 --branch "${SDK_REF}" "${SDK_REPO}" extern/FidelityFX-SDK \ + || echo "Warning: FidelityFX-SDK clone failed" +fi + +echo "==> [macos/${ARCH}] Configuring with CMake (Release, Ninja, osxcross ${TARGET})..." +cmake -S . -B "${OUT}" \ + -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_SYSTEM_NAME=Darwin \ + -DCMAKE_OSX_ARCHITECTURES="${ARCH}" \ + -DCMAKE_OSX_DEPLOYMENT_TARGET="${MACOSX_DEPLOYMENT_TARGET:-13.0}" \ + -DCMAKE_C_COMPILER="${OSXCROSS_BIN}/${TARGET}-clang" \ + -DCMAKE_CXX_COMPILER="${OSXCROSS_BIN}/${TARGET}-clang++" \ + -DCMAKE_AR="${OSXCROSS_BIN}/${TARGET}-ar" \ + -DCMAKE_RANLIB="${OSXCROSS_BIN}/${TARGET}-ranlib" \ + -DCMAKE_TOOLCHAIN_FILE="${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" \ + -DVCPKG_TARGET_TRIPLET="${VCPKG_TRIPLET}" \ + -DVCPKG_OVERLAY_TRIPLETS=/opt/vcpkg-triplets \ + -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=OFF \ + -DWOWEE_ENABLE_ASAN=OFF + +echo "==> [macos/${ARCH}] Building with ${NPROC} cores..." +cmake --build "${OUT}" --parallel "${NPROC}" + +echo "" +echo "==> [macos/${ARCH}] Build complete. Artifacts in: ./build/macos/" +echo " Binary: ./build/macos/bin/wowee" diff --git a/container/build-windows.sh b/container/build-windows.sh new file mode 100755 index 00000000..7f0478ef --- /dev/null +++ b/container/build-windows.sh @@ -0,0 +1,110 @@ +#!/bin/bash +# Windows cross-compile entrypoint — runs INSIDE the windows container. +# Toolchain: LLVM-MinGW (Clang + LLD), target: x86_64-w64-mingw32-ucrt +# Bind-mounts: +# /src (ro) — project source +# /out (rw) — host ./build/windows + +set -euo pipefail + +SRC=/src +OUT=/out +NPROC=$(nproc) +TARGET=x86_64-w64-mingw32 + +# vcpkg's MinGW applocal hook always appends a powershell.exe post-build step to +# copy DLLs next to each binary, even when VCPKG_APPLOCAL_DEPS=OFF. For the +# x64-mingw-static triplet the bin/ dir is empty (no DLLs) so the script does +# nothing — but it still needs to exit 0. Provide a no-op stub if the real +# PowerShell isn't available. +if ! command -v powershell.exe &>/dev/null; then + printf '#!/bin/sh\nexit 0\n' > /usr/local/bin/powershell.exe + chmod +x /usr/local/bin/powershell.exe +fi + +echo "==> [windows] Copying source tree..." +mkdir -p /wowee-build-src +tar -C "${SRC}" \ + --exclude='./build' --exclude='./logs' --exclude='./cache' \ + --exclude='./container' --exclude='./.git' \ + --exclude='./Data/character' --exclude='./Data/creature' \ + --exclude='./Data/db' --exclude='./Data/environment' \ + --exclude='./Data/interface' --exclude='./Data/item' \ + --exclude='./Data/misc' --exclude='./Data/sound' \ + --exclude='./Data/spell' --exclude='./Data/terrain' \ + --exclude='./Data/world' \ + -cf - . | tar -C /wowee-build-src -xf - + + +cd /wowee-build-src + +echo "==> [windows] Fetching external SDKs (if needed)..." +if [ ! -f extern/FidelityFX-FSR2/src/ffx-fsr2-api/ffx_fsr2.h ]; then + git clone --depth 1 \ + https://github.com/GPUOpen-Effects/FidelityFX-FSR2.git \ + extern/FidelityFX-FSR2 || echo "Warning: FSR2 clone failed" +fi + +SDK_REPO="${WOWEE_FFX_SDK_REPO:-https://github.com/Kelsidavis/FidelityFX-SDK.git}" +SDK_REF="${WOWEE_FFX_SDK_REF:-main}" +if [ ! -f "extern/FidelityFX-SDK/sdk/include/FidelityFX/host/ffx_frameinterpolation.h" ]; then + git clone --depth 1 --branch "${SDK_REF}" "${SDK_REPO}" extern/FidelityFX-SDK \ + || echo "Warning: FidelityFX-SDK clone failed" +fi + +echo "==> [windows] Generating Vulkan import library for cross-compile..." +# Windows applications link against vulkan-1.dll (the Khronos Vulkan loader). +# The cross-compile toolchain only ships Vulkan *headers* (via vcpkg), not the +# import library. Generate a minimal libvulkan-1.a from the header prototypes +# so the linker can resolve vk* symbols → vulkan-1.dll at runtime. +# We use the host libvulkan-dev header for function name extraction — the Vulkan +# API prototypes are platform-independent. +VULKAN_IMP_DIR="${OUT}/vulkan-import" +if [ ! -f "${VULKAN_IMP_DIR}/libvulkan-1.a" ]; then + mkdir -p "${VULKAN_IMP_DIR}" + # Try vcpkg-installed header first (available on incremental builds), + # then fall back to the host libvulkan-dev header (always present in the image). + VK_HEADER="${OUT}/vcpkg_installed/x64-mingw-static/include/vulkan/vulkan_core.h" + if [ ! -f "${VK_HEADER}" ]; then + VK_HEADER="/usr/include/vulkan/vulkan_core.h" + fi + { + echo "LIBRARY vulkan-1.dll" + echo "EXPORTS" + grep -oP 'VKAPI_ATTR \S+ VKAPI_CALL \K(vk\w+)' "${VK_HEADER}" | sort -u | sed 's/^/ /' + } > "${VULKAN_IMP_DIR}/vulkan-1.def" + "${TARGET}-dlltool" -d "${VULKAN_IMP_DIR}/vulkan-1.def" \ + -l "${VULKAN_IMP_DIR}/libvulkan-1.a" -m i386:x86-64 + echo " Generated $(wc -l < "${VULKAN_IMP_DIR}/vulkan-1.def") export entries" +fi + +echo "==> [windows] Configuring with CMake (Release, Ninja, LLVM-MinGW cross)..." +# Lock pkg-config to the cross-compiled vcpkg packages only. +# Without this, CMake's Vulkan pkg-config fallback finds the *Linux* libvulkan-dev +# and injects /usr/include into every MinGW compile command, which then fails +# because the glibc-specific bits/libc-header-start.h is not in the MinGW sysroot. +export PKG_CONFIG_LIBDIR="${OUT}/vcpkg_installed/x64-mingw-static/lib/pkgconfig" +export PKG_CONFIG_PATH="" +cmake -S . -B "${OUT}" \ + -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_SYSTEM_NAME=Windows \ + -DCMAKE_C_COMPILER="${TARGET}-clang" \ + -DCMAKE_CXX_COMPILER="${TARGET}-clang++" \ + -DCMAKE_RC_COMPILER="${TARGET}-windres" \ + -DCMAKE_AR="/opt/llvm-mingw/bin/llvm-ar" \ + -DCMAKE_RANLIB="/opt/llvm-mingw/bin/llvm-ranlib" \ + -DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=lld" \ + -DCMAKE_SHARED_LINKER_FLAGS="-fuse-ld=lld" \ + -DCMAKE_TOOLCHAIN_FILE="${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" \ + -DVCPKG_TARGET_TRIPLET=x64-mingw-static \ + -DVCPKG_APPLOCAL_DEPS=OFF \ + -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=OFF \ + -DWOWEE_ENABLE_ASAN=OFF + +echo "==> [windows] Building with ${NPROC} cores..." +cmake --build "${OUT}" --parallel "${NPROC}" + +echo "" +echo "==> [windows] Build complete. Artifacts in: ./build/windows/" +echo " Binary: ./build/windows/bin/wowee.exe" diff --git a/container/build-wowee.sh b/container/build-wowee.sh deleted file mode 100755 index aabd8396..00000000 --- a/container/build-wowee.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -set -eu -set -o pipefail - -cp -r /WoWee-src /WoWee - -pushd /WoWee -./build.sh -popd - -pushd /WoWee/build -cmake --install . --prefix=/build -popd diff --git a/container/builder-linux.Dockerfile b/container/builder-linux.Dockerfile new file mode 100644 index 00000000..1f9e12dc --- /dev/null +++ b/container/builder-linux.Dockerfile @@ -0,0 +1,33 @@ +FROM ubuntu:24.04 + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + cmake \ + ninja-build \ + build-essential \ + pkg-config \ + git \ + python3 \ + glslang-tools \ + spirv-tools \ + libsdl2-dev \ + libglew-dev \ + libglm-dev \ + libssl-dev \ + zlib1g-dev \ + libavformat-dev \ + libavcodec-dev \ + libswscale-dev \ + libavutil-dev \ + libvulkan-dev \ + vulkan-tools \ + libstorm-dev \ + libunicorn-dev && \ + rm -rf /var/lib/apt/lists/* + +COPY build-linux.sh /build-platform.sh +RUN chmod +x /build-platform.sh + +ENTRYPOINT ["/build-platform.sh"] diff --git a/container/builder-macos.Dockerfile b/container/builder-macos.Dockerfile new file mode 100644 index 00000000..48b47751 --- /dev/null +++ b/container/builder-macos.Dockerfile @@ -0,0 +1,143 @@ +FROM ubuntu:24.04 AS sdk-fetcher + +# Stage 1: Fetch macOS SDK from Apple's public software update catalog. +# This avoids requiring the user to supply the SDK tarball manually. +# The SDK is downloaded, extracted, and packaged as a .tar.gz. + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + ca-certificates \ + python3 \ + cpio \ + tar \ + gzip \ + xz-utils && \ + rm -rf /var/lib/apt/lists/* + +COPY macos/sdk-fetcher.py /opt/sdk-fetcher.py +RUN python3 /opt/sdk-fetcher.py /opt/sdk + +# --------------------------------------------------------------------------- + +FROM ubuntu:24.04 AS builder + +# Stage 2: macOS cross-compile image using osxcross + Clang 18. +# +# Target triplets (auto-detected from osxcross): +# arm64-apple-darwinNN (Apple Silicon) +# x86_64-apple-darwinNN (Intel) +# Default: arm64. Override with MACOS_ARCH=x86_64 env var at run time. + +ENV DEBIAN_FRONTEND=noninteractive +ENV OSXCROSS_VERSION=1.5 + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + ca-certificates \ + cmake \ + ninja-build \ + git \ + python3 \ + curl \ + wget \ + xz-utils \ + zip \ + unzip \ + tar \ + make \ + patch \ + libssl-dev \ + zlib1g-dev \ + pkg-config \ + libbz2-dev \ + libxml2-dev \ + libz-dev \ + liblzma-dev \ + uuid-dev \ + python3-lxml \ + gnupg \ + software-properties-common && \ + wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - && \ + echo "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-18 main" > /etc/apt/sources.list.d/llvm-18.list && \ + apt-get update && \ + apt-get install -y --no-install-recommends \ + clang-18 \ + lld-18 \ + llvm-18 && \ + ln -sf /usr/bin/clang-18 /usr/bin/clang && \ + ln -sf /usr/bin/clang++-18 /usr/bin/clang++ && \ + ln -sf /usr/bin/lld-18 /usr/bin/lld && \ + ln -sf /usr/bin/ld.lld-18 /usr/bin/ld.lld && \ + ln -sf /usr/bin/llvm-ar-18 /usr/bin/llvm-ar && \ + rm -rf /var/lib/apt/lists/* + +# Build osxcross with SDK from stage 1 +RUN git clone --depth 1 https://github.com/tpoechtrager/osxcross.git /opt/osxcross + +COPY --from=sdk-fetcher /opt/sdk/ /opt/osxcross/tarballs/ + +ENV MACOSX_DEPLOYMENT_TARGET=13.0 +RUN cd /opt/osxcross && \ + unset OSXCROSS_VERSION && \ + UNATTENDED=1 ./build.sh && \ + rm -rf /opt/osxcross/build /opt/osxcross/tarballs + +ENV PATH="/opt/osxcross/target/bin:${PATH}" +ENV OSXCROSS_TARGET_DIR="/opt/osxcross/target" +ENV MACOSX_DEPLOYMENT_TARGET=13.0 + +# Create unprefixed symlinks for macOS tools that vcpkg/CMake expect +RUN cd /opt/osxcross/target/bin && \ + for tool in install_name_tool otool lipo codesign; do \ + src="$(ls *-apple-darwin*-"${tool}" 2>/dev/null | head -1)"; \ + if [ -n "$src" ]; then \ + ln -sf "$src" "$tool"; \ + fi; \ + done + +# Custom osxcross toolchain + vcpkg triplets +COPY macos/osxcross-toolchain.cmake /opt/osxcross-toolchain.cmake +COPY macos/triplets/ /opt/vcpkg-triplets/ + +# Extra tools needed by vcpkg's Mach-O rpath fixup and ffmpeg x86 asm +RUN apt-get update && \ + apt-get install -y --no-install-recommends file nasm && \ + rm -rf /var/lib/apt/lists/* + +# vcpkg — macOS cross triplets (arm64-osx-cross / x64-osx-cross) +ENV VCPKG_ROOT=/opt/vcpkg +RUN git clone --depth 1 https://github.com/microsoft/vcpkg.git "${VCPKG_ROOT}" && \ + "${VCPKG_ROOT}/bootstrap-vcpkg.sh" -disableMetrics + +# Pre-install deps for both arches; the launcher script picks the right one at run time. +RUN "${VCPKG_ROOT}/vcpkg" install \ + sdl2[vulkan] \ + openssl \ + glew \ + glm \ + zlib \ + ffmpeg \ + --triplet arm64-osx-cross \ + --overlay-triplets=/opt/vcpkg-triplets + +RUN "${VCPKG_ROOT}/vcpkg" install \ + sdl2[vulkan] \ + openssl \ + glew \ + glm \ + zlib \ + ffmpeg \ + --triplet x64-osx-cross \ + --overlay-triplets=/opt/vcpkg-triplets + +# Vulkan SDK headers (MoltenVK is the runtime — headers only needed to compile) +RUN apt-get update && \ + apt-get install -y --no-install-recommends libvulkan-dev glslang-tools && \ + rm -rf /var/lib/apt/lists/* + +COPY build-macos.sh /build-platform.sh +RUN chmod +x /build-platform.sh + +ENTRYPOINT ["/build-platform.sh"] diff --git a/container/builder-ubuntu.Dockerfile b/container/builder-ubuntu.Dockerfile deleted file mode 100644 index 26f32b50..00000000 --- a/container/builder-ubuntu.Dockerfile +++ /dev/null @@ -1,25 +0,0 @@ -FROM ubuntu:24.04 - -RUN apt-get update && \ - apt install -y \ - cmake \ - build-essential \ - pkg-config \ - git \ - libsdl2-dev \ - libglew-dev \ - libglm-dev \ - libssl-dev \ - zlib1g-dev \ - libavformat-dev \ - libavcodec-dev \ - libswscale-dev \ - libavutil-dev \ - libvulkan-dev \ - vulkan-tools \ - libstorm-dev && \ - rm -rf /var/lib/apt/lists/* - -COPY build-wowee.sh / - -ENTRYPOINT ./build-wowee.sh diff --git a/container/builder-windows.Dockerfile b/container/builder-windows.Dockerfile new file mode 100644 index 00000000..0c5abf83 --- /dev/null +++ b/container/builder-windows.Dockerfile @@ -0,0 +1,67 @@ +FROM ubuntu:24.04 + +# Windows cross-compile using LLVM-MinGW — best-in-class Clang/LLD toolchain +# targeting x86_64-w64-mingw32. Produces native .exe/.dll without MSVC or Wine. +# LLVM-MinGW ships: clang, clang++, lld, libc++ / libunwind headers, winpthreads. + +ENV DEBIAN_FRONTEND=noninteractive +ENV LLVM_MINGW_VERSION=20240619 +ENV LLVM_MINGW_URL=https://github.com/mstorsjo/llvm-mingw/releases/download/${LLVM_MINGW_VERSION}/llvm-mingw-${LLVM_MINGW_VERSION}-ucrt-ubuntu-20.04-x86_64.tar.xz + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + ca-certificates \ + build-essential \ + cmake \ + ninja-build \ + git \ + python3 \ + curl \ + zip \ + unzip \ + tar \ + xz-utils \ + pkg-config \ + nasm \ + libssl-dev \ + zlib1g-dev && \ + rm -rf /var/lib/apt/lists/* + +# Install LLVM-MinGW toolchain +RUN curl -fsSL "${LLVM_MINGW_URL}" -o /tmp/llvm-mingw.tar.xz && \ + tar -xf /tmp/llvm-mingw.tar.xz -C /opt && \ + mv /opt/llvm-mingw-${LLVM_MINGW_VERSION}-ucrt-ubuntu-20.04-x86_64 /opt/llvm-mingw && \ + rm /tmp/llvm-mingw.tar.xz + +ENV PATH="/opt/llvm-mingw/bin:${PATH}" + +# Windows dependencies via vcpkg (static, x64-mingw-static triplet) +ENV VCPKG_ROOT=/opt/vcpkg +RUN git clone --depth 1 https://github.com/microsoft/vcpkg.git "${VCPKG_ROOT}" && \ + "${VCPKG_ROOT}/bootstrap-vcpkg.sh" -disableMetrics + +ENV VCPKG_DEFAULT_TRIPLET=x64-mingw-static +RUN "${VCPKG_ROOT}/vcpkg" install \ + sdl2[vulkan] \ + openssl \ + glew \ + glm \ + zlib \ + ffmpeg \ + --triplet x64-mingw-static + +# Vulkan SDK headers (loader is linked statically via SDL2's vulkan surface) +RUN apt-get update && \ + apt-get install -y --no-install-recommends libvulkan-dev glslang-tools && \ + rm -rf /var/lib/apt/lists/* + +# Provide a no-op powershell.exe so vcpkg's MinGW applocal post-build hook +# exits cleanly. The x64-mingw-static triplet is fully static (no DLLs to +# copy), so the script has nothing to do — it just needs to not fail. +RUN printf '#!/bin/sh\nexit 0\n' > /usr/local/bin/powershell.exe && \ + chmod +x /usr/local/bin/powershell.exe + +COPY build-windows.sh /build-platform.sh +RUN chmod +x /build-platform.sh + +ENTRYPOINT ["/build-platform.sh"] diff --git a/container/macos/osxcross-toolchain.cmake b/container/macos/osxcross-toolchain.cmake new file mode 100644 index 00000000..c432830d --- /dev/null +++ b/container/macos/osxcross-toolchain.cmake @@ -0,0 +1,62 @@ +# osxcross CMake toolchain file for cross-compiling to macOS from Linux. +# Used by vcpkg triplets and the WoWee build. +# Auto-detects SDK, darwin version, and arch from the osxcross installation +# and the VCPKG_OSX_ARCHITECTURES / CMAKE_OSX_ARCHITECTURES setting. + +set(CMAKE_SYSTEM_NAME Darwin) + +# ── osxcross paths ────────────────────────────────────────────────── +set(_target_dir "/opt/osxcross/target") +if(DEFINED ENV{OSXCROSS_TARGET_DIR}) + set(_target_dir "$ENV{OSXCROSS_TARGET_DIR}") +endif() + +# Auto-detect SDK (pick the newest if several are present) +file(GLOB _sdk_dirs "${_target_dir}/SDK/MacOSX*.sdk") +list(SORT _sdk_dirs) +list(GET _sdk_dirs -1 _sdk_dir) +set(CMAKE_OSX_SYSROOT "${_sdk_dir}" CACHE PATH "" FORCE) + +# Deployment target +set(CMAKE_OSX_DEPLOYMENT_TARGET "13.0" CACHE STRING "" FORCE) +if(DEFINED ENV{MACOSX_DEPLOYMENT_TARGET}) + set(CMAKE_OSX_DEPLOYMENT_TARGET "$ENV{MACOSX_DEPLOYMENT_TARGET}" CACHE STRING "" FORCE) +endif() + +# ── auto-detect darwin version from compiler names ────────────────── +file(GLOB _darwin_compilers "${_target_dir}/bin/*-apple-darwin*-clang") +list(GET _darwin_compilers 0 _first_compiler) +get_filename_component(_compiler_name "${_first_compiler}" NAME) +string(REGEX MATCH "apple-darwin[0-9.]+" _darwin_part "${_compiler_name}") + +# ── pick architecture ─────────────────────────────────────────────── +# CMAKE_OSX_ARCHITECTURES is set by vcpkg from VCPKG_OSX_ARCHITECTURES +if(CMAKE_OSX_ARCHITECTURES STREQUAL "arm64") + set(_arch "arm64") +elseif(CMAKE_OSX_ARCHITECTURES STREQUAL "x86_64") + set(_arch "x86_64") +elseif(DEFINED ENV{OSXCROSS_ARCH}) + set(_arch "$ENV{OSXCROSS_ARCH}") +else() + set(_arch "arm64") +endif() + +set(_host "${_arch}-${_darwin_part}") +set(CMAKE_SYSTEM_PROCESSOR "${_arch}" CACHE STRING "" FORCE) + +# ── compilers ─────────────────────────────────────────────────────── +set(CMAKE_C_COMPILER "${_target_dir}/bin/${_host}-clang" CACHE FILEPATH "" FORCE) +set(CMAKE_CXX_COMPILER "${_target_dir}/bin/${_host}-clang++" CACHE FILEPATH "" FORCE) + +# ── tools ─────────────────────────────────────────────────────────── +set(CMAKE_AR "${_target_dir}/bin/${_host}-ar" CACHE FILEPATH "" FORCE) +set(CMAKE_RANLIB "${_target_dir}/bin/${_host}-ranlib" CACHE FILEPATH "" FORCE) +set(CMAKE_STRIP "${_target_dir}/bin/${_host}-strip" CACHE FILEPATH "" FORCE) +set(CMAKE_INSTALL_NAME_TOOL "${_target_dir}/bin/${_host}-install_name_tool" CACHE FILEPATH "" FORCE) + +# ── search paths ──────────────────────────────────────────────────── +set(CMAKE_FIND_ROOT_PATH "${_sdk_dir}" "${_target_dir}") +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) diff --git a/container/macos/sdk-fetcher.py b/container/macos/sdk-fetcher.py new file mode 100644 index 00000000..cccda8ab --- /dev/null +++ b/container/macos/sdk-fetcher.py @@ -0,0 +1,366 @@ +#!/usr/bin/env python3 +"""Download and extract macOS SDK from Apple's Command Line Tools package. + +Apple publishes Command Line Tools (CLT) packages via their publicly +accessible software update catalog. This script downloads the latest CLT, +extracts just the macOS SDK, and packages it as a .tar.gz tarball suitable +for osxcross. + +No Apple ID or paid developer account required. + +Usage: + python3 sdk-fetcher.py [output_dir] + +The script prints the absolute path of the resulting tarball to stdout. +All progress / status messages go to stderr. +If a cached SDK tarball already exists in output_dir, it is reused. + +Dependencies: python3 (>= 3.6), cpio, tar, gzip +Optional: bsdtar (libarchive-tools) or xar -- faster XAR extraction. + Falls back to a pure-Python XAR parser when neither is available. +""" + +import glob +import gzip +import lzma +import os +import plistlib +import re +import shutil +import struct +import subprocess +import sys +import tempfile +import urllib.request +import xml.etree.ElementTree as ET +import zlib + +# -- Configuration ----------------------------------------------------------- + +CATALOG_URLS = [ + # Try newest catalog first; first successful fetch wins. + "https://swscan.apple.com/content/catalogs/others/" + "index-16-15-14-13-12-10.16-10.15-10.14-10.13-10.12-10.11-10.10-10.9-" + "mountainlion-lion-snowleopard-leopard.merged-1.sucatalog.gz", + + "https://swscan.apple.com/content/catalogs/others/" + "index-15-14-13-12-10.16-10.15-10.14-10.13-10.12-10.11-10.10-10.9-" + "mountainlion-lion-snowleopard-leopard.merged-1.sucatalog.gz", + + "https://swscan.apple.com/content/catalogs/others/" + "index-14-13-12-10.16-10.15-10.14-10.13-10.12-10.11-10.10-10.9-" + "mountainlion-lion-snowleopard-leopard.merged-1.sucatalog.gz", +] + +USER_AGENT = "Software%20Update" + + +# -- Helpers ----------------------------------------------------------------- + +def log(msg): + print(msg, file=sys.stderr, flush=True) + + +# -- 1) Catalog & URL discovery ---------------------------------------------- + +def find_sdk_pkg_url(): + """Search Apple catalogs for the latest CLTools_macOSNMOS_SDK.pkg URL.""" + for cat_url in CATALOG_URLS: + short = cat_url.split("/index-")[1][:25] + "..." + log(f" Trying catalog: {short}") + try: + req = urllib.request.Request(cat_url, headers={"User-Agent": USER_AGENT}) + with urllib.request.urlopen(req, timeout=60) as resp: + raw = gzip.decompress(resp.read()) + catalog = plistlib.loads(raw) + except Exception as exc: + log(f" -> fetch failed: {exc}") + continue + + products = catalog.get("Products", {}) + candidates = [] + for pid, product in products.items(): + post_date = str(product.get("PostDate", "")) + for pkg in product.get("Packages", []): + url = pkg.get("URL", "") + size = pkg.get("Size", 0) + if "CLTools_macOSNMOS_SDK" in url and url.endswith(".pkg"): + candidates.append((post_date, url, size, pid)) + + if not candidates: + log(f" -> no CLTools SDK packages in this catalog, trying next...") + continue + + candidates.sort(reverse=True) + _date, url, size, pid = candidates[0] + log(f"==> Found: CLTools_macOSNMOS_SDK (product {pid}, {size // 1048576} MB)") + return url + + log("ERROR: No CLTools SDK packages found in any Apple catalog.") + sys.exit(1) + + +# -- 2) Download ------------------------------------------------------------- + +def download(url, dest): + """Download *url* to *dest* with a basic progress indicator.""" + req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT}) + with urllib.request.urlopen(req, timeout=600) as resp: + total = int(resp.headers.get("Content-Length", 0)) + done = 0 + with open(dest, "wb") as f: + while True: + chunk = resp.read(1 << 20) + if not chunk: + break + f.write(chunk) + done += len(chunk) + if total: + pct = done * 100 // total + log(f"\r {done // 1048576} / {total // 1048576} MB ({pct}%)") + log("") + + +# -- 3) XAR extraction ------------------------------------------------------- + +def extract_xar(pkg_path, dest_dir): + """Extract a XAR (.pkg) archive -- external tool or pure-Python fallback.""" + for tool in ("bsdtar", "xar"): + if shutil.which(tool): + log(f"==> Extracting .pkg with {tool}...") + r = subprocess.run([tool, "-xf", pkg_path, "-C", dest_dir], + capture_output=True) + if r.returncode == 0: + return + log(f" {tool} exited {r.returncode}, trying next method...") + + log("==> Extracting .pkg with built-in Python XAR parser...") + _extract_xar_python(pkg_path, dest_dir) + + +def _extract_xar_python(pkg_path, dest_dir): + """Pure-Python XAR extractor (no external dependencies).""" + with open(pkg_path, "rb") as f: + raw = f.read(28) + if len(raw) < 28: + raise ValueError("File too small to be a valid XAR archive") + magic, hdr_size, _ver, toc_clen, _toc_ulen, _ck = struct.unpack( + ">4sHHQQI", raw, + ) + if magic != b"xar!": + raise ValueError(f"Not a XAR file (magic: {magic!r})") + + f.seek(hdr_size) + toc_xml = zlib.decompress(f.read(toc_clen)) + heap_off = hdr_size + toc_clen + + root = ET.fromstring(toc_xml) + toc = root.find("toc") + if toc is None: + raise ValueError("Malformed XAR: no element") + + def _walk(elem, base): + for fe in elem.findall("file"): + name = fe.findtext("name", "") + ftype = fe.findtext("type", "file") + path = os.path.join(base, name) + + if ftype == "directory": + os.makedirs(path, exist_ok=True) + _walk(fe, path) + continue + + de = fe.find("data") + if de is None: + continue + offset = int(de.findtext("offset", "0")) + size = int(de.findtext("size", "0")) + enc_el = de.find("encoding") + enc = enc_el.get("style", "") if enc_el is not None else "" + + os.makedirs(os.path.dirname(path), exist_ok=True) + f.seek(heap_off + offset) + + if "gzip" in enc: + with open(path, "wb") as out: + out.write(zlib.decompress(f.read(size), 15 + 32)) + elif "bzip2" in enc: + import bz2 + with open(path, "wb") as out: + out.write(bz2.decompress(f.read(size))) + else: + with open(path, "wb") as out: + rem = size + while rem > 0: + blk = f.read(min(rem, 1 << 20)) + if not blk: + break + out.write(blk) + rem -= len(blk) + + _walk(toc, dest_dir) + + +# -- 4) Payload extraction (pbzx / gzip cpio) -------------------------------- + +def _pbzx_stream(path): + """Yield decompressed chunks from a pbzx-compressed file.""" + with open(path, "rb") as f: + if f.read(4) != b"pbzx": + raise ValueError("Not a pbzx file") + f.read(8) + while True: + hdr = f.read(16) + if len(hdr) < 16: + break + _usize, csize = struct.unpack(">QQ", hdr) + data = f.read(csize) + if len(data) < csize: + break + if csize == _usize: + yield data + else: + yield lzma.decompress(data) + + +def _gzip_stream(path): + """Yield decompressed chunks from a gzip file.""" + with gzip.open(path, "rb") as f: + while True: + chunk = f.read(1 << 20) + if not chunk: + break + yield chunk + + +def _raw_stream(path): + """Yield raw 1 MiB chunks (last resort).""" + with open(path, "rb") as f: + while True: + chunk = f.read(1 << 20) + if not chunk: + break + yield chunk + + +def extract_payload(payload_path, out_dir): + """Decompress a CLT Payload (pbzx or gzip cpio) into *out_dir*.""" + with open(payload_path, "rb") as pf: + magic = pf.read(4) + + if magic == b"pbzx": + log(" Payload format: pbzx (LZMA chunks)") + stream = _pbzx_stream(payload_path) + elif magic[:2] == b"\x1f\x8b": + log(" Payload format: gzip") + stream = _gzip_stream(payload_path) + else: + log(f" Payload format: unknown (magic: {magic.hex()}), trying raw cpio...") + stream = _raw_stream(payload_path) + + proc = subprocess.Popen( + ["cpio", "-id", "--quiet"], + stdin=subprocess.PIPE, + cwd=out_dir, + stderr=subprocess.PIPE, + ) + for chunk in stream: + try: + proc.stdin.write(chunk) + except BrokenPipeError: + break + proc.stdin.close() + proc.wait() + + +# -- Main -------------------------------------------------------------------- + +def main(): + output_dir = os.path.abspath(sys.argv[1]) if len(sys.argv) > 1 else os.getcwd() + os.makedirs(output_dir, exist_ok=True) + + # Re-use a previously fetched SDK if present. + cached = glob.glob(os.path.join(output_dir, "MacOSX*.sdk.tar.*")) + if cached: + cached.sort() + result = os.path.realpath(cached[-1]) + log(f"==> Using cached SDK: {os.path.basename(result)}") + print(result) + return + + work = tempfile.mkdtemp(prefix="fetch-macos-sdk-") + + try: + # 1 -- Locate SDK package URL from Apple's catalog + log("==> Searching Apple software-update catalogs...") + sdk_url = find_sdk_pkg_url() + + # 2 -- Download (just the SDK component, ~55 MB) + pkg = os.path.join(work, "sdk.pkg") + log("==> Downloading CLTools SDK package...") + download(sdk_url, pkg) + + # 3 -- Extract the flat .pkg (XAR format) to get the Payload + pkg_dir = os.path.join(work, "pkg") + os.makedirs(pkg_dir) + extract_xar(pkg, pkg_dir) + os.unlink(pkg) + + # 4 -- Locate the Payload file + log("==> Locating SDK payload...") + sdk_payload = None + for dirpath, _dirs, files in os.walk(pkg_dir): + if "Payload" in files: + sdk_payload = os.path.join(dirpath, "Payload") + log(f" Found: {os.path.relpath(sdk_payload, pkg_dir)}") + break + + if sdk_payload is None: + log("ERROR: No Payload found in extracted package") + sys.exit(1) + + # 5 -- Decompress Payload -> cpio -> filesystem + sdk_root = os.path.join(work, "sdk") + os.makedirs(sdk_root) + log("==> Extracting SDK from payload (this may take a minute)...") + extract_payload(sdk_payload, sdk_root) + shutil.rmtree(pkg_dir) + + # 6 -- Find MacOSX*.sdk directory + sdk_found = None + for dirpath, dirs, _files in os.walk(sdk_root): + for d in dirs: + if re.match(r"MacOSX\d+(\.\d+)?\.sdk$", d): + sdk_found = os.path.join(dirpath, d) + break + if sdk_found: + break + + if not sdk_found: + log("ERROR: MacOSX*.sdk directory not found. Extracted contents:") + for dp, ds, fs in os.walk(sdk_root): + depth = dp.replace(sdk_root, "").count(os.sep) + if depth < 4: + log(f" {' ' * depth}{os.path.basename(dp)}/") + sys.exit(1) + + sdk_name = os.path.basename(sdk_found) + log(f"==> Found: {sdk_name}") + + # 7 -- Package as .tar.gz + tarball = os.path.join(output_dir, f"{sdk_name}.tar.gz") + log(f"==> Packaging: {sdk_name}.tar.gz ...") + subprocess.run( + ["tar", "-czf", tarball, "-C", os.path.dirname(sdk_found), sdk_name], + check=True, + ) + + log(f"==> macOS SDK ready: {tarball}") + print(tarball) + + finally: + shutil.rmtree(work, ignore_errors=True) + + +if __name__ == "__main__": + main() diff --git a/container/macos/triplets/arm64-osx-cross.cmake b/container/macos/triplets/arm64-osx-cross.cmake new file mode 100644 index 00000000..5f38d553 --- /dev/null +++ b/container/macos/triplets/arm64-osx-cross.cmake @@ -0,0 +1,10 @@ +set(VCPKG_TARGET_ARCHITECTURE arm64) +set(VCPKG_CRT_LINKAGE dynamic) +set(VCPKG_LIBRARY_LINKAGE static) +set(VCPKG_CMAKE_SYSTEM_NAME Darwin) + +set(VCPKG_OSX_ARCHITECTURES arm64) +set(VCPKG_OSX_DEPLOYMENT_TARGET 13.0) + +# osxcross toolchain +set(VCPKG_CHAINLOAD_TOOLCHAIN_FILE /opt/osxcross-toolchain.cmake) diff --git a/container/macos/triplets/x64-osx-cross.cmake b/container/macos/triplets/x64-osx-cross.cmake new file mode 100644 index 00000000..84161bba --- /dev/null +++ b/container/macos/triplets/x64-osx-cross.cmake @@ -0,0 +1,10 @@ +set(VCPKG_TARGET_ARCHITECTURE x64) +set(VCPKG_CRT_LINKAGE dynamic) +set(VCPKG_LIBRARY_LINKAGE static) +set(VCPKG_CMAKE_SYSTEM_NAME Darwin) + +set(VCPKG_OSX_ARCHITECTURES x86_64) +set(VCPKG_OSX_DEPLOYMENT_TARGET 13.0) + +# osxcross toolchain +set(VCPKG_CHAINLOAD_TOOLCHAIN_FILE /opt/osxcross-toolchain.cmake) diff --git a/container/run-linux.ps1 b/container/run-linux.ps1 new file mode 100644 index 00000000..67ce067f --- /dev/null +++ b/container/run-linux.ps1 @@ -0,0 +1,64 @@ +# run-linux.ps1 — Build WoWee for Linux (amd64) inside a Docker container. +# +# Usage (run from project root): +# .\container\run-linux.ps1 [-RebuildImage] +# +# Environment variables: +# WOWEE_FFX_SDK_REPO — FidelityFX SDK git repo URL (passed through to container) +# WOWEE_FFX_SDK_REF — FidelityFX SDK git ref / tag (passed through to container) + +param( + [switch]$RebuildImage +) + +$ErrorActionPreference = "Stop" + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition +$ProjectRoot = (Resolve-Path "$ScriptDir\..").Path + +$ImageName = "wowee-builder-linux" +$BuildOutput = "$ProjectRoot\build\linux" + +# Verify Docker is available +if (-not (Get-Command docker -ErrorAction SilentlyContinue)) { + Write-Error "docker is not installed or not in PATH." + exit 1 +} + +# Build the image (skip if already present and -RebuildImage not given) +$imageExists = docker image inspect $ImageName 2>$null +if ($RebuildImage -or -not $imageExists) { + Write-Host "==> Building Docker image: $ImageName" + docker build ` + -f "$ScriptDir\builder-linux.Dockerfile" ` + -t $ImageName ` + "$ScriptDir" + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } +} else { + Write-Host "==> Using existing Docker image: $ImageName" +} + +# Create output directory on the host +New-Item -ItemType Directory -Force -Path $BuildOutput | Out-Null + +Write-Host "==> Starting Linux build (output: $BuildOutput)" + +$dockerArgs = @( + "run", "--rm", + "--mount", "type=bind,src=$ProjectRoot,dst=/src,readonly", + "--mount", "type=bind,src=$BuildOutput,dst=/out" +) + +if ($env:WOWEE_FFX_SDK_REPO) { + $dockerArgs += @("--env", "WOWEE_FFX_SDK_REPO=$env:WOWEE_FFX_SDK_REPO") +} +if ($env:WOWEE_FFX_SDK_REF) { + $dockerArgs += @("--env", "WOWEE_FFX_SDK_REF=$env:WOWEE_FFX_SDK_REF") +} + +$dockerArgs += $ImageName + +& docker @dockerArgs +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + +Write-Host "==> Linux build complete. Artifacts in: $BuildOutput" diff --git a/container/run-linux.sh b/container/run-linux.sh new file mode 100755 index 00000000..db45bea7 --- /dev/null +++ b/container/run-linux.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +# run-linux.sh — Build WoWee for Linux (amd64) inside a Docker container. +# +# Usage (run from project root): +# ./container/run-linux.sh [--rebuild-image] +# +# Environment variables: +# WOWEE_FFX_SDK_REPO — FidelityFX SDK git repo URL (passed through to container) +# WOWEE_FFX_SDK_REF — FidelityFX SDK git ref / tag (passed through to container) +# REBUILD_IMAGE — Set to 1 to force a fresh docker build (same as --rebuild-image) + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" + +IMAGE_NAME="wowee-builder-linux" +BUILD_OUTPUT="${PROJECT_ROOT}/build/linux" + +# Parse arguments +REBUILD_IMAGE="${REBUILD_IMAGE:-0}" +for arg in "$@"; do + case "$arg" in + --rebuild-image) REBUILD_IMAGE=1 ;; + *) echo "Unknown argument: $arg" >&2; exit 1 ;; + esac +done + +# Verify Docker is available +if ! command -v docker &>/dev/null; then + echo "Error: docker is not installed or not in PATH." >&2 + exit 1 +fi + +# Build the image (skip if already present and --rebuild-image not given) +if [[ "$REBUILD_IMAGE" == "1" ]] || ! docker image inspect "$IMAGE_NAME" &>/dev/null; then + echo "==> Building Docker image: ${IMAGE_NAME}" + docker build \ + -f "${SCRIPT_DIR}/builder-linux.Dockerfile" \ + -t "$IMAGE_NAME" \ + "${SCRIPT_DIR}" +else + echo "==> Using existing Docker image: ${IMAGE_NAME}" +fi + +# Create output directory on the host +mkdir -p "$BUILD_OUTPUT" + +echo "==> Starting Linux build (output: ${BUILD_OUTPUT})" + +docker run --rm \ + --mount "type=bind,src=${PROJECT_ROOT},dst=/src,readonly" \ + --mount "type=bind,src=${BUILD_OUTPUT},dst=/out" \ + ${WOWEE_FFX_SDK_REPO:+--env "WOWEE_FFX_SDK_REPO=${WOWEE_FFX_SDK_REPO}"} \ + ${WOWEE_FFX_SDK_REF:+--env "WOWEE_FFX_SDK_REF=${WOWEE_FFX_SDK_REF}"} \ + "$IMAGE_NAME" + +echo "==> Linux build complete. Artifacts in: ${BUILD_OUTPUT}" diff --git a/container/run-macos.ps1 b/container/run-macos.ps1 new file mode 100644 index 00000000..b690edfb --- /dev/null +++ b/container/run-macos.ps1 @@ -0,0 +1,71 @@ +# run-macos.ps1 — Cross-compile WoWee for macOS (arm64 or x86_64) inside a Docker container. +# +# Usage (run from project root): +# .\container\run-macos.ps1 [-RebuildImage] [-Arch arm64|x86_64] +# +# The macOS SDK is fetched automatically inside the Docker build from Apple's +# public software update catalog. No manual SDK download required. +# +# Environment variables: +# WOWEE_FFX_SDK_REPO — FidelityFX SDK git repo URL (passed through to container) +# WOWEE_FFX_SDK_REF — FidelityFX SDK git ref / tag (passed through to container) + +param( + [switch]$RebuildImage, + [ValidateSet("arm64", "x86_64")] + [string]$Arch = "arm64" +) + +$ErrorActionPreference = "Stop" + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition +$ProjectRoot = (Resolve-Path "$ScriptDir\..").Path + +$ImageName = "wowee-builder-macos" +$BuildOutput = "$ProjectRoot\build\macos" + +# Verify Docker is available +if (-not (Get-Command docker -ErrorAction SilentlyContinue)) { + Write-Error "docker is not installed or not in PATH." + exit 1 +} + +# Build the image (skip if already present and -RebuildImage not given) +$imageExists = docker image inspect $ImageName 2>$null +if ($RebuildImage -or -not $imageExists) { + Write-Host "==> Building Docker image: $ImageName" + Write-Host " (SDK will be fetched automatically from Apple's catalog)" + docker build ` + -f "$ScriptDir\builder-macos.Dockerfile" ` + -t $ImageName ` + "$ScriptDir" + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } +} else { + Write-Host "==> Using existing Docker image: $ImageName" +} + +# Create output directory on the host +New-Item -ItemType Directory -Force -Path $BuildOutput | Out-Null + +Write-Host "==> Starting macOS cross-compile build (arch=$Arch, output: $BuildOutput)" + +$dockerArgs = @( + "run", "--rm", + "--mount", "type=bind,src=$ProjectRoot,dst=/src,readonly", + "--mount", "type=bind,src=$BuildOutput,dst=/out", + "--env", "MACOS_ARCH=$Arch" +) + +if ($env:WOWEE_FFX_SDK_REPO) { + $dockerArgs += @("--env", "WOWEE_FFX_SDK_REPO=$env:WOWEE_FFX_SDK_REPO") +} +if ($env:WOWEE_FFX_SDK_REF) { + $dockerArgs += @("--env", "WOWEE_FFX_SDK_REF=$env:WOWEE_FFX_SDK_REF") +} + +$dockerArgs += $ImageName + +& docker @dockerArgs +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + +Write-Host "==> macOS cross-compile build complete. Artifacts in: $BuildOutput" diff --git a/container/run-macos.sh b/container/run-macos.sh new file mode 100755 index 00000000..63e1b971 --- /dev/null +++ b/container/run-macos.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# run-macos.sh — Cross-compile WoWee for macOS (arm64 or x86_64) inside a Docker container. +# +# Usage (run from project root): +# ./container/run-macos.sh [--rebuild-image] +# +# The macOS SDK is fetched automatically inside the Docker build from Apple's +# public software update catalog. No manual SDK download required. +# +# Environment variables: +# MACOS_ARCH — Target arch: arm64 (default) or x86_64 +# WOWEE_FFX_SDK_REPO — FidelityFX SDK git repo URL (passed through to container) +# WOWEE_FFX_SDK_REF — FidelityFX SDK git ref / tag (passed through to container) +# REBUILD_IMAGE — Set to 1 to force a fresh docker build (same as --rebuild-image) +# +# Toolchain: osxcross (Clang + Apple ld) +# vcpkg triplets: arm64-osx-cross (arm64) / x64-osx-cross (x86_64) + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" + +IMAGE_NAME="wowee-builder-macos" +MACOS_ARCH="${MACOS_ARCH:-arm64}" +BUILD_OUTPUT="${PROJECT_ROOT}/build/macos" + +# Parse arguments +REBUILD_IMAGE="${REBUILD_IMAGE:-0}" +for arg in "$@"; do + case "$arg" in + --rebuild-image) REBUILD_IMAGE=1 ;; + *) echo "Unknown argument: $arg" >&2; exit 1 ;; + esac +done + +# Validate arch +if [[ "$MACOS_ARCH" != "arm64" && "$MACOS_ARCH" != "x86_64" ]]; then + echo "Error: MACOS_ARCH must be 'arm64' or 'x86_64' (got: ${MACOS_ARCH})" >&2 + exit 1 +fi + +# Verify Docker is available +if ! command -v docker &>/dev/null; then + echo "Error: docker is not installed or not in PATH." >&2 + exit 1 +fi + +# Build the image (skip if already present and --rebuild-image not given) +if [[ "$REBUILD_IMAGE" == "1" ]] || ! docker image inspect "$IMAGE_NAME" &>/dev/null; then + echo "==> Building Docker image: ${IMAGE_NAME}" + echo " (SDK will be fetched automatically from Apple's catalog)" + docker build \ + -f "${SCRIPT_DIR}/builder-macos.Dockerfile" \ + -t "$IMAGE_NAME" \ + "${SCRIPT_DIR}" +else + echo "==> Using existing Docker image: ${IMAGE_NAME}" +fi + +# Create output directory on the host +mkdir -p "$BUILD_OUTPUT" + +echo "==> Starting macOS cross-compile build (arch=${MACOS_ARCH}, output: ${BUILD_OUTPUT})" + +docker run --rm \ + --mount "type=bind,src=${PROJECT_ROOT},dst=/src,readonly" \ + --mount "type=bind,src=${BUILD_OUTPUT},dst=/out" \ + --env "MACOS_ARCH=${MACOS_ARCH}" \ + ${WOWEE_FFX_SDK_REPO:+--env "WOWEE_FFX_SDK_REPO=${WOWEE_FFX_SDK_REPO}"} \ + ${WOWEE_FFX_SDK_REF:+--env "WOWEE_FFX_SDK_REF=${WOWEE_FFX_SDK_REF}"} \ + "$IMAGE_NAME" + +echo "==> macOS cross-compile build complete. Artifacts in: ${BUILD_OUTPUT}" diff --git a/container/run-windows.ps1 b/container/run-windows.ps1 new file mode 100644 index 00000000..1ce6c724 --- /dev/null +++ b/container/run-windows.ps1 @@ -0,0 +1,64 @@ +# run-windows.ps1 — Cross-compile WoWee for Windows (x86_64) inside a Docker container. +# +# Usage (run from project root): +# .\container\run-windows.ps1 [-RebuildImage] +# +# Environment variables: +# WOWEE_FFX_SDK_REPO — FidelityFX SDK git repo URL (passed through to container) +# WOWEE_FFX_SDK_REF — FidelityFX SDK git ref / tag (passed through to container) + +param( + [switch]$RebuildImage +) + +$ErrorActionPreference = "Stop" + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition +$ProjectRoot = (Resolve-Path "$ScriptDir\..").Path + +$ImageName = "wowee-builder-windows" +$BuildOutput = "$ProjectRoot\build\windows" + +# Verify Docker is available +if (-not (Get-Command docker -ErrorAction SilentlyContinue)) { + Write-Error "docker is not installed or not in PATH." + exit 1 +} + +# Build the image (skip if already present and -RebuildImage not given) +$imageExists = docker image inspect $ImageName 2>$null +if ($RebuildImage -or -not $imageExists) { + Write-Host "==> Building Docker image: $ImageName" + docker build ` + -f "$ScriptDir\builder-windows.Dockerfile" ` + -t $ImageName ` + "$ScriptDir" + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } +} else { + Write-Host "==> Using existing Docker image: $ImageName" +} + +# Create output directory on the host +New-Item -ItemType Directory -Force -Path $BuildOutput | Out-Null + +Write-Host "==> Starting Windows cross-compile build (output: $BuildOutput)" + +$dockerArgs = @( + "run", "--rm", + "--mount", "type=bind,src=$ProjectRoot,dst=/src,readonly", + "--mount", "type=bind,src=$BuildOutput,dst=/out" +) + +if ($env:WOWEE_FFX_SDK_REPO) { + $dockerArgs += @("--env", "WOWEE_FFX_SDK_REPO=$env:WOWEE_FFX_SDK_REPO") +} +if ($env:WOWEE_FFX_SDK_REF) { + $dockerArgs += @("--env", "WOWEE_FFX_SDK_REF=$env:WOWEE_FFX_SDK_REF") +} + +$dockerArgs += $ImageName + +& docker @dockerArgs +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + +Write-Host "==> Windows cross-compile build complete. Artifacts in: $BuildOutput" diff --git a/container/run-windows.sh b/container/run-windows.sh new file mode 100755 index 00000000..b7a89ff7 --- /dev/null +++ b/container/run-windows.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# run-windows.sh — Cross-compile WoWee for Windows (x86_64) inside a Docker container. +# +# Usage (run from project root): +# ./container/run-windows.sh [--rebuild-image] +# +# Environment variables: +# WOWEE_FFX_SDK_REPO — FidelityFX SDK git repo URL (passed through to container) +# WOWEE_FFX_SDK_REF — FidelityFX SDK git ref / tag (passed through to container) +# REBUILD_IMAGE — Set to 1 to force a fresh docker build (same as --rebuild-image) +# +# Toolchain: LLVM-MinGW (Clang + LLD) targeting x86_64-w64-mingw32-ucrt +# vcpkg triplet: x64-mingw-static + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" + +IMAGE_NAME="wowee-builder-windows" +BUILD_OUTPUT="${PROJECT_ROOT}/build/windows" + +# Parse arguments +REBUILD_IMAGE="${REBUILD_IMAGE:-0}" +for arg in "$@"; do + case "$arg" in + --rebuild-image) REBUILD_IMAGE=1 ;; + *) echo "Unknown argument: $arg" >&2; exit 1 ;; + esac +done + +# Verify Docker is available +if ! command -v docker &>/dev/null; then + echo "Error: docker is not installed or not in PATH." >&2 + exit 1 +fi + +# Build the image (skip if already present and --rebuild-image not given) +if [[ "$REBUILD_IMAGE" == "1" ]] || ! docker image inspect "$IMAGE_NAME" &>/dev/null; then + echo "==> Building Docker image: ${IMAGE_NAME}" + docker build \ + -f "${SCRIPT_DIR}/builder-windows.Dockerfile" \ + -t "$IMAGE_NAME" \ + "${SCRIPT_DIR}" +else + echo "==> Using existing Docker image: ${IMAGE_NAME}" +fi + +# Create output directory on the host +mkdir -p "$BUILD_OUTPUT" + +echo "==> Starting Windows cross-compile build (output: ${BUILD_OUTPUT})" + +docker run --rm \ + --mount "type=bind,src=${PROJECT_ROOT},dst=/src,readonly" \ + --mount "type=bind,src=${BUILD_OUTPUT},dst=/out" \ + ${WOWEE_FFX_SDK_REPO:+--env "WOWEE_FFX_SDK_REPO=${WOWEE_FFX_SDK_REPO}"} \ + ${WOWEE_FFX_SDK_REF:+--env "WOWEE_FFX_SDK_REF=${WOWEE_FFX_SDK_REF}"} \ + "$IMAGE_NAME" + +echo "==> Windows cross-compile build complete. Artifacts in: ${BUILD_OUTPUT}" diff --git a/resources/wowee.rc b/resources/wowee.rc index a39b5c55..45939fce 100644 --- a/resources/wowee.rc +++ b/resources/wowee.rc @@ -1 +1 @@ -IDI_APP_ICON ICON "assets\\wowee.ico" +IDI_APP_ICON ICON "assets/wowee.ico" diff --git a/vcpkg.json b/vcpkg.json index 60b8639e..7399d6ff 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -10,6 +10,7 @@ "glew", "glm", "zlib", - "ffmpeg" + "ffmpeg", + "vulkan-headers" ] }