diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b3884f5..6d96b85 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,6 +18,75 @@ concurrency: # when authoring new shaders (offline by the contributor). jobs: + # The boot animation's shaders (theseus/desktop/shaders/xboot/) for D3D11, + # the renderer bgfx picks by default on Windows. shaderc hands HLSL to + # fxc, which only exists on Windows, so the Linux and macOS jobs cannot + # make these. This job builds shaderc, compiles them and checks them + # against the .bin files in Data/shaders/dx11/; when they differ the new + # ones are in the job's artifact (and in its log, base64) to commit. + shaders-dx11: + name: Shaders / D3D11 + runs-on: windows-latest + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@v4 + + - name: Checkout bgfx + id: bgfx + run: | + git submodule update --init --depth 1 theseus/third-party/bx theseus/third-party/bimg theseus/third-party/bgfx + echo "sha=$(git rev-parse HEAD:theseus/third-party/bgfx)" >> "$GITHUB_OUTPUT" + + - name: Cache shaderc + id: cache-shaderc + uses: actions/cache@v4 + with: + path: theseus/third-party/bgfx/.build/win64_vs2022/bin/shadercRelease.exe + key: shaderc-win64-${{ steps.bgfx.outputs.sha }} + + - uses: microsoft/setup-msbuild@v2 + if: steps.cache-shaderc.outputs.cache-hit != 'true' + + - name: Build shaderc + if: steps.cache-shaderc.outputs.cache-hit != 'true' + run: | + cd theseus/third-party/bgfx + ../bx/tools/bin/windows/genie.exe --with-tools vs2022 + MSBuild.exe .build/projects/vs2022/shaderc.vcxproj -p:Configuration=Release -p:Platform=x64 -m -v:minimal + + - name: Compile and compare + run: | + SHADERC=theseus/third-party/bgfx/.build/win64_vs2022/bin/shadercRelease.exe + SRC=theseus/desktop/shaders/xboot + OUT=out/dx11 + mkdir -p "$OUT" + for SC in "$SRC"/vs_*.sc "$SRC"/fs_*.sc; do + NAME=$(basename "$SC" .sc) + case $NAME in vs_*) T=v ;; *) T=f ;; esac + "$SHADERC" -f "$SC" -o "$OUT/xboot_$NAME.bin" --type $T --platform windows --profile s_5_0 \ + --varyingdef "$SRC/varying.def.sc" -i theseus/third-party/bgfx/src -i "$SRC" + done + STALE=0 + for F in "$OUT"/*.bin; do + cmp -s "$F" "Data/shaders/dx11/$(basename "$F")" || { echo "differs: $(basename "$F")"; STALE=1; } + done + if [ $STALE = 1 ]; then + echo "Data/shaders/dx11 is out of date. Commit the job's artifact, or decode this:" + tar czf - -C "$OUT" . | base64 -w 0 + echo + exit 1 + fi + + - name: Upload D3D11 shaders + if: always() + uses: actions/upload-artifact@v4 + with: + name: xboot-shaders-dx11 + path: out/dx11/ + if-no-files-found: ignore + desktop-macos: name: Desktop / macOS runs-on: macos-latest @@ -319,7 +388,7 @@ jobs: cp -R ~/builds/theseus/desktop-win64/. "$DROP/" find "$DROP" -name '*.o' -delete find "$DROP" -name '*.d' -delete - rm -rf "$DROP/desktop" "$DROP/shared" "$DROP/engine" "$DROP/render" "$DROP/xbox" || true + rm -rf "$DROP/desktop" "$DROP/shared" "$DROP/engine" "$DROP/render" "$DROP/xbox" "$DROP/xboot" || true ls -la "$DROP" - name: Upload Win64 build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e2936a7..244f4fa 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -371,7 +371,7 @@ jobs: # Zip up; remove the *.o cruft from object subdirs first. find "${DROP}" -name '*.o' -delete find "${DROP}" -name '*.d' -delete - rm -rf "${DROP}/desktop" "${DROP}/shared" "${DROP}/engine" "${DROP}/render" "${DROP}/xbox" || true + rm -rf "${DROP}/desktop" "${DROP}/shared" "${DROP}/engine" "${DROP}/render" "${DROP}/xbox" "${DROP}/xboot" || true zip -r "${DROP}.zip" "${DROP}" echo "ARCHIVE=${DROP}.zip" >> "$GITHUB_ENV" diff --git a/.gitmodules b/.gitmodules index dde1490..a8d1a27 100644 --- a/.gitmodules +++ b/.gitmodules @@ -15,3 +15,7 @@ path = theseus/third-party/libdatachannel url = https://github.com/paullouisageneau/libdatachannel.git ignore = untracked +[submodule "theseus/third-party/xboot"] + path = theseus/third-party/xboot + url = https://github.com/austinbaccus/re-xbox-boot.git + ignore = untracked diff --git a/Configs/xbox_boot.mp4 b/Configs/xbox_boot.mp4 deleted file mode 100644 index c2808f3..0000000 Binary files a/Configs/xbox_boot.mp4 and /dev/null differ diff --git a/Data/shaders/dx11/xboot_fs_blob.bin b/Data/shaders/dx11/xboot_fs_blob.bin new file mode 100644 index 0000000..6c9b4c0 Binary files /dev/null and b/Data/shaders/dx11/xboot_fs_blob.bin differ diff --git a/Data/shaders/dx11/xboot_fs_depth.bin b/Data/shaders/dx11/xboot_fs_depth.bin new file mode 100644 index 0000000..d3676fc Binary files /dev/null and b/Data/shaders/dx11/xboot_fs_depth.bin differ diff --git a/Data/shaders/dx11/xboot_fs_drop.bin b/Data/shaders/dx11/xboot_fs_drop.bin new file mode 100644 index 0000000..41ce441 Binary files /dev/null and b/Data/shaders/dx11/xboot_fs_drop.bin differ diff --git a/Data/shaders/dx11/xboot_fs_fixed.bin b/Data/shaders/dx11/xboot_fs_fixed.bin new file mode 100644 index 0000000..4adaab9 Binary files /dev/null and b/Data/shaders/dx11/xboot_fs_fixed.bin differ diff --git a/Data/shaders/dx11/xboot_fs_lightdepth.bin b/Data/shaders/dx11/xboot_fs_lightdepth.bin new file mode 100644 index 0000000..e03e9b8 Binary files /dev/null and b/Data/shaders/dx11/xboot_fs_lightdepth.bin differ diff --git a/Data/shaders/dx11/xboot_fs_logod.bin b/Data/shaders/dx11/xboot_fs_logod.bin new file mode 100644 index 0000000..98ef8c7 Binary files /dev/null and b/Data/shaders/dx11/xboot_fs_logod.bin differ diff --git a/Data/shaders/dx11/xboot_fs_plain.bin b/Data/shaders/dx11/xboot_fs_plain.bin new file mode 100644 index 0000000..f83107b Binary files /dev/null and b/Data/shaders/dx11/xboot_fs_plain.bin differ diff --git a/Data/shaders/dx11/xboot_fs_present.bin b/Data/shaders/dx11/xboot_fs_present.bin new file mode 100644 index 0000000..4f220fc Binary files /dev/null and b/Data/shaders/dx11/xboot_fs_present.bin differ diff --git a/Data/shaders/dx11/xboot_fs_relief.bin b/Data/shaders/dx11/xboot_fs_relief.bin new file mode 100644 index 0000000..2c2e951 Binary files /dev/null and b/Data/shaders/dx11/xboot_fs_relief.bin differ diff --git a/Data/shaders/dx11/xboot_fs_smooth.bin b/Data/shaders/dx11/xboot_fs_smooth.bin new file mode 100644 index 0000000..e752bf3 Binary files /dev/null and b/Data/shaders/dx11/xboot_fs_smooth.bin differ diff --git a/Data/shaders/dx11/xboot_fs_veil.bin b/Data/shaders/dx11/xboot_fs_veil.bin new file mode 100644 index 0000000..bbda27c Binary files /dev/null and b/Data/shaders/dx11/xboot_fs_veil.bin differ diff --git a/Data/shaders/dx11/xboot_vs_blob.bin b/Data/shaders/dx11/xboot_vs_blob.bin new file mode 100644 index 0000000..4f868b6 Binary files /dev/null and b/Data/shaders/dx11/xboot_vs_blob.bin differ diff --git a/Data/shaders/dx11/xboot_vs_depth.bin b/Data/shaders/dx11/xboot_vs_depth.bin new file mode 100644 index 0000000..50027bd Binary files /dev/null and b/Data/shaders/dx11/xboot_vs_depth.bin differ diff --git a/Data/shaders/dx11/xboot_vs_drop.bin b/Data/shaders/dx11/xboot_vs_drop.bin new file mode 100644 index 0000000..20a6522 Binary files /dev/null and b/Data/shaders/dx11/xboot_vs_drop.bin differ diff --git a/Data/shaders/dx11/xboot_vs_fixed.bin b/Data/shaders/dx11/xboot_vs_fixed.bin new file mode 100644 index 0000000..10b376b Binary files /dev/null and b/Data/shaders/dx11/xboot_vs_fixed.bin differ diff --git a/Data/shaders/dx11/xboot_vs_lightdepth.bin b/Data/shaders/dx11/xboot_vs_lightdepth.bin new file mode 100644 index 0000000..5fa584c Binary files /dev/null and b/Data/shaders/dx11/xboot_vs_lightdepth.bin differ diff --git a/Data/shaders/dx11/xboot_vs_logod.bin b/Data/shaders/dx11/xboot_vs_logod.bin new file mode 100644 index 0000000..5f6ed81 Binary files /dev/null and b/Data/shaders/dx11/xboot_vs_logod.bin differ diff --git a/Data/shaders/dx11/xboot_vs_plain.bin b/Data/shaders/dx11/xboot_vs_plain.bin new file mode 100644 index 0000000..5fa584c Binary files /dev/null and b/Data/shaders/dx11/xboot_vs_plain.bin differ diff --git a/Data/shaders/dx11/xboot_vs_present.bin b/Data/shaders/dx11/xboot_vs_present.bin new file mode 100644 index 0000000..73e7ad7 Binary files /dev/null and b/Data/shaders/dx11/xboot_vs_present.bin differ diff --git a/Data/shaders/dx11/xboot_vs_relief.bin b/Data/shaders/dx11/xboot_vs_relief.bin new file mode 100644 index 0000000..c2e2090 Binary files /dev/null and b/Data/shaders/dx11/xboot_vs_relief.bin differ diff --git a/Data/shaders/dx11/xboot_vs_smooth.bin b/Data/shaders/dx11/xboot_vs_smooth.bin new file mode 100644 index 0000000..c7859d5 Binary files /dev/null and b/Data/shaders/dx11/xboot_vs_smooth.bin differ diff --git a/Data/shaders/dx11/xboot_vs_veil.bin b/Data/shaders/dx11/xboot_vs_veil.bin new file mode 100644 index 0000000..8561665 Binary files /dev/null and b/Data/shaders/dx11/xboot_vs_veil.bin differ diff --git a/Data/shaders/essl/xboot_fs_blob.bin b/Data/shaders/essl/xboot_fs_blob.bin new file mode 100644 index 0000000..192b826 Binary files /dev/null and b/Data/shaders/essl/xboot_fs_blob.bin differ diff --git a/Data/shaders/essl/xboot_fs_depth.bin b/Data/shaders/essl/xboot_fs_depth.bin new file mode 100644 index 0000000..273f5c5 Binary files /dev/null and b/Data/shaders/essl/xboot_fs_depth.bin differ diff --git a/Data/shaders/essl/xboot_fs_drop.bin b/Data/shaders/essl/xboot_fs_drop.bin new file mode 100644 index 0000000..c5739ae Binary files /dev/null and b/Data/shaders/essl/xboot_fs_drop.bin differ diff --git a/Data/shaders/essl/xboot_fs_fixed.bin b/Data/shaders/essl/xboot_fs_fixed.bin new file mode 100644 index 0000000..85baa74 Binary files /dev/null and b/Data/shaders/essl/xboot_fs_fixed.bin differ diff --git a/Data/shaders/essl/xboot_fs_lightdepth.bin b/Data/shaders/essl/xboot_fs_lightdepth.bin new file mode 100644 index 0000000..93ac02a Binary files /dev/null and b/Data/shaders/essl/xboot_fs_lightdepth.bin differ diff --git a/Data/shaders/essl/xboot_fs_logod.bin b/Data/shaders/essl/xboot_fs_logod.bin new file mode 100644 index 0000000..7a9c8aa Binary files /dev/null and b/Data/shaders/essl/xboot_fs_logod.bin differ diff --git a/Data/shaders/essl/xboot_fs_plain.bin b/Data/shaders/essl/xboot_fs_plain.bin new file mode 100644 index 0000000..131140a Binary files /dev/null and b/Data/shaders/essl/xboot_fs_plain.bin differ diff --git a/Data/shaders/essl/xboot_fs_present.bin b/Data/shaders/essl/xboot_fs_present.bin new file mode 100644 index 0000000..05195d2 Binary files /dev/null and b/Data/shaders/essl/xboot_fs_present.bin differ diff --git a/Data/shaders/essl/xboot_fs_relief.bin b/Data/shaders/essl/xboot_fs_relief.bin new file mode 100644 index 0000000..4d39b30 Binary files /dev/null and b/Data/shaders/essl/xboot_fs_relief.bin differ diff --git a/Data/shaders/essl/xboot_fs_smooth.bin b/Data/shaders/essl/xboot_fs_smooth.bin new file mode 100644 index 0000000..a4f5ef0 Binary files /dev/null and b/Data/shaders/essl/xboot_fs_smooth.bin differ diff --git a/Data/shaders/essl/xboot_fs_veil.bin b/Data/shaders/essl/xboot_fs_veil.bin new file mode 100644 index 0000000..644407f Binary files /dev/null and b/Data/shaders/essl/xboot_fs_veil.bin differ diff --git a/Data/shaders/essl/xboot_vs_blob.bin b/Data/shaders/essl/xboot_vs_blob.bin new file mode 100644 index 0000000..442584d Binary files /dev/null and b/Data/shaders/essl/xboot_vs_blob.bin differ diff --git a/Data/shaders/essl/xboot_vs_depth.bin b/Data/shaders/essl/xboot_vs_depth.bin new file mode 100644 index 0000000..db292fd Binary files /dev/null and b/Data/shaders/essl/xboot_vs_depth.bin differ diff --git a/Data/shaders/essl/xboot_vs_drop.bin b/Data/shaders/essl/xboot_vs_drop.bin new file mode 100644 index 0000000..64e0ccf Binary files /dev/null and b/Data/shaders/essl/xboot_vs_drop.bin differ diff --git a/Data/shaders/essl/xboot_vs_fixed.bin b/Data/shaders/essl/xboot_vs_fixed.bin new file mode 100644 index 0000000..7d8bf5f Binary files /dev/null and b/Data/shaders/essl/xboot_vs_fixed.bin differ diff --git a/Data/shaders/essl/xboot_vs_lightdepth.bin b/Data/shaders/essl/xboot_vs_lightdepth.bin new file mode 100644 index 0000000..52d6cea Binary files /dev/null and b/Data/shaders/essl/xboot_vs_lightdepth.bin differ diff --git a/Data/shaders/essl/xboot_vs_logod.bin b/Data/shaders/essl/xboot_vs_logod.bin new file mode 100644 index 0000000..f9c5a3c Binary files /dev/null and b/Data/shaders/essl/xboot_vs_logod.bin differ diff --git a/Data/shaders/essl/xboot_vs_plain.bin b/Data/shaders/essl/xboot_vs_plain.bin new file mode 100644 index 0000000..52d6cea Binary files /dev/null and b/Data/shaders/essl/xboot_vs_plain.bin differ diff --git a/Data/shaders/essl/xboot_vs_present.bin b/Data/shaders/essl/xboot_vs_present.bin new file mode 100644 index 0000000..adabe76 Binary files /dev/null and b/Data/shaders/essl/xboot_vs_present.bin differ diff --git a/Data/shaders/essl/xboot_vs_relief.bin b/Data/shaders/essl/xboot_vs_relief.bin new file mode 100644 index 0000000..e9ec782 Binary files /dev/null and b/Data/shaders/essl/xboot_vs_relief.bin differ diff --git a/Data/shaders/essl/xboot_vs_smooth.bin b/Data/shaders/essl/xboot_vs_smooth.bin new file mode 100644 index 0000000..1575690 Binary files /dev/null and b/Data/shaders/essl/xboot_vs_smooth.bin differ diff --git a/Data/shaders/essl/xboot_vs_veil.bin b/Data/shaders/essl/xboot_vs_veil.bin new file mode 100644 index 0000000..8a3c58e Binary files /dev/null and b/Data/shaders/essl/xboot_vs_veil.bin differ diff --git a/Data/shaders/glsl/xboot_fs_blob.bin b/Data/shaders/glsl/xboot_fs_blob.bin new file mode 100644 index 0000000..8d03026 Binary files /dev/null and b/Data/shaders/glsl/xboot_fs_blob.bin differ diff --git a/Data/shaders/glsl/xboot_fs_depth.bin b/Data/shaders/glsl/xboot_fs_depth.bin new file mode 100644 index 0000000..59a50d5 Binary files /dev/null and b/Data/shaders/glsl/xboot_fs_depth.bin differ diff --git a/Data/shaders/glsl/xboot_fs_drop.bin b/Data/shaders/glsl/xboot_fs_drop.bin new file mode 100644 index 0000000..d5c3c15 Binary files /dev/null and b/Data/shaders/glsl/xboot_fs_drop.bin differ diff --git a/Data/shaders/glsl/xboot_fs_fixed.bin b/Data/shaders/glsl/xboot_fs_fixed.bin new file mode 100644 index 0000000..266cec3 Binary files /dev/null and b/Data/shaders/glsl/xboot_fs_fixed.bin differ diff --git a/Data/shaders/glsl/xboot_fs_lightdepth.bin b/Data/shaders/glsl/xboot_fs_lightdepth.bin new file mode 100644 index 0000000..bd736f8 Binary files /dev/null and b/Data/shaders/glsl/xboot_fs_lightdepth.bin differ diff --git a/Data/shaders/glsl/xboot_fs_logod.bin b/Data/shaders/glsl/xboot_fs_logod.bin new file mode 100644 index 0000000..890d64f Binary files /dev/null and b/Data/shaders/glsl/xboot_fs_logod.bin differ diff --git a/Data/shaders/glsl/xboot_fs_plain.bin b/Data/shaders/glsl/xboot_fs_plain.bin new file mode 100644 index 0000000..e1ce262 Binary files /dev/null and b/Data/shaders/glsl/xboot_fs_plain.bin differ diff --git a/Data/shaders/glsl/xboot_fs_present.bin b/Data/shaders/glsl/xboot_fs_present.bin new file mode 100644 index 0000000..0da3191 Binary files /dev/null and b/Data/shaders/glsl/xboot_fs_present.bin differ diff --git a/Data/shaders/glsl/xboot_fs_relief.bin b/Data/shaders/glsl/xboot_fs_relief.bin new file mode 100644 index 0000000..8a5e96f Binary files /dev/null and b/Data/shaders/glsl/xboot_fs_relief.bin differ diff --git a/Data/shaders/glsl/xboot_fs_smooth.bin b/Data/shaders/glsl/xboot_fs_smooth.bin new file mode 100644 index 0000000..c12342c Binary files /dev/null and b/Data/shaders/glsl/xboot_fs_smooth.bin differ diff --git a/Data/shaders/glsl/xboot_fs_veil.bin b/Data/shaders/glsl/xboot_fs_veil.bin new file mode 100644 index 0000000..adea64d Binary files /dev/null and b/Data/shaders/glsl/xboot_fs_veil.bin differ diff --git a/Data/shaders/glsl/xboot_vs_blob.bin b/Data/shaders/glsl/xboot_vs_blob.bin new file mode 100644 index 0000000..e3716cc Binary files /dev/null and b/Data/shaders/glsl/xboot_vs_blob.bin differ diff --git a/Data/shaders/glsl/xboot_vs_depth.bin b/Data/shaders/glsl/xboot_vs_depth.bin new file mode 100644 index 0000000..f6dd084 Binary files /dev/null and b/Data/shaders/glsl/xboot_vs_depth.bin differ diff --git a/Data/shaders/glsl/xboot_vs_drop.bin b/Data/shaders/glsl/xboot_vs_drop.bin new file mode 100644 index 0000000..0d9ef44 Binary files /dev/null and b/Data/shaders/glsl/xboot_vs_drop.bin differ diff --git a/Data/shaders/glsl/xboot_vs_fixed.bin b/Data/shaders/glsl/xboot_vs_fixed.bin new file mode 100644 index 0000000..9c7a331 Binary files /dev/null and b/Data/shaders/glsl/xboot_vs_fixed.bin differ diff --git a/Data/shaders/glsl/xboot_vs_lightdepth.bin b/Data/shaders/glsl/xboot_vs_lightdepth.bin new file mode 100644 index 0000000..db1418f Binary files /dev/null and b/Data/shaders/glsl/xboot_vs_lightdepth.bin differ diff --git a/Data/shaders/glsl/xboot_vs_logod.bin b/Data/shaders/glsl/xboot_vs_logod.bin new file mode 100644 index 0000000..07c7f90 Binary files /dev/null and b/Data/shaders/glsl/xboot_vs_logod.bin differ diff --git a/Data/shaders/glsl/xboot_vs_plain.bin b/Data/shaders/glsl/xboot_vs_plain.bin new file mode 100644 index 0000000..db1418f Binary files /dev/null and b/Data/shaders/glsl/xboot_vs_plain.bin differ diff --git a/Data/shaders/glsl/xboot_vs_present.bin b/Data/shaders/glsl/xboot_vs_present.bin new file mode 100644 index 0000000..b694841 Binary files /dev/null and b/Data/shaders/glsl/xboot_vs_present.bin differ diff --git a/Data/shaders/glsl/xboot_vs_relief.bin b/Data/shaders/glsl/xboot_vs_relief.bin new file mode 100644 index 0000000..bc483a7 Binary files /dev/null and b/Data/shaders/glsl/xboot_vs_relief.bin differ diff --git a/Data/shaders/glsl/xboot_vs_smooth.bin b/Data/shaders/glsl/xboot_vs_smooth.bin new file mode 100644 index 0000000..c867f03 Binary files /dev/null and b/Data/shaders/glsl/xboot_vs_smooth.bin differ diff --git a/Data/shaders/glsl/xboot_vs_veil.bin b/Data/shaders/glsl/xboot_vs_veil.bin new file mode 100644 index 0000000..4ab6c68 Binary files /dev/null and b/Data/shaders/glsl/xboot_vs_veil.bin differ diff --git a/Data/shaders/metal/xboot_fs_blob.bin b/Data/shaders/metal/xboot_fs_blob.bin new file mode 100644 index 0000000..2161117 Binary files /dev/null and b/Data/shaders/metal/xboot_fs_blob.bin differ diff --git a/Data/shaders/metal/xboot_fs_depth.bin b/Data/shaders/metal/xboot_fs_depth.bin new file mode 100644 index 0000000..f7e2ebc Binary files /dev/null and b/Data/shaders/metal/xboot_fs_depth.bin differ diff --git a/Data/shaders/metal/xboot_fs_drop.bin b/Data/shaders/metal/xboot_fs_drop.bin new file mode 100644 index 0000000..00afdf8 Binary files /dev/null and b/Data/shaders/metal/xboot_fs_drop.bin differ diff --git a/Data/shaders/metal/xboot_fs_fixed.bin b/Data/shaders/metal/xboot_fs_fixed.bin new file mode 100644 index 0000000..9ce5b1e Binary files /dev/null and b/Data/shaders/metal/xboot_fs_fixed.bin differ diff --git a/Data/shaders/metal/xboot_fs_lightdepth.bin b/Data/shaders/metal/xboot_fs_lightdepth.bin new file mode 100644 index 0000000..1b22c42 Binary files /dev/null and b/Data/shaders/metal/xboot_fs_lightdepth.bin differ diff --git a/Data/shaders/metal/xboot_fs_logod.bin b/Data/shaders/metal/xboot_fs_logod.bin new file mode 100644 index 0000000..8046d14 Binary files /dev/null and b/Data/shaders/metal/xboot_fs_logod.bin differ diff --git a/Data/shaders/metal/xboot_fs_plain.bin b/Data/shaders/metal/xboot_fs_plain.bin new file mode 100644 index 0000000..95de1c1 Binary files /dev/null and b/Data/shaders/metal/xboot_fs_plain.bin differ diff --git a/Data/shaders/metal/xboot_fs_present.bin b/Data/shaders/metal/xboot_fs_present.bin new file mode 100644 index 0000000..8fd3bbe Binary files /dev/null and b/Data/shaders/metal/xboot_fs_present.bin differ diff --git a/Data/shaders/metal/xboot_fs_relief.bin b/Data/shaders/metal/xboot_fs_relief.bin new file mode 100644 index 0000000..cbb3ed8 Binary files /dev/null and b/Data/shaders/metal/xboot_fs_relief.bin differ diff --git a/Data/shaders/metal/xboot_fs_smooth.bin b/Data/shaders/metal/xboot_fs_smooth.bin new file mode 100644 index 0000000..2a67dba Binary files /dev/null and b/Data/shaders/metal/xboot_fs_smooth.bin differ diff --git a/Data/shaders/metal/xboot_fs_veil.bin b/Data/shaders/metal/xboot_fs_veil.bin new file mode 100644 index 0000000..b460764 Binary files /dev/null and b/Data/shaders/metal/xboot_fs_veil.bin differ diff --git a/Data/shaders/metal/xboot_vs_blob.bin b/Data/shaders/metal/xboot_vs_blob.bin new file mode 100644 index 0000000..b717309 Binary files /dev/null and b/Data/shaders/metal/xboot_vs_blob.bin differ diff --git a/Data/shaders/metal/xboot_vs_depth.bin b/Data/shaders/metal/xboot_vs_depth.bin new file mode 100644 index 0000000..194615f Binary files /dev/null and b/Data/shaders/metal/xboot_vs_depth.bin differ diff --git a/Data/shaders/metal/xboot_vs_drop.bin b/Data/shaders/metal/xboot_vs_drop.bin new file mode 100644 index 0000000..6936bc5 Binary files /dev/null and b/Data/shaders/metal/xboot_vs_drop.bin differ diff --git a/Data/shaders/metal/xboot_vs_fixed.bin b/Data/shaders/metal/xboot_vs_fixed.bin new file mode 100644 index 0000000..215dc3d Binary files /dev/null and b/Data/shaders/metal/xboot_vs_fixed.bin differ diff --git a/Data/shaders/metal/xboot_vs_lightdepth.bin b/Data/shaders/metal/xboot_vs_lightdepth.bin new file mode 100644 index 0000000..d5e8df0 Binary files /dev/null and b/Data/shaders/metal/xboot_vs_lightdepth.bin differ diff --git a/Data/shaders/metal/xboot_vs_logod.bin b/Data/shaders/metal/xboot_vs_logod.bin new file mode 100644 index 0000000..ea169cf Binary files /dev/null and b/Data/shaders/metal/xboot_vs_logod.bin differ diff --git a/Data/shaders/metal/xboot_vs_plain.bin b/Data/shaders/metal/xboot_vs_plain.bin new file mode 100644 index 0000000..d5e8df0 Binary files /dev/null and b/Data/shaders/metal/xboot_vs_plain.bin differ diff --git a/Data/shaders/metal/xboot_vs_present.bin b/Data/shaders/metal/xboot_vs_present.bin new file mode 100644 index 0000000..1cbfaca Binary files /dev/null and b/Data/shaders/metal/xboot_vs_present.bin differ diff --git a/Data/shaders/metal/xboot_vs_relief.bin b/Data/shaders/metal/xboot_vs_relief.bin new file mode 100644 index 0000000..9a7e15e Binary files /dev/null and b/Data/shaders/metal/xboot_vs_relief.bin differ diff --git a/Data/shaders/metal/xboot_vs_smooth.bin b/Data/shaders/metal/xboot_vs_smooth.bin new file mode 100644 index 0000000..0787145 Binary files /dev/null and b/Data/shaders/metal/xboot_vs_smooth.bin differ diff --git a/Data/shaders/metal/xboot_vs_veil.bin b/Data/shaders/metal/xboot_vs_veil.bin new file mode 100644 index 0000000..d1a8df1 Binary files /dev/null and b/Data/shaders/metal/xboot_vs_veil.bin differ diff --git a/Data/shaders/spirv/xboot_fs_blob.bin b/Data/shaders/spirv/xboot_fs_blob.bin new file mode 100644 index 0000000..06e292f Binary files /dev/null and b/Data/shaders/spirv/xboot_fs_blob.bin differ diff --git a/Data/shaders/spirv/xboot_fs_depth.bin b/Data/shaders/spirv/xboot_fs_depth.bin new file mode 100644 index 0000000..a2423d6 Binary files /dev/null and b/Data/shaders/spirv/xboot_fs_depth.bin differ diff --git a/Data/shaders/spirv/xboot_fs_drop.bin b/Data/shaders/spirv/xboot_fs_drop.bin new file mode 100644 index 0000000..f2bda95 Binary files /dev/null and b/Data/shaders/spirv/xboot_fs_drop.bin differ diff --git a/Data/shaders/spirv/xboot_fs_fixed.bin b/Data/shaders/spirv/xboot_fs_fixed.bin new file mode 100644 index 0000000..0d318f6 Binary files /dev/null and b/Data/shaders/spirv/xboot_fs_fixed.bin differ diff --git a/Data/shaders/spirv/xboot_fs_lightdepth.bin b/Data/shaders/spirv/xboot_fs_lightdepth.bin new file mode 100644 index 0000000..604fc66 Binary files /dev/null and b/Data/shaders/spirv/xboot_fs_lightdepth.bin differ diff --git a/Data/shaders/spirv/xboot_fs_logod.bin b/Data/shaders/spirv/xboot_fs_logod.bin new file mode 100644 index 0000000..a890ee5 Binary files /dev/null and b/Data/shaders/spirv/xboot_fs_logod.bin differ diff --git a/Data/shaders/spirv/xboot_fs_plain.bin b/Data/shaders/spirv/xboot_fs_plain.bin new file mode 100644 index 0000000..cb5687c Binary files /dev/null and b/Data/shaders/spirv/xboot_fs_plain.bin differ diff --git a/Data/shaders/spirv/xboot_fs_present.bin b/Data/shaders/spirv/xboot_fs_present.bin new file mode 100644 index 0000000..ebf022a Binary files /dev/null and b/Data/shaders/spirv/xboot_fs_present.bin differ diff --git a/Data/shaders/spirv/xboot_fs_relief.bin b/Data/shaders/spirv/xboot_fs_relief.bin new file mode 100644 index 0000000..73c955e Binary files /dev/null and b/Data/shaders/spirv/xboot_fs_relief.bin differ diff --git a/Data/shaders/spirv/xboot_fs_smooth.bin b/Data/shaders/spirv/xboot_fs_smooth.bin new file mode 100644 index 0000000..52a0d22 Binary files /dev/null and b/Data/shaders/spirv/xboot_fs_smooth.bin differ diff --git a/Data/shaders/spirv/xboot_fs_veil.bin b/Data/shaders/spirv/xboot_fs_veil.bin new file mode 100644 index 0000000..c3ce10b Binary files /dev/null and b/Data/shaders/spirv/xboot_fs_veil.bin differ diff --git a/Data/shaders/spirv/xboot_vs_blob.bin b/Data/shaders/spirv/xboot_vs_blob.bin new file mode 100644 index 0000000..e9cd23e Binary files /dev/null and b/Data/shaders/spirv/xboot_vs_blob.bin differ diff --git a/Data/shaders/spirv/xboot_vs_depth.bin b/Data/shaders/spirv/xboot_vs_depth.bin new file mode 100644 index 0000000..8bd9fc6 Binary files /dev/null and b/Data/shaders/spirv/xboot_vs_depth.bin differ diff --git a/Data/shaders/spirv/xboot_vs_drop.bin b/Data/shaders/spirv/xboot_vs_drop.bin new file mode 100644 index 0000000..ab44a27 Binary files /dev/null and b/Data/shaders/spirv/xboot_vs_drop.bin differ diff --git a/Data/shaders/spirv/xboot_vs_fixed.bin b/Data/shaders/spirv/xboot_vs_fixed.bin new file mode 100644 index 0000000..8f30e0a Binary files /dev/null and b/Data/shaders/spirv/xboot_vs_fixed.bin differ diff --git a/Data/shaders/spirv/xboot_vs_lightdepth.bin b/Data/shaders/spirv/xboot_vs_lightdepth.bin new file mode 100644 index 0000000..7c5fcea Binary files /dev/null and b/Data/shaders/spirv/xboot_vs_lightdepth.bin differ diff --git a/Data/shaders/spirv/xboot_vs_logod.bin b/Data/shaders/spirv/xboot_vs_logod.bin new file mode 100644 index 0000000..dcdd48a Binary files /dev/null and b/Data/shaders/spirv/xboot_vs_logod.bin differ diff --git a/Data/shaders/spirv/xboot_vs_plain.bin b/Data/shaders/spirv/xboot_vs_plain.bin new file mode 100644 index 0000000..7c5fcea Binary files /dev/null and b/Data/shaders/spirv/xboot_vs_plain.bin differ diff --git a/Data/shaders/spirv/xboot_vs_present.bin b/Data/shaders/spirv/xboot_vs_present.bin new file mode 100644 index 0000000..473aea6 Binary files /dev/null and b/Data/shaders/spirv/xboot_vs_present.bin differ diff --git a/Data/shaders/spirv/xboot_vs_relief.bin b/Data/shaders/spirv/xboot_vs_relief.bin new file mode 100644 index 0000000..5f7b461 Binary files /dev/null and b/Data/shaders/spirv/xboot_vs_relief.bin differ diff --git a/Data/shaders/spirv/xboot_vs_smooth.bin b/Data/shaders/spirv/xboot_vs_smooth.bin new file mode 100644 index 0000000..589dd85 Binary files /dev/null and b/Data/shaders/spirv/xboot_vs_smooth.bin differ diff --git a/Data/shaders/spirv/xboot_vs_veil.bin b/Data/shaders/spirv/xboot_vs_veil.bin new file mode 100644 index 0000000..7f59f16 Binary files /dev/null and b/Data/shaders/spirv/xboot_vs_veil.bin differ diff --git a/LICENSE-THIRD-PARTY.md b/LICENSE-THIRD-PARTY.md index 2a686b1..a04a4bf 100644 --- a/LICENSE-THIRD-PARTY.md +++ b/LICENSE-THIRD-PARTY.md @@ -39,9 +39,12 @@ Used by the desktop port: | [ImGuiColorTextEdit](https://github.com/BalazsJako/ImGuiColorTextEdit) | MIT | XAP script editor with syntax highlighting | | [stb_image](https://github.com/nothings/stb) | Public Domain (or MIT, dual-licensed) | Image loading on the desktop side | | [GLEW](https://glew.sourceforge.net/) | Modified BSD / MIT | OpenGL extension loader (Windows desktop build only) | +| [re-xbox-boot](https://github.com/austinbaccus/re-xbox-boot) (`theseus/third-party/xboot`) | MIT | The boot animation, reconstructed from the BIOS: portable C core, renderer ported to bgfx in `boot_anim.cpp` and `shaders/xboot/` | All of the above are GPL-3.0-compatible, either by being permissive (MIT, BSD, zlib, public domain) or by being LGPL (linkable from GPL). +The boot animation's data pack is built from `theseus/third-party/xboot/data/`, geometry, samples and tables extracted from the original BIOS. As that repository notes, their content is Microsoft's work; the boot animation itself was created by Pipeworks Software in 2001. + ## Xbox Dashboard Assets (`xboxfs/`) The XIPs and skin assets in `xboxfs/` are authored by [UIX Lite](https://github.com/OfficialTeamUIX/UIX-Lite) and contain TeamUIX modifications of dashboard scripts present on every retail Xbox console. The `uixdata` directory layout is a convention established by JbOnE's earlier source-level modification, *User.Interface.X* (UIX); Theseus follows that convention so the same content runs on both projects. UIX Lite's modifications are licensed under GPL-3.0-or-later. The underlying script formats and any unmodified original components are referenced for compatibility and preservation; Microsoft retains its rights to its original work. Every Xbox owner already possesses the equivalent original files on their console. diff --git a/README.md b/README.md index 1d2d6db..5dab326 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,7 @@ A drop-in replacement for the stock Xbox dashboard on modded consoles. Same look The Theseus engine compiled for your computer, with the modern features bolted on. macOS, Linux, Windows, Steam Deck friendly. +- **The original boot animation**, rendered live from the [reverse engineered BIOS animation](https://github.com/austinbaccus/re-xbox-boot) with its synthesized sound, then straight into the dashboard (Settings > Show Boot Animation; Esc, Enter, Space or A/B/Start skips it) - **3D launcher** for native PC games, Steam libraries, RetroArch ROMs, and Xbox ISOs via [xemu](https://xemu.app) - **xCloud / Game Pass streaming** signs in with your Xbox account and drops your whole cloud library into the games grid, box art and all. Remote play for your own console shows up under "Your Xbox" - **Media library** that scans your Movies and TV folders, pulls posters from [TMDB](https://www.themoviedb.org/), and plays back through libmpv. [Plex](https://www.plex.tv/) and [Jellyfin](https://jellyfin.org/) servers browse and stream in the same UI (Jellyfin signs in with Quick Connect) @@ -196,6 +197,7 @@ manipulation don't translate to a stick. Keyboard equivalents: | F4 | Settings | | F5 | Xbox HDD browser | | F6 | Playlist Maker | +| F9 | FPS / debug overlay | | F10 | Toggle menu bar | | F11 | Toggle fullscreen | | Ctrl+M | Mute | diff --git a/art/logo.jpg b/art/logo.jpg new file mode 100644 index 0000000..0762f21 Binary files /dev/null and b/art/logo.jpg differ diff --git a/build/Makefile b/build/Makefile index 49656f2..7d75ae1 100644 --- a/build/Makefile +++ b/build/Makefile @@ -772,6 +772,41 @@ DESKTOP_ALL_SRCS = \ $(DESKTOP_SRCS_IMGUI) DESKTOP_OBJS = $(patsubst $(THESEUS_SRC)/%.cpp,$(DESKTOP_DIR)/%.o,$(DESKTOP_ALL_SRCS)) + +# ------------------------------------------------------------------------ +# Boot animation core (third-party/xboot, the re-xbox-boot repo). Portable +# C99, compiled with the flags its own build uses (core/build.py) so the +# numerics stay as the boot repo measured them. Its data pack is built from +# the repo's data/ by its own tools/pack/pack.py (checked against the +# SHA-256 it ships) and embedded into the binary as a header, so there is +# no runtime file to lose. Every desktop target below links these objects; +# boot_anim.cpp is the only C++ user. +# ------------------------------------------------------------------------ +XBOOT_ROOT := $(THESEUS_SRC)/third-party/xboot +XBOOT_C_SRCS := $(sort $(wildcard $(XBOOT_ROOT)/core/src/*.c)) +XBOOT_PAK := $(XBOOT_ROOT)/out/pack/xboot.pak +XBOOT_PAK_H := $(XBOOT_ROOT)/out/xboot_pak.h +XBOOT_INC := -I$(XBOOT_ROOT)/core/include -I$(XBOOT_ROOT)/out +XBOOT_CFLAGS := -std=c99 $(if $(DEBUG),-g -O0,-O2) -MMD -MP \ + -ffp-contract=off -fno-fast-math -fno-math-errno \ + -I$(XBOOT_ROOT)/core/include -I$(XBOOT_ROOT)/core/src +PYTHON ?= python3 + +$(XBOOT_ROOT)/core/include/xboot.h: + @echo "xboot submodule not initialized; running git submodule update..." + cd $(THESEUS_SRC)/.. && git submodule update --init --depth 1 theseus/third-party/xboot + +$(XBOOT_PAK_H): $(XBOOT_ROOT)/core/include/xboot.h $(THESEUS_SRC)/../build/embed_xboot_pak.py + $(PYTHON) $(XBOOT_ROOT)/tools/pack/pack.py --check + $(PYTHON) $(THESEUS_SRC)/../build/embed_xboot_pak.py $(XBOOT_PAK) $@ + +DESKTOP_CFLAGS += $(XBOOT_INC) +DESKTOP_OBJS += $(patsubst $(XBOOT_ROOT)/core/src/%.c,$(DESKTOP_DIR)/xboot/%.o,$(XBOOT_C_SRCS)) +$(DESKTOP_DIR)/desktop/boot_anim.o: $(XBOOT_PAK_H) +$(DESKTOP_DIR)/xboot/%.o: $(XBOOT_ROOT)/core/src/%.c + @mkdir -p $(dir $@) + $(DESKTOP_CC) $(XBOOT_CFLAGS) -c -o $@ $< + DESKTOP_DEPS = $(DESKTOP_OBJS:.o=.d) -include $(DESKTOP_DEPS) @@ -914,6 +949,38 @@ shaders-bgfx-dx11: @echo "=== bgfx shaders compiled to $(SHADERS_OUT_DIR)/dx11/ ===" +# The boot animation's shaders (theseus/desktop/shaders/xboot/, see +# boot_anim.cpp) have a varying.def.sc of their own and land next to the +# others as Data/shaders//xboot_.bin. Same tools and +# profiles as above; dx11 again only on Windows (fxc). +# make -C build shaders-xboot metal, spirv, glsl, essl +# make -C build shaders-xboot-dx11 dx11, on Windows +XBOOT_SHADERS_DIR := $(SHADERS_SRC_DIR)/xboot +# $(call xboot-shaders,,,) +define xboot-shaders + @mkdir -p $(SHADERS_OUT_DIR)/$(1) + @for SC in $(XBOOT_SHADERS_DIR)/vs_*.sc $(XBOOT_SHADERS_DIR)/fs_*.sc; do \ + NAME=$$(basename $$SC .sc); \ + case $$NAME in vs_*) T=v ;; *) T=f ;; esac; \ + echo "shaderc [$(1)] xboot_$$NAME"; \ + $(SHADERC) -f $$SC -o $(SHADERS_OUT_DIR)/$(1)/xboot_$$NAME.bin \ + --type $$T --platform $(2) --profile $(3) \ + --varyingdef $(XBOOT_SHADERS_DIR)/varying.def.sc \ + -i $(BGFX_SHADER_INC) -i $(XBOOT_SHADERS_DIR) \ + || exit 1; \ + done +endef + +.PHONY: shaders-xboot shaders-xboot-dx11 +shaders-xboot: + $(call xboot-shaders,metal,osx,metal) + $(call xboot-shaders,spirv,windows,spirv) + $(call xboot-shaders,glsl,linux,150) + $(call xboot-shaders,essl,android,300_es) + +shaders-xboot-dx11: + $(call xboot-shaders,dx11,windows,s_5_0) + desktop: projectm-deps $(DESKTOP_TARGET) @echo "=== desktop build complete: $(DESKTOP_TARGET) ===" @@ -1195,6 +1262,16 @@ WIN64_SRCS += $(THESEUS_SRC)/desktop/imgui_impl_bgfx.cpp # Same source list as desktop. the compiler reads the same files. WIN64_OBJS = $(patsubst $(THESEUS_SRC)/%.cpp,$(WIN64_DIR)/%.o,$(WIN64_SRCS)) + +# The boot animation's core, as for the native desktop target. +WIN64_CC = x86_64-w64-mingw32-gcc +WIN64_CFLAGS += $(XBOOT_INC) +WIN64_OBJS += $(patsubst $(XBOOT_ROOT)/core/src/%.c,$(WIN64_DIR)/xboot/%.o,$(XBOOT_C_SRCS)) +$(WIN64_DIR)/desktop/boot_anim.o: $(XBOOT_PAK_H) +$(WIN64_DIR)/xboot/%.o: $(XBOOT_ROOT)/core/src/%.c + @mkdir -p $(dir $@) + $(WIN64_CC) $(XBOOT_CFLAGS) -c -o $@ $< + WIN64_DEPS = $(WIN64_OBJS:.o=.d) -include $(WIN64_DEPS) @@ -1395,6 +1472,16 @@ LINUX_ARM64_LDFLAGS = \ # Same source list as desktop. the compiler reads the same files. LINUX_ARM64_OBJS = $(patsubst $(THESEUS_SRC)/%.cpp,$(LINUX_ARM64_DIR)/%.o,$(DESKTOP_ALL_SRCS)) + +# The boot animation's core, as for the native desktop target. +LINUX_ARM64_CC = aarch64-linux-gnu-gcc +LINUX_ARM64_CFLAGS += $(XBOOT_INC) +LINUX_ARM64_OBJS += $(patsubst $(XBOOT_ROOT)/core/src/%.c,$(LINUX_ARM64_DIR)/xboot/%.o,$(XBOOT_C_SRCS)) +$(LINUX_ARM64_DIR)/desktop/boot_anim.o: $(XBOOT_PAK_H) +$(LINUX_ARM64_DIR)/xboot/%.o: $(XBOOT_ROOT)/core/src/%.c + @mkdir -p $(dir $@) + $(LINUX_ARM64_CC) $(XBOOT_CFLAGS) -c -o $@ $< + LINUX_ARM64_DEPS = $(LINUX_ARM64_OBJS:.o=.d) -include $(LINUX_ARM64_DEPS) diff --git a/build/embed_xboot_pak.py b/build/embed_xboot_pak.py new file mode 100644 index 0000000..c4b1509 --- /dev/null +++ b/build/embed_xboot_pak.py @@ -0,0 +1,33 @@ +"""Writes the boot animation's data pack as a C++ header. + + python3 build/embed_xboot_pak.py + +The pack comes from theseus/third-party/xboot/tools/pack/pack.py (the +Makefile runs it first). The core reads the pack in place and wants it +aligned to 16 bytes (XB_ALIGN), so the array is declared that way. +""" + +import sys + + +def main(): + src, dst = sys.argv[1], sys.argv[2] + data = open(src, 'rb').read() + lines = [ + '// Generated by build/embed_xboot_pak.py from %s. Do not edit.' % src.replace('\\', '/').split('/')[-1], + '#pragma once', + '#include ', + '', + 'alignas(16) static const unsigned char kXbootPak[%d] = {' % len(data), + ] + for i in range(0, len(data), 16): + lines.append('\t' + ', '.join('0x%02x' % b for b in data[i:i + 16]) + ',') + lines.append('};') + lines.append('static const size_t kXbootPakSize = sizeof(kXbootPak);') + lines.append('') + with open(dst, 'w', newline='\n') as f: + f.write('\n'.join(lines)) + + +if __name__ == '__main__': + main() diff --git a/theseus/desktop/boot_anim.cpp b/theseus/desktop/boot_anim.cpp index bf0d3f5..3147f36 100644 --- a/theseus/desktop/boot_anim.cpp +++ b/theseus/desktop/boot_anim.cpp @@ -1,173 +1,911 @@ -// boot_anim.cpp: cold-boot animation playback. Brings up a fresh libmpv -// instance, points it at the boot video, renders fullscreen until the -// file ends or the user skips with Esc/Enter/Space. On exit the mpv -// context is torn down completely so the dashboard's own libmpv instance -// (the CDVDPlayer node) can initialize cleanly afterward. +// boot_anim.cpp: cold-boot animation. Runs the reverse engineered boot +// animation of the original Xbox (third-party/xboot, re-xbox-boot) live: +// its portable C core computes every frame from the data pack, and this +// file draws what the core lists through bgfx and plays the sound the core +// synthesizes. When the animation ends (t = 8 s) or the user skips it, +// everything is torn down and the dashboard starts in the same window. // -// The bundled video (Configs/xbox_boot.mp4) is a 1080p60 -// capture of the original Xbox 2001 boot animation. Source: -// https://www.youtube.com/watch?v=oADANrDGhoQ -// Original boot animation (c) Microsoft / Pipeworks Software 2001. +// This is a port of the boot repo's WebGPU renderer (hosts/web/src/ +// renderer.js, docs/web.md 4) without its workbench. The shaders are in +// shaders/xboot/, ported from hosts/web/src/shaders.js. Section numbers +// below (GPU.md, FrameOrder.md, ...) refer to the boot repo's docs/decomp/. +// +// The boot animation (c) Microsoft / Pipeworks Software 2001. #include "display.h" #include "boot_anim.h" #include - #include -#include "d3d8_sdl.h" // for g_bgfxProgBlit, g_bgfxSamplerBlit -#include -#include +#include "xboot.h" +#include "xboot_pak.h" // generated from third-party/xboot/data by build/embed_xboot_pak.py +#include #include -#include #include -#include +#include +#include +#include + +bgfx::ShaderHandle theseus_bgfx_load_shader(const char* name); -// Boot anim: libmpv software-renders into a CPU buffer, we upload that to -// a bgfx texture each frame and draw it as a fullscreen quad (vs_blit/fs_blit). -bool BootAnim_PlayAndWait(SDL_Window* win, const char* path) +namespace { + +// Sizes of the original's targets (Textures.md 1, Logo.md 6). +const uint16_t kLightSize = 512, kFxW = 1024, kFxH = 512, kLogoSize = 1024; +const float kPixelBias = 0.53125f; // SET_VIEWPORT_OFFSET (GPU.md 3) +const int kConstants = 20; +// bgfx views of the boot animation. The dashboard has not set up any yet, +// and the ones used here are reset on the way out. +const bgfx::ViewId kViewBase = 100, kViewMax = 240; + +// The programs of GPU.md 3 by their address in the ROM, plus the fixed +// function's two and the present. +enum Kind { K_SMOOTH, K_RELIEF, K_LIGHTDEPTH, K_DEPTH, K_BLOB, K_DROP, K_VEIL, K_LOGOD, + K_FIXED, K_PLAIN, K_PRESENT, K_COUNT }; +const char* const kKindName[K_COUNT] = { "smooth", "relief", "lightdepth", "depth", "blob", "drop", + "veil", "logod", "fixed", "plain", "present" }; + +Kind KindOfProgram(uint32_t program) { - if (!win || !path || !*path) return false; + switch (program) { + case 0x80065B60: return K_SMOOTH; + case 0x80065998: return K_RELIEF; + case 0x80065D28: return K_DEPTH; + case 0x800662E8: return K_BLOB; + case 0x80066470: return K_DROP; + case 0x80066690: return K_VEIL; + case 0x80066868: return K_LOGOD; + default: return K_COUNT; + } +} - struct stat st; - if (stat(path, &st) != 0) { - fprintf(stderr, "[boot_anim] file not found: %s\n", path); - return false; +// GPU.md 5: what the library's combiners take, by kind of draw. The logo's +// meshes A, B, C and its picture take the texture alone, with the border +// colour of GPU.md 6.1. +enum { ST_TEXTURE = 1, ST_FACTOR = 2, ST_TEXTURE_ALPHA = 4, ST_BORDER = 8 }; +unsigned StagesOf(uint16_t what) +{ + switch (what) { + case XB_DRAW_BLOB_GLOW: return ST_TEXTURE | ST_FACTOR; + case XB_DRAW_GLOW: return ST_TEXTURE | ST_FACTOR | ST_TEXTURE_ALPHA; + case XB_DRAW_LOGO_Q1: + case XB_DRAW_LOGO_Q2: return ST_TEXTURE | ST_TEXTURE_ALPHA | ST_BORDER; + default: return ST_TEXTURE | ST_BORDER; + } +} + +const uint32_t kClampLinear = BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP | BGFX_SAMPLER_W_CLAMP | BGFX_SAMPLER_MIP_POINT; +const uint32_t kRepeatLinear = BGFX_SAMPLER_MIP_POINT; +const uint32_t kPointClamp = BGFX_SAMPLER_POINT | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP; + +// NV2A comparison 0x200 + n (GPU.md 6) +const uint64_t kDepthTest[8] = { + BGFX_STATE_DEPTH_TEST_NEVER, BGFX_STATE_DEPTH_TEST_LESS, BGFX_STATE_DEPTH_TEST_EQUAL, + BGFX_STATE_DEPTH_TEST_LEQUAL, BGFX_STATE_DEPTH_TEST_GREATER, BGFX_STATE_DEPTH_TEST_NOTEQUAL, + BGFX_STATE_DEPTH_TEST_GEQUAL, BGFX_STATE_DEPTH_TEST_ALWAYS }; + +uint64_t BlendFactor(uint16_t v) +{ + switch (v) { + case 0: return BGFX_STATE_BLEND_ZERO; + case 1: return BGFX_STATE_BLEND_ONE; + case 0x302: return BGFX_STATE_BLEND_SRC_ALPHA; + case 0x303: return BGFX_STATE_BLEND_INV_SRC_ALPHA; + default: + fprintf(stderr, "[boot_anim] blend factor 0x%x\n", v); + return BGFX_STATE_BLEND_ONE; + } +} + +// 0x800410B0 packs a colour to bytes: clamped, trunc(f32(x * 255 + 0.5)) (GPU.md 4.2) +float Packed(float x) +{ + float c = x < 0.f ? 0.f : (x > 1.f ? 1.f : x); + volatile float f = c * 255.f + 0.5f; + return (float)(int)f / 255.f; +} + +void* AlignedAlloc(size_t size, void** raw) +{ + *raw = std::calloc(1, size + XB_ALIGN); + if (!*raw) return nullptr; + return (void*)(((uintptr_t)*raw + XB_ALIGN - 1) & ~(uintptr_t)(XB_ALIGN - 1)); +} + +// Where texel (x, y) of a w x h texture lies in the core's layout +// (Textures.md 3): from bit 0 up one bit of x while x has bits left, then +// one of y while y has. +uint32_t Swizzled(uint32_t x, uint32_t y, uint32_t w, uint32_t h) +{ + uint32_t i = 0, bit = 0; + for (uint32_t k = 0; (1u << k) < w || (1u << k) < h; k++) { + if ((1u << k) < w) i |= ((x >> k) & 1u) << bit++; + if ((1u << k) < h) i |= ((y >> k) & 1u) << bit++; } + return i; +} + +// The indices of a draw as a triangle list: bgfx has no fans. +void Triangles(uint8_t prim, const std::vector& idx, std::vector& out) +{ + size_t n = idx.size(); + if (prim == 5) { + out.assign(idx.begin(), idx.begin() + (n - n % 3)); + return; + } + for (size_t i = 2; i < n; i++) { + if (prim == 7) { out.push_back(idx[0]); out.push_back(idx[i - 1]); out.push_back(idx[i]); } + else if (i & 1) { out.push_back(idx[i - 1]); out.push_back(idx[i - 2]); out.push_back(idx[i]); } + else { out.push_back(idx[i - 2]); out.push_back(idx[i - 1]); out.push_back(idx[i]); } + } +} + +void Mul4(const double* a, const double* b, double* m) +{ + for (int r = 0; r < 4; r++) + for (int c = 0; c < 4; c++) { + double s = 0; + for (int k = 0; k < 4; k++) s += a[4 * r + k] * b[4 * k + c]; + m[4 * r + c] = s; + } +} - if (!bgfx::isValid(g_bgfxProgBlit)) { - fprintf(stderr, "[boot_anim] blit program not initialized; skipping\n"); +struct Mesh { + bgfx::VertexBufferHandle pos = BGFX_INVALID_HANDLE; + bgfx::VertexBufferHandle attr = BGFX_INVALID_HANDLE; // chamber: axes per vertex + uint32_t floats = 3; + std::vector idx; +}; + +struct Surface { + bgfx::TextureHandle tex = BGFX_INVALID_HANDLE; + uint16_t w = 0, h = 0; + bool owned = true; +}; + +struct Uniforms { + float c[kConstants][4]; + float k[4][4]; + float bias[4], misc[4], mode[4], api[4]; +}; + +// The sound runs on a core of its own, so that the audio thread never +// touches the state the picture advances. +struct Sound { + SDL_AudioDeviceID dev = 0; + void* stateRaw = nullptr; + void* audioRaw = nullptr; + xb_state* state = nullptr; +}; +Sound g_sound; + +void SDLCALL SoundCallback(void* user, Uint8* stream, int len) +{ + xb_state* s = (xb_state*)user; + xb_audio(s, (int16_t*)stream, (uint32_t)(len / 4)); +} + +void SoundClose() +{ + if (g_sound.dev) SDL_CloseAudioDevice(g_sound.dev); + g_sound.dev = 0; + std::free(g_sound.stateRaw); + std::free(g_sound.audioRaw); + g_sound = Sound(); +} + +bool SoundOpen(const xb_config& cfg) +{ + g_sound.state = (xb_state*)AlignedAlloc(xb_state_size(), &g_sound.stateRaw); + void* mem = AlignedAlloc(xb_audio_size(), &g_sound.audioRaw); + if (!g_sound.state || !mem || + xb_init(g_sound.state, kXbootPak, kXbootPakSize, &cfg) != XB_OK || + xb_audio_init(g_sound.state, mem, xb_audio_size()) != XB_OK) { + SoundClose(); + return false; + } + SDL_AudioSpec want, have; + SDL_zero(want); + want.freq = XB_AUDIO_RATE; + want.format = AUDIO_S16SYS; + want.channels = 2; + want.samples = 1024; + want.callback = SoundCallback; + want.userdata = g_sound.state; + g_sound.dev = SDL_OpenAudioDevice(nullptr, 0, &want, &have, 0); + if (!g_sound.dev) { + fprintf(stderr, "[boot_anim] no sound: %s\n", SDL_GetError()); + SoundClose(); return false; } + return true; +} + +class BootRenderer { +public: + bool Init(const xb_config& cfg); + void Shutdown(); + void ClearLogo(); + void Frame(int winW, int winH); + + xb_state* state = nullptr; + +private: + void Resize(uint16_t picW, uint16_t picH); + Mesh* MeshOf(const xb_draw& d); + bool List(const xb_draw& d, const Mesh& m, bgfx::IndexBufferHandle& ib, uint32_t& count); + bgfx::FrameBufferHandle FrameBufferOf(uint8_t color, uint8_t zeta); + bgfx::ViewId OpenView(uint8_t color, uint8_t zeta, bool clearDepth, bool clearColour, uint32_t rgba); + void Submit(bgfx::ViewId view, const xb_draw& d, const float* params); + void SetTexture(uint8_t stage, int sampler, uint8_t surf, uint32_t flags, bool cube); + + void* stateRaw_ = nullptr; + const bgfx::Caps* caps_ = nullptr; + bool flip_ = false; + + bgfx::VertexLayout lP3_, lP5_, lP7_, lA9_, lA11_, lDef_; + std::map meshes_; // XB_MESH_* * 2 + detail + bgfx::DynamicVertexBufferHandle deformed_ = BGFX_INVALID_HANDLE; + std::vector deformedData_; + std::map> lists_; - mpv_handle* mpv = mpv_create(); - if (!mpv) return false; + Surface surf_[XB_SURFACES]; + bgfx::TextureHandle dummy2d_ = BGFX_INVALID_HANDLE, dummyCube_ = BGFX_INVALID_HANDLE; + std::map fbs_; + uint16_t picW_ = 0, picH_ = 0; + // Horizontal scale of clip space for a picture wider than 4:3: the + // original's 4:3 projection times (4/3) / aspect, so a wider window + // sees more of the chamber at the sides instead of a stretched one. + float xs_ = 1.f; - mpv_set_option_string(mpv, "vo", "libmpv"); - mpv_set_option_string(mpv, "hwdec", "no"); - mpv_set_option_string(mpv, "keep-open", "no"); - mpv_set_option_string(mpv, "video", "yes"); - mpv_set_option_string(mpv, "audio", "yes"); - mpv_set_option_string(mpv, "ao", "coreaudio,wasapi,pulse,sdl"); - mpv_set_option_string(mpv, "volume", "100"); - mpv_set_option_string(mpv, "mute", "no"); - mpv_set_option_string(mpv, "osc", "no"); - mpv_set_option_string(mpv, "terminal", "no"); - mpv_set_option_string(mpv, "msg-level", "all=error"); + bgfx::ProgramHandle prog_[K_COUNT]; + bgfx::UniformHandle uC_, uK_, uBias_, uMisc_, uMode_, uApi_; + // sampler uniforms by the name the shaders give them + enum { S_CUBE, S_LIGHT, S_RELIEF, S_AIMED, S_NOISE1, S_NOISE2, S_NOISE3, S_IMAGE, S_SCREEN, S_COUNT }; + bgfx::UniformHandle uTex_[S_COUNT]; - if (mpv_initialize(mpv) < 0) { - mpv_destroy(mpv); + std::vector draws_; + std::vector params_; + bgfx::ViewId nextView_ = kViewBase; + bgfx::ViewId usedViews_ = kViewBase; + unsigned nanSkipped_ = 0; +}; + +bool BootRenderer::Init(const xb_config& cfg) +{ + for (auto& p : prog_) p = BGFX_INVALID_HANDLE; + uC_ = uK_ = uBias_ = uMisc_ = uMode_ = uApi_ = BGFX_INVALID_HANDLE; + for (auto& u : uTex_) u = BGFX_INVALID_HANDLE; + + state = (xb_state*)AlignedAlloc(xb_state_size(), &stateRaw_); + if (!state) return false; + int rc = xb_init(state, kXbootPak, kXbootPakSize, &cfg); + if (rc != XB_OK) { + fprintf(stderr, "[boot_anim] xb_init: %d\n", rc); return false; } - int apiSw = (int)MPV_RENDER_API_TYPE_SW; - (void)apiSw; - mpv_render_param createParams[] = { - { MPV_RENDER_PARAM_API_TYPE, (void*)MPV_RENDER_API_TYPE_SW }, - { MPV_RENDER_PARAM_INVALID, nullptr } + caps_ = bgfx::getCaps(); + flip_ = caps_->originBottomLeft; + + for (int k = 0; k < K_COUNT; k++) { + char vs[64], fs[64]; + snprintf(vs, sizeof(vs), "xboot_vs_%s", kKindName[k]); + snprintf(fs, sizeof(fs), "xboot_fs_%s", kKindName[k]); + bgfx::ShaderHandle v = theseus_bgfx_load_shader(vs); + bgfx::ShaderHandle f = theseus_bgfx_load_shader(fs); + if (!bgfx::isValid(v) || !bgfx::isValid(f)) { + if (bgfx::isValid(v)) bgfx::destroy(v); + if (bgfx::isValid(f)) bgfx::destroy(f); + fprintf(stderr, "[boot_anim] shaders for %s missing\n", kKindName[k]); + return false; + } + prog_[k] = bgfx::createProgram(v, f, true); + if (!bgfx::isValid(prog_[k])) return false; + } + uC_ = bgfx::createUniform("u_c", bgfx::UniformType::Vec4, kConstants); + uK_ = bgfx::createUniform("u_k", bgfx::UniformType::Vec4, 4); + uBias_ = bgfx::createUniform("u_bias", bgfx::UniformType::Vec4); + uMisc_ = bgfx::createUniform("u_misc", bgfx::UniformType::Vec4); + uMode_ = bgfx::createUniform("u_mode", bgfx::UniformType::Vec4); + uApi_ = bgfx::createUniform("u_api", bgfx::UniformType::Vec4); + static const char* const kSamplers[S_COUNT] = { "s_cube", "s_light", "s_relief", "s_aimed", "s_noise1", + "s_noise2", "s_noise3", "s_image", "s_screen" }; + for (int i = 0; i < S_COUNT; i++) uTex_[i] = bgfx::createUniform(kSamplers[i], bgfx::UniformType::Sampler); + + lP3_.begin().add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float).end(); + lP5_.begin().add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float) + .add(bgfx::Attrib::TexCoord0, 2, bgfx::AttribType::Float).end(); + lP7_.begin().add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float) + .add(bgfx::Attrib::TexCoord0, 2, bgfx::AttribType::Float) + .add(bgfx::Attrib::TexCoord1, 2, bgfx::AttribType::Float).end(); + lA9_.begin().add(bgfx::Attrib::TexCoord2, 3, bgfx::AttribType::Float) + .add(bgfx::Attrib::TexCoord3, 3, bgfx::AttribType::Float) + .add(bgfx::Attrib::TexCoord4, 3, bgfx::AttribType::Float).end(); + lA11_.begin().add(bgfx::Attrib::TexCoord5, 2, bgfx::AttribType::Float) + .add(bgfx::Attrib::TexCoord2, 3, bgfx::AttribType::Float) + .add(bgfx::Attrib::TexCoord3, 3, bgfx::AttribType::Float) + .add(bgfx::Attrib::TexCoord4, 3, bgfx::AttribType::Float).end(); + lDef_.begin().add(bgfx::Attrib::TexCoord6, 4, bgfx::AttribType::Float).end(); + + auto layoutOf = [&](uint32_t floats) -> const bgfx::VertexLayout& { + return floats == 7 ? lP7_ : floats == 5 ? lP5_ : lP3_; + }; + auto addMesh = [&](int key, std::vector& v, uint32_t floats, std::vector&& idx) { + Mesh m; + m.floats = floats; + m.idx = std::move(idx); + m.pos = bgfx::createVertexBuffer(bgfx::copy(v.data(), (uint32_t)(v.size() * sizeof(float))), layoutOf(floats)); + meshes_[key] = std::move(m); + return &meshes_[key]; }; - mpv_render_context* rc = nullptr; - if (mpv_render_context_create(&rc, mpv, createParams) < 0) { - mpv_destroy(mpv); - return false; + // Meshes, loaded once (docs/web.md 4): the chamber in both detail levels + // with its axes per vertex, blob, drops, the seven of the logo, the two + // quads of the veil. + for (uint32_t g = 0; g < XB_CHAMBER_GROUPS; g++) + for (uint32_t detail = 0; detail < 2; detail++) { + xb_mesh_info mi; + if (xb_chamber_mesh_info(state, g, detail, &mi) != XB_OK) return false; + std::vector pos(3 * mi.vertices), attr(mi.attr_floats * mi.vertices); + std::vector idx(mi.indices); + std::vector forms(mi.forms); + if (xb_chamber_mesh(state, g, detail, pos.data(), attr.data(), idx.data(), forms.data()) != XB_OK) + return false; + Mesh* m = addMesh((XB_MESH_CHAMBER + g) * 2 + detail, pos, 3, std::move(idx)); + m->attr = bgfx::createVertexBuffer(bgfx::copy(attr.data(), (uint32_t)(attr.size() * sizeof(float))), + mi.attr_floats == 11 ? lA11_ : lA9_); + } + const uint32_t blobN[2][2] = { { XB_BLOB_VERTICES, XB_BLOB_INDICES }, { XB_BLOB_SMALL_VERTICES, XB_BLOB_SMALL_INDICES } }; + for (uint32_t small = 0; small < 2; small++) { + std::vector unit(3 * blobN[small][0]); + std::vector idx(blobN[small][1]); + if (xb_blob_mesh(state, small, unit.data(), idx.data()) != XB_OK) return false; + addMesh((small ? XB_MESH_DROP : XB_MESH_BLOB) * 2, unit, 3, std::move(idx)); + } + for (uint32_t i = 0; i < XB_LOGO_MESHES; i++) { + xb_logo_mesh_desc ld; + if (xb_logo_mesh_info(state, i, &ld) != XB_OK) return false; + std::vector v(ld.floats * ld.vertices); + std::vector idx(ld.indices); + if (xb_logo_mesh(state, i, v.data(), idx.data()) != XB_OK) return false; + addMesh((XB_MESH_LOGO + i) * 2, v, ld.floats, std::move(idx)); } + { + std::vector veil(4 * 7), quad(4 * 3); + xb_fx_quads((xb_real(*)[7])veil.data(), (xb_real(*)[3])quad.data()); + addMesh(XB_MESH_VEIL * 2, veil, 7, {}); + addMesh(XB_MESH_FX_QUAD * 2, quad, 3, {}); + } + deformedData_.resize(4 * XB_BLOB_VERTICES); + deformed_ = bgfx::createDynamicVertexBuffer(XB_BLOB_VERTICES, lDef_); - const char* loadCmd[] = { "loadfile", path, nullptr }; - if (mpv_command(mpv, loadCmd) < 0) { - mpv_render_context_free(rc); - mpv_destroy(mpv); - return false; + // The eleven textures of the set-up, level 0 only: every stage of the + // original reads with SET_TEXTURE_FILTER 0x02022000, which stays on + // level 0 (GPU.md 6). Put back from the core's layout into rows: R G B A + // for the formats 6 and 7, one byte for 0x19. + for (uint32_t t = 0; t < XB_TEXTURES; t++) { + xb_texture_desc td; + if (xb_texture_info(state, t, &td) != XB_OK) return false; + std::vector raw(td.bytes); + if (xb_texture(state, t, raw.data(), raw.size()) != XB_OK) return false; + const uint32_t w = td.width, h = td.height, n = td.texel_bytes; + const uint32_t faceBytes = td.bytes / td.faces; + const bgfx::Memory* mem = bgfx::alloc(w * h * (n == 1 ? 1 : 4) * td.faces); + for (uint32_t face = 0; face < td.faces; face++) { + uint8_t* out = mem->data + face * w * h * (n == 1 ? 1 : 4); + const uint8_t* from = raw.data() + face * faceBytes; + for (uint32_t y = 0; y < h; y++) + for (uint32_t x = 0; x < w; x++) { + const uint8_t* s = from + Swizzled(x, y, w, h) * n; + uint32_t i = y * w + x; + if (n == 1) { out[i] = s[0]; continue; } + out[4 * i + 0] = s[2]; + out[4 * i + 1] = s[1]; + out[4 * i + 2] = s[0]; + out[4 * i + 3] = td.format == 7 ? 255 : s[3]; + } + } + Surface& sf = surf_[XB_SURF_TEX + t]; + sf.w = (uint16_t)w; + sf.h = (uint16_t)h; + if (td.faces == 6) + sf.tex = bgfx::createTextureCube((uint16_t)w, false, 1, bgfx::TextureFormat::RGBA8, kClampLinear, mem); + else + sf.tex = bgfx::createTexture2D((uint16_t)w, (uint16_t)h, false, 1, + n == 1 ? bgfx::TextureFormat::R8 : bgfx::TextureFormat::RGBA8, 0, mem); } - // Source resolution is fixed (1080p capture); SW render targets a - // CPU buffer of this size and the GPU upsamples via linear filter - // when we draw the fullscreen quad. - const int kW = 1920; - const int kH = 1080; - const size_t kStride = (size_t)kW * 4; - const size_t kBufSize = kStride * (size_t)kH; - uint8_t* swBuf = (uint8_t*)std::calloc(1, kBufSize); - if (!swBuf) { - mpv_render_context_free(rc); - mpv_terminate_destroy(mpv); - return false; + // Targets. Depth of the light views 16 bits as in the original, the + // others float where the GPU can. + auto rt = [&](int id, uint16_t w, uint16_t h, bgfx::TextureFormat::Enum fmt) { + surf_[id].tex = bgfx::createTexture2D(w, h, false, 1, fmt, BGFX_TEXTURE_RT | kPointClamp); + surf_[id].w = w; + surf_[id].h = h; + }; + const uint16_t fbDepthCaps = BGFX_CAPS_FORMAT_TEXTURE_FRAMEBUFFER | BGFX_CAPS_FORMAT_TEXTURE_2D; + bgfx::TextureFormat::Enum lightFmt = (caps_->formats[bgfx::TextureFormat::D16] & fbDepthCaps) == fbDepthCaps + ? bgfx::TextureFormat::D16 : bgfx::TextureFormat::D32F; + rt(XB_SURF_LIGHT0, kLightSize, kLightSize, lightFmt); + rt(XB_SURF_LIGHT1, kLightSize, kLightSize, lightFmt); + rt(XB_SURF_FX_A, kFxW, kFxH, bgfx::TextureFormat::RGBA8); + rt(XB_SURF_FX_B, kFxW, kFxH, bgfx::TextureFormat::RGBA8); + rt(XB_SURF_FX_DEPTH, kFxW, kFxH, bgfx::TextureFormat::D32F); + rt(XB_SURF_LOGO_TARGET, kLogoSize, kLogoSize, bgfx::TextureFormat::RGBA8); + rt(XB_SURF_LOGO_DEPTH, kLogoSize, kLogoSize, bgfx::TextureFormat::D32F); + // logo_render_to_texture copies its target into the texture logo+0xD0 + // (Logo.md 6). Nothing draws into the target but that copy's source, + // and every frame that draws it copies it, so the texture is the + // target itself here. + surf_[XB_SURF_LOGO] = surf_[XB_SURF_LOGO_TARGET]; + surf_[XB_SURF_LOGO].owned = false; + + const uint32_t white = 0xFFFFFFFF; + dummy2d_ = bgfx::createTexture2D(1, 1, false, 1, bgfx::TextureFormat::RGBA8, 0, bgfx::copy(&white, 4)); + uint32_t white6[6] = { white, white, white, white, white, white }; + dummyCube_ = bgfx::createTextureCube(1, false, 1, bgfx::TextureFormat::RGBA8, 0, bgfx::copy(white6, sizeof(white6))); + + draws_.resize(XB_FRAME_DRAWS_MAX); + params_.resize(XB_FRAME_PARAMS_MAX); + return true; +} + +void BootRenderer::Shutdown() +{ + for (auto& f : fbs_) bgfx::destroy(f.second); + fbs_.clear(); + for (auto& s : surf_) + if (s.owned && bgfx::isValid(s.tex)) bgfx::destroy(s.tex); + for (auto& m : meshes_) { + if (bgfx::isValid(m.second.pos)) bgfx::destroy(m.second.pos); + if (bgfx::isValid(m.second.attr)) bgfx::destroy(m.second.attr); + } + meshes_.clear(); + for (auto& l : lists_) bgfx::destroy(l.second.first); + lists_.clear(); + if (bgfx::isValid(deformed_)) bgfx::destroy(deformed_); + if (bgfx::isValid(dummy2d_)) bgfx::destroy(dummy2d_); + if (bgfx::isValid(dummyCube_)) bgfx::destroy(dummyCube_); + for (auto& p : prog_) + if (bgfx::isValid(p)) bgfx::destroy(p); + for (bgfx::UniformHandle u : { uC_, uK_, uBias_, uMisc_, uMode_, uApi_ }) + if (bgfx::isValid(u)) bgfx::destroy(u); + for (bgfx::UniformHandle u : uTex_) + if (bgfx::isValid(u)) bgfx::destroy(u); + for (bgfx::ViewId v = kViewBase; v < usedViews_; v++) { + bgfx::setViewFrameBuffer(v, BGFX_INVALID_HANDLE); + bgfx::setViewClear(v, BGFX_CLEAR_NONE); + bgfx::setViewMode(v, bgfx::ViewMode::Default); + } + std::free(stateRaw_); + stateRaw_ = nullptr; + state = nullptr; + if (nanSkipped_) + fprintf(stderr, "[boot_anim] %u draws with NaN constants left out (Chamber.md 6)\n", nanSkipped_); +} + +// The drawing surface for a picture of picW x picH: twice as wide, so that +// D5 is the mean of two pixels (GPU.md 7). At 640 x 480 the original's +// 1280 x 480. +void BootRenderer::Resize(uint16_t picW, uint16_t picH) +{ + if (picW == picW_ && picH == picH_) return; + for (auto it = fbs_.begin(); it != fbs_.end();) { + int color = it->first >> 8, zeta = it->first & 255; + if (color == XB_SURF_SCREEN || zeta == XB_SURF_SCREEN_DEPTH) { + bgfx::destroy(it->second); + it = fbs_.erase(it); + } else { + ++it; + } + } + for (int id : { XB_SURF_SCREEN, XB_SURF_SCREEN_DEPTH }) + if (bgfx::isValid(surf_[id].tex)) bgfx::destroy(surf_[id].tex); + picW_ = picW; + picH_ = picH; + const uint16_t w = (uint16_t)(2 * picW); + surf_[XB_SURF_SCREEN].tex = bgfx::createTexture2D(w, picH, false, 1, bgfx::TextureFormat::RGBA8, + BGFX_TEXTURE_RT | kPointClamp); + surf_[XB_SURF_SCREEN_DEPTH].tex = bgfx::createTexture2D(w, picH, false, 1, bgfx::TextureFormat::D32F, + BGFX_TEXTURE_RT | kPointClamp); + for (int id : { XB_SURF_SCREEN, XB_SURF_SCREEN_DEPTH }) { + surf_[id].w = w; + surf_[id].h = picH; + surf_[id].owned = true; + } +} + +Mesh* BootRenderer::MeshOf(const xb_draw& d) +{ + auto it = meshes_.find(d.mesh * 2 + (d.mesh < XB_MESH_BLOB ? d.detail : 0)); + return it == meshes_.end() ? nullptr : &it->second; +} + +// Triangle list of a draw, cached as its own index buffer. +bool BootRenderer::List(const xb_draw& d, const Mesh& m, bgfx::IndexBufferHandle& ib, uint32_t& count) +{ + const bool indexed = (d.flags & XB_DRAW_INDEXED) != 0; + const uint64_t key = (uint64_t)d.mesh << 56 | (uint64_t)d.detail << 55 | (uint64_t)d.prim << 52 | + (uint64_t)indexed << 51 | (uint64_t)(d.first & 0xFFFFFF) << 24 | (d.count & 0xFFFFFF); + auto it = lists_.find(key); + if (it == lists_.end()) { + std::vector src(d.count), tri; + for (uint32_t i = 0; i < d.count; i++) { + if (!indexed) { src[i] = (uint16_t)(d.first + i); continue; } + if (d.first + i >= m.idx.size()) return false; + src[i] = m.idx[d.first + i]; + } + Triangles(d.prim, src, tri); + bgfx::IndexBufferHandle h = BGFX_INVALID_HANDLE; + if (!tri.empty()) + h = bgfx::createIndexBuffer(bgfx::copy(tri.data(), (uint32_t)(tri.size() * sizeof(uint16_t)))); + it = lists_.emplace(key, std::make_pair(h, (uint32_t)tri.size())).first; } + ib = it->second.first; + count = it->second.second; + return bgfx::isValid(ib) && count > 0; +} - bgfx::TextureHandle tex = bgfx::createTexture2D( - (uint16_t)kW, (uint16_t)kH, false, 1, - bgfx::TextureFormat::BGRA8, - BGFX_TEXTURE_NONE | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP); +bgfx::FrameBufferHandle BootRenderer::FrameBufferOf(uint8_t color, uint8_t zeta) +{ + const int key = color << 8 | zeta; + auto it = fbs_.find(key); + if (it != fbs_.end()) return it->second; + bgfx::TextureHandle att[2]; + uint8_t n = 0; + if (color != XB_SURF_NONE) att[n++] = surf_[color].tex; + att[n++] = surf_[zeta].tex; + bgfx::FrameBufferHandle fb = bgfx::createFrameBuffer(n, att, false); + fbs_[key] = fb; + return fb; +} + +bgfx::ViewId BootRenderer::OpenView(uint8_t color, uint8_t zeta, bool clearDepth, bool clearColour, uint32_t rgba) +{ + if (nextView_ >= kViewMax) return kViewMax - 1; // never seen; keeps drawing into the last one + bgfx::ViewId v = nextView_++; + if (nextView_ > usedViews_) usedViews_ = nextView_; + bgfx::setViewMode(v, bgfx::ViewMode::Sequential); + bgfx::setViewFrameBuffer(v, FrameBufferOf(color, zeta)); + bgfx::setViewRect(v, 0, 0, surf_[zeta].w, surf_[zeta].h); + bgfx::setViewTransform(v, nullptr, nullptr); + uint16_t flags = (clearDepth ? BGFX_CLEAR_DEPTH : 0) | (clearColour ? BGFX_CLEAR_COLOR : 0); + bgfx::setViewClear(v, flags ? flags : BGFX_CLEAR_NONE, rgba, 1.0f, 0); + bgfx::touch(v); + return v; +} - bool running = true; - bool sawAnyDraw = false; +void BootRenderer::SetTexture(uint8_t stage, int sampler, uint8_t surf, uint32_t flags, bool cube) +{ + bgfx::TextureHandle t = BGFX_INVALID_HANDLE; + if (surf != XB_SURF_NONE && surf < XB_SURFACES) t = surf_[surf].tex; + if (!bgfx::isValid(t)) t = cube ? dummyCube_ : dummy2d_; + bgfx::setTexture(stage, uTex_[sampler], t, flags); +} +void BootRenderer::ClearLogo() +{ + nextView_ = kViewBase; + OpenView(XB_SURF_LOGO_TARGET, XB_SURF_LOGO_DEPTH, true, true, 0x000000ff); + bgfx::frame(); +} + +// One entry of the frame (renderer.js, prepare and pass). +void BootRenderer::Submit(bgfx::ViewId view, const xb_draw& d, const float* params) +{ + Mesh* mesh = nullptr; + if (d.mesh != XB_MESH_INLINE && !(mesh = MeshOf(d))) return; + + Kind kind; + if (d.step == XB_STEP_U1) kind = K_LIGHTDEPTH; + else if ((kind = KindOfProgram(d.program)) == K_COUNT) + kind = d.what == XB_DRAW_LOGO_E ? K_PLAIN : K_FIXED; + if ((kind == K_SMOOTH || kind == K_RELIEF) && !bgfx::isValid(mesh->attr)) return; + + Uniforms u; + std::memset(&u, 0, sizeof(u)); + if (d.flags & XB_DRAW_MATRICES) { + // clip = v * world * view * proj with row vectors: row j of the + // constants is column j of the product + double w[16], v[16], p[16], wv[16], m[16]; + for (int i = 0; i < 16; i++) { w[i] = params[d.world + i]; v[i] = params[d.view + i]; p[i] = params[d.proj + i]; } + Mul4(w, v, wv); + Mul4(wv, p, m); + for (int j = 0; j < 4; j++) + for (int i = 0; i < 4; i++) u.c[j][i] = (float)m[4 * i + j]; + } else { + for (const xb_const_run& run : d.vs) + for (uint32_t k = 0; k < run.count && run.base + k < (uint32_t)kConstants; k++) + std::memcpy(u.c[run.base + k], params + run.first + 4 * k, 4 * sizeof(float)); + } + // Wider than 4:3 (Frame): narrow clip x of everything drawn in the + // picture's projection, the surface and the veil's target, by scaling + // the row of the constants that gives clip x. The veil and the quad + // into its target cover the screen as they are; the quad's disc and the + // chamber's depth in the target read clip x through c[114].x (Effects.md + // 3), which takes the inverse so the disc stays round. + if (xs_ != 1.f && (d.zeta == XB_SURF_SCREEN_DEPTH || d.zeta == XB_SURF_FX_DEPTH)) { + if (kind != K_VEIL && d.what != XB_DRAW_FX_QUAD) { + const int row = (kind == K_BLOB || kind == K_DROP) ? 4 : 0; + for (int i = 0; i < 4; i++) u.c[row][i] *= xs_; + } + if (kind == K_DEPTH) u.c[18][0] /= xs_; + } + // Parts at the ROM's quaternion 238 have NaN in their matrices until + // t = 1.17 (Chamber.md 6). In xemu they leave no depth + // (FrameOrder.md 4); not drawn here. + for (int i = 0; i < kConstants; i++) + for (int j = 0; j < 4; j++) + if (std::isnan(u.c[i][j])) { nanSkipped_++; return; } + const uint32_t ps = 4 * (d.ps_count < 3 ? d.ps_count : 3); + for (uint32_t i = 0; i < ps; i++) u.k[i / 4][i % 4] = Packed(params[d.ps_first + i]); + if (kind == K_FIXED || kind == K_PLAIN) { + uint32_t f = (d.flags & XB_DRAW_FACTOR) ? d.factor : 0xFFFFFFFFu; + u.k[3][0] = ((f >> 16) & 255) / 255.f; + u.k[3][1] = ((f >> 8) & 255) / 255.f; + u.k[3][2] = (f & 255) / 255.f; + u.k[3][3] = (f >> 24) / 255.f; + u.k[2][3] = 1.f; // SET_TEXTURE_BORDER_COLOR 0xFF000000 (GPU.md 2.2) + } + const Surface& target = surf_[d.zeta]; + u.bias[0] = kPixelBias; + u.bias[2] = target.w / 2.f; + u.bias[3] = target.h / 2.f; + if (d.state.alpha_test && d.state.alpha_func != 0x206) { + static bool warned = false; + if (!warned) fprintf(stderr, "[boot_anim] alpha function 0x%x\n", d.state.alpha_func); + warned = true; + } + u.misc[0] = (float)d.state.alpha_ref; + u.misc[2] = d.state.alpha_test ? 1.f : 0.f; + u.misc[3] = d.state.shadow_func == 0x204 ? 1.f : 0.f; + const unsigned stages = StagesOf(d.what); + u.mode[0] = (stages & ST_TEXTURE) ? 1.f : 0.f; + u.mode[1] = (stages & ST_FACTOR) ? 1.f : 0.f; + u.mode[2] = (stages & ST_TEXTURE_ALPHA) ? 1.f : 0.f; + u.mode[3] = (stages & ST_BORDER) ? 1.f : 0.f; + u.api[0] = flip_ ? -1.f : 1.f; + u.api[1] = caps_->homogeneousDepth ? 1.f : 0.f; + if (d.tex[0] < XB_SURFACES) { + u.api[2] = surf_[d.tex[0]].w; + u.api[3] = surf_[d.tex[0]].h; + } + + // Indices and vertices + bgfx::TransientVertexBuffer tvb; + if (d.mesh == XB_MESH_INLINE) { + const uint32_t n = d.vtx_count / 5; + if (n == 0 || bgfx::getAvailTransientVertexBuffer(n, lP5_) < n) return; + bgfx::allocTransientVertexBuffer(&tvb, n, lP5_); + std::memcpy(tvb.data, params + d.vtx_first, n * 5 * sizeof(float)); + } + static const Mesh kInline; + bgfx::IndexBufferHandle ib; + uint32_t count = 0; + if (!List(d, mesh ? *mesh : kInline, ib, count)) return; + + // Render states in effect at the draw (GPU.md 6) + uint64_t st = 0; + if (kind != K_LIGHTDEPTH && d.color != XB_SURF_NONE) { + const uint32_t cm = d.state.colour_mask; + if (cm & 0x00010000u) st |= BGFX_STATE_WRITE_R; + if (cm & 0x00000100u) st |= BGFX_STATE_WRITE_G; + if (cm & 0x00000001u) st |= BGFX_STATE_WRITE_B; + if (cm & 0x01000000u) st |= BGFX_STATE_WRITE_A; + } + // without the depth test nothing is compared and nothing written + if (d.state.depth_test) { + st |= kDepthTest[d.state.depth_func & 7]; + if (d.state.depth_write) st |= BGFX_STATE_WRITE_Z; + } + if (d.state.blend) + st |= BGFX_STATE_BLEND_FUNC(BlendFactor(d.state.blend_src), BlendFactor(d.state.blend_dst)); + if (d.state.cull) { + // front faces are clockwise (GPU.md 6.3); a target stored bottom up + // turns every triangle over + bool cullCw = d.state.cull_face == 0x404; + if (flip_) cullCw = !cullCw; + st |= cullCw ? BGFX_STATE_CULL_CW : BGFX_STATE_CULL_CCW; + } + + switch (kind) { + case K_SMOOTH: + case K_RELIEF: + SetTexture(0, S_CUBE, d.tex[1], kClampLinear, true); + SetTexture(1, S_LIGHT, d.tex[3], kPointClamp, false); + if (kind == K_RELIEF) SetTexture(2, S_RELIEF, d.tex[0], kRepeatLinear, false); + break; + case K_BLOB: + case K_DROP: + SetTexture(0, S_CUBE, d.tex[0], kClampLinear, true); + break; + case K_VEIL: + SetTexture(0, S_AIMED, d.tex[0], kClampLinear, false); + for (uint8_t s = 1; s < 4; s++) SetTexture(s, S_NOISE1 + s - 1, d.tex[s], kRepeatLinear, false); + break; + case K_FIXED: + SetTexture(0, S_IMAGE, d.tex[0], kPointClamp, false); + break; + default: + break; + } + + bgfx::setUniform(uC_, u.c, kConstants); + bgfx::setUniform(uK_, u.k, 4); + bgfx::setUniform(uBias_, u.bias); + bgfx::setUniform(uMisc_, u.misc); + bgfx::setUniform(uMode_, u.mode); + bgfx::setUniform(uApi_, u.api); + bgfx::setState(st); + if (d.mesh == XB_MESH_INLINE) { + bgfx::setVertexBuffer(0, &tvb); + } else { + bgfx::setVertexBuffer(0, mesh->pos); + if (kind == K_SMOOTH || kind == K_RELIEF) bgfx::setVertexBuffer(1, mesh->attr); + if (kind == K_BLOB) bgfx::setVertexBuffer(1, deformed_); + } + bgfx::setIndexBuffer(ib, 0, count); + bgfx::submit(view, prog_[kind]); +} + +// Draws the frame of the core's last advance (renderer.js, draw). +void BootRenderer::Frame(int winW, int winH) +{ + // The picture fills a window of 4:3 or wider (16:9, 21:9, 32:9 see + // more of the chamber at the sides); a narrower one gets 4:3 as large as + // it holds, centred. + uint16_t picW = (uint16_t)winW, picH = (uint16_t)winH; + if ((uint32_t)winW * 3 < (uint32_t)winH * 4) picH = (uint16_t)(winW * 3 / 4); + const uint32_t maxTex = caps_->limits.maxTextureSize; + if (2u * picW > maxTex) { + picH = (uint16_t)((uint32_t)picH * (maxTex / 2) / picW); + picW = (uint16_t)(maxTex / 2); + } + if (picW < 4 || picH < 3) { picW = 640; picH = 480; } + xs_ = (4.f * picH) / (3.f * picW); + if (xs_ > 1.f) xs_ = 1.f; + Resize(picW, picH); + + xb_frame_out fo; + std::memset(&fo, 0, sizeof(fo)); + fo.draws = draws_.data(); + fo.draw_cap = (uint32_t)draws_.size(); + fo.params = params_.data(); + fo.param_cap = (uint32_t)params_.size(); + int rc = xb_frame(state, &fo); + if (rc != XB_OK) { + fprintf(stderr, "[boot_anim] xb_frame: %d\n", rc); + return; + } + if (xb_blob_deformed(state, (xb_real(*)[4])deformedData_.data()) == XB_OK) + bgfx::update(deformed_, 0, bgfx::copy(deformedData_.data(), (uint32_t)(deformedData_.size() * sizeof(float)))); + + // Consecutive draws into one target are one pass. A target is cleared + // where the frame first draws into it: the light views to depth 1 + // (Chamber.md 8), the veil's target to 0xFFFF0000 (Effects.md 3), the + // logo's and the surface to black (Logo.md 6, FrameOrder.md 1). + nextView_ = kViewBase; + bool begun[XB_SURFACES] = {}; + bool open = false, cleared = !fo.clear_depth; + uint8_t curColor = 0, curZeta = 0; + bgfx::ViewId view = kViewBase; + auto openGroup = [&](uint8_t color, uint8_t zeta, bool clearDepth) { + const bool first = !begun[zeta]; + begun[zeta] = true; + const bool fx = color == XB_SURF_FX_A || color == XB_SURF_FX_B; + view = OpenView(color, zeta, first || clearDepth, first && color != XB_SURF_NONE, fx ? 0xff0000ff : 0x000000ff); + curColor = color; + curZeta = zeta; + open = true; + }; + for (uint32_t i = 0; i < fo.draw_count; i++) { + const xb_draw& d = draws_[i]; + if (d.color == XB_SURF_DISPLAY || d.what == XB_DRAW_PRESENT) continue; + if (d.zeta >= XB_SURFACES || !bgfx::isValid(surf_[d.zeta].tex)) continue; // the cube of the set-up + if (d.color != XB_SURF_NONE && (d.color >= XB_SURFACES || !bgfx::isValid(surf_[d.color].tex))) continue; + // anim_draw clears depth before the logo when the core says so + const bool clearNow = d.color == XB_SURF_SCREEN && d.step == XB_STEP_D3 && !cleared; + if (!open || curColor != d.color || curZeta != d.zeta || clearNow) + openGroup(d.color, d.zeta, clearNow); + cleared = cleared || clearNow; + Submit(view, d, params_.data()); + } + if (!begun[XB_SURF_SCREEN_DEPTH]) + openGroup(XB_SURF_SCREEN, XB_SURF_SCREEN_DEPTH, true); + + // D5: the picture into the window, black around it. + bgfx::ViewId clearView = nextView_ < kViewMax ? nextView_++ : kViewMax - 1; + bgfx::ViewId presentView = nextView_ < kViewMax ? nextView_++ : kViewMax - 1; + if (nextView_ > usedViews_) usedViews_ = nextView_; + bgfx::setViewFrameBuffer(clearView, BGFX_INVALID_HANDLE); + bgfx::setViewRect(clearView, 0, 0, (uint16_t)winW, (uint16_t)winH); + bgfx::setViewClear(clearView, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0x000000ff, 1.0f, 0); + bgfx::touch(clearView); + bgfx::setViewFrameBuffer(presentView, BGFX_INVALID_HANDLE); + bgfx::setViewRect(presentView, (uint16_t)((winW - picW) / 2), (uint16_t)((winH - picH) / 2), picW, picH); + bgfx::setViewClear(presentView, BGFX_CLEAR_NONE); + bgfx::setViewTransform(presentView, nullptr, nullptr); + if (bgfx::getAvailTransientVertexBuffer(3, lP5_) >= 3) { + bgfx::TransientVertexBuffer tvb; + bgfx::allocTransientVertexBuffer(&tvb, 3, lP5_); + const float tri[15] = { -1.f, 1.f, 0.f, 0.f, 0.f, + 3.f, 1.f, 0.f, 2.f, 0.f, + -1.f, -3.f, 0.f, 0.f, 2.f }; + std::memcpy(tvb.data, tri, sizeof(tri)); + float api[4] = { 1.f, 0.f, (float)picW, (float)picH }; + bgfx::setUniform(uApi_, api); + bgfx::setTexture(0, uTex_[S_SCREEN], surf_[XB_SURF_SCREEN].tex, kPointClamp); + bgfx::setVertexBuffer(0, &tvb); + bgfx::setState(BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A); + bgfx::submit(presentView, prog_[K_PRESENT]); + } +} + +} // namespace + +bool BootAnim_PlayAndWait(SDL_Window* win) +{ + if (!win) return false; + + xb_config cfg; + cfg.flags = 0; + cfg.start_tick = 0; + BootRenderer r; + if (!r.Init(cfg)) { + fprintf(stderr, "[boot_anim] could not start the boot animation; skipping\n"); + r.Shutdown(); + return false; + } + r.ClearLogo(); + const bool sound = SoundOpen(cfg); + + bool running = true, drew = false; + const Uint32 start = SDL_GetTicks(); + if (sound) SDL_PauseAudioDevice(g_sound.dev, 0); while (running) { SDL_Event e; while (SDL_PollEvent(&e)) { if (e.type == SDL_QUIT) running = false; else if (e.type == SDL_KEYDOWN && - (e.key.keysym.sym == SDLK_ESCAPE || - e.key.keysym.sym == SDLK_RETURN || + (e.key.keysym.sym == SDLK_ESCAPE || e.key.keysym.sym == SDLK_RETURN || e.key.keysym.sym == SDLK_SPACE)) running = false; - } - - while (true) { - mpv_event* ev = mpv_wait_event(mpv, 0.0); - if (!ev || ev->event_id == MPV_EVENT_NONE) break; - if (ev->event_id == MPV_EVENT_END_FILE) { running = false; break; } + else if (e.type == SDL_CONTROLLERBUTTONDOWN && + (e.cbutton.button == SDL_CONTROLLER_BUTTON_A || e.cbutton.button == SDL_CONTROLLER_BUTTON_B || + e.cbutton.button == SDL_CONTROLLER_BUTTON_START)) + running = false; } if (!running) break; - uint64_t flags = mpv_render_context_update(rc); - if (flags & MPV_RENDER_UPDATE_FRAME) { - int swSize[2] = { kW, kH }; - char swFmt[] = "bgra"; // libmpv writes B,G,R,A bytes in - // memory order; bgfx BGRA8 reads - // those bytes as R=B G=G B=R A=A - // (it's a misnomer. BGRA8 means - // "bytes B,G,R,A" in bgfx land). - size_t swStride = kStride; - mpv_render_param drawParams[] = { - { MPV_RENDER_PARAM_SW_SIZE, swSize }, - { MPV_RENDER_PARAM_SW_FORMAT, swFmt }, - { MPV_RENDER_PARAM_SW_STRIDE, &swStride }, - { MPV_RENDER_PARAM_SW_POINTER, swBuf }, - { MPV_RENDER_PARAM_INVALID, nullptr } - }; - if (mpv_render_context_render(rc, drawParams) == 0) { - bgfx::updateTexture2D(tex, 0, 0, 0, 0, - (uint16_t)kW, (uint16_t)kH, - bgfx::copy(swBuf, (uint32_t)kBufSize)); - sawAnyDraw = true; - } + // The core runs by the millisecond counter, not by frames (Timing.md 2). + int rc = xb_advance(r.state, SDL_GetTicks() - start); + if (rc < 0) { + fprintf(stderr, "[boot_anim] xb_advance: %d\n", rc); + break; } // Runs before the main loop exists, so it has to do its own // reconcile or the whole animation plays at the wrong scale. extern void Gfx_ReconcileDrawable(); Gfx_ReconcileDrawable(); - - // View 0 in pixels, not points, or it covers a quarter of the - // surface on a HiDPI display. int winW = 0, winH = 0; Plat_GetDrawableSize(&winW, &winH); if (winW <= 0) winW = 1; if (winH <= 0) winH = 1; - bgfx::setViewRect(0, 0, 0, (uint16_t)winW, (uint16_t)winH); - bgfx::setViewClear(0, BGFX_CLEAR_COLOR, 0x000000ff, 1.0f, 0); - - Bgfx_BlitTexturedQuad(tex, 1.f, 1.f, 0); - + r.Frame(winW, winH); bgfx::frame(); + drew = true; + if (rc == XB_DONE) running = false; } - bgfx::destroy(tex); - mpv_render_context_free(rc); - mpv_terminate_destroy(mpv); - std::free(swBuf); - return sawAnyDraw; + r.Shutdown(); + // One more frame so the views above are reset before the dashboard draws. + bgfx::frame(); + + // The sound's tail after t = 8 is some 40 dB down and gone by 9.6 s; it + // stops here so the dashboard's mixer gets the audio device to itself. + if (sound) SoundClose(); + return drew; } diff --git a/theseus/desktop/boot_anim.h b/theseus/desktop/boot_anim.h index 74c14d7..ebc0945 100644 --- a/theseus/desktop/boot_anim.h +++ b/theseus/desktop/boot_anim.h @@ -1,12 +1,13 @@ -// boot_anim.h: one-shot fullscreen boot animation playback. Call once at -// app startup before the dashboard initializes. Blocks until the video ends -// or the user presses Esc/Enter/Space. +// boot_anim.h: the original Xbox boot animation, rendered live before the +// dashboard starts. Call once at app startup before the dashboard +// initializes. Blocks until the animation ends or the user skips it. #pragma once struct SDL_Window; -// Plays the boot animation video at `path` fullscreen via libmpv. Blocks -// until END_FILE or a skip key. Returns true if anything was rendered; -// false if the video could not be opened (caller should just move on). -bool BootAnim_PlayAndWait(SDL_Window* win, const char* path); +// Runs the reverse engineered boot animation (third-party/xboot) through +// bgfx, with its own synthesized sound. Blocks until the animation ends +// (t = 8 s) or a skip key / controller button. Returns true if anything +// was rendered; false if it could not start (caller should just move on). +bool BootAnim_PlayAndWait(SDL_Window* win); diff --git a/theseus/desktop/d3d8_sdl.h b/theseus/desktop/d3d8_sdl.h index 452dae9..8589643 100644 --- a/theseus/desktop/d3d8_sdl.h +++ b/theseus/desktop/d3d8_sdl.h @@ -59,7 +59,7 @@ struct BgfxFFUniforms { extern BgfxFFUniforms g_bgfxFF; extern bgfx::TextureHandle g_bgfxWhiteTex; -// Fullscreen-quad blit program + sampler. Used by the boot anim, the media +// Fullscreen-quad blit program + sampler. Used by the media // player, the xCloud stream, and CRT post. Owned in sdl_main.cpp. extern bgfx::ProgramHandle g_bgfxProgBlit; extern bgfx::UniformHandle g_bgfxSamplerBlit; @@ -1005,7 +1005,7 @@ inline void CRT_EndAndBlit_Bgfx(float time, int width, int height) { bgfx::setViewRect(1, 0, 0, (uint16_t)width, (uint16_t)height); bgfx::setViewClear(1, 0); // backbuffer is fully overwritten by the quad - // Same fullscreen quad as boot_anim's blit. Layout has to match vs_blit's + // Same fullscreen quad as Bgfx_BlitTexturedQuad. Layout has to match vs_blit's // declared $input slots. bgfx::VertexLayout layout; layout.begin() @@ -1016,7 +1016,7 @@ inline void CRT_EndAndBlit_Bgfx(float time, int width, int height) { .add(bgfx::Attrib::TexCoord0, 2, bgfx::AttribType::Float) .end(); - // UV layout matches boot_anim's blit: bottom of the quad samples V=1, + // UV layout matches Bgfx_BlitTexturedQuad: bottom of the quad samples V=1, // top samples V=0. The offscreen FB written by view 0 follows the // active backend's render-target origin, so the top-of-screen pixel // sits at the top of the texture. Without this mapping the post- diff --git a/theseus/desktop/dashapp.cpp b/theseus/desktop/dashapp.cpp index dd782d1..55c4ce3 100755 --- a/theseus/desktop/dashapp.cpp +++ b/theseus/desktop/dashapp.cpp @@ -822,7 +822,14 @@ void CleanupApp() void Advance() { - XTIME now = (float)GetTickCount() / 1000.0f; + // Seconds since the process started, from the high resolution counter. + // This was GetTickCount() / 1000 through a float: on Windows that is + // system uptime with a 15.6 ms tick, and a float of it loses the + // fraction as uptime grows (steps of 1/16 s after a week, 1/8 s after + // two), so animation advanced in coarse, uneven jumps however fast + // frames were drawn. + static const Uint64 s_t0 = SDL_GetPerformanceCounter(); + XTIME now = (XTIME)(SDL_GetPerformanceCounter() - s_t0) / (XTIME)SDL_GetPerformanceFrequency(); float nDelta = (float)(now - g_now); if (nDelta == 0.0f) nDelta = 0.001f; diff --git a/theseus/desktop/menu_bar.cpp b/theseus/desktop/menu_bar.cpp index 76478ff..87b72d7 100644 --- a/theseus/desktop/menu_bar.cpp +++ b/theseus/desktop/menu_bar.cpp @@ -452,6 +452,14 @@ void RenderMainMenuBar() { (wf & SDL_WINDOW_BORDERLESS) ? SDL_TRUE : SDL_FALSE); } + { + extern bool g_graphicsDebug; + if (ImGui::MenuItem("FPS / Debug Overlay", "F9", g_graphicsDebug)) { + g_graphicsDebug = !g_graphicsDebug; + bgfx::setDebug(g_graphicsDebug ? BGFX_DEBUG_PROFILER : BGFX_DEBUG_NONE); + } + } + ImGui::Separator(); if (ImGui::BeginMenu("Menu Bar")) { @@ -624,7 +632,7 @@ void RenderSettingsWindow() { ImGui::SameLine(); ImGui::TextDisabled("(?)"); if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Plays the original Xbox boot animation once on app startup\nbefore the dashboard initializes. Press Esc/Enter/Space to skip."); + ImGui::SetTooltip("Plays the original Xbox boot animation once on app startup\nbefore the dashboard initializes. Press Esc/Enter/Space or A/B/Start to skip."); ImGui::Spacing(); if (ImGui::Button("Save All Settings")) @@ -1325,6 +1333,7 @@ void RenderShortcutsWindow() { ImGui::Text("F4 Settings"); ImGui::Text("F5 HDD Browser"); ImGui::Text("F6 Playlist Maker"); + ImGui::Text("F9 FPS / Debug Overlay"); ImGui::Text("F10 Hide Menu Bar"); ImGui::Text("F11 Toggle Fullscreen"); ImGui::Text("F12 Toggle Borderless Window"); diff --git a/theseus/desktop/sdl_main.cpp b/theseus/desktop/sdl_main.cpp index 7ca83f7..22f7e4b 100644 --- a/theseus/desktop/sdl_main.cpp +++ b/theseus/desktop/sdl_main.cpp @@ -346,7 +346,9 @@ static void ApplyEffectiveMute() void ApplyEffectiveMute_Public() { ApplyEffectiveMute(); } bool g_startMinimized = false; bool g_graphicsDebug = false; -extern double g_perfDrawMs, g_perfImguiMs, g_perfSwapMs, g_perfFrameMs, g_perfFps; +extern double g_perfAdvanceMs, g_perfDrawMs, g_perfImguiMs, g_perfSwapMs, g_perfFrameMs, g_perfFps; +extern float g_perfHist[240]; +extern int g_perfHistPos; float g_muteOverlayTimer = 0.0f; // seconds remaining to show the overlay toast // Instance lists for recreating GPU resources on device reset. @@ -385,7 +387,7 @@ char g_jellyfinClientId[64] = ""; char g_jellyfinUserName[128] = ""; int g_startupMode = 0; // 0 = ask, 1 = dashboard, 2 = development bool g_bUseOnScreenKeyboard = false; // when true, ignore physical keyboard during keyboard popups -bool g_bShowBootAnimation = true; // play xbox_boot.mp4 once at startup before the dashboard +bool g_bShowBootAnimation = true; // play the boot animation once at startup before the dashboard // Legacy [Progressive] / [Dashboard Settings] sections, round-tripped through // desktop.ini so the truncate-rewrite save doesn't drop them. @@ -998,17 +1000,53 @@ static void PreSwapOverlays() { } if (g_graphicsDebug) { + extern int g_vsyncMode; + extern int g_msaaSamples; + extern int g_fpsCap; + const bgfx::Stats* st = bgfx::getStats(); + double gpuMs = (st->gpuTimerFreq > 0 && st->gpuTimeEnd > st->gpuTimeBegin) + ? (double)(st->gpuTimeEnd - st->gpuTimeBegin) * 1000.0 / (double)st->gpuTimerFreq : 0.0; + double cpuMs = (st->cpuTimerFreq > 0 && st->cpuTimeEnd > st->cpuTimeBegin) + ? (double)(st->cpuTimeEnd - st->cpuTimeBegin) * 1000.0 / (double)st->cpuTimerFreq : 0.0; + double waitMs = (st->cpuTimerFreq > 0) + ? (double)(st->waitRender + st->waitSubmit) * 1000.0 / (double)st->cpuTimerFreq : 0.0; + int refresh = 0; + SDL_DisplayMode dm; + if (g_pSDLWindow && SDL_GetWindowDisplayMode(g_pSDLWindow, &dm) == 0) refresh = dm.refresh_rate; + ImGuiWindowFlags fl = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav; ImGui::SetNextWindowBgAlpha(0.6f); ImGui::SetNextWindowPos(ImVec2(8, 30), ImGuiCond_FirstUseEver); if (ImGui::Begin("##perf", nullptr, fl)) { - ImGui::Text("FPS: %.1f", g_perfFps); - ImGui::Text("Frame: %.2f ms", g_perfFrameMs); + ImGui::Text("FPS: %.1f (%.2f ms)", g_perfFps, g_perfFrameMs); + float hist[240]; + float histMax = 0.0f; + for (int i = 0; i < 240; i++) { + hist[i] = g_perfHist[(g_perfHistPos + i) % 240]; + if (hist[i] > histMax) histMax = hist[i]; + } + ImGui::PlotLines("##frametimes", hist, 240, 0, nullptr, 0.0f, + histMax > 33.4f ? histMax : 33.4f, ImVec2(240, 40)); + ImGui::Separator(); + ImGui::Text("Update: %.2f ms", g_perfAdvanceMs); + ImGui::Text("Draw: %.2f ms", g_perfDrawMs); + ImGui::Text("ImGui: %.2f ms", g_perfImguiMs); + ImGui::Text("Present: %.2f ms", g_perfSwapMs); + ImGui::Separator(); + ImGui::Text("GPU: %.2f ms", gpuMs); + ImGui::Text("Render thread: %.2f ms (wait %.2f)", cpuMs, waitMs); + ImGui::Text("Draw calls: %u Views: %u", st->numDraw, (unsigned)st->numViews); ImGui::Separator(); - ImGui::Text("Draw: %.2f ms", g_perfDrawMs); - ImGui::Text("ImGui: %.2f ms", g_perfImguiMs); - ImGui::Text("Swap: %.2f ms", g_perfSwapMs); + ImGui::Text("%s %ux%u", bgfx::getRendererName(bgfx::getCaps()->rendererType), + (unsigned)st->width, (unsigned)st->height); + ImGui::Text("Vsync %s MSAA %dx Cap %s CRT %s", + g_vsyncMode == 2 ? "off" : "on", + g_msaaSamples > 1 ? g_msaaSamples : 1, + g_fpsCap > 0 ? std::to_string(g_fpsCap).c_str() : "off", + g_crt.enabled ? "on" : "off"); + if (refresh > 0) ImGui::Text("Display: %d Hz", refresh); + ImGui::TextDisabled("F9 to hide"); } ImGui::End(); } @@ -1049,12 +1087,18 @@ int g_windowMode = 0; bool g_displayChangeRequested = false; // Per-pass timings populated each frame in the main loop. Read by the -// --graphics-debug overlay. Single-frame ms; FPS is EMA-smoothed. +// debug overlay (F9, or --graphics-debug to start with it open). +// Single-frame ms; FPS is EMA-smoothed over the real frame interval, so it +// counts event handling, Advance and any cap sleep, not just the draw. +double g_perfAdvanceMs = 0.0; double g_perfDrawMs = 0.0; double g_perfImguiMs = 0.0; double g_perfSwapMs = 0.0; double g_perfFrameMs = 0.0; double g_perfFps = 0.0; +// Frame interval history for the overlay graph, oldest first from g_perfHistPos. +float g_perfHist[240] = {}; +int g_perfHistPos = 0; // bgfx reset flags from g_vsyncMode + g_msaaSamples. bgfx has no adaptive // vsync, so only an explicit "off" (mode 2) disables it; the default and @@ -1105,9 +1149,16 @@ void ApplyDisplayMode() { SDL_SetWindowFullscreen(g_pSDLWindow, 0); if (g_windowResolution > 0) { - // 16:9 width derived from vertical pixel count. + // Width from the vertical pixel count: 16:9 in a window, the + // display's own shape in exclusive fullscreen so an ultrawide + // (21:9, 32:9) monitor gets a mode that fills it. int h = g_windowResolution; int w = (h * 16) / 9; + SDL_DisplayMode desk; + int disp = SDL_GetWindowDisplayIndex(g_pSDLWindow); + if (g_windowMode == 2 && disp >= 0 && SDL_GetDesktopDisplayMode(disp, &desk) == 0 && + desk.w > 0 && desk.h > 0) + w = (int)((long long)h * desk.w / desk.h) & ~1; SDL_SetWindowSize(g_pSDLWindow, w, h); SDL_SetWindowPosition(g_pSDLWindow, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); } @@ -1355,9 +1406,13 @@ int main(int argc, char* argv[]) { char logPath[1024]; Plat_MkdirP(Plat_UserDataDir()); snprintf(logPath, sizeof(logPath), "%s/theseus.log", Plat_UserDataDir()); - if (freopen(logPath, "w", stdout)) { + // A GUI process has no fd behind stderr (_fileno gives -2), so it + // can't be _dup2'd onto. Truncate once, then reopen both streams in + // append mode so neither writes over the other. + if (freopen(logPath, "w", stdout) && freopen(logPath, "a", stdout)) { setvbuf(stdout, NULL, _IOLBF, 0); - _dup2(_fileno(stdout), _fileno(stderr)); + if (freopen(logPath, "a", stderr)) + setvbuf(stderr, NULL, _IONBF, 0); } } @@ -1570,6 +1625,7 @@ int main(int argc, char* argv[]) { // before the first frame. This can read low if the window // isn't realised yet, which the per frame check above heals. BgfxApplyReset(); + if (g_graphicsDebug) bgfx::setDebug(BGFX_DEBUG_PROFILER); fprintf(stdout, "bgfx backbuffer: %dx%d px (scale %.2f)\n", initW, initH, Plat_GetDisplayScale()); @@ -1816,11 +1872,12 @@ int main(int argc, char* argv[]) { : "UIX Desktop"); // Cold-boot animation. Plays once before dashboard init mirrors what the - // original Xbox does on power-on. Skipped in dev mode (the dashboard - // isn't the focus there) and toggleable via Settings > Show Boot Animation. + // original Xbox does on power-on: the reverse engineered animation from + // third-party/xboot, rendered live, then straight into the dashboard. + // Skipped in dev mode (the dashboard isn't the focus there) and + // toggleable via Settings > Show Boot Animation. if (g_bShowBootAnimation && !g_extractedMode) { - BootAnim_PlayAndWait(g_pSDLWindow, - AppPath("Configs/xbox_boot.mp4")); + BootAnim_PlayAndWait(g_pSDLWindow); } // Initialize app globals @@ -1840,6 +1897,13 @@ int main(int argc, char* argv[]) { return 1; } + // InitApp's InitD3D sets the view size to a 1280x720 placeholder. Put + // it back to the real drawable now, or the projection keeps 16:9 until + // the next resize event: a 21:9 or 32:9 window (fullscreen especially, + // which sends none) showed the dashboard stretched until it was + // minimized and restored. + BgfxApplyReset(); + // ES-DE import runs here, not before the window: it walks the rom tree, // which on a network share with a cold cache is slow, and doing it any // earlier means staring at no window at all. The launcher reads VGames @@ -2045,6 +2109,13 @@ int main(int argc, char* argv[]) { else Theseus_D3D_DumpTrace(); } + if (event.key.keysym.sym == SDLK_F9) { + // Debug overlay: FPS, frame graph, per-pass and GPU + // timings. Same panel --graphics-debug opens. + g_graphicsDebug = !g_graphicsDebug; + if (g_graphicsDebug) bgfx::setDebug(BGFX_DEBUG_PROFILER); + else bgfx::setDebug(BGFX_DEBUG_NONE); + } if (event.key.keysym.sym == SDLK_F10) { g_showMenuBar = !g_showMenuBar; } @@ -2208,7 +2279,9 @@ int main(int argc, char* argv[]) { ApplyEffectiveMute(); } + Uint64 tAdv0 = SDL_GetPerformanceCounter(); if (!g_mediaFullscreen && !g_xcloudFullscreen) Advance(); + g_perfAdvanceMs = (SDL_GetPerformanceCounter() - tAdv0) * 1000.0 / (double)perfFreq; // Same idea under bgfx: allocate / resize the offscreen FB to // window size, point view 0 at it so the scene draws there @@ -2301,14 +2374,22 @@ int main(int argc, char* argv[]) { bgfx::frame(); Uint64 tSwap1 = SDL_GetPerformanceCounter(); - if (g_graphicsDebug) { + { + // Timed every frame (it's a few counter reads) so the overlay + // has real numbers the moment F9 opens it. + static Uint64 s_prevSwap1 = 0; double freq = (double)perfFreq; g_perfDrawMs = (tDraw1 - tDraw0) * 1000.0 / freq; g_perfImguiMs = (tImgui1 - tImgui0) * 1000.0 / freq; g_perfSwapMs = (tSwap1 - tSwap0) * 1000.0 / freq; - g_perfFrameMs = (tSwap1 - tDraw0) * 1000.0 / freq; - double instFps = (g_perfFrameMs > 0.0) ? 1000.0 / g_perfFrameMs : 0.0; - g_perfFps = (g_perfFps == 0.0) ? instFps : 0.9 * g_perfFps + 0.1 * instFps; + if (s_prevSwap1) { + g_perfFrameMs = (tSwap1 - s_prevSwap1) * 1000.0 / freq; + double instFps = (g_perfFrameMs > 0.0) ? 1000.0 / g_perfFrameMs : 0.0; + g_perfFps = (g_perfFps == 0.0) ? instFps : 0.9 * g_perfFps + 0.1 * instFps; + g_perfHist[g_perfHistPos] = (float)g_perfFrameMs; + g_perfHistPos = (g_perfHistPos + 1) % 240; + } + s_prevSwap1 = tSwap1; } // (Old code re-applied Mix_Volume(-1, 0) every frame while muted. diff --git a/theseus/desktop/shaders/xboot/fs_blob.sc b/theseus/desktop/shaders/xboot/fs_blob.sc new file mode 100644 index 0000000..db436f0 --- /dev/null +++ b/theseus/desktop/shaders/xboot/fs_blob.sc @@ -0,0 +1,17 @@ +$input v_d0, v_t0, v_t1, v_t2, v_t3 + +// The block of the blob: the rim from normal and direction to the eye, +// both through the cube of directions. + +#include +#include "xboot.sh" + +SAMPLERCUBE(s_cube, 0); + +void main() +{ + float c = clamp(dot(ex3(textureCubeLod(s_cube, v_t0.xyz, 0.0).rgb), + ex3(textureCubeLod(s_cube, v_t1.xyz, 0.0).rgb)), -1.0, 1.0); + float r = (1.0 - clamp(c, 0.0, 1.0)) * (1.0 - clamp(c, 0.0, 1.0)); + gl_FragColor = vec4(st3(st3((1.0 - r) * u_k[0].rgb) + u_k[1].rgb), u_k[0].a); +} diff --git a/theseus/desktop/shaders/xboot/fs_depth.sc b/theseus/desktop/shaders/xboot/fs_depth.sc new file mode 100644 index 0000000..a31a0e1 --- /dev/null +++ b/theseus/desktop/shaders/xboot/fs_depth.sc @@ -0,0 +1,14 @@ +$input v_d0, v_t0, v_t1, v_t2, v_t3 + +// The block "Depth for the veil" (GPU.md 4.3). + +#include +#include "xboot.sh" + +void main() +{ + vec3 e = ex3(v_t0.rgb); + float m = 1.0 - clamp(dot(e, e), 0.0, 1.0); + if (alpha_fails(1.0)) discard; + gl_FragColor = vec4(max(st3(v_d0.rgb * (m * m)), vec3_splat(0.0)), 1.0); +} diff --git a/theseus/desktop/shaders/xboot/fs_drop.sc b/theseus/desktop/shaders/xboot/fs_drop.sc new file mode 100644 index 0000000..3dad044 --- /dev/null +++ b/theseus/desktop/shaders/xboot/fs_drop.sc @@ -0,0 +1,17 @@ +$input v_d0, v_t0, v_t1, v_t2, v_t3 + +// The block of the drops: the blob's rim, its alpha faded by u_k[2].a. + +#include +#include "xboot.sh" + +SAMPLERCUBE(s_cube, 0); + +void main() +{ + float c = clamp(dot(ex3(textureCubeLod(s_cube, v_t0.xyz, 0.0).rgb), + ex3(textureCubeLod(s_cube, v_t1.xyz, 0.0).rgb)), -1.0, 1.0); + float r = (1.0 - clamp(c, 0.0, 1.0)) * (1.0 - clamp(c, 0.0, 1.0)); + gl_FragColor = vec4(st3(st3((1.0 - r) * u_k[0].rgb) + u_k[1].rgb), + clamp((1.0 - r) * u_k[0].a, -1.0, 1.0) * u_k[2].a); +} diff --git a/theseus/desktop/shaders/xboot/fs_fixed.sc b/theseus/desktop/shaders/xboot/fs_fixed.sc new file mode 100644 index 0000000..df80bd1 --- /dev/null +++ b/theseus/desktop/shaders/xboot/fs_fixed.sc @@ -0,0 +1,33 @@ +$input v_d0, v_t0, v_t1, v_t2, v_t3 + +// Combiners from the texture stages (GPU.md 5), u_mode says what they take. +// Filtered between four texels, level 0 alone; where the stage has a border +// colour (u_mode.w) the shader reads the texels itself and takes u_k[2] +// outside the texture. + +#include +#include "xboot.sh" + +SAMPLER2D(s_image, 0); + +vec4 tap(vec2 p) +{ + ivec2 size = ivec2(u_api.zw); + ivec2 q = ivec2(p); + if (u_mode.w != 0.0 && (q.x < 0 || q.y < 0 || q.x >= size.x || q.y >= size.y)) return u_k[2]; + return texelFetch(s_image, clamp(q, ivec2(0, 0), size - ivec2(1, 1)), 0); +} + +void main() +{ + vec2 p = v_t0.xy / v_t0.w * u_api.zw - 0.5; + vec2 i = floor(p); + vec2 f = p - i; + vec4 t0 = mix(mix(tap(i), tap(i + vec2(1.0, 0.0)), f.x), mix(tap(i + vec2(0.0, 1.0)), tap(i + vec2(1.0, 1.0)), f.x), f.y); + vec4 c = vec4(1.0, 1.0, 1.0, u_k[3].a); + if (u_mode.x != 0.0) c.rgb = c.rgb * t0.rgb; + if (u_mode.y != 0.0) c.rgb = c.rgb * u_k[3].rgb; + if (u_mode.z != 0.0) c.a = c.a * t0.a; + if (alpha_fails(c.a)) discard; + gl_FragColor = c; +} diff --git a/theseus/desktop/shaders/xboot/fs_lightdepth.sc b/theseus/desktop/shaders/xboot/fs_lightdepth.sc new file mode 100644 index 0000000..a62028b --- /dev/null +++ b/theseus/desktop/shaders/xboot/fs_lightdepth.sc @@ -0,0 +1,12 @@ +// The light views' depth offset, SET_POLYGON_OFFSET_BIAS 10 and +// _SCALE_FACTOR 4 (GPU.md 6.2). bgfx has no depth bias state, so the +// fragment applies it: units of the 16-bit depth buffer plus the slope. + +#include + +void main() +{ + float z = gl_FragCoord.z; + float slope = max(abs(dFdx(z)), abs(dFdy(z))); + gl_FragDepth = clamp(z + 10.0 / 65536.0 + 4.0 * slope, 0.0, 1.0); +} diff --git a/theseus/desktop/shaders/xboot/fs_logod.sc b/theseus/desktop/shaders/xboot/fs_logod.sc new file mode 100644 index 0000000..22033bb --- /dev/null +++ b/theseus/desktop/shaders/xboot/fs_logod.sc @@ -0,0 +1,8 @@ +$input v_d0, v_t0, v_t1, v_t2, v_t3 + +#include + +void main() +{ + gl_FragColor = v_d0; +} diff --git a/theseus/desktop/shaders/xboot/fs_plain.sc b/theseus/desktop/shaders/xboot/fs_plain.sc new file mode 100644 index 0000000..3bb39f1 --- /dev/null +++ b/theseus/desktop/shaders/xboot/fs_plain.sc @@ -0,0 +1,7 @@ +#include +#include "xboot.sh" + +void main() +{ + gl_FragColor = u_k[3]; +} diff --git a/theseus/desktop/shaders/xboot/fs_present.sc b/theseus/desktop/shaders/xboot/fs_present.sc new file mode 100644 index 0000000..197c0ee --- /dev/null +++ b/theseus/desktop/shaders/xboot/fs_present.sc @@ -0,0 +1,17 @@ +$input v_d0, v_t0, v_t1, v_t2, v_t3 + +// D5 (GPU.md 7): pixel (x, y) of the picture is the mean of pixels (2x, y) +// and (2x + 1, y) of the drawing surface. u_api.zw is the picture's size. + +#include +#include "xboot.sh" + +SAMPLER2D(s_screen, 0); + +void main() +{ + ivec2 size = ivec2(u_api.zw); + ivec2 xy = clamp(ivec2(floor(v_t0.xy * u_api.zw)), ivec2(0, 0), size - ivec2(1, 1)); + ivec2 at = ivec2(xy.x * 2, xy.y); + gl_FragColor = (texelFetch(s_screen, at, 0) + texelFetch(s_screen, at + ivec2(1, 0), 0)) * 0.5; +} diff --git a/theseus/desktop/shaders/xboot/fs_relief.sc b/theseus/desktop/shaders/xboot/fs_relief.sc new file mode 100644 index 0000000..5fa1346 --- /dev/null +++ b/theseus/desktop/shaders/xboot/fs_relief.sc @@ -0,0 +1,26 @@ +$input v_d0, v_t0, v_t1, v_t2, v_t3 + +// GPU.md 4.3, the block of the parts of the chamber with relief: as +// fs_smooth, the normal from the relief texture and nh to the power 16. + +#include +#include "xboot.sh" + +SAMPLERCUBE(s_cube, 0); +SAMPLER2D(s_light, 1); +SAMPLER2D(s_relief, 2); + +#include "xboot_shadow.sh" + +void main() +{ + vec3 t1 = ex3(textureCubeLod(s_cube, v_t1.xyz, 0.0).rgb); + vec3 t2 = ex3(textureCubeLod(s_cube, v_t2.xyz, 0.0).rgb); + vec3 normal = ex3(texture2DLod(s_relief, v_t0.xy / v_t0.w, 0.0).rgb); + float nl = clamp(dot(normal, t1), -1.0, 1.0); + float s = max(clamp(dot(normal, t2), -1.0, 1.0), 0.0); + s = s * s; s = s * s; s = s * s; s = s * s; + vec3 lit = st3(u_k[1].rgb * max(nl, 0.0) + u_k[2].rgb * s); + vec3 r0 = max(st3(v_d0.rgb * lit + u_k[0].rgb), vec3_splat(0.0)); + gl_FragColor = vec4(r0 * (63.0 / 255.0) + r0 * shadow(v_t3), 1.0); +} diff --git a/theseus/desktop/shaders/xboot/fs_smooth.sc b/theseus/desktop/shaders/xboot/fs_smooth.sc new file mode 100644 index 0000000..691554d --- /dev/null +++ b/theseus/desktop/shaders/xboot/fs_smooth.sc @@ -0,0 +1,26 @@ +$input v_d0, v_t0, v_t1, v_t2, v_t3 + +// GPU.md 4.3, the block of the smooth parts of the chamber: nl and nh from +// the cube of directions, nh to the power 32, then +// colour0 * (Kd nl + Ks s) + Ka, once with K4 and once with the shadow. + +#include +#include "xboot.sh" + +SAMPLERCUBE(s_cube, 0); +SAMPLER2D(s_light, 1); + +#include "xboot_shadow.sh" + +void main() +{ + vec3 t1 = ex3(textureCubeLod(s_cube, v_t1.xyz, 0.0).rgb); + vec3 t2 = ex3(textureCubeLod(s_cube, v_t2.xyz, 0.0).rgb); + vec3 normal = vec3(0.0, 0.0, 1.0); + float nl = clamp(dot(normal, t1), -1.0, 1.0); + float s = max(clamp(dot(normal, t2), -1.0, 1.0), 0.0); + s = s * s; s = s * s; s = s * s; s = s * s; s = s * s; + vec3 lit = st3(u_k[1].rgb * max(nl, 0.0) + u_k[2].rgb * s); + vec3 r0 = max(st3(v_d0.rgb * lit + u_k[0].rgb), vec3_splat(0.0)); + gl_FragColor = vec4(r0 * (63.0 / 255.0) + r0 * shadow(v_t3), 1.0); +} diff --git a/theseus/desktop/shaders/xboot/fs_veil.sc b/theseus/desktop/shaders/xboot/fs_veil.sc new file mode 100644 index 0000000..66531eb --- /dev/null +++ b/theseus/desktop/shaders/xboot/fs_veil.sc @@ -0,0 +1,20 @@ +$input v_d0, v_t0, v_t1, v_t2, v_t3 + +// The block of the veil: target * (noise * K0) + K1. + +#include +#include "xboot.sh" + +SAMPLER2D(s_aimed, 0); +SAMPLER2D(s_noise1, 1); +SAMPLER2D(s_noise2, 2); +SAMPLER2D(s_noise3, 3); + +void main() +{ + vec3 t0 = texture2DLod(s_aimed, v_t0.xy / v_t0.w, 0.0).rgb; + float n = min(texture2DLod(s_noise1, v_t1.xy / v_t1.w, 0.0).r + texture2DLod(s_noise2, v_t2.xy / v_t2.w, 0.0).r, 1.0); + n = min(n + texture2DLod(s_noise3, v_t3.xy / v_t3.w, 0.0).r, 1.0); + if (alpha_fails(1.0)) discard; + gl_FragColor = vec4(st3(st3(t0 * (n * u_k[0].rgb)) + u_k[1].rgb), 1.0); +} diff --git a/theseus/desktop/shaders/xboot/varying.def.sc b/theseus/desktop/shaders/xboot/varying.def.sc new file mode 100644 index 0000000..3016a75 --- /dev/null +++ b/theseus/desktop/shaders/xboot/varying.def.sc @@ -0,0 +1,14 @@ +vec4 v_d0 : COLOR0 = vec4(0.0, 0.0, 0.0, 1.0); +vec4 v_t0 : TEXCOORD0 = vec4(0.0, 0.0, 0.0, 1.0); +vec4 v_t1 : TEXCOORD1 = vec4(0.0, 0.0, 0.0, 1.0); +vec4 v_t2 : TEXCOORD2 = vec4(0.0, 0.0, 0.0, 1.0); +vec4 v_t3 : TEXCOORD3 = vec4(0.0, 0.0, 0.0, 1.0); + +vec3 a_position : POSITION; +vec2 a_texcoord0 : TEXCOORD0; +vec2 a_texcoord1 : TEXCOORD1; +vec3 a_texcoord2 : TEXCOORD2; +vec3 a_texcoord3 : TEXCOORD3; +vec3 a_texcoord4 : TEXCOORD4; +vec2 a_texcoord5 : TEXCOORD5; +vec4 a_texcoord6 : TEXCOORD6; diff --git a/theseus/desktop/shaders/xboot/vs_blob.sc b/theseus/desktop/shaders/xboot/vs_blob.sc new file mode 100644 index 0000000..08d1ee9 --- /dev/null +++ b/theseus/desktop/shaders/xboot/vs_blob.sc @@ -0,0 +1,23 @@ +$input a_position, a_texcoord6 +$output v_d0, v_t0, v_t1, v_t2, v_t3 + +// GPU.md 3.4: the blob's mesh, a_texcoord6 its deformation (summed +// direction and height, Blob.md 6.4). + +#include +#include "xboot.sh" + +void main() +{ + vec3 dir = a_position * u_c[11].xyz; + vec4 q = vec4(a_position * u_c[10].xyz + u_c[12].xyz + rsq(dot(dir, dir)) * dir * a_texcoord6.w, + u_c[10].w + u_c[12].w + u_c[8].x * u_c[8].y); + vec3 eye = u_c[9].xyz - q.xyz; + vec3 n = a_texcoord6.xyz * u_c[11].xyz; + gl_Position = place(xmul(q, 4)); + v_d0 = vec4(0.0, 0.0, 0.0, 1.0); + v_t0 = vec4(rsq(dot(n, n)) * n, u_c[8].y); + v_t1 = vec4(rsq(dot(eye, eye)) * eye, u_c[8].y); + v_t2 = vec4(0.0, 0.0, 0.0, 1.0); + v_t3 = vec4(0.0, 0.0, 0.0, 1.0); +} diff --git a/theseus/desktop/shaders/xboot/vs_depth.sc b/theseus/desktop/shaders/xboot/vs_depth.sc new file mode 100644 index 0000000..03ff4dd --- /dev/null +++ b/theseus/desktop/shaders/xboot/vs_depth.sc @@ -0,0 +1,19 @@ +$input a_position +$output v_d0, v_t0, v_t1, v_t2, v_t3 + +// GPU.md 3.3: the disc around the picture of the origin, darker where the +// chamber is near (Effects.md 3). + +#include +#include "xboot.sh" + +void main() +{ + vec4 r = xmul(vec4(a_position, 1.0), 0); + gl_Position = place(r); + v_d0 = vec4(sat3(max(u_c[16].xyz * (vec3_splat(r.z) + u_c[17].xyz), vec3_splat(0.0))), 1.0); + v_t0 = vec4(r.xyz * (1.0 / r.w) * u_c[18].xyz + u_c[19].xyz, 1.0); + v_t1 = vec4(0.0, 0.0, 0.0, 1.0); + v_t2 = vec4(0.0, 0.0, 0.0, 1.0); + v_t3 = vec4(0.0, 0.0, 0.0, 1.0); +} diff --git a/theseus/desktop/shaders/xboot/vs_drop.sc b/theseus/desktop/shaders/xboot/vs_drop.sc new file mode 100644 index 0000000..2523217 --- /dev/null +++ b/theseus/desktop/shaders/xboot/vs_drop.sc @@ -0,0 +1,19 @@ +$input a_position +$output v_d0, v_t0, v_t1, v_t2, v_t3 + +// GPU.md 3.5: a drop of the blob. + +#include +#include "xboot.sh" + +void main() +{ + vec4 q = vec4(u_c[12].xyz * a_position + dot(a_position, u_c[11].xyz) * u_c[13].xyz + u_c[10].xyz, u_c[8].y); + vec3 eye = u_c[9].xyz - q.xyz; + gl_Position = place(xmul(q, 4)); + v_d0 = vec4(0.0, 0.0, 0.0, 1.0); + v_t0 = vec4(a_position, 1.0); + v_t1 = vec4(rsq(dot(eye, eye)) * eye, u_c[8].y); + v_t2 = vec4(0.0, 0.0, 0.0, 1.0); + v_t3 = vec4(0.0, 0.0, 0.0, 1.0); +} diff --git a/theseus/desktop/shaders/xboot/vs_fixed.sc b/theseus/desktop/shaders/xboot/vs_fixed.sc new file mode 100644 index 0000000..89980a5 --- /dev/null +++ b/theseus/desktop/shaders/xboot/vs_fixed.sc @@ -0,0 +1,18 @@ +$input a_position, a_texcoord0 +$output v_d0, v_t0, v_t1, v_t2, v_t3 + +// Fixed function (GPU.md 2.1): the rows of world * view * projection stand +// in u_c[0..3]. + +#include +#include "xboot.sh" + +void main() +{ + gl_Position = place(xmul(vec4(a_position, 1.0), 0)); + v_d0 = vec4(0.0, 0.0, 0.0, 1.0); + v_t0 = vec4(a_texcoord0, 0.0, 1.0); + v_t1 = vec4(0.0, 0.0, 0.0, 1.0); + v_t2 = vec4(0.0, 0.0, 0.0, 1.0); + v_t3 = vec4(0.0, 0.0, 0.0, 1.0); +} diff --git a/theseus/desktop/shaders/xboot/vs_lightdepth.sc b/theseus/desktop/shaders/xboot/vs_lightdepth.sc new file mode 100644 index 0000000..3404e8f --- /dev/null +++ b/theseus/desktop/shaders/xboot/vs_lightdepth.sc @@ -0,0 +1,11 @@ +$input a_position + +// U1, the chamber into the two light views: fixed function, depth alone. + +#include +#include "xboot.sh" + +void main() +{ + gl_Position = place(xmul(vec4(a_position, 1.0), 0)); +} diff --git a/theseus/desktop/shaders/xboot/vs_logod.sc b/theseus/desktop/shaders/xboot/vs_logod.sc new file mode 100644 index 0000000..0ec42e8 --- /dev/null +++ b/theseus/desktop/shaders/xboot/vs_logod.sc @@ -0,0 +1,19 @@ +$input a_position, a_texcoord0 +$output v_d0, v_t0, v_t1, v_t2, v_t3 + +// GPU.md 3.7: logo D, its colour from the vertex. + +#include +#include "xboot.sh" + +void main() +{ + vec3 a = u_c[5].xyz + (u_c[4].xyz - u_c[5].xyz) * a_texcoord0.y; + vec3 b = u_c[7].xyz + (u_c[6].xyz - u_c[7].xyz) * a_texcoord0.y; + gl_Position = place(xmul(vec4(a_position, 1.0), 0)); + v_d0 = vec4(sat3(a * u_c[8].xyz + b - b * u_c[8].xyz), 1.0); + v_t0 = vec4(0.0, 0.0, 0.0, 1.0); + v_t1 = vec4(0.0, 0.0, 0.0, 1.0); + v_t2 = vec4(0.0, 0.0, 0.0, 1.0); + v_t3 = vec4(0.0, 0.0, 0.0, 1.0); +} diff --git a/theseus/desktop/shaders/xboot/vs_plain.sc b/theseus/desktop/shaders/xboot/vs_plain.sc new file mode 100644 index 0000000..431e821 --- /dev/null +++ b/theseus/desktop/shaders/xboot/vs_plain.sc @@ -0,0 +1,11 @@ +$input a_position + +// The lettering of the logo: the factor colour alone. + +#include +#include "xboot.sh" + +void main() +{ + gl_Position = place(xmul(vec4(a_position, 1.0), 0)); +} diff --git a/theseus/desktop/shaders/xboot/vs_present.sc b/theseus/desktop/shaders/xboot/vs_present.sc new file mode 100644 index 0000000..af83e79 --- /dev/null +++ b/theseus/desktop/shaders/xboot/vs_present.sc @@ -0,0 +1,16 @@ +$input a_position, a_texcoord0 +$output v_d0, v_t0, v_t1, v_t2, v_t3 + +// D5 (GPU.md 7): a quad over the picture's place in the window. + +#include + +void main() +{ + gl_Position = vec4(a_position, 1.0); + v_d0 = vec4(0.0, 0.0, 0.0, 1.0); + v_t0 = vec4(a_texcoord0, 0.0, 1.0); + v_t1 = vec4(0.0, 0.0, 0.0, 1.0); + v_t2 = vec4(0.0, 0.0, 0.0, 1.0); + v_t3 = vec4(0.0, 0.0, 0.0, 1.0); +} diff --git a/theseus/desktop/shaders/xboot/vs_relief.sc b/theseus/desktop/shaders/xboot/vs_relief.sc new file mode 100644 index 0000000..367dc45 --- /dev/null +++ b/theseus/desktop/shaders/xboot/vs_relief.sc @@ -0,0 +1,27 @@ +$input a_position, a_texcoord2, a_texcoord3, a_texcoord4, a_texcoord5 +$output v_d0, v_t0, v_t1, v_t2, v_t3 + +// GPU.md 3.1 and 3.2, a part of the chamber. v_t3 is the place in the light +// view, v_d0 the attenuation, v_t1 and v_t2 light and half vector along the +// part's axes a_texcoord2, 3 and 4. + +#include +#include "xboot.sh" + +void main() +{ + vec4 p = vec4(a_position, 1.0); + gl_Position = place(xmul(p, 0)); + v_t3 = vec4(dot(p, u_c[8]), dot(p, u_c[9]), dot(p, u_c[10]), max(dot(p, u_c[11]), 0.0)); + vec3 q = a_position * u_c[6].xyz; + vec3 l = u_c[4].xyz - q; + vec3 e = u_c[5].xyz - q; + float l2 = dot(l, l); + float rl = rsq(l2); + vec3 L = rl * l; + vec3 H = rsq(dot(e, e)) * e + L; + v_d0 = vec4(sat3(vec3_splat(1.0 / dot(vec3(1.0, l2 * rl, l2), u_c[7].xyz))), 1.0); + v_t1 = vec4(dot(L, a_texcoord2), dot(L, a_texcoord3), dot(L, a_texcoord4), 1.0); + v_t2 = vec4(dot(H, a_texcoord2), dot(H, a_texcoord3), dot(H, a_texcoord4), 1.0); + v_t0 = vec4(a_texcoord5, 0.0, 1.0); +} diff --git a/theseus/desktop/shaders/xboot/vs_smooth.sc b/theseus/desktop/shaders/xboot/vs_smooth.sc new file mode 100644 index 0000000..6c0c574 --- /dev/null +++ b/theseus/desktop/shaders/xboot/vs_smooth.sc @@ -0,0 +1,27 @@ +$input a_position, a_texcoord2, a_texcoord3, a_texcoord4 +$output v_d0, v_t0, v_t1, v_t2, v_t3 + +// GPU.md 3.1 and 3.2, a part of the chamber. v_t3 is the place in the light +// view, v_d0 the attenuation, v_t1 and v_t2 light and half vector along the +// part's axes a_texcoord2, 3 and 4. + +#include +#include "xboot.sh" + +void main() +{ + vec4 p = vec4(a_position, 1.0); + gl_Position = place(xmul(p, 0)); + v_t3 = vec4(dot(p, u_c[8]), dot(p, u_c[9]), dot(p, u_c[10]), max(dot(p, u_c[11]), 0.0)); + vec3 q = a_position * u_c[6].xyz; + vec3 l = u_c[4].xyz - q; + vec3 e = u_c[5].xyz - q; + float l2 = dot(l, l); + float rl = rsq(l2); + vec3 L = rl * l; + vec3 H = rsq(dot(e, e)) * e + L; + v_d0 = vec4(sat3(vec3_splat(1.0 / dot(vec3(1.0, l2 * rl, l2), u_c[7].xyz))), 1.0); + v_t1 = vec4(dot(L, a_texcoord2), dot(L, a_texcoord3), dot(L, a_texcoord4), 1.0); + v_t2 = vec4(dot(H, a_texcoord2), dot(H, a_texcoord3), dot(H, a_texcoord4), 1.0); + v_t0 = vec4(0.0, 0.0, 0.0, 1.0); +} diff --git a/theseus/desktop/shaders/xboot/vs_veil.sc b/theseus/desktop/shaders/xboot/vs_veil.sc new file mode 100644 index 0000000..70197f5 --- /dev/null +++ b/theseus/desktop/shaders/xboot/vs_veil.sc @@ -0,0 +1,18 @@ +$input a_position, a_texcoord0, a_texcoord1 +$output v_d0, v_t0, v_t1, v_t2, v_t3 + +// GPU.md 3.6: the veil, the target of U2/U3 and three layers of noise. + +#include +#include "xboot.sh" + +void main() +{ + vec4 s = vec4(a_texcoord1, 0.0, 1.0); + gl_Position = place(vec4(a_position, 1.0)); + v_d0 = vec4(0.0, 0.0, 0.0, 1.0); + v_t0 = vec4(a_texcoord0, 0.0, 1.0); + v_t1 = s * u_c[1] + u_c[0]; + v_t2 = s * u_c[3] + u_c[2]; + v_t3 = s * u_c[5] + u_c[4]; +} diff --git a/theseus/desktop/shaders/xboot/xboot.sh b/theseus/desktop/shaders/xboot/xboot.sh new file mode 100644 index 0000000..631dde9 --- /dev/null +++ b/theseus/desktop/shaders/xboot/xboot.sh @@ -0,0 +1,58 @@ +// xboot.sh: shared part of the boot animation's shaders, a port of the +// WGSL in re-xbox-boot's hosts/web/src/shaders.js (the vertex programs and +// combiners of its docs/decomp/GPU.md 3 to 5). Only the picture of the +// original is kept; the web workbench's display modes are left out. +// +// u_c[k] is vertex constant k of the core, c[96 + k] of GPU.md. u_k[0..2] +// are the colours of 0x800410B0 packed to bytes (GPU.md 4.2), u_k[3] the +// factor colour of 0x8003CED0. +// +// u_bias x pixel offset 0.53125, y 1 = no snap, zw half the target size +// u_misc x alpha ref, z alpha test on, w shadow on +// u_mode the fixed function's stages: x texture colour, y factor colour, +// z texture alpha, w border colour instead of the clamped edge +// u_api x -1 when a render target is stored bottom up (OpenGL), y 1 for +// clip z in [-1, 1], zw the size of the texture on stage 0 + +uniform vec4 u_c[20]; +uniform vec4 u_k[4]; +uniform vec4 u_bias; +uniform vec4 u_misc; +uniform vec4 u_mode; +uniform vec4 u_api; + +// From the D3D-style clip space the core computes in to the backend's. +vec4 backend(vec4 p) +{ + p.y = p.y * u_api.x; + if (u_api.y > 0.5) p.z = 2.0 * p.z - p.w; + return p; +} + +// Every position lies 0.53125 pixels right and down, and the rasteriser +// takes it in sixteenths of a pixel, cut off towards zero (GPU.md 3). +vec4 place(vec4 p) +{ + if (abs(p.w) < 1e-12) return backend(p); + vec2 hs = u_bias.zw; + vec2 screen = p.xy / p.w * vec2(hs.x, -hs.y) + hs + u_bias.x; + vec2 cut = u_bias.y != 0.0 ? screen : trunc(screen * 16.0) / 16.0; + p.xy = (cut - hs) / vec2(hs.x, -hs.y) * p.w; + return backend(p); +} + +vec4 xmul(vec4 v, int k) +{ + return vec4(dot(v, u_c[k]), dot(v, u_c[k + 1]), dot(v, u_c[k + 2]), dot(v, u_c[k + 3])); +} + +float rsq(float x) { return inversesqrt(abs(x)); } +vec3 sat3(vec3 x) { return clamp(x, vec3_splat(0.0), vec3_splat(1.0)); } +vec3 st3(vec3 x) { return clamp(x, vec3_splat(-1.0), vec3_splat(1.0)); } +vec3 ex3(vec3 x) { return 2.0 * max(x, vec3_splat(0.0)) - 1.0; } + +// SET_ALPHA_FUNC 0x206: kept when round(a * 255) >= SET_ALPHA_REF +bool alpha_fails(float a) +{ + return u_misc.z != 0.0 && floor(a * 255.0 + 0.5) < u_misc.x; +} diff --git a/theseus/desktop/shaders/xboot/xboot_shadow.sh b/theseus/desktop/shaders/xboot/xboot_shadow.sh new file mode 100644 index 0000000..221dcb6 --- /dev/null +++ b/theseus/desktop/shaders/xboot/xboot_shadow.sh @@ -0,0 +1,26 @@ +// The shadow of the chamber (GPU.md 4.4): the depth of the light view, +// filtered, against t.z / t.w. The light view is s_light, which the +// including shader declares. It is addressed in texels and has a white +// border. + +float light_tap(vec2 p) +{ + ivec2 q = ivec2(p); + if (q.x < 0 || q.y < 0 || q.x >= 512 || q.y >= 512) return 1.0; + return texelFetch(s_light, q, 0).x; +} + +float shadow(vec4 t) +{ + if (u_misc.w == 0.0) return 1.0; + // t.w is max(w, 0): behind the light it is 0, the place lies outside + // the texture, whose border is the far depth, and z / w is the nearest + // or the farthest depth by the sign of z + if (t.w <= 0.0) return t.z < 0.0 ? 1.0 : 0.0; + vec2 p = t.xy / t.w - 0.5; + vec2 i = floor(p); + vec2 f = p - i; + float stored = mix(mix(light_tap(i), light_tap(i + vec2(1.0, 0.0)), f.x), + mix(light_tap(i + vec2(0.0, 1.0)), light_tap(i + vec2(1.0, 1.0)), f.x), f.y) * 65535.0; + return stored > clamp(t.z / t.w, 0.0, 65535.0) ? 1.0 : 0.0; +} diff --git a/theseus/desktop/virtual_games.h b/theseus/desktop/virtual_games.h index 9fb2e41..78e054f 100644 --- a/theseus/desktop/virtual_games.h +++ b/theseus/desktop/virtual_games.h @@ -221,11 +221,14 @@ inline void VGames_DeleteByName(const char* name) { // Match a disk path like "Library/Games/Road Rage" to a game index, or -1. inline int VGames_MatchFolder(const char* localPath) { VGames_Load(); + // Callers hand us forward slashes, but on Windows the prefix comes from + // %APPDATA% with backslashes, so fold both sides to '/'. char norm[512]; int j = 0; for (int k = 0; localPath[k] && j < 510; k++) { - if (localPath[k] == '/' && j > 0 && norm[j-1] == '/') continue; - norm[j++] = localPath[k]; + char c = (localPath[k] == '\\') ? '/' : localPath[k]; + if (c == '/' && j > 0 && norm[j-1] == '/') continue; + norm[j++] = c; } if (j > 0 && norm[j-1] == '/') j--; norm[j] = 0; @@ -240,6 +243,7 @@ inline int VGames_MatchFolder(const char* localPath) { char expected[512]; snprintf(expected, sizeof(expected), "%s/%s/%s", prefix, g_vgames.games[i].category, g_vgames.games[i].name); + for (char* p = expected; *p; p++) if (*p == '\\') *p = '/'; if (strcasecmp(norm, expected) == 0) return i; } diff --git a/theseus/desktop/xboxfs.h b/theseus/desktop/xboxfs.h index d15142f..34b1502 100644 --- a/theseus/desktop/xboxfs.h +++ b/theseus/desktop/xboxfs.h @@ -69,6 +69,11 @@ inline bool XboxFS_IsVirtualTree(const char* p) { } inline bool XboxFS_IsHostPath(const char* p) { + // "\Device\Harddisk0\PartitionN\..." is what launch() hands us. It has + // no drive letter, so without this it reads as a real Windows path and + // never reaches the partition remap below. + if (strncmp(p, "\\Device\\", 8) == 0) return false; + // Already resolved through AppPath, so never translate it twice. const char* roots[2] = { Plat_ShippedDir(), Plat_UserDataDir() }; for (int i = 0; i < 2; i++) { diff --git a/theseus/render/scene_groups.cpp b/theseus/render/scene_groups.cpp index 013c2ed..306f157 100755 --- a/theseus/render/scene_groups.cpp +++ b/theseus/render/scene_groups.cpp @@ -1384,12 +1384,29 @@ void CLayer::Render() fieldOfView = pViewpoint->m_fieldOfView; float aspect; +#ifdef _XBOX if (TheseusGetEmulateXboxRes()) { aspect = 720.0f/480.0f; if (TheseusGetStretchWidescreen()) aspect *= 1.25f; } +#else + if (TheseusGetEmulateXboxRes()) + { + // The 720x480 framing as authored, never stretched. A window + // wider than 3:2 (16:9, 21:9, 32:9) keeps the vertical field of + // view and sees more at the sides; a narrower one keeps the 3:2 + // horizontal field and sees more above and below. The console + // keeps its fixed 3:2 (the TV does the stretching). + const float design = 720.0f/480.0f; + const float vw = TheseusGetViewWidth(); + const float vh = TheseusGetViewHeight(); + aspect = (vh > 0.0f && vw > 0.0f) ? (vw / vh) : design; + if (aspect < design) + fieldOfView = 2.0f * atanf(tanf(fieldOfView * 0.5f) * design / aspect); + } +#endif else { // Real window aspect, so the scene fills the display instead of diff --git a/theseus/third-party/xboot b/theseus/third-party/xboot new file mode 160000 index 0000000..12a3396 --- /dev/null +++ b/theseus/third-party/xboot @@ -0,0 +1 @@ +Subproject commit 12a3396aa77f72c8d2fd94c0988c7e11539b47e7