Compare commits
3 Commits
920842a465
..
v3.0.2
| Author | SHA1 | Date | |
|---|---|---|---|
| a2fb95e57b | |||
| 581af95e7b | |||
| 824b4a2506 |
@@ -0,0 +1,2 @@
|
|||||||
|
* text=auto
|
||||||
|
*.sh text eol=lf
|
||||||
+19
-10
@@ -1,14 +1,16 @@
|
|||||||
name: build
|
name: build
|
||||||
|
|
||||||
# Builds a versioned Linux release tarball (see `make package`) on every
|
# Builds versioned Linux and Windows release packages (see `make
|
||||||
# push/PR, plus on-demand via the Gitea "Run workflow" button. Runs
|
# package`, which builds both - a .tar.gz and a .zip) on every push/PR,
|
||||||
|
# plus on-demand via the Gitea "Run workflow" button. Runs
|
||||||
# inside the build-image (see ../../build-image/Dockerfile, built/pushed
|
# inside the build-image (see ../../build-image/Dockerfile, built/pushed
|
||||||
# via ../../build-image.sh and ../../upload-image.sh), which bundles
|
# via ../../build-image.sh and ../../upload-image.sh), which bundles
|
||||||
# every dependency engine/ needs to compile - no network access needed
|
# every dependency engine/ needs to compile (for both platforms,
|
||||||
# at job runtime. Since the job's container already *is* the build-image
|
# including the Windows cross-toolchain) - no network access needed at
|
||||||
# (QUESTSHOCK_BUILD_IMAGE=1), `make package`'s `engine` prerequisite
|
# job runtime. Since the job's container already *is* the build-image
|
||||||
# compiles directly instead of trying to docker-run it again, which
|
# (QUESTSHOCK_BUILD_IMAGE=1), `make package`'s `engine`/`engine-win`
|
||||||
# wouldn't work here (no nested docker).
|
# prerequisites compile directly instead of trying to docker-run it
|
||||||
|
# again, which wouldn't work here (no nested docker).
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
pull_request:
|
pull_request:
|
||||||
@@ -50,17 +52,22 @@ jobs:
|
|||||||
# comment) - openxr was added to this image in the same
|
# comment) - openxr was added to this image in the same
|
||||||
# Android/Quest layer, so it's an equally good marker of that.
|
# Android/Quest layer, so it's an equally good marker of that.
|
||||||
[ -d /opt/prebuilt/android/openxr ] || { echo "PREFLIGHT FAIL: /opt/prebuilt/android/openxr missing - runner is using a build-image older than the Android/Quest layer" >&2; exit 1; }
|
[ -d /opt/prebuilt/android/openxr ] || { echo "PREFLIGHT FAIL: /opt/prebuilt/android/openxr missing - runner is using a build-image older than the Android/Quest layer" >&2; exit 1; }
|
||||||
|
[ -d /opt/prebuilt/win/sdl2 ] || { echo "PREFLIGHT FAIL: /opt/prebuilt/win/sdl2 missing - runner is using a build-image older than the Windows cross-build layer" >&2; exit 1; }
|
||||||
|
command -v x86_64-w64-mingw32-gcc >/dev/null 2>&1 || { echo "PREFLIGHT FAIL: x86_64-w64-mingw32-gcc not in PATH" >&2; exit 1; }
|
||||||
|
command -v zip >/dev/null 2>&1 || { echo "PREFLIGHT FAIL: zip not in PATH (needed by make package-win)" >&2; exit 1; }
|
||||||
|
|
||||||
- name: Build package
|
- name: Build package
|
||||||
run: make package
|
run: make package
|
||||||
|
|
||||||
- name: Upload build artifact
|
- name: Upload build artifacts
|
||||||
# v4 uses the newer @actions/artifact backend, which this Gitea
|
# v4 uses the newer @actions/artifact backend, which this Gitea
|
||||||
# instance's artifact storage doesn't support (GHESNotSupportedError) - v3 works.
|
# instance's artifact storage doesn't support (GHESNotSupportedError) - v3 works.
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: shockolate
|
name: shockolate
|
||||||
path: dist/shockolate-*-linux-*.tar.gz
|
path: |
|
||||||
|
dist/shockolate-*-linux-*.tar.gz
|
||||||
|
dist/shockolate-*-windows-*.zip
|
||||||
|
|
||||||
- name: Build Quest APK
|
- name: Build Quest APK
|
||||||
# QUESTSHOCK_BUILD_IMAGE is already set (see Preflight above), so
|
# QUESTSHOCK_BUILD_IMAGE is already set (see Preflight above), so
|
||||||
@@ -78,13 +85,14 @@ jobs:
|
|||||||
path: dist/questshock-*-android-*.apk
|
path: dist/questshock-*-android-*.apk
|
||||||
|
|
||||||
- name: Publish to dl.ladkau.de
|
- name: Publish to dl.ladkau.de
|
||||||
# Uploads the tarball and APK over SFTP instead of using
|
# Uploads the tarball, zip, and APK over SFTP instead of using
|
||||||
# actions/upload-artifact (whose zip wrapping can't be disabled).
|
# actions/upload-artifact (whose zip wrapping can't be disabled).
|
||||||
# Only runs on push so PR builds don't publish.
|
# Only runs on push so PR builds don't publish.
|
||||||
if: gitea.event_name == 'push'
|
if: gitea.event_name == 'push'
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
TARBALL="$(ls dist/shockolate-*-linux-*.tar.gz)"
|
TARBALL="$(ls dist/shockolate-*-linux-*.tar.gz)"
|
||||||
|
ZIP="$(ls dist/shockolate-*-windows-*.zip)"
|
||||||
APK="$(ls dist/questshock-*-android-*.apk)"
|
APK="$(ls dist/questshock-*-android-*.apk)"
|
||||||
mkdir -p ~/.ssh
|
mkdir -p ~/.ssh
|
||||||
echo "${{ secrets.DL_SFTP_KEY }}" > ~/.ssh/dl_sftp_key
|
echo "${{ secrets.DL_SFTP_KEY }}" > ~/.ssh/dl_sftp_key
|
||||||
@@ -94,5 +102,6 @@ jobs:
|
|||||||
uploader@dl.ladkau.de <<EOF
|
uploader@dl.ladkau.de <<EOF
|
||||||
-mkdir files/questshock
|
-mkdir files/questshock
|
||||||
put $TARBALL files/questshock/$(basename "$TARBALL")
|
put $TARBALL files/questshock/$(basename "$TARBALL")
|
||||||
|
put $ZIP files/questshock/$(basename "$ZIP")
|
||||||
put $APK files/questshock/$(basename "$APK")
|
put $APK files/questshock/$(basename "$APK")
|
||||||
EOF
|
EOF
|
||||||
|
|||||||
+6
-1
@@ -3,16 +3,21 @@
|
|||||||
|
|
||||||
# Game assets are not included in the repo
|
# Game assets are not included in the repo
|
||||||
/res/assets/setup_system_shock_enhanced*
|
/res/assets/setup_system_shock_enhanced*
|
||||||
|
/res/assets/*.zip
|
||||||
/res/assets/ss_ee/
|
/res/assets/ss_ee/
|
||||||
|
|
||||||
# Build output
|
# Build output
|
||||||
/dist/
|
/dist/
|
||||||
|
/dist-win/
|
||||||
/build/
|
/build/
|
||||||
|
|
||||||
# Engine build artifacts (engine/ is committed as a source snapshot; these
|
# Engine build artifacts (engine/ is committed as a source snapshot; these
|
||||||
# are generated by run-image.sh / build-image/build-engine.sh)
|
# are generated by run-image.sh / build-image/build-engine.sh /
|
||||||
|
# build-image/build-engine-win.sh - the Windows cross-build's own scratch
|
||||||
|
# copy of engine/ lives under /build/win-engine/, already covered above)
|
||||||
/engine/build_ext/
|
/engine/build_ext/
|
||||||
/engine/.build-output/
|
/engine/.build-output/
|
||||||
|
/engine/.build-output-win/
|
||||||
/engine/CMakeCache.txt
|
/engine/CMakeCache.txt
|
||||||
/engine/CMakeFiles/
|
/engine/CMakeFiles/
|
||||||
/engine/cmake_install.cmake
|
/engine/cmake_install.cmake
|
||||||
|
|||||||
@@ -1,20 +1,28 @@
|
|||||||
# Assembles dist/ - a self-contained, runnable copy of System Shock -
|
# Assembles dist/ (Linux) and dist-win/ (Windows, cross-compiled via
|
||||||
# from the compiled engine (built via Docker, see build-image.sh/
|
# MinGW) - self-contained, runnable copies of System Shock - from the
|
||||||
# run-image.sh) and the game assets extracted from a purchased copy (see
|
# compiled engine (built via Docker, see build-image.sh/run-image.sh) and
|
||||||
|
# the game assets extracted from a purchased copy (see
|
||||||
# res/assets/extract_assets.sh). Building the engine needs the build-image
|
# res/assets/extract_assets.sh). Building the engine needs the build-image
|
||||||
# (./build-image.sh, once); everything else here is plain file copying.
|
# (./build-image.sh, once); everything else here is plain file copying.
|
||||||
|
# `dist-linux`/`dist-win` build just one platform; plain `dist` builds
|
||||||
|
# both.
|
||||||
#
|
#
|
||||||
# `make package` instead builds a redistributable tarball that omits the
|
# `make package` instead builds redistributable archives (a .tar.gz for
|
||||||
# proprietary game assets entirely (see res/assets/GET_ASSETS.txt, which
|
# Linux, a .zip for Windows) that omit the proprietary game assets
|
||||||
# it ships in their place) - this is what CI publishes.
|
# entirely (see res/assets/GET_ASSETS.txt, which they ship in their
|
||||||
|
# place) - this is what CI publishes. Same dist-linux/dist-win/(both)
|
||||||
|
# split via package-linux/package-win/package.
|
||||||
|
|
||||||
DIST_DIR := dist
|
DIST_DIR := dist
|
||||||
ENGINE_OUT := engine/.build-output
|
DIST_WIN_DIR := dist-win
|
||||||
ASSETS_DIR := res/assets/ss_ee
|
ENGINE_OUT := engine/.build-output
|
||||||
BUILD_DIR := build
|
ENGINE_OUT_WIN := engine/.build-output-win
|
||||||
ARCH := $(shell uname -m)
|
ASSETS_DIR := res/assets/ss_ee
|
||||||
|
BUILD_DIR := build
|
||||||
|
ARCH := $(shell uname -m)
|
||||||
|
|
||||||
.PHONY: all dist build-image engine assets package apk android-studio clean
|
.PHONY: all dist dist-linux dist-win build-image engine engine-win assets \
|
||||||
|
package package-linux package-win apk android-studio clean
|
||||||
|
|
||||||
all: dist
|
all: dist
|
||||||
|
|
||||||
@@ -40,6 +48,16 @@ engine:
|
|||||||
./run-image.sh; \
|
./run-image.sh; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Cross-compiles engine/ for Windows via MinGW - see build-engine-win.sh
|
||||||
|
# for why this can't share build-engine.sh's engine/build_ext/ (a scratch
|
||||||
|
# copy is used instead). Same QUESTSHOCK_BUILD_IMAGE detection as `engine`.
|
||||||
|
engine-win:
|
||||||
|
@if [ -n "$$QUESTSHOCK_BUILD_IMAGE" ]; then \
|
||||||
|
bash build-image/build-engine-win.sh; \
|
||||||
|
else \
|
||||||
|
./run-image.sh bash build-image/build-engine-win.sh; \
|
||||||
|
fi
|
||||||
|
|
||||||
# Fails with a pointer to extract_assets.sh if the purchased game assets
|
# Fails with a pointer to extract_assets.sh if the purchased game assets
|
||||||
# haven't been extracted yet.
|
# haven't been extracted yet.
|
||||||
assets:
|
assets:
|
||||||
@@ -49,7 +67,10 @@ assets:
|
|||||||
exit 1; \
|
exit 1; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
dist: engine assets
|
# Builds both platforms. `dist-linux`/`dist-win` build just one.
|
||||||
|
dist: dist-linux dist-win
|
||||||
|
|
||||||
|
dist-linux: engine assets
|
||||||
@echo "== Assembling $(DIST_DIR) =="
|
@echo "== Assembling $(DIST_DIR) =="
|
||||||
rm -rf "$(DIST_DIR)/systemshock" "$(DIST_DIR)/lib" "$(DIST_DIR)/res" \
|
rm -rf "$(DIST_DIR)/systemshock" "$(DIST_DIR)/lib" "$(DIST_DIR)/res" \
|
||||||
"$(DIST_DIR)/shaders" "$(DIST_DIR)/run.sh"
|
"$(DIST_DIR)/shaders" "$(DIST_DIR)/run.sh"
|
||||||
@@ -64,6 +85,27 @@ dist: engine assets
|
|||||||
chmod +x "$(DIST_DIR)/run.sh"
|
chmod +x "$(DIST_DIR)/run.sh"
|
||||||
@echo "== Done - run $(DIST_DIR)/run.sh to play =="
|
@echo "== Done - run $(DIST_DIR)/run.sh to play =="
|
||||||
|
|
||||||
|
# Windows counterpart of dist-linux. Unlike dist/, the DLLs sit flat
|
||||||
|
# alongside systemshock.exe instead of in a lib/ subdirectory - Windows'
|
||||||
|
# default DLL search order already checks the executable's own directory
|
||||||
|
# first, so (unlike run.sh's LD_LIBRARY_PATH) run.bat needs no extra
|
||||||
|
# wiring for that.
|
||||||
|
dist-win: engine-win assets
|
||||||
|
@echo "== Assembling $(DIST_WIN_DIR) =="
|
||||||
|
rm -rf "$(DIST_WIN_DIR)"
|
||||||
|
mkdir -p "$(DIST_WIN_DIR)/res/data" "$(DIST_WIN_DIR)/res/sound"
|
||||||
|
cp "$(ENGINE_OUT_WIN)/systemshock.exe" "$(ENGINE_OUT_WIN)"/*.dll "$(DIST_WIN_DIR)/"
|
||||||
|
cp "$(ENGINE_OUT_WIN)/soundfont.sf2" "$(DIST_WIN_DIR)/res/"
|
||||||
|
cp -a engine/shaders "$(DIST_WIN_DIR)/shaders"
|
||||||
|
cp -a "$(ASSETS_DIR)/data/." "$(DIST_WIN_DIR)/res/data/"
|
||||||
|
cp -a "$(ASSETS_DIR)/sound/." "$(DIST_WIN_DIR)/res/sound/"
|
||||||
|
cp res/run.bat "$(DIST_WIN_DIR)/run.bat"
|
||||||
|
@echo "== Done - run $(DIST_WIN_DIR)/run.bat (or systemshock.exe) to play =="
|
||||||
|
|
||||||
|
# Builds both platforms' redistributable archives. `package-linux`/
|
||||||
|
# `package-win` build just one.
|
||||||
|
package: package-linux package-win
|
||||||
|
|
||||||
# Builds a versioned, redistributable Linux release tarball at
|
# Builds a versioned, redistributable Linux release tarball at
|
||||||
# dist/shockolate-<version>-linux-<arch>.tar.gz - everything needed to
|
# dist/shockolate-<version>-linux-<arch>.tar.gz - everything needed to
|
||||||
# run except the proprietary game assets (res/GET_ASSETS.txt explains how
|
# run except the proprietary game assets (res/GET_ASSETS.txt explains how
|
||||||
@@ -72,7 +114,7 @@ dist: engine assets
|
|||||||
# tag to drive a release. Override with `make package VERSION=1.2.3`, or
|
# tag to drive a release. Override with `make package VERSION=1.2.3`, or
|
||||||
# just run it untagged for a local dev build (gets a 0.0.0-dev+<sha>
|
# just run it untagged for a local dev build (gets a 0.0.0-dev+<sha>
|
||||||
# placeholder version, with a warning).
|
# placeholder version, with a warning).
|
||||||
package: engine
|
package-linux: engine
|
||||||
@git config --global --add safe.directory "$$(pwd)" 2>/dev/null || true
|
@git config --global --add safe.directory "$$(pwd)" 2>/dev/null || true
|
||||||
@V="$$(VERSION="$(VERSION)" ./build-image/version.sh)"; \
|
@V="$$(VERSION="$(VERSION)" ./build-image/version.sh)"; \
|
||||||
PKG_NAME="shockolate-$$V-linux-$(ARCH)"; \
|
PKG_NAME="shockolate-$$V-linux-$(ARCH)"; \
|
||||||
@@ -95,6 +137,33 @@ package: engine
|
|||||||
rm -rf "$(BUILD_DIR)/package"; \
|
rm -rf "$(BUILD_DIR)/package"; \
|
||||||
echo "Wrote $(DIST_DIR)/$$PKG_NAME.tar.gz"
|
echo "Wrote $(DIST_DIR)/$$PKG_NAME.tar.gz"
|
||||||
|
|
||||||
|
# Windows counterpart of package-linux: dist/shockolate-<version>-windows-
|
||||||
|
# x86_64.zip. Same versioning (build-image/version.sh) and VERSION=
|
||||||
|
# override. Needs `zip` on whatever host runs `make package-win` itself
|
||||||
|
# (unlike engine-win's own build, packaging isn't run inside the
|
||||||
|
# build-image) - see README's Desktop build prerequisites.
|
||||||
|
package-win: engine-win
|
||||||
|
@git config --global --add safe.directory "$$(pwd)" 2>/dev/null || true
|
||||||
|
@command -v zip >/dev/null 2>&1 || { echo "PREFLIGHT FAIL: zip not found in PATH" >&2; exit 1; }
|
||||||
|
@V="$$(VERSION="$(VERSION)" ./build-image/version.sh)"; \
|
||||||
|
PKG_NAME="shockolate-$$V-windows-x86_64"; \
|
||||||
|
PKG_STAGE="$(BUILD_DIR)/package/$$PKG_NAME"; \
|
||||||
|
echo "Packaging $$PKG_NAME"; \
|
||||||
|
rm -rf "$$PKG_STAGE"; \
|
||||||
|
mkdir -p "$$PKG_STAGE/res"; \
|
||||||
|
cp "$(ENGINE_OUT_WIN)/systemshock.exe" "$(ENGINE_OUT_WIN)"/*.dll "$$PKG_STAGE/"; \
|
||||||
|
cp -a engine/shaders "$$PKG_STAGE/shaders"; \
|
||||||
|
cp "$(ENGINE_OUT_WIN)/soundfont.sf2" "$$PKG_STAGE/res/"; \
|
||||||
|
cp res/assets/GET_ASSETS.txt "$$PKG_STAGE/res/GET_ASSETS.txt"; \
|
||||||
|
cp res/run.bat "$$PKG_STAGE/run.bat"; \
|
||||||
|
cp LICENSE "$$PKG_STAGE/LICENSE"; \
|
||||||
|
cp engine/LICENSE "$$PKG_STAGE/LICENSE.Shockolate"; \
|
||||||
|
cp NOTICE.txt "$$PKG_STAGE/NOTICE.txt"; \
|
||||||
|
mkdir -p "$(DIST_DIR)"; \
|
||||||
|
(cd "$(BUILD_DIR)/package" && zip -rq "$(CURDIR)/$(DIST_DIR)/$$PKG_NAME.zip" "$$PKG_NAME"); \
|
||||||
|
rm -rf "$(BUILD_DIR)/package"; \
|
||||||
|
echo "Wrote $(DIST_DIR)/$$PKG_NAME.zip"
|
||||||
|
|
||||||
# Builds the Quest APK (see build-image/build-apk.sh and
|
# Builds the Quest APK (see build-image/build-apk.sh and
|
||||||
# android/engine-patches/ - engine/ itself is never modified; a patch is
|
# android/engine-patches/ - engine/ itself is never modified; a patch is
|
||||||
# applied to a scratch copy at build time instead). Same
|
# applied to a scratch copy at build time instead). Same
|
||||||
@@ -124,7 +193,7 @@ android-studio:
|
|||||||
./run-image.sh bash build-image/prepare-android-project.sh --host-paths
|
./run-image.sh bash build-image/prepare-android-project.sh --host-paths
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf "$(DIST_DIR)" "$(BUILD_DIR)" "$(ENGINE_OUT)" \
|
rm -rf "$(DIST_DIR)" "$(DIST_WIN_DIR)" "$(BUILD_DIR)" "$(ENGINE_OUT)" "$(ENGINE_OUT_WIN)" \
|
||||||
engine/build_ext engine/CMakeCache.txt engine/CMakeFiles \
|
engine/build_ext engine/CMakeCache.txt engine/CMakeFiles \
|
||||||
engine/cmake_install.cmake engine/Makefile engine/systemshock \
|
engine/cmake_install.cmake engine/Makefile engine/systemshock \
|
||||||
engine/src/Libraries/CMakeFiles \
|
engine/src/Libraries/CMakeFiles \
|
||||||
|
|||||||
+16
@@ -16,3 +16,19 @@ The Android build additionally bundles:
|
|||||||
- The Khronos Group's OpenXR-SDK loader
|
- The Khronos Group's OpenXR-SDK loader
|
||||||
(https://github.com/KhronosGroup/OpenXR-SDK), prebuilt unmodified as
|
(https://github.com/KhronosGroup/OpenXR-SDK), prebuilt unmodified as
|
||||||
lib/arm64-v8a/libopenxr_loader.so. It is Apache 2.0-licensed.
|
lib/arm64-v8a/libopenxr_loader.so. It is Apache 2.0-licensed.
|
||||||
|
|
||||||
|
The Windows build (cross-compiled via MinGW - see README's "4.2. Windows
|
||||||
|
cross-build") additionally bundles:
|
||||||
|
|
||||||
|
- GLEW (http://glew.sourceforge.net/), unmodified, as glew32.dll - used
|
||||||
|
for OpenGL extension loading, which Windows' own opengl32.dll doesn't
|
||||||
|
provide past OpenGL 1.1 (Linux instead gets this straight from Mesa's
|
||||||
|
headers, needing no separate loader library - see
|
||||||
|
engine/src/MacSrc/OpenGL.cc). It is licensed under a combination of the
|
||||||
|
Modified BSD License, the MIT License, and the Khronos License, all
|
||||||
|
permissive.
|
||||||
|
- The MinGW-w64 runtime's winpthreads library, as libwinpthread-1.dll. It
|
||||||
|
is MIT-licensed. (GCC's own runtime, libgcc/libstdc++, is linked
|
||||||
|
statically into systemshock.exe instead of shipped as a DLL, under the
|
||||||
|
GCC Runtime Library Exception - this doesn't subject the rest of the
|
||||||
|
binary to the GPL.)
|
||||||
|
|||||||
@@ -92,10 +92,30 @@ below.
|
|||||||
|
|
||||||
## 4. Desktop build
|
## 4. Desktop build
|
||||||
|
|
||||||
Builds and runs Questshock natively on Linux (your dev machine, or any
|
Builds and runs Questshock natively on the desktop (your dev machine, or
|
||||||
Linux box) - useful for local development and testing without a VR
|
any Linux/Windows box) - useful for local development and testing
|
||||||
headset at all. For the VR-headset build, see "5. Android / Quest build"
|
without a VR headset at all. For the VR-headset build, see "5. Android /
|
||||||
below instead.
|
Quest build" below instead.
|
||||||
|
|
||||||
|
Two platforms: Linux (native) and Windows (x86_64, cross-compiled via
|
||||||
|
MinGW - see "4.2. Windows cross-build" below for how). `make dist`/`make
|
||||||
|
package` build **both** by default; use `make dist-linux`/`make
|
||||||
|
dist-win` (or `package-linux`/`package-win`) to build just one.
|
||||||
|
|
||||||
|
**Prerequisites:**
|
||||||
|
|
||||||
|
- **Docker** - `build-image.sh`/`run-image.sh` build and run the engine
|
||||||
|
build-image; no other build tool touches the host directly. The
|
||||||
|
Windows cross-toolchain (MinGW) is baked into the same build-image, so
|
||||||
|
building `dist-win` needs nothing extra beyond Docker itself.
|
||||||
|
- **`innoextract` and `unzip`** - optional, only needed by
|
||||||
|
`res/assets/extract_assets.sh` (see "3. Game assets" above) to unpack
|
||||||
|
the GOG installer locally; if either is missing, that script falls
|
||||||
|
back to running the extraction inside a throwaway Docker container
|
||||||
|
instead.
|
||||||
|
- **`zip`** - only needed on the host for `make package`/`package-win`
|
||||||
|
(the Windows release archive); `make dist`/`dist-win` and
|
||||||
|
`package-linux` don't need it.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# 1. Build the engine build-image (once, or after build-image/ changes)
|
# 1. Build the engine build-image (once, or after build-image/ changes)
|
||||||
@@ -104,11 +124,12 @@ below instead.
|
|||||||
# 2. Get your own copy of the game data (see "3. Game assets" above), then:
|
# 2. Get your own copy of the game data (see "3. Game assets" above), then:
|
||||||
res/assets/extract_assets.sh
|
res/assets/extract_assets.sh
|
||||||
|
|
||||||
# 3. Compile the engine and assemble dist/
|
# 3. Compile the engine and assemble dist/ (Linux) and dist-win/ (Windows)
|
||||||
make dist
|
make dist
|
||||||
|
|
||||||
# 4. Play
|
# 4. Play
|
||||||
dist/run.sh
|
dist/run.sh # Linux
|
||||||
|
dist-win/run.bat # Windows (or systemshock.exe directly)
|
||||||
```
|
```
|
||||||
|
|
||||||
`make dist` always recompiles the engine from the current `engine/`
|
`make dist` always recompiles the engine from the current `engine/`
|
||||||
@@ -117,14 +138,38 @@ source (via `run-image.sh`), so a fresh build-image plus a re-run of
|
|||||||
|
|
||||||
### 4.1. Packaging a distributable build
|
### 4.1. Packaging a distributable build
|
||||||
|
|
||||||
`make package` builds `dist/shockolate-<version>-linux-<arch>.tar.gz`: the
|
`make package` builds both `dist/shockolate-<version>-linux-<arch>.tar.gz`
|
||||||
compiled binary, its runtime libraries, shaders, a default MIDI
|
and `dist/shockolate-<version>-windows-x86_64.zip`: the compiled
|
||||||
soundfont, license information, and `res/GET_ASSETS.txt` in place of the
|
binary/DLLs, shaders, a default MIDI soundfont, license information, and
|
||||||
actual game data (which the tarball never includes). Version comes from
|
`res/GET_ASSETS.txt` in place of the actual game data (which neither
|
||||||
the current git tag (push a `vX.Y.Z` tag to drive a release); without one
|
archive ever includes). Version comes from the current git tag (push a
|
||||||
it builds an untagged `0.0.0-dev+<sha>` placeholder. `make apk` (see
|
`vX.Y.Z` tag to drive a release); without one it builds an untagged
|
||||||
"5. Android / Quest build" below) is versioned identically, via the same
|
`0.0.0-dev+<sha>` placeholder. `make apk` (see "5. Android / Quest build"
|
||||||
`build-image/version.sh`.
|
below) is versioned identically, via the same `build-image/version.sh`.
|
||||||
|
|
||||||
|
### 4.2. Windows cross-build
|
||||||
|
|
||||||
|
`make dist-win`/`make package-win` cross-compile a native Windows x86_64
|
||||||
|
build via MinGW (`x86_64-w64-mingw32-gcc`/`g++`, baked into the
|
||||||
|
build-image alongside the Linux toolchain - see
|
||||||
|
`build-image/build-engine-win.sh` and the "Windows cross-compile" section
|
||||||
|
of `build-image/Dockerfile`) - no Windows machine, Wine, or VM involved
|
||||||
|
in building it. `engine/CMakeLists.txt` already has working `WIN32`/
|
||||||
|
`MINGW` branches from Shockolate's own upstream Windows build (built
|
||||||
|
natively via Git Bash/MinGW on a real Windows machine - see
|
||||||
|
`engine/build_win64.sh`/`engine/appveyor.yml`), so unlike the Quest
|
||||||
|
build's `android/engine-patches/`, no source patching is needed here -
|
||||||
|
this cross-compiles those same branches offline and reproducibly, from
|
||||||
|
Linux, via Docker.
|
||||||
|
|
||||||
|
`dist-win/`/the packaged `.zip` ship `systemshock.exe` with its DLLs
|
||||||
|
(SDL2, SDL2_mixer, GLEW, fluidsynth-lite, plus the MinGW pthread runtime)
|
||||||
|
sitting flat alongside it, rather than in a `lib/` subdirectory like
|
||||||
|
`dist/` - Windows' default DLL search order already checks the
|
||||||
|
executable's own directory first, so no `PATH`/library-path setup is
|
||||||
|
needed the way `dist/run.sh` needs `LD_LIBRARY_PATH`.
|
||||||
|
|
||||||
|
Confirmed working (and playable) on a real Windows machine.
|
||||||
|
|
||||||
A Gitea Actions workflow (`.gitea/workflows/build.yml`) builds this
|
A Gitea Actions workflow (`.gitea/workflows/build.yml`) builds this
|
||||||
package on every push, using the build-image as its container (so no
|
package on every push, using the build-image as its container (so no
|
||||||
@@ -133,6 +178,18 @@ resulting tarball to dl.ladkau.de.
|
|||||||
|
|
||||||
## 5. Android / Quest build
|
## 5. Android / Quest build
|
||||||
|
|
||||||
|
**Prerequisites:**
|
||||||
|
|
||||||
|
- **Docker** - same build-image as the desktop build (see "4. Desktop
|
||||||
|
build" above), plus network access at build time for Gradle/AGP's own
|
||||||
|
dependency resolution (the one target that isn't fully offline).
|
||||||
|
- **SideQuest or `adb`** (Android Platform Tools) - to sideload the built
|
||||||
|
APK onto the headset, and to enable/verify USB debugging; see "5.1.
|
||||||
|
Installing and playing" below.
|
||||||
|
- Building/debugging natively in Android Studio instead of via `make
|
||||||
|
apk` needs its own separate toolchain - see "5.2. Building natively in
|
||||||
|
Android Studio" below.
|
||||||
|
|
||||||
`make apk` builds `dist/questshock-<version>-android-arm64.apk` - an
|
`make apk` builds `dist/questshock-<version>-android-arm64.apk` - an
|
||||||
immersive OpenXR app (see `android/app/src/main/cpp/xr_session.c`) that
|
immersive OpenXR app (see `android/app/src/main/cpp/xr_session.c`) that
|
||||||
can be sideloaded onto any Android-based VR headset with OpenXR support
|
can be sideloaded onto any Android-based VR headset with OpenXR support
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ android {
|
|||||||
"-DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=BOTH", \
|
"-DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=BOTH", \
|
||||||
"-DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=BOTH", \
|
"-DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=BOTH", \
|
||||||
"-DCMAKE_SHARED_LINKER_FLAGS=-Wl,-z,max-page-size=16384", \
|
"-DCMAKE_SHARED_LINKER_FLAGS=-Wl,-z,max-page-size=16384", \
|
||||||
"-DANDROID_EXTRA_SOURCES=${projectDir}/src/main/cpp/questshock_native.c;${projectDir}/src/main/cpp/xr_session.c;${projectDir}/src/main/cpp/xr_input.c;${projectDir}/src/main/cpp/xr_overlay.c;${projectDir}/src/main/cpp/xr_swapchain.c"
|
"-DANDROID_EXTRA_SOURCES=${projectDir}/src/main/cpp/questshock_native.c;${projectDir}/src/main/cpp/xr_session.c;${projectDir}/src/main/cpp/xr_input.c;${projectDir}/src/main/cpp/xr_overlay.c;${projectDir}/src/main/cpp/xr_swapchain.c;${projectDir}/src/main/cpp/xr_mouse.c"
|
||||||
abiFilters 'arm64-v8a'
|
abiFilters 'arm64-v8a'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+126
-129
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include <GLES3/gl3.h>
|
#include <GLES3/gl3.h>
|
||||||
|
|
||||||
|
#include "xr_mouse.h"
|
||||||
#include "xr_overlay.h"
|
#include "xr_overlay.h"
|
||||||
#include "xr_session.h"
|
#include "xr_session.h"
|
||||||
#include "xr_swapchain.h"
|
#include "xr_swapchain.h"
|
||||||
@@ -53,6 +54,7 @@ static XrSpace g_aim_space[2] = {XR_NULL_HANDLE, XR_NULL_HANDLE};
|
|||||||
static bool g_prev_select[2] = {false, false};
|
static bool g_prev_select[2] = {false, false};
|
||||||
static bool g_prev_menu = false;
|
static bool g_prev_menu = false;
|
||||||
static bool g_game_prev_hit[2] = {false, false};
|
static bool g_game_prev_hit[2] = {false, false};
|
||||||
|
static bool g_game_touch_active[2] = {false, false};
|
||||||
static bool g_menu_prev_hit[2] = {false, false};
|
static bool g_menu_prev_hit[2] = {false, false};
|
||||||
static bool g_menu_touch_active[2] = {false, false};
|
static bool g_menu_touch_active[2] = {false, false};
|
||||||
static bool g_keyboard_prev_hit[2] = {false, false};
|
static bool g_keyboard_prev_hit[2] = {false, false};
|
||||||
@@ -70,9 +72,6 @@ static int g_keyboard_drag_hand = -1;
|
|||||||
static float g_keyboard_drag_offset_x = 0.0f;
|
static float g_keyboard_drag_offset_x = 0.0f;
|
||||||
static float g_keyboard_drag_offset_y = 0.0f;
|
static float g_keyboard_drag_offset_y = 0.0f;
|
||||||
|
|
||||||
static GLuint g_reticle_program = 0;
|
|
||||||
static GLint g_reticle_color_loc = -1;
|
|
||||||
|
|
||||||
// A "view" reference space, located once per frame (not per hand) to get
|
// A "view" reference space, located once per frame (not per hand) to get
|
||||||
// an approximate head position for the laser-beam billboard math below -
|
// an approximate head position for the laser-beam billboard math below -
|
||||||
// the only consumer of a head pose in this file. Both LOCAL (g_local_space,
|
// the only consumer of a head pose in this file. Both LOCAL (g_local_space,
|
||||||
@@ -82,9 +81,9 @@ static XrSpace g_view_space = XR_NULL_HANDLE;
|
|||||||
|
|
||||||
// Per-hand laser-beam state: a thin, billboarded quad spanning from the
|
// Per-hand laser-beam state: a thin, billboarded quad spanning from the
|
||||||
// controller to wherever that hand's ray currently crosses the plane of
|
// controller to wherever that hand's ray currently crosses the plane of
|
||||||
// whichever overlay (keyboard/menu) claimed it this frame - see
|
// whichever target (keyboard, menu, or the game quad) claimed it this
|
||||||
// xr_input_build_beam()/xr_input_get_beam_layer(). Each hand gets its own
|
// frame - see xr_input_build_beam()/xr_input_get_beam_layer(). Each hand
|
||||||
// tiny solid-color swapchain (no shared tint on XrCompositionLayerQuad,
|
// gets its own tiny solid-color swapchain (no shared tint on XrCompositionLayerQuad,
|
||||||
// so two separately-colored textures is simplest). g_beam_quad_valid is
|
// so two separately-colored textures is simplest). g_beam_quad_valid is
|
||||||
// reset to false at the top of every xr_input_sync_and_draw() call and
|
// reset to false at the top of every xr_input_sync_and_draw() call and
|
||||||
// only set back to true if that hand actually claims a beam this frame.
|
// only set back to true if that hand actually claims a beam this frame.
|
||||||
@@ -183,63 +182,6 @@ static void mat3_to_quat(const float x[3], const float y[3], const float z[3], X
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static GLuint compile_shader(GLenum type, const char *src) {
|
|
||||||
GLuint shader = glCreateShader(type);
|
|
||||||
glShaderSource(shader, 1, &src, NULL);
|
|
||||||
glCompileShader(shader);
|
|
||||||
GLint compiled = GL_FALSE;
|
|
||||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
|
|
||||||
if (!compiled) {
|
|
||||||
char log[512];
|
|
||||||
glGetShaderInfoLog(shader, sizeof(log), NULL, log);
|
|
||||||
LOGE("XR: reticle shader compile failed: %s", log);
|
|
||||||
}
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
// A standalone flat-color shader, independent of the engine's own
|
|
||||||
// textureShaderProgram (OpenGL.cc is a separate, C++-only translation
|
|
||||||
// unit, and shader state isn't shared across programs anyway) - position
|
|
||||||
// is emitted directly in clip space, matching the same [-1,1] local quad
|
|
||||||
// coordinates android_draw_surface_as_quad() (see android/engine-patches/
|
|
||||||
// 11-android-openxr-present.patch) already uses for its own vertex
|
|
||||||
// positions, so no view/projection matrix is needed here either.
|
|
||||||
static const char *kVertexSrc = "attribute vec3 position;\n"
|
|
||||||
"void main() { gl_Position = vec4(position, 1.0); }\n";
|
|
||||||
static const char *kFragmentSrc = "precision mediump float;\n"
|
|
||||||
"uniform vec4 color;\n"
|
|
||||||
"void main() { gl_FragColor = color; }\n";
|
|
||||||
|
|
||||||
// Attribute 0 to match kVertexSrc's single "position" attribute - fine to
|
|
||||||
// reuse the same numeric index the engine's own immediate-mode drawing
|
|
||||||
// treats specially, since that's a per-program binding and this program is
|
|
||||||
// never current at the same time as gl4es's immediate-mode emulation runs;
|
|
||||||
// xr_input_sync_and_draw() disables the array again right after drawing,
|
|
||||||
// the same discipline android_draw_surface_as_quad() already established.
|
|
||||||
#define RETICLE_POSITION_LOC 0
|
|
||||||
|
|
||||||
static bool xr_input_init_reticle_program(void) {
|
|
||||||
GLuint vs = compile_shader(GL_VERTEX_SHADER, kVertexSrc);
|
|
||||||
GLuint fs = compile_shader(GL_FRAGMENT_SHADER, kFragmentSrc);
|
|
||||||
g_reticle_program = glCreateProgram();
|
|
||||||
glAttachShader(g_reticle_program, vs);
|
|
||||||
glAttachShader(g_reticle_program, fs);
|
|
||||||
glBindAttribLocation(g_reticle_program, RETICLE_POSITION_LOC, "position");
|
|
||||||
glLinkProgram(g_reticle_program);
|
|
||||||
GLint linked = GL_FALSE;
|
|
||||||
glGetProgramiv(g_reticle_program, GL_LINK_STATUS, &linked);
|
|
||||||
glDeleteShader(vs);
|
|
||||||
glDeleteShader(fs);
|
|
||||||
if (!linked) {
|
|
||||||
char log[512];
|
|
||||||
glGetProgramInfoLog(g_reticle_program, sizeof(log), NULL, log);
|
|
||||||
LOGE("XR: reticle program link failed: %s", log);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
g_reticle_color_loc = glGetUniformLocation(g_reticle_program, "color");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool xr_input_init(XrInstance instance, XrSession session, int64_t swapchain_format) {
|
bool xr_input_init(XrInstance instance, XrSession session, int64_t swapchain_format) {
|
||||||
g_instance = instance;
|
g_instance = instance;
|
||||||
g_session = session;
|
g_session = session;
|
||||||
@@ -349,9 +291,6 @@ bool xr_input_init(XrInstance instance, XrSession session, int64_t swapchain_for
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!xr_input_init_reticle_program())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
LOGI("XR: input action set ready (aim pose + trigger + menu-toggle)");
|
LOGI("XR: input action set ready (aim pose + trigger + menu-toggle)");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -361,10 +300,13 @@ bool xr_input_init(XrInstance instance, XrSession session, int64_t swapchain_for
|
|||||||
// below. dragCapable enables the keyboard-only title-bar/drag-handle
|
// below. dragCapable enables the keyboard-only title-bar/drag-handle
|
||||||
// handling (see TITLE_BAR_V_FRACTION/CLOSE_BUTTON_U_FRACTION); the menu
|
// handling (see TITLE_BAR_V_FRACTION/CLOSE_BUTTON_U_FRACTION); the menu
|
||||||
// launcher has no title bar, so it's always false there and every hit is a
|
// launcher has no title bar, so it's always false there and every hit is a
|
||||||
// plain click. Returns true if this overlay is visible - claiming the
|
// plain click. Returns true only when this hand's ray is actually
|
||||||
// hand's ray processing for this frame, regardless of whether the ray
|
// relevant to this overlay this frame - landing on it now, or continuing
|
||||||
// actually hits it - so the caller should stop trying other targets (menu/
|
// a touch/drag begun on a previous frame for this hand that hasn't been
|
||||||
// keyboard/game quad are mutually exclusive per hand, per frame).
|
// released yet - not merely because the overlay is visible. The caller
|
||||||
|
// should only stop trying other targets (menu/keyboard/game quad) when
|
||||||
|
// this returns true; a visible-but-unclaimed overlay falls through so a
|
||||||
|
// lower-priority target (ultimately the game quad) can still be aimed at.
|
||||||
static bool xr_input_try_overlay(XrOverlay *overlay, bool dragCapable, int hand,
|
static bool xr_input_try_overlay(XrOverlay *overlay, bool dragCapable, int hand,
|
||||||
const XrSpaceLocation *location, float fx, float fy, float fz,
|
const XrSpaceLocation *location, float fx, float fy, float fz,
|
||||||
bool selectDownEdge, bool selectUpEdge, bool *touchActive,
|
bool selectDownEdge, bool selectUpEdge, bool *touchActive,
|
||||||
@@ -375,6 +317,14 @@ static bool xr_input_try_overlay(XrOverlay *overlay, bool dragCapable, int hand,
|
|||||||
if (!xr_overlay_is_visible(overlay))
|
if (!xr_overlay_is_visible(overlay))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// Captured before this call's own down/up-edge handling below can
|
||||||
|
// mutate them, so a hand with a pending down-touch (waiting for its
|
||||||
|
// matching up) or an in-progress drag on *this* overlay still claims
|
||||||
|
// the ray this frame even if it has strayed off the panel's current
|
||||||
|
// bounds - see `claims` below.
|
||||||
|
bool hadTouch = *touchActive;
|
||||||
|
bool hadDrag = dragCapable && g_keyboard_dragging && g_keyboard_drag_hand == hand;
|
||||||
|
|
||||||
float quadCenterX, quadCenterY, distance, halfWidth, halfHeight;
|
float quadCenterX, quadCenterY, distance, halfWidth, halfHeight;
|
||||||
xr_overlay_get_quad_extent(overlay, &quadCenterX, &quadCenterY, &distance, &halfWidth,
|
xr_overlay_get_quad_extent(overlay, &quadCenterX, &quadCenterY, &distance, &halfWidth,
|
||||||
&halfHeight);
|
&halfHeight);
|
||||||
@@ -416,6 +366,12 @@ static bool xr_input_try_overlay(XrOverlay *overlay, bool dragCapable, int hand,
|
|||||||
*outCursorV = v;
|
*outCursorV = v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only claim (and thus route/beam) this hand's ray to this overlay
|
||||||
|
// when it's actually relevant this frame: landing on it now, or
|
||||||
|
// continuing a touch/drag that started on it - never merely because
|
||||||
|
// it's visible.
|
||||||
|
bool claims = hit || hadTouch || hadDrag;
|
||||||
|
|
||||||
if (!dragCapable) {
|
if (!dragCapable) {
|
||||||
if (selectDownEdge && hit) {
|
if (selectDownEdge && hit) {
|
||||||
xr_overlay_touch(overlay, u, v, true);
|
xr_overlay_touch(overlay, u, v, true);
|
||||||
@@ -424,7 +380,7 @@ static bool xr_input_try_overlay(XrOverlay *overlay, bool dragCapable, int hand,
|
|||||||
xr_overlay_touch(overlay, u, v, false);
|
xr_overlay_touch(overlay, u, v, false);
|
||||||
*touchActive = false;
|
*touchActive = false;
|
||||||
}
|
}
|
||||||
return true;
|
return claims;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool onTitleBar = hit && v < TITLE_BAR_V_FRACTION;
|
bool onTitleBar = hit && v < TITLE_BAR_V_FRACTION;
|
||||||
@@ -457,7 +413,7 @@ static bool xr_input_try_overlay(XrOverlay *overlay, bool dragCapable, int hand,
|
|||||||
xr_overlay_set_position(overlay, worldX - g_keyboard_drag_offset_x,
|
xr_overlay_set_position(overlay, worldX - g_keyboard_drag_offset_x,
|
||||||
worldY - g_keyboard_drag_offset_y);
|
worldY - g_keyboard_drag_offset_y);
|
||||||
}
|
}
|
||||||
return true;
|
return claims;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Builds this hand's laser-beam quad - a thin, billboarded ribbon from the
|
// Builds this hand's laser-beam quad - a thin, billboarded ribbon from the
|
||||||
@@ -568,6 +524,48 @@ static void xr_input_build_beam(int hand, const XrSpaceLocation *location, float
|
|||||||
g_beam_quad_valid[hand] = true;
|
g_beam_quad_valid[hand] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hit-tests this hand's ray against the game quad (xr_get_game_quad_extent())
|
||||||
|
// - the lowest-priority target, tried only once neither the keyboard nor
|
||||||
|
// menu overlay claimed the ray this frame (or, symmetrically, once this
|
||||||
|
// hand already has a pending mouse-down on the game quad - see
|
||||||
|
// g_game_touch_active and the keyboard/menu call sites in
|
||||||
|
// xr_input_sync_and_draw()). outU/outV (only meaningful when the
|
||||||
|
// returned hit is true) let the caller drive the game's own mouse cursor
|
||||||
|
// via xr_mouse.h. Returns plain `hit` - drag/touch dispatch for actual
|
||||||
|
// clicks is the caller's responsibility (xr_mouse_click()), not this
|
||||||
|
// function's, unlike xr_input_try_overlay().
|
||||||
|
static bool xr_input_try_game_quad(int hand, const XrSpaceLocation *location, float fx, float fy,
|
||||||
|
float fz, bool *outPlaneHit, float *outPlaneDistance,
|
||||||
|
float *outU, float *outV) {
|
||||||
|
float distance, halfWidth, halfHeight;
|
||||||
|
xr_get_game_quad_extent(&distance, &halfWidth, &halfHeight);
|
||||||
|
|
||||||
|
bool planeHit = false, hit = false;
|
||||||
|
float u = 0.0f, v = 0.0f, t = 0.0f;
|
||||||
|
if (fabsf(fz) > 1e-5f) {
|
||||||
|
t = (-distance - location->pose.position.z) / fz;
|
||||||
|
if (t > 0.0f) {
|
||||||
|
planeHit = true;
|
||||||
|
float cx = (location->pose.position.x + t * fx) / halfWidth;
|
||||||
|
float cy = (location->pose.position.y + t * fy) / halfHeight;
|
||||||
|
hit = fabsf(cx) <= 1.0f && fabsf(cy) <= 1.0f;
|
||||||
|
u = (cx + 1.0f) * 0.5f;
|
||||||
|
v = (1.0f - cy) * 0.5f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*outPlaneHit = planeHit;
|
||||||
|
*outPlaneDistance = t;
|
||||||
|
*outU = u;
|
||||||
|
*outV = v;
|
||||||
|
|
||||||
|
if (hit != g_game_prev_hit[hand]) {
|
||||||
|
LOGI("XR: %s aim ray %s game quad (u=%.2f v=%.2f)", kHandName[hand],
|
||||||
|
hit ? "entered" : "left", u, v);
|
||||||
|
g_game_prev_hit[hand] = hit;
|
||||||
|
}
|
||||||
|
return hit;
|
||||||
|
}
|
||||||
|
|
||||||
void xr_input_sync_and_draw(XrSpace baseSpace, XrTime time, bool draw) {
|
void xr_input_sync_and_draw(XrSpace baseSpace, XrTime time, bool draw) {
|
||||||
if (g_action_set == XR_NULL_HANDLE)
|
if (g_action_set == XR_NULL_HANDLE)
|
||||||
return;
|
return;
|
||||||
@@ -597,8 +595,10 @@ void xr_input_sync_and_draw(XrSpace baseSpace, XrTime time, bool draw) {
|
|||||||
g_beam_quad_valid[LEFT] = g_beam_quad_valid[RIGHT] = false;
|
g_beam_quad_valid[LEFT] = g_beam_quad_valid[RIGHT] = false;
|
||||||
|
|
||||||
// The billboard math needs an approximate head position - only bother
|
// The billboard math needs an approximate head position - only bother
|
||||||
// locating it on frames where a beam could possibly be drawn at all.
|
// locating it on frames where a beam could possibly be drawn at all
|
||||||
bool needBeams = draw && (menuVisible || keyboardVisible);
|
// (i.e. any drawn frame - the game quad can claim a beam on its own
|
||||||
|
// even with both overlays closed).
|
||||||
|
bool needBeams = draw;
|
||||||
XrSpaceLocation headLoc = {XR_TYPE_SPACE_LOCATION};
|
XrSpaceLocation headLoc = {XR_TYPE_SPACE_LOCATION};
|
||||||
bool haveHead = false;
|
bool haveHead = false;
|
||||||
if (needBeams) {
|
if (needBeams) {
|
||||||
@@ -609,17 +609,6 @@ void xr_input_sync_and_draw(XrSpace baseSpace, XrTime time, bool draw) {
|
|||||||
(headLoc.locationFlags & neededHead) == neededHead;
|
(headLoc.locationFlags & neededHead) == neededHead;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The GL reticle is drawn directly into whatever framebuffer is
|
|
||||||
// currently bound - the game quad's swapchain image (see xr_frame_end(),
|
|
||||||
// which calls this while that image is still bound). That only makes
|
|
||||||
// sense while aiming at the game quad; the menu/keyboard overlays' own
|
|
||||||
// cursors are drawn by their Java views instead (see
|
|
||||||
// xr_overlay_update_cursor() below), composited into their Bitmaps the
|
|
||||||
// same way their other content is.
|
|
||||||
bool drawReticle = draw && !menuVisible && !keyboardVisible;
|
|
||||||
if (drawReticle)
|
|
||||||
glUseProgram(g_reticle_program);
|
|
||||||
|
|
||||||
// Fed to xr_overlay_update_cursor() after the loop below - whichever
|
// Fed to xr_overlay_update_cursor() after the loop below - whichever
|
||||||
// hand's ray hits a given overlay last wins if both do, good enough
|
// hand's ray hits a given overlay last wins if both do, good enough
|
||||||
// for a single on-quad cursor per overlay (only ever updated when hit
|
// for a single on-quad cursor per overlay (only ever updated when hit
|
||||||
@@ -686,15 +675,23 @@ void xr_input_sync_and_draw(XrSpace baseSpace, XrTime time, bool draw) {
|
|||||||
quat_rotate_vec(&location.pose.orientation, 0.0f, 0.0f, -1.0f, &fx, &fy, &fz);
|
quat_rotate_vec(&location.pose.orientation, 0.0f, 0.0f, -1.0f, &fx, &fy, &fz);
|
||||||
|
|
||||||
// Keyboard first (it's the more likely target while it's up), then
|
// Keyboard first (it's the more likely target while it's up), then
|
||||||
// the menu launcher, then - only if neither is visible - the game
|
// the menu launcher, then - only once neither actually claims the
|
||||||
// quad's own reticle below. A single ray only ever interacts with
|
// ray this frame (not merely "isn't visible" - see
|
||||||
// one target per hand per frame. Whichever overlay claims the ray
|
// xr_input_try_overlay()'s doc comment) - the game quad itself.
|
||||||
// also gets a laser-beam quad built for it (if a head pose is
|
// Whichever target claims the ray gets a laser-beam quad built for
|
||||||
// available and the ray actually crosses that overlay's plane) -
|
// it (if a head pose is available and the ray actually crosses
|
||||||
// see xr_input_build_beam().
|
// that target's plane) - see xr_input_build_beam(). Keyboard/menu
|
||||||
|
// are additionally gated on !g_game_touch_active[hand]: a pending
|
||||||
|
// mouse-down on the game quad (trigger still held since a
|
||||||
|
// down-edge dispatched there) must keep claiming the ray even if
|
||||||
|
// it strays onto another panel before release, the cross-target
|
||||||
|
// analogue of xr_input_try_overlay()'s own hadTouch/hadDrag - the
|
||||||
|
// eventual mouse-up has to reach the game quad, not whatever the
|
||||||
|
// ray happens to be over on the release frame.
|
||||||
bool keyboardPlaneHit = false;
|
bool keyboardPlaneHit = false;
|
||||||
float keyboardPlaneDistance = 0.0f;
|
float keyboardPlaneDistance = 0.0f;
|
||||||
if (xr_input_try_overlay(keyboardOverlay, true, hand, &location, fx, fy, fz,
|
if (!g_game_touch_active[hand] &&
|
||||||
|
xr_input_try_overlay(keyboardOverlay, true, hand, &location, fx, fy, fz,
|
||||||
selectDownEdge, selectUpEdge, &g_keyboard_touch_active[hand],
|
selectDownEdge, selectUpEdge, &g_keyboard_touch_active[hand],
|
||||||
&g_keyboard_prev_hit[hand], "keyboard", &keyboardCursorU,
|
&g_keyboard_prev_hit[hand], "keyboard", &keyboardCursorU,
|
||||||
&keyboardCursorV, &keyboardCursorHit, &keyboardPlaneHit,
|
&keyboardCursorV, &keyboardCursorHit, &keyboardPlaneHit,
|
||||||
@@ -706,7 +703,8 @@ void xr_input_sync_and_draw(XrSpace baseSpace, XrTime time, bool draw) {
|
|||||||
}
|
}
|
||||||
bool menuPlaneHit = false;
|
bool menuPlaneHit = false;
|
||||||
float menuPlaneDistance = 0.0f;
|
float menuPlaneDistance = 0.0f;
|
||||||
if (xr_input_try_overlay(menuOverlay, false, hand, &location, fx, fy, fz, selectDownEdge,
|
if (!g_game_touch_active[hand] &&
|
||||||
|
xr_input_try_overlay(menuOverlay, false, hand, &location, fx, fy, fz, selectDownEdge,
|
||||||
selectUpEdge, &g_menu_touch_active[hand], &g_menu_prev_hit[hand],
|
selectUpEdge, &g_menu_touch_active[hand], &g_menu_prev_hit[hand],
|
||||||
"menu", &menuCursorU, &menuCursorV, &menuCursorHit, &menuPlaneHit,
|
"menu", &menuCursorU, &menuCursorV, &menuCursorHit, &menuPlaneHit,
|
||||||
&menuPlaneDistance)) {
|
&menuPlaneDistance)) {
|
||||||
@@ -716,40 +714,42 @@ void xr_input_sync_and_draw(XrSpace baseSpace, XrTime time, bool draw) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
float distance, halfWidth, halfHeight;
|
// Lowest priority (for a fresh hit): the game quad itself - drives
|
||||||
xr_get_game_quad_extent(&distance, &halfWidth, &halfHeight);
|
// the engine's own mouse cursor/clicks via xr_mouse.h, exactly
|
||||||
|
// like a desktop mouse (absolute position + left button), plus the
|
||||||
bool hit = false;
|
// same laser-beam visual the other two targets get.
|
||||||
float u = 0.0f, v = 0.0f, cx = 0.0f, cy = 0.0f;
|
bool gamePlaneHit = false;
|
||||||
if (fabsf(fz) > 1e-5f) {
|
float gamePlaneDistance = 0.0f, gameU = 0.0f, gameV = 0.0f;
|
||||||
float t = (-distance - location.pose.position.z) / fz;
|
bool gameHit = xr_input_try_game_quad(hand, &location, fx, fy, fz, &gamePlaneHit,
|
||||||
if (t > 0.0f) {
|
&gamePlaneDistance, &gameU, &gameV);
|
||||||
cx = (location.pose.position.x + t * fx) / halfWidth;
|
if (gameHit) {
|
||||||
cy = (location.pose.position.y + t * fy) / halfHeight;
|
int gameWidth, gameHeight;
|
||||||
hit = fabsf(cx) <= 1.0f && fabsf(cy) <= 1.0f;
|
xr_get_game_resolution(&gameWidth, &gameHeight);
|
||||||
u = (cx + 1.0f) * 0.5f;
|
int px = (int)(gameU * (float)gameWidth);
|
||||||
v = (1.0f - cy) * 0.5f;
|
int py = (int)(gameV * (float)gameHeight);
|
||||||
}
|
if (px < 0)
|
||||||
|
px = 0;
|
||||||
|
else if (px >= gameWidth)
|
||||||
|
px = gameWidth - 1;
|
||||||
|
if (py < 0)
|
||||||
|
py = 0;
|
||||||
|
else if (py >= gameHeight)
|
||||||
|
py = gameHeight - 1;
|
||||||
|
xr_mouse_move(px, py);
|
||||||
}
|
}
|
||||||
|
if (selectDownEdge && gameHit) {
|
||||||
if (hit != g_game_prev_hit[hand]) {
|
xr_mouse_click(true);
|
||||||
LOGI("XR: %s aim ray %s game quad (u=%.2f v=%.2f)", kHandName[hand],
|
g_game_touch_active[hand] = true;
|
||||||
hit ? "entered" : "left", u, v);
|
} else if (selectUpEdge && g_game_touch_active[hand]) {
|
||||||
g_game_prev_hit[hand] = hit;
|
xr_mouse_click(false);
|
||||||
|
g_game_touch_active[hand] = false;
|
||||||
}
|
}
|
||||||
|
// gameHit implies gamePlaneHit (both only ever set together above),
|
||||||
if (!hit || !drawReticle)
|
// kept as a separate out-param for symmetry with
|
||||||
continue;
|
// xr_input_try_overlay()'s outPlaneHit/outPlaneDistance pair.
|
||||||
|
if (haveHead && gameHit)
|
||||||
const float kSize = 0.03f;
|
xr_input_build_beam(hand, &location, fx, fy, fz,
|
||||||
const float verts[] = {
|
fminf(gamePlaneDistance, MAX_BEAM_LENGTH_METERS), &headLoc);
|
||||||
cx - kSize, cy, 0.0f, cx + kSize, cy, 0.0f, cx, cy - kSize, 0.0f, cx, cy + kSize, 0.0f,
|
|
||||||
};
|
|
||||||
glUniform4fv(g_reticle_color_loc, 1, kHandColor[hand]);
|
|
||||||
glEnableVertexAttribArray(RETICLE_POSITION_LOC);
|
|
||||||
glVertexAttribPointer(RETICLE_POSITION_LOC, 3, GL_FLOAT, GL_FALSE, 0, verts);
|
|
||||||
glDrawArrays(GL_LINES, 0, 4);
|
|
||||||
glDisableVertexAttribArray(RETICLE_POSITION_LOC);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (menuVisible)
|
if (menuVisible)
|
||||||
@@ -783,14 +783,11 @@ void xr_input_shutdown(void) {
|
|||||||
g_select_click_action = XR_NULL_HANDLE;
|
g_select_click_action = XR_NULL_HANDLE;
|
||||||
g_menu_toggle_action = XR_NULL_HANDLE;
|
g_menu_toggle_action = XR_NULL_HANDLE;
|
||||||
|
|
||||||
if (g_reticle_program != 0)
|
|
||||||
glDeleteProgram(g_reticle_program);
|
|
||||||
g_reticle_program = 0;
|
|
||||||
|
|
||||||
g_instance = XR_NULL_HANDLE;
|
g_instance = XR_NULL_HANDLE;
|
||||||
g_session = XR_NULL_HANDLE;
|
g_session = XR_NULL_HANDLE;
|
||||||
memset(g_prev_select, 0, sizeof(g_prev_select));
|
memset(g_prev_select, 0, sizeof(g_prev_select));
|
||||||
memset(g_game_prev_hit, 0, sizeof(g_game_prev_hit));
|
memset(g_game_prev_hit, 0, sizeof(g_game_prev_hit));
|
||||||
|
memset(g_game_touch_active, 0, sizeof(g_game_touch_active));
|
||||||
memset(g_menu_prev_hit, 0, sizeof(g_menu_prev_hit));
|
memset(g_menu_prev_hit, 0, sizeof(g_menu_prev_hit));
|
||||||
memset(g_menu_touch_active, 0, sizeof(g_menu_touch_active));
|
memset(g_menu_touch_active, 0, sizeof(g_menu_touch_active));
|
||||||
memset(g_keyboard_prev_hit, 0, sizeof(g_keyboard_prev_hit));
|
memset(g_keyboard_prev_hit, 0, sizeof(g_keyboard_prev_hit));
|
||||||
|
|||||||
@@ -1,16 +1,25 @@
|
|||||||
// Controller input for questshock's immersive Quest build: one OpenXR
|
// Controller input for questshock's immersive Quest build: one OpenXR
|
||||||
// action set (aim pose + trigger click per hand, a menu-toggle button on
|
// action set (aim pose + trigger click per hand, a menu-toggle button on
|
||||||
// the left controller) plus ray/quad hit-testing. While neither the menu
|
// the left controller) plus ray/quad hit-testing. Each hand's ray is
|
||||||
// launcher nor keyboard overlay (see xr_overlay.h, xr_session.c) is
|
// tried against the keyboard overlay, then the menu overlay (see
|
||||||
// visible, this tests against the game quad xr_session.c submits and draws
|
// xr_overlay.h, xr_session.c), then - only if neither actually claims it
|
||||||
// a small reticle where each hand's aim ray crosses it; while either
|
// this frame (landing on it now, or continuing a touch/drag begun on a
|
||||||
// overlay is visible, it tests against that overlay's quad instead (the
|
// previous frame - not merely because that overlay happens to be visible)
|
||||||
// keyboard is tried first), forwards trigger edges as synthetic touches,
|
// - the game quad xr_session.c submits, forwarding trigger edges to
|
||||||
// and - since neither overlay's own on-quad cursor gives any feedback
|
// whichever overlay claims the ray as synthetic touches. The game quad
|
||||||
// while the ray is short of actually landing on it - builds a thin,
|
// itself acts as a plain desktop-style mouse pointer into the engine's
|
||||||
|
// own UI (see xr_mouse.h) - absolute cursor position while the ray hits
|
||||||
|
// it, plus a left click on the trigger edge; a pending click there
|
||||||
|
// likewise keeps claiming the ray ahead of the keyboard/menu until
|
||||||
|
// released, so the eventual mouse-up isn't lost if the ray strays. Since
|
||||||
|
// none of the three targets' own feedback (the overlays' on-quad cursor,
|
||||||
|
// drawn by their Java views; the engine's own mouse cursor sprite for the
|
||||||
|
// game quad) gives any indication while the ray is short of actually
|
||||||
|
// landing on something, every claimed target also builds a thin,
|
||||||
// billboarded laser-beam quad per hand from the controller to wherever
|
// billboarded laser-beam quad per hand from the controller to wherever
|
||||||
// the ray currently crosses that overlay's plane (see
|
// the ray currently crosses that target's plane (see
|
||||||
// xr_input_get_beam_layer()).
|
// xr_input_get_beam_layer()) - so aiming looks and behaves the same
|
||||||
|
// whether the target is an overlay or the game quad itself.
|
||||||
#ifndef QUESTSHOCK_XR_INPUT_H
|
#ifndef QUESTSHOCK_XR_INPUT_H
|
||||||
#define QUESTSHOCK_XR_INPUT_H
|
#define QUESTSHOCK_XR_INPUT_H
|
||||||
|
|
||||||
@@ -38,17 +47,18 @@ bool xr_input_init(XrInstance instance, XrSession session, int64_t swapchain_for
|
|||||||
// Call once per frame from xr_frame_end(), before releasing the acquired
|
// Call once per frame from xr_frame_end(), before releasing the acquired
|
||||||
// swapchain image - syncs this frame's action states (always, so edge
|
// swapchain image - syncs this frame's action states (always, so edge
|
||||||
// detection stays correct even on frames with nothing to draw), handles
|
// detection stays correct even on frames with nothing to draw), handles
|
||||||
// the menu_toggle button's edge, and either draws a game-quad reticle or
|
// the menu_toggle button's edge, and hit-tests/dispatches each hand's ray
|
||||||
// forwards menu-quad touches, per the menu's current visibility (see the
|
// against keyboard/menu/game quad in that priority order (see the file
|
||||||
// file comment above). draw gates only the reticle - if false (nothing to
|
// comment above). draw gates only the laser-beam visuals (and the
|
||||||
// draw into this frame, e.g. no swapchain image was acquired), hit-testing
|
// head-pose locate that feeds them) - if false (nothing to draw into this
|
||||||
// and touch-forwarding still run. baseSpace/time must match whatever
|
// frame, e.g. no swapchain image was acquired), hit-testing and
|
||||||
|
// touch-forwarding still run. baseSpace/time must match whatever
|
||||||
// xr_frame_begin() used to predict this frame.
|
// xr_frame_begin() used to predict this frame.
|
||||||
void xr_input_sync_and_draw(XrSpace baseSpace, XrTime time, bool draw);
|
void xr_input_sync_and_draw(XrSpace baseSpace, XrTime time, bool draw);
|
||||||
|
|
||||||
// Call once per hand (hand: 0=left, 1=right) after xr_input_sync_and_draw()
|
// Call once per hand (hand: 0=left, 1=right) after xr_input_sync_and_draw()
|
||||||
// in the same frame - if that hand's ray claimed the keyboard or menu
|
// in the same frame - if that hand's ray claimed the keyboard, menu, or
|
||||||
// overlay this frame (see the file comment above), fills *out_quad's
|
// game quad this frame (see the file comment above), fills *out_quad's
|
||||||
// subImage/pose/size for its laser-beam ribbon and returns true.
|
// subImage/pose/size for its laser-beam ribbon and returns true.
|
||||||
// space/eyeVisibility/layerFlags are left for the caller to set, same
|
// space/eyeVisibility/layerFlags are left for the caller to set, same
|
||||||
// convention as xr_overlay_render_and_build_layer(). Returns false
|
// convention as xr_overlay_render_and_build_layer(). Returns false
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#include "xr_mouse.h"
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
|
||||||
|
#include "mouse.h" // engine/src/Libraries/INPUT/Source/mouse.h - mouse_put_xy()
|
||||||
|
|
||||||
|
void xr_mouse_move(int x, int y) { mouse_put_xy((short)x, (short)y); }
|
||||||
|
|
||||||
|
void xr_mouse_click(bool down) {
|
||||||
|
SDL_Event event;
|
||||||
|
SDL_zero(event);
|
||||||
|
event.type = down ? SDL_MOUSEBUTTONDOWN : SDL_MOUSEBUTTONUP;
|
||||||
|
event.button.timestamp = SDL_GetTicks();
|
||||||
|
event.button.windowID = 0;
|
||||||
|
event.button.which = 0;
|
||||||
|
event.button.button = SDL_BUTTON_LEFT;
|
||||||
|
event.button.state = down ? SDL_PRESSED : SDL_RELEASED;
|
||||||
|
event.button.clicks = 1;
|
||||||
|
SDL_PushEvent(&event);
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// Bridges VR aim-ray hit-testing (xr_input.c) to the vendored engine's
|
||||||
|
// mouse input (engine/src/Libraries/INPUT/Source/mouse.h) - lets the
|
||||||
|
// controller's laser pointer drive the game's own UI (inventory, menus,
|
||||||
|
// dialogs) exactly like a desktop mouse: absolute cursor position plus a
|
||||||
|
// single left button, no relative/mouselook mode, no right button.
|
||||||
|
#ifndef QUESTSHOCK_XR_MOUSE_H
|
||||||
|
#define QUESTSHOCK_XR_MOUSE_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Sets the engine's absolute mouse position (mouse_put_xy()) - call every
|
||||||
|
// frame the aim ray is actually hitting the game quad; x/y are pixel
|
||||||
|
// coordinates in the game's own logical resolution (see
|
||||||
|
// xr_get_game_resolution() in xr_session.h), already clamped by the
|
||||||
|
// caller. Do not call on frames the ray isn't hitting the game quad - the
|
||||||
|
// cursor should hold its last position, not snap elsewhere.
|
||||||
|
void xr_mouse_move(int x, int y);
|
||||||
|
|
||||||
|
// Pushes a synthetic SDL_MOUSEBUTTONDOWN/UP (SDL_BUTTON_LEFT) - matches
|
||||||
|
// KeyboardOverlay's nativeSendPrintableChar() in questshock_native.c: a
|
||||||
|
// plain SDL_Event built by hand and handed to the public SDL_PushEvent(),
|
||||||
|
// no engine patch needed. pump_events() (sdl_events.c) reads whatever
|
||||||
|
// mouse_put_xy() last set, not any x/y carried on the event itself - call
|
||||||
|
// xr_mouse_move() first if position needs updating this frame.
|
||||||
|
void xr_mouse_click(bool down);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -350,14 +350,13 @@ void xr_frame_end(void) {
|
|||||||
if (g_session == XR_NULL_HANDLE)
|
if (g_session == XR_NULL_HANDLE)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Draw the laser-pointer reticle into the still-bound game swapchain
|
// Syncs actions and hit-tests/dispatches each hand's ray against
|
||||||
// framebuffer before releasing it, so it composites on top of whatever
|
// keyboard/menu/game quad (see xr_input.c) before the game swapchain
|
||||||
// this frame's game content already drew there. Syncing actions
|
// image is released below - this always runs, even when there's
|
||||||
// happens even when there's nothing to draw (no acquired image this
|
// nothing to draw (no acquired image this frame), so edge detection
|
||||||
// frame), so edge detection (trigger/menu-button clicks) doesn't miss a
|
// (trigger/menu-button clicks) doesn't miss a frame. Any resulting
|
||||||
// frame. Only ever draws while neither overlay is visible (see
|
// laser-beam quads are submitted as their own composition layers
|
||||||
// xr_input.c) - the menu/keyboard render into their own independent
|
// further down, not drawn into the game swapchain itself.
|
||||||
// swapchain images below.
|
|
||||||
if (xr_is_session_running())
|
if (xr_is_session_running())
|
||||||
xr_input_sync_and_draw(g_local_space, g_predicted_display_time,
|
xr_input_sync_and_draw(g_local_space, g_predicted_display_time,
|
||||||
g_have_acquired_game_image);
|
g_have_acquired_game_image);
|
||||||
@@ -374,8 +373,8 @@ void xr_frame_end(void) {
|
|||||||
// xr_overlay_render_and_build_layer()) any time before xrEndFrame,
|
// xr_overlay_render_and_build_layer()) any time before xrEndFrame,
|
||||||
// unlike the game quad there's no per-frame engine rendering to wrap
|
// unlike the game quad there's no per-frame engine rendering to wrap
|
||||||
// around here, just each overlay's own blit - and, per hand, a
|
// around here, just each overlay's own blit - and, per hand, a
|
||||||
// laser-beam quad while that hand's ray is aimed at one of those two
|
// laser-beam quad while that hand's ray is aimed at the keyboard,
|
||||||
// overlays (see xr_input_get_beam_layer()).
|
// menu, or the game quad itself (see xr_input_get_beam_layer()).
|
||||||
XrCompositionLayerQuad gameQuad = {XR_TYPE_COMPOSITION_LAYER_QUAD};
|
XrCompositionLayerQuad gameQuad = {XR_TYPE_COMPOSITION_LAYER_QUAD};
|
||||||
gameQuad.space = g_local_space;
|
gameQuad.space = g_local_space;
|
||||||
gameQuad.eyeVisibility = XR_EYE_VISIBILITY_BOTH;
|
gameQuad.eyeVisibility = XR_EYE_VISIBILITY_BOTH;
|
||||||
@@ -470,3 +469,8 @@ void xr_get_game_quad_extent(float *distance_m, float *half_width_m, float *half
|
|||||||
*half_height_m =
|
*half_height_m =
|
||||||
QUAD_WIDTH_METERS * 0.5f * (float)g_game_swapchain.height / (float)g_game_swapchain.width;
|
QUAD_WIDTH_METERS * 0.5f * (float)g_game_swapchain.height / (float)g_game_swapchain.width;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void xr_get_game_resolution(int *width, int *height) {
|
||||||
|
*width = g_game_swapchain.width;
|
||||||
|
*height = g_game_swapchain.height;
|
||||||
|
}
|
||||||
|
|||||||
@@ -74,6 +74,13 @@ void xr_shutdown(void);
|
|||||||
// laser pointer always matches whatever's actually visible.
|
// laser pointer always matches whatever's actually visible.
|
||||||
void xr_get_game_quad_extent(float *distance_m, float *half_width_m, float *half_height_m);
|
void xr_get_game_quad_extent(float *distance_m, float *half_width_m, float *half_height_m);
|
||||||
|
|
||||||
|
// Pixel resolution of the game quad's own swapchain - i.e. game_width/
|
||||||
|
// game_height as passed to xr_init() (grd_cap->w/h, see Shock.c's
|
||||||
|
// InitSDL()) - the logical coordinate space xr_input.c needs to convert a
|
||||||
|
// game-quad ray hit's normalized u,v into engine mouse coordinates (see
|
||||||
|
// xr_mouse.h).
|
||||||
|
void xr_get_game_resolution(int *width, int *height);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -146,8 +146,7 @@ bool xr_swapchain_acquire(XrInstance instance, XrSwapchainState *state) {
|
|||||||
// gl4es's own (linked, not dlsym'd) bind - this targets an FBO id gl4es
|
// gl4es's own (linked, not dlsym'd) bind - this targets an FBO id gl4es
|
||||||
// itself created (see xr_swapchain_create()), so its own "current FBO"
|
// itself created (see xr_swapchain_create()), so its own "current FBO"
|
||||||
// bookkeeping updates correctly and its immediate-mode draw calls
|
// bookkeeping updates correctly and its immediate-mode draw calls
|
||||||
// (android_draw_surface_as_quad(), the laser reticle) land in the right
|
// (android_draw_surface_as_quad()) land in the right place.
|
||||||
// place.
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, state->fbos[imageIndex]);
|
glBindFramebuffer(GL_FRAMEBUFFER, state->fbos[imageIndex]);
|
||||||
glViewport(0, 0, state->width, state->height);
|
glViewport(0, 0, state->width, state->height);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
+113
-3
@@ -18,6 +18,13 @@ ARG FLUIDSYNTH_LITE_REF=c539a8d9270ba5a3f7d6e460606483fc2ab1eb61
|
|||||||
# Soundfont used for MIDI music, matching what engine/build_deps.sh itself
|
# Soundfont used for MIDI music, matching what engine/build_deps.sh itself
|
||||||
# fetches (a free substitute for the Windows default GM soundfont).
|
# fetches (a free substitute for the Windows default GM soundfont).
|
||||||
ARG SOUNDFONT_URL=http://rancid.kapsi.fi/windows.sf2
|
ARG SOUNDFONT_URL=http://rancid.kapsi.fi/windows.sf2
|
||||||
|
# GLEW, for the Windows/MinGW cross-build only (engine/CMakeLists.txt's
|
||||||
|
# WIN32 branch - Windows' own opengl32.dll only exposes OpenGL 1.1, so
|
||||||
|
# anything newer needs GLEW's runtime extension loading; Linux instead
|
||||||
|
# gets modern prototypes straight from Mesa's headers, no loader needed -
|
||||||
|
# see engine/src/MacSrc/OpenGL.cc). Matches the version engine/'s own
|
||||||
|
# upstream Windows build script (build_win64.sh) used.
|
||||||
|
ARG GLEW_VERSION=2.1.0
|
||||||
# Gitea/GitHub Actions' JS-based actions (actions/checkout,
|
# Gitea/GitHub Actions' JS-based actions (actions/checkout,
|
||||||
# actions/upload-artifact, ...) need a node binary in the container job's
|
# actions/upload-artifact, ...) need a node binary in the container job's
|
||||||
# PATH - this image is otherwise pure C toolchain, so it isn't pulled in
|
# PATH - this image is otherwise pure C toolchain, so it isn't pulled in
|
||||||
@@ -99,6 +106,15 @@ ENV DEBIAN_FRONTEND=noninteractive
|
|||||||
# openssh-client: the CI workflow's `sftp` publish step.
|
# openssh-client: the CI workflow's `sftp` publish step.
|
||||||
# openjdk-17-jdk-headless: Gradle/AGP's own minimum JDK for the APK build.
|
# openjdk-17-jdk-headless: Gradle/AGP's own minimum JDK for the APK build.
|
||||||
# unzip: extracts the Android cmdline-tools zip below.
|
# unzip: extracts the Android cmdline-tools zip below.
|
||||||
|
# mingw-w64: the x86_64-w64-mingw32-{gcc,g++,windres,ar,...} cross
|
||||||
|
# toolchain for the Windows desktop build (see the "Windows cross-compile"
|
||||||
|
# section below). Ubuntu ships both a win32-thread-model and a
|
||||||
|
# posix-thread-model variant behind update-alternatives; the default
|
||||||
|
# (win32) is fine here since nothing in engine/ uses std::thread.
|
||||||
|
# zip: `make package-win`'s Windows release archive - a CI job's own
|
||||||
|
# `make package` runs inside this image as its container (see
|
||||||
|
# .gitea/workflows/build.yml), so it needs to be baked in here, not just
|
||||||
|
# available on a local dev machine's own host (see README).
|
||||||
#
|
#
|
||||||
# All apt installs deliberately live in this one RUN, first, so editing
|
# All apt installs deliberately live in this one RUN, first, so editing
|
||||||
# anything below it (in particular the Android cross-compile steps, the
|
# anything below it (in particular the Android cross-compile steps, the
|
||||||
@@ -111,7 +127,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
libgl1-mesa-dev libglx-dev libxext-dev libx11-dev libxrandr-dev \
|
libgl1-mesa-dev libglx-dev libxext-dev libx11-dev libxrandr-dev \
|
||||||
libxi-dev libxfixes-dev libxss-dev libxinerama-dev libxcursor-dev \
|
libxi-dev libxfixes-dev libxss-dev libxinerama-dev libxcursor-dev \
|
||||||
libogg-dev libvorbis-dev libasound2-dev openssh-client \
|
libogg-dev libvorbis-dev libasound2-dev openssh-client \
|
||||||
openjdk-17-jdk-headless unzip \
|
openjdk-17-jdk-headless unzip mingw-w64 zip \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /opt/prebuilt
|
WORKDIR /opt/prebuilt
|
||||||
@@ -150,10 +166,96 @@ RUN git clone https://github.com/EtherTyper/fluidsynth-lite.git \
|
|||||||
&& rm -rf .git
|
&& rm -rf .git
|
||||||
|
|
||||||
# General MIDI soundfont for fluidsynth playback - engine/build_deps.sh
|
# General MIDI soundfont for fluidsynth playback - engine/build_deps.sh
|
||||||
# fetches the same file and drops it into engine/res/.
|
# fetches the same file and drops it into engine/res/. Shared by both the
|
||||||
|
# Linux and Windows builds below.
|
||||||
RUN mkdir -p soundfont \
|
RUN mkdir -p soundfont \
|
||||||
&& curl -sSL -o soundfont/default.sf2 "${SOUNDFONT_URL}"
|
&& curl -sSL -o soundfont/default.sf2 "${SOUNDFONT_URL}"
|
||||||
|
|
||||||
|
# CMake toolchain file for the Windows/MinGW cross-build below, reused at
|
||||||
|
# container-run time by build-image/build-engine-win.sh (see
|
||||||
|
# MINGW_TOOLCHAIN_FILE) to cross-compile engine/ itself the same way. GCC's
|
||||||
|
# runtime (libgcc/libstdc++) is linked statically so only the SDL2/
|
||||||
|
# SDL2_mixer/GLEW/fluidsynth-lite DLLs (plus libwinpthread, which isn't
|
||||||
|
# safe to static-link the same way) need shipping alongside systemshock.exe.
|
||||||
|
ENV MINGW_TOOLCHAIN_FILE=/opt/mingw-toolchain.cmake
|
||||||
|
RUN printf '%s\n' \
|
||||||
|
'set(CMAKE_SYSTEM_NAME Windows)' \
|
||||||
|
'set(CMAKE_SYSTEM_PROCESSOR x86_64)' \
|
||||||
|
'set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)' \
|
||||||
|
'set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)' \
|
||||||
|
'set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)' \
|
||||||
|
'set(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32)' \
|
||||||
|
'# LIBRARY/INCLUDE/PACKAGE deliberately left at CMake'"'"'s own' \
|
||||||
|
'# cross-compiling default (BOTH) - engine/CMakeLists.txt'"'"'s' \
|
||||||
|
'# BUNDLED SDL2/SDL2_mixer/FluidSynth find_library() calls point at' \
|
||||||
|
'# build_ext/ (outside this sysroot entirely), which ONLY would' \
|
||||||
|
'# refuse to search.' \
|
||||||
|
'set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)' \
|
||||||
|
'set(CMAKE_EXE_LINKER_FLAGS_INIT "-static-libgcc -static-libstdc++")' \
|
||||||
|
> "${MINGW_TOOLCHAIN_FILE}"
|
||||||
|
|
||||||
|
# SDL2 and SDL2_mixer for Windows: unlike the desktop build above, these
|
||||||
|
# are libsdl.org's own official prebuilt MinGW devel packages (headers +
|
||||||
|
# import libs + DLLs for x86_64-w64-mingw32), not built from source here -
|
||||||
|
# SDL2's autotools setup targets *nix; upstream ships MinGW builds
|
||||||
|
# pre-made instead, same as engine/'s own upstream Windows build script
|
||||||
|
# (build_win64.sh) uses. Same version pins as the Linux build above, so
|
||||||
|
# both desktop builds ship the same SDL2/SDL2_mixer release.
|
||||||
|
RUN curl -sSLO "https://www.libsdl.org/release/SDL2-devel-${SDL2_VERSION}-mingw.tar.gz" \
|
||||||
|
&& tar xf "SDL2-devel-${SDL2_VERSION}-mingw.tar.gz" \
|
||||||
|
&& mkdir -p /opt/prebuilt/win \
|
||||||
|
&& mv "SDL2-${SDL2_VERSION}/x86_64-w64-mingw32" /opt/prebuilt/win/sdl2 \
|
||||||
|
&& rm -rf "SDL2-${SDL2_VERSION}" "SDL2-devel-${SDL2_VERSION}-mingw.tar.gz"
|
||||||
|
|
||||||
|
RUN curl -sSLO "https://www.libsdl.org/projects/SDL_mixer/release/SDL2_mixer-devel-${SDL2_MIXER_VERSION}-mingw.tar.gz" \
|
||||||
|
&& tar xf "SDL2_mixer-devel-${SDL2_MIXER_VERSION}-mingw.tar.gz" --exclude=Xcode \
|
||||||
|
&& mv "SDL2_mixer-${SDL2_MIXER_VERSION}/x86_64-w64-mingw32" /opt/prebuilt/win/sdl2_mixer \
|
||||||
|
&& rm -rf "SDL2_mixer-${SDL2_MIXER_VERSION}" "SDL2_mixer-devel-${SDL2_MIXER_VERSION}-mingw.tar.gz"
|
||||||
|
|
||||||
|
# GLEW for Windows: compiled directly instead of via GLEW's own bundled
|
||||||
|
# cross-compile Makefile configs (config/Makefile.linux-mingw64 et al) -
|
||||||
|
# those pass raw `-soname`/`--out-implib` straight to whatever $(LD) is
|
||||||
|
# set to, which only works if LD is the real `ld` binary, not gcc-as-
|
||||||
|
# linker-driver (and that config also hardcodes a 32-bit `i686-w64-
|
||||||
|
# mingw32` host despite the "64" in its name) - simpler and more robust
|
||||||
|
# to just compile+link GLEW's one source file ourselves. Produces
|
||||||
|
# glew32.dll (to ship alongside systemshock.exe) and libglew32.dll.a (the
|
||||||
|
# MinGW import library engine/CMakeLists.txt's WIN32 branch links
|
||||||
|
# against).
|
||||||
|
RUN curl -sSL -o "glew-${GLEW_VERSION}.tgz" \
|
||||||
|
"https://sourceforge.net/projects/glew/files/glew/${GLEW_VERSION}/glew-${GLEW_VERSION}.tgz/download" \
|
||||||
|
&& tar xf "glew-${GLEW_VERSION}.tgz" \
|
||||||
|
&& cd "glew-${GLEW_VERSION}" \
|
||||||
|
&& mkdir -p /opt/prebuilt/win/glew/include/GL /opt/prebuilt/win/glew/lib \
|
||||||
|
&& x86_64-w64-mingw32-gcc -DGLEW_NO_GLU -O2 -Iinclude -c src/glew.c -o glew.o \
|
||||||
|
&& x86_64-w64-mingw32-gcc -shared \
|
||||||
|
-Wl,--out-implib,/opt/prebuilt/win/glew/lib/libglew32.dll.a \
|
||||||
|
-o /opt/prebuilt/win/glew/lib/glew32.dll \
|
||||||
|
glew.o -lopengl32 -lgdi32 -luser32 -lkernel32 \
|
||||||
|
&& cp include/GL/glew.h include/GL/wglew.h /opt/prebuilt/win/glew/include/GL/ \
|
||||||
|
&& cd .. && rm -rf "glew-${GLEW_VERSION}" "glew-${GLEW_VERSION}.tgz"
|
||||||
|
|
||||||
|
# fluidsynth-lite for Windows: same source/ref/DLL-mode patch as the
|
||||||
|
# desktop build above, cross-compiled via the MinGW toolchain file. WIN32
|
||||||
|
# skips fluidsynth-lite's own pthread dependency (see its CMakeLists.txt),
|
||||||
|
# so no libwinpthread linkage to worry about here.
|
||||||
|
RUN git clone https://github.com/EtherTyper/fluidsynth-lite.git fluidsynth-lite-win \
|
||||||
|
&& cd fluidsynth-lite-win \
|
||||||
|
&& git checkout "${FLUIDSYNTH_LITE_REF}" \
|
||||||
|
&& sed -i 's/DLL"\ off/DLL"\ on/' CMakeLists.txt \
|
||||||
|
&& rm -rf .git \
|
||||||
|
&& cd .. \
|
||||||
|
&& cmake -S fluidsynth-lite-win -B build-fluidsynth-win \
|
||||||
|
-DCMAKE_TOOLCHAIN_FILE="${MINGW_TOOLCHAIN_FILE}" \
|
||||||
|
&& cmake --build build-fluidsynth-win -j"$(nproc)" \
|
||||||
|
&& mkdir -p /opt/prebuilt/win/fluidsynth-lite/lib /opt/prebuilt/win/fluidsynth-lite/include \
|
||||||
|
&& cp build-fluidsynth-win/src/*.dll build-fluidsynth-win/src/*.dll.a \
|
||||||
|
/opt/prebuilt/win/fluidsynth-lite/lib/ \
|
||||||
|
&& cp -a fluidsynth-lite-win/include/. /opt/prebuilt/win/fluidsynth-lite/include/ \
|
||||||
|
&& cp build-fluidsynth-win/include/fluidsynth/version.h \
|
||||||
|
/opt/prebuilt/win/fluidsynth-lite/include/fluidsynth/version.h \
|
||||||
|
&& rm -rf fluidsynth-lite-win build-fluidsynth-win
|
||||||
|
|
||||||
# Node.js: needed only so Gitea/GitHub Actions' JS-based actions can run
|
# Node.js: needed only so Gitea/GitHub Actions' JS-based actions can run
|
||||||
# when this image is used as a CI job's container - see NODE_VERSION above.
|
# when this image is used as a CI job's container - see NODE_VERSION above.
|
||||||
RUN curl -sSL -o /tmp/node.tar.xz \
|
RUN curl -sSL -o /tmp/node.tar.xz \
|
||||||
@@ -283,7 +385,15 @@ RUN git clone --branch "release-${ANDROID_OPENXR_VERSION}" --depth 1 \
|
|||||||
|
|
||||||
COPY build-image/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
COPY build-image/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
||||||
COPY build-image/build-engine.sh /usr/local/bin/build-engine.sh
|
COPY build-image/build-engine.sh /usr/local/bin/build-engine.sh
|
||||||
RUN chmod +x /usr/local/bin/docker-entrypoint.sh /usr/local/bin/build-engine.sh
|
# 755, not +x: the entrypoint drops to a `builder` user matching the
|
||||||
|
# *host's* UID/GID (see docker-entrypoint.sh), which is never in these
|
||||||
|
# root-owned files' group, so it needs the image's own explicit
|
||||||
|
# world-read+execute here - `chmod +x` alone only adds execute bits on
|
||||||
|
# top of whatever "other" permissions the source file happened to have
|
||||||
|
# (which depends on the host's umask at checkout, e.g. a restrictive
|
||||||
|
# 0007 umask yields unreadable-by-other files, which then round-trip
|
||||||
|
# into the image and cause a Permission denied at container run time).
|
||||||
|
RUN chmod 755 /usr/local/bin/docker-entrypoint.sh /usr/local/bin/build-engine.sh
|
||||||
|
|
||||||
# Marks a shell as already running inside this image (with every engine
|
# Marks a shell as already running inside this image (with every engine
|
||||||
# build dependency prebuilt above) - lets the Makefile's `engine` target
|
# build dependency prebuilt above) - lets the Makefile's `engine` target
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Cross-compiles engine/ (the vendored Shockolate snapshot) for Windows
|
||||||
|
# (x86_64) via MinGW, against the dependencies prebuilt into this image at
|
||||||
|
# /opt/prebuilt/win - no network access needed. engine/CMakeLists.txt
|
||||||
|
# already has working WIN32/MINGW branches (the upstream Shockolate
|
||||||
|
# project's own build_win64.sh/appveyor.yml build the same way, natively
|
||||||
|
# on Windows via Git Bash/MinGW; this cross-compiles the same branches
|
||||||
|
# fully offline and reproducibly from Linux instead), so no source
|
||||||
|
# patching is needed, unlike the Quest build's android/engine-patches/.
|
||||||
|
#
|
||||||
|
# Builds from a scratch copy of engine/ (like android/engine-patches/
|
||||||
|
# does for the Quest build) rather than engine/ itself: its BUNDLED
|
||||||
|
# dependency paths (build_ext/built_sdl etc.) and in-source CMake cache
|
||||||
|
# are relative to/inside the source tree, and would otherwise collide
|
||||||
|
# with the Linux desktop build's own build_ext/CMakeCache.txt (see
|
||||||
|
# build-engine.sh) if both are built from the same checkout, as `make
|
||||||
|
# dist` (dist-linux + dist-win) does.
|
||||||
|
#
|
||||||
|
# Must be run with the repo root as the working directory - either via
|
||||||
|
# ../run-image.sh, or directly when already inside this image (see the
|
||||||
|
# Makefile's `engine-win` target, which picks whichever of these
|
||||||
|
# applies, same as `engine`).
|
||||||
|
#
|
||||||
|
# Output lands in engine/.build-output-win/: systemshock.exe, the DLLs
|
||||||
|
# it needs at runtime, and a default MIDI soundfont - everything the
|
||||||
|
# root Makefile needs to assemble dist-win/.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPO_ROOT="$(pwd)"
|
||||||
|
SCRATCH_ENGINE="$REPO_ROOT/build/win-engine"
|
||||||
|
OUT_DIR="$REPO_ROOT/engine/.build-output-win"
|
||||||
|
|
||||||
|
echo "== Preparing a scratch copy of engine/ for the Windows cross-build =="
|
||||||
|
rm -rf "$SCRATCH_ENGINE"
|
||||||
|
mkdir -p "$(dirname "$SCRATCH_ENGINE")"
|
||||||
|
|
||||||
|
# See build-engine.sh for why this detection is needed (e.g. a VirtualBox
|
||||||
|
# vboxsf shared folder refusing to create symlinks or preserve hard-link
|
||||||
|
# relationships) - applied here too since build/ is under the same
|
||||||
|
# bind-mounted repo checkout.
|
||||||
|
CP_FLAGS=(-a)
|
||||||
|
mkdir -p "$(dirname "$SCRATCH_ENGINE")/.symlink-test-dir"
|
||||||
|
if ! ln -s test-target "$(dirname "$SCRATCH_ENGINE")/.symlink-test-dir/test" 2>/dev/null; then
|
||||||
|
echo "(destination filesystem doesn't support symlinks - copying real file content instead)"
|
||||||
|
CP_FLAGS=(-a --dereference --no-preserve=links)
|
||||||
|
fi
|
||||||
|
rm -rf "$(dirname "$SCRATCH_ENGINE")/.symlink-test-dir"
|
||||||
|
|
||||||
|
cp "${CP_FLAGS[@]}" "$REPO_ROOT/engine" "$SCRATCH_ENGINE"
|
||||||
|
cd "$SCRATCH_ENGINE"
|
||||||
|
|
||||||
|
# If engine/ has ever been built in-source directly (`make dist`/`make
|
||||||
|
# engine` - see build-engine.sh), that leftover CMakeCache.txt etc. just
|
||||||
|
# got copied along verbatim - and it still points at the real engine/
|
||||||
|
# path, not this scratch copy, which CMake refuses to configure against
|
||||||
|
# ("directory is different than the directory where CMakeCache.txt was
|
||||||
|
# created"). Same artifact list as .gitignore's "Engine build artifacts"
|
||||||
|
# section.
|
||||||
|
rm -rf build_ext CMakeCache.txt CMakeFiles cmake_install.cmake Makefile \
|
||||||
|
systemshock src/Libraries/CMakeFiles
|
||||||
|
|
||||||
|
echo "== Wiring up prebuilt SDL2/SDL2_mixer/GLEW/fluidsynth-lite (Windows/MinGW) from the image =="
|
||||||
|
rm -rf build_ext
|
||||||
|
mkdir -p build_ext
|
||||||
|
cp "${CP_FLAGS[@]}" /opt/prebuilt/win/sdl2 build_ext/built_sdl
|
||||||
|
cp "${CP_FLAGS[@]}" /opt/prebuilt/win/sdl2_mixer build_ext/built_sdl_mixer
|
||||||
|
cp "${CP_FLAGS[@]}" /opt/prebuilt/win/glew build_ext/built_glew
|
||||||
|
# engine/CMakeLists.txt's BUNDLED FluidSynth mode hardcodes
|
||||||
|
# build_ext/fluidsynth-lite/src as the library search path (matching the
|
||||||
|
# desktop Linux in-source build's own output layout) - same lib/ -> src/
|
||||||
|
# remapping build-image/prepare-android-project.sh does for the Quest
|
||||||
|
# build's own prebuilt fluidsynth-lite.
|
||||||
|
mkdir -p build_ext/fluidsynth-lite/src build_ext/fluidsynth-lite/include
|
||||||
|
cp "${CP_FLAGS[@]}" /opt/prebuilt/win/fluidsynth-lite/lib/. build_ext/fluidsynth-lite/src/
|
||||||
|
cp "${CP_FLAGS[@]}" /opt/prebuilt/win/fluidsynth-lite/include/. build_ext/fluidsynth-lite/include/
|
||||||
|
|
||||||
|
echo "== Configuring (CMake, MinGW cross-compile, BUNDLED SDL2/SDL2_mixer/FluidSynth) =="
|
||||||
|
cmake -DCMAKE_TOOLCHAIN_FILE="$MINGW_TOOLCHAIN_FILE" \
|
||||||
|
-DENABLE_SDL2=BUNDLED -DENABLE_SOUND=BUNDLED -DENABLE_FLUIDSYNTH=BUNDLED .
|
||||||
|
|
||||||
|
echo "== Compiling =="
|
||||||
|
make -j"$(nproc)" systemshock
|
||||||
|
|
||||||
|
echo "== Assembling engine/.build-output-win =="
|
||||||
|
rm -rf "$OUT_DIR"
|
||||||
|
mkdir -p "$OUT_DIR"
|
||||||
|
cp systemshock.exe "$OUT_DIR/"
|
||||||
|
# Only SDL2.dll/SDL2_mixer.dll themselves - not SDL2_mixer's own bundled
|
||||||
|
# codec DLLs (libvorbis, libmodplug, libopus, ...), matching what
|
||||||
|
# engine/'s own upstream Windows build script (build_win64.sh) ships:
|
||||||
|
# Shockolate only ever calls Mix_LoadWAV_RW/Mix_HookMusic (same as the
|
||||||
|
# Quest build - see build-image/Dockerfile's Android layer comment),
|
||||||
|
# never loading the OGG/MOD/MP3 game data those codecs would be for.
|
||||||
|
cp build_ext/built_sdl/bin/SDL2.dll build_ext/built_sdl_mixer/bin/SDL2_mixer.dll "$OUT_DIR/"
|
||||||
|
cp build_ext/built_glew/lib/glew32.dll "$OUT_DIR/"
|
||||||
|
cp build_ext/fluidsynth-lite/src/*.dll "$OUT_DIR/"
|
||||||
|
# libgcc/libstdc++ are statically linked (see the Dockerfile's
|
||||||
|
# MINGW_TOOLCHAIN_FILE), but fluidsynth-lite/SDL2 still pull in the
|
||||||
|
# MinGW pthread emulation dynamically.
|
||||||
|
cp /usr/x86_64-w64-mingw32/lib/libwinpthread-1.dll "$OUT_DIR/"
|
||||||
|
cp /opt/prebuilt/soundfont/default.sf2 "$OUT_DIR/soundfont.sf2"
|
||||||
|
|
||||||
|
echo "== Done =="
|
||||||
|
echo "Binary: $OUT_DIR/systemshock.exe"
|
||||||
|
echo "DLLs: $OUT_DIR/*.dll"
|
||||||
|
echo "Soundfont: $OUT_DIR/soundfont.sf2"
|
||||||
@@ -22,9 +22,28 @@ cd "$ENGINE_DIR"
|
|||||||
echo "== Wiring up prebuilt SDL2/SDL2_mixer/fluidsynth-lite from the image =="
|
echo "== Wiring up prebuilt SDL2/SDL2_mixer/fluidsynth-lite from the image =="
|
||||||
rm -rf build_ext
|
rm -rf build_ext
|
||||||
mkdir -p build_ext
|
mkdir -p build_ext
|
||||||
cp -a /opt/prebuilt/built_sdl build_ext/
|
|
||||||
cp -a /opt/prebuilt/built_sdl_mixer build_ext/
|
# Some filesystems the repo might be checked out on (e.g. a VirtualBox
|
||||||
cp -a /opt/prebuilt/fluidsynth-lite build_ext/
|
# vboxsf shared folder) refuse to create symlinks at all ("Operation not
|
||||||
|
# permitted"), which plain `cp -a` needs for SDL2/SDL2_mixer's
|
||||||
|
# libFoo.so -> libFoo.so.N -> libFoo.so.N.M dev-symlink chain. Detect
|
||||||
|
# that up front, once, and copy real file content instead of recreating
|
||||||
|
# links if so, rather than failing partway through. `-a` implies
|
||||||
|
# --preserve=all (which includes hard-link relationships between files,
|
||||||
|
# not just symlinks - SDL2's install hard-links the two most-specific
|
||||||
|
# version files together), so --no-preserve=links is needed alongside
|
||||||
|
# --dereference to avoid that too.
|
||||||
|
CP_FLAGS=(-a)
|
||||||
|
if ! ln -s test-target build_ext/.symlink-test 2>/dev/null; then
|
||||||
|
echo "(destination filesystem doesn't support symlinks - copying real file content instead)"
|
||||||
|
CP_FLAGS=(-a --dereference --no-preserve=links)
|
||||||
|
else
|
||||||
|
rm -f build_ext/.symlink-test
|
||||||
|
fi
|
||||||
|
|
||||||
|
cp "${CP_FLAGS[@]}" /opt/prebuilt/built_sdl build_ext/
|
||||||
|
cp "${CP_FLAGS[@]}" /opt/prebuilt/built_sdl_mixer build_ext/
|
||||||
|
cp "${CP_FLAGS[@]}" /opt/prebuilt/fluidsynth-lite build_ext/
|
||||||
|
|
||||||
echo "== Configuring (CMake, BUNDLED SDL2/SDL2_mixer/FluidSynth) =="
|
echo "== Configuring (CMake, BUNDLED SDL2/SDL2_mixer/FluidSynth) =="
|
||||||
rm -f CMakeCache.txt
|
rm -f CMakeCache.txt
|
||||||
@@ -38,7 +57,7 @@ rm -rf "$OUT_DIR"
|
|||||||
mkdir -p "$OUT_DIR/lib"
|
mkdir -p "$OUT_DIR/lib"
|
||||||
cp systemshock "$OUT_DIR/"
|
cp systemshock "$OUT_DIR/"
|
||||||
find build_ext/built_sdl/lib build_ext/built_sdl_mixer/lib build_ext/fluidsynth-lite/src \
|
find build_ext/built_sdl/lib build_ext/built_sdl_mixer/lib build_ext/fluidsynth-lite/src \
|
||||||
-name '*.so*' -not -name '*.la' -exec cp -a {} "$OUT_DIR/lib/" \;
|
-name '*.so*' -not -name '*.la' -exec cp "${CP_FLAGS[@]}" {} "$OUT_DIR/lib/" \;
|
||||||
cp /opt/prebuilt/soundfont/default.sf2 "$OUT_DIR/soundfont.sf2"
|
cp /opt/prebuilt/soundfont/default.sf2 "$OUT_DIR/soundfont.sf2"
|
||||||
|
|
||||||
echo "== Done =="
|
echo "== Done =="
|
||||||
|
|||||||
@@ -11,4 +11,17 @@ USER_GID="${HOST_GID:-1000}"
|
|||||||
groupadd -g "$USER_GID" builder 2>/dev/null || true
|
groupadd -g "$USER_GID" builder 2>/dev/null || true
|
||||||
useradd -u "$USER_UID" -g "$USER_GID" -m -s /bin/bash builder 2>/dev/null || true
|
useradd -u "$USER_UID" -g "$USER_GID" -m -s /bin/bash builder 2>/dev/null || true
|
||||||
|
|
||||||
|
# /workspace (the bind-mounted repo) may actually be owned by a group the
|
||||||
|
# host user only has via *supplementary* membership rather than their
|
||||||
|
# primary GID above - e.g. a VirtualBox vboxsf shared folder, which shows
|
||||||
|
# up as root:vboxsf on the host and would otherwise be inaccessible to
|
||||||
|
# `builder` here (falls through to "other", which vboxsf's default mode
|
||||||
|
# leaves with no permissions at all). Join whatever group actually owns
|
||||||
|
# /workspace too, if it differs.
|
||||||
|
WORKSPACE_GID="$(stat -c %g /workspace 2>/dev/null || true)"
|
||||||
|
if [ -n "$WORKSPACE_GID" ] && [ "$WORKSPACE_GID" != "$USER_GID" ]; then
|
||||||
|
groupadd -g "$WORKSPACE_GID" workspace 2>/dev/null || true
|
||||||
|
usermod -aG "$WORKSPACE_GID" builder 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
exec gosu builder "$@"
|
exec gosu builder "$@"
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
@echo off
|
||||||
|
cd /d "%~dp0"
|
||||||
|
systemshock.exe %*
|
||||||
|
exit /b %errorlevel%
|
||||||
Reference in New Issue
Block a user