feat: Linux and SteamOS build (Vulkan through SDL_GPU, gamepad launcher)

Merged
TM
23d ago
+1806-215

Summary

Adds a Linux x86-64 build, aimed at SteamOS (Steam Deck), on top of the macOS
port from #99. No issue is open for this; it follows the offer in #99.

  • Runtime: win32_compat.c gains the Linux stat-time and xattr fields and
    a case-insensitive path lookup; xbe/sha.cpp gains a portable SHA-1/SHA-256
    for hosts with neither bcrypt nor CommonCrypto. CMake uses the system SDL3,
    or builds it when RECOMP_FETCH_SDL3=ON (build_game.py sets it on Linux).
    Hosts with neither still configure, so the custody job stays offline.
  • Presenter: SDL_GPU selects Vulkan on Linux, which takes SPIR-V. The
    presenter emits GLSL beside the MSL and compiles it at runtime with shaderc,
    loaded with dlopen (no build-time dependency).
  • Per-draw cost (shared with macOS): pipelines are chosen when a draw is
    queued; redundant binds, uniform pushes and dynamic state are skipped; MSAA
    resolves once per frame rather than at each segment flush.
  • Uniform layout (shared with macOS): every draw pushes a 144-float header
    that now holds directional_flags; lit draws add the directional block;
    vertex programs push their constants to a second slot. SDL's Vulkan backend
    binds 4 KB per uniform slot, which the former 1048-float block exceeded.
  • Tools: launcher.sh on Linux runs a small SDL settings launcher
    (tools/launcher_sdl.cpp: gamepad, keyboard, mouse, touch). SteamOS has no
    compiler, so it is built beside the runner and started by
    run_game.py --launcher. docs/building.md gains a Linux and SteamOS
    section.
  • RECOMP_PERF_COUNTER=1 prints an fps/gap/draws line once a second, in the
    Direct3D 11 presenter's format.

Not included: custom soundtracks (Media Foundation only), SMAA, a Linux CI job,
and CHANGELOG / docs/public-status.md entries, which I left for the release
notes.

Verification

  • ctest (public tests): 22 pass on Linux x86-64 (Ubuntu 24.04 container,
    SDL_VIDEODRIVER=offscreen) and on macOS arm64; custom music skipped.
  • python -m unittest discover -s tools -p "test_*.py" and
    python tools/public_export.py verify --require-public-tree pass on macOS.
    In an Ubuntu container without SDL3, test_build_game.py's CMake
    authentication test passes.
  • Authenticated local build (recipe 46c5de9c…, manifest eedd0728…),
    cross-built in an Ubuntu 24.04 container and run on a Steam Deck (SteamOS,
    gamescope) at 720p, 4x MSAA, with scripted A presses: it reached save select
    and the controller settings menu over the island backdrop, about 1550 draws
    a frame, at 60 fps. Before the event-pump fix that landed in #99, the same
    path ran at 46-58 fps. Frames captured at six swaps matched the previous
    presenter build within timing differences.
  • The same runner starts and presents frames on a second SteamOS machine
    (Navi 33). The macOS runner built from this branch reaches the same menus at
    60 fps with no shader or pipeline errors.
  • These are smoke runs. No one has play-tested this branch by hand yet; a
    hand play test on the Deck is still to come. The Deck and Steam Machine runs
    used the build from before this branch was rebased onto main; the runtime
    sources are identical apart from the SDL3 lookup in CMakeLists.txt, and
    the rebased tree builds and passes the tests above.
  • Not run: Windows CI for this branch (the D3D11 path is untouched).

Contribution checklist

  • The change follows AGENTS.md and CONTRIBUTING.md.
  • No XBE, generated game C, retail instruction bytes, extracted assets or
    filenames, saves, BIOS data, private paths, or run evidence are included.
  • Third-party material and its license are identified, if applicable:
    SDL3 (zlib) is fetched at build time when the system has none; shaderc
    (Apache-2.0) is loaded from the system at runtime. Neither is vendored.

Summary by cubic

Adds a Linux x86-64 build for SteamOS (Steam Deck) by extending the macOS SDL_GPU presenter to Vulkan, emitting GLSL compiled at runtime with shaderc, loaded via dlopen. Per-draw cost cuts keep an authenticated build at 60 fps on the Deck; custom soundtracks (Media Foundation only), SMAA, and a Linux CI job remain out. Verified with 22 passing public tests on Linux and macOS; smoke runs on a Steam Deck and a Navi 33 SteamOS machine at 720p/4x MSAA (~1550 draws/frame) are done, with no hand play test yet.

Runtime and build

  • win32_compat gains Linux stat-time and xattr fields plus case-insensitive path lookup, since the disc's filenames differ in case from the game's; storing READONLY now preserves the other attribute bits even when the file is already read-only, by granting owner write first.
  • Portable SHA-1/SHA-256 for hosts with neither bcrypt nor CommonCrypto.
  • CMake uses the system SDL3, or fetches and builds it when RECOMP_FETCH_SDL3=ON; the fetched SDL3 release is pinned by hash.
  • launcher.sh runs a new SDL settings launcher (tools/launcher_sdl.cpp) with gamepad, keyboard, mouse, and touch support; SteamOS has no compiler, so it is built beside the runner.

Presenter and fixes

  • Pipelines resolve when a draw is queued; redundant binds, uniform pushes, and dynamic state are skipped at flush. MSAA resolves once a frame instead of at each segment flush.
  • The 144-float uniform header now carries directional_flags; vertex program constants move to a second slot that fits SDL's 4 KB Vulkan per-slot limit.
  • shaderc loads once, under libshaderc_shared or its Debian name libshaderc.so.1, and failed shaders/pipelines are cached so they are not rebuilt or logged on every draw. SDL_GPU samplers now sample mipmaps (their default clamps to base level).
  • Vertex-program draws require FVF 0x112 with a translated program so shaders never read unwritten outputs.