mirror of
https://github.com/mihawk-99/PS5_RetroArch.git
synced 2026-10-18 23:09:29 -05:00
The Vulkan driver's shader subframes present the generated frame at j/N between the previous and the current content frame (video_framegen, with video_shader_subframes 2 to 4). The hooks are anchor edits; the generator and its shim are built from ../PS5_FrameGen and the trained network is staged at assets/framegen/network.fgw.
352 lines
19 KiB
Bash
Executable File
352 lines
19 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# PS5 RetroArch - compile the RetroArch frontend with the pipeline's toolchain.
|
|
#
|
|
# tools/build-retroarch.sh compile the frontend objects and report
|
|
# tools/build-retroarch.sh --list print the source list it would build
|
|
#
|
|
# The source list comes from RetroArch's own build (tools/retroarch-sources.sh),
|
|
# which runs configure and `make info` and prints exactly the objects a link
|
|
# needs. Nothing here is a previous build's artefact or this project's guess.
|
|
#
|
|
# A source that does not compile is reported and skipped, not fatal: the point of
|
|
# this step is to find the real set the SDK can actually build.
|
|
|
|
set -euo pipefail
|
|
|
|
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
|
cd "$root"
|
|
|
|
upstream="$root/vendor/retroarch"
|
|
sdk="${PS5_PAYLOAD_SDK:-$root/../ps5-native-app-boilerplate-main/.deps/native/ps5-payload-sdk}"
|
|
out="$root/build/ra"
|
|
obj="$out/obj"
|
|
|
|
[[ -d $upstream ]] || { echo "error: run tools/fetch-retroarch.sh first" >&2; exit 2; }
|
|
[[ -d $sdk ]] || { echo "error: no SDK at $sdk" >&2; exit 2; }
|
|
|
|
# The tree that gets compiled is the configured copy under build/, not
|
|
# vendor/retroarch, and that is not a matter of taste. configure writes config.h,
|
|
# config.mk and the object list into the tree it runs in, and this port's changes
|
|
# are applied to that same copy - so vendor/retroarch is upstream exactly as it
|
|
# was fetched and build/ra-conf is upstream plus the named changes, compiled. A
|
|
# build that read the sources from vendor/ and the headers from build/ compiled
|
|
# RetroArch's untouched video_driver.c against the patched video_driver.h: it
|
|
# linked, it signed, and it started with video_drivers[] holding no ps5 entry at
|
|
# all, because the file that lists the drivers came from the tree that had never
|
|
# heard of it. Source and patch have to come from the same place.
|
|
tree="$root/build/ra-conf"
|
|
|
|
# RetroArch's sources include the generated header by a relative path -
|
|
# "../config.h" from gfx/, "../../config.h" from deeper still - and every one of
|
|
# those resolves inside the tree being compiled, because configure wrote config.h
|
|
# at that tree's root. Nothing needs a copy at this repository's root, and an
|
|
# earlier version of this script left one there: a generated file, dirty in git,
|
|
# that no source read. The include path does the work instead.
|
|
configured="$tree/config.h"
|
|
# Refresh configuration before deriving feature flags. The source script caches
|
|
# by configure arguments so enabling a menu cannot reuse the old disabled build.
|
|
"$root/tools/retroarch-sources.sh" >/dev/null
|
|
[[ -f $configured ]] || { echo "error: configure did not produce build/ra-conf/config.h" >&2; exit 2; }
|
|
|
|
# The -D flags are not written here. RetroArch passes each enabled feature on the
|
|
# compiler command line as well as defining it in config.h, so a list maintained
|
|
# by hand is wrong in both directions: a name left out compiles a feature's
|
|
# sources against headers that do not know it (libchdr without zlib), and a name
|
|
# added that configure turned off compiles code whose sources are not in the
|
|
# object list at all (the BSV movie recorder, the soft filters, the video
|
|
# filters, the translator). tools/retroarch-flags.sh asks `make` for the flags it
|
|
# would use, which is the same answer a working build gets.
|
|
mapfile -t defines < <("$root/tools/retroarch-flags.sh" | tr ' ' '\n' | grep -E '^-D' || true)
|
|
(( ${#defines[@]} > 0 )) || { echo "error: no compile flags from tools/retroarch-flags.sh" >&2; exit 2; }
|
|
|
|
# Plus the three things `make` cannot know, because they are about this title
|
|
# rather than about RetroArch:
|
|
defines+=(
|
|
# HAVE_MAIN is deliberately NOT defined, and defining it is the mistake this
|
|
# build already made once. The name reads like "this platform has a main", but
|
|
# the comment above rarch_main says what it does: without HAVE_MAIN, rarch_main
|
|
# initialises and then runs the frontend's main loop; with it, rarch_main only
|
|
# initialises and returns. The title built with it initialised every driver -
|
|
# this project's video driver included, its display open at 1920x1080, per the
|
|
# trace in the title folder - and then exited zero without drawing a frame.
|
|
# The duplicate `main` symbol it was meant to avoid is removed by renaming
|
|
# upstream's, in patches/series 0003.
|
|
# This SDK's time.h only defines the POSIX clock ids when
|
|
# __POSIX_VISIBLE >= 200112, which the -std=c11 the pipeline uses suppresses.
|
|
# The ids are pre-defined here with the header's own values (CLOCK_REALTIME 0,
|
|
# CLOCK_MONOTONIC 4), so this is the same declaration the header would have
|
|
# made. Both are needed together: the header wraps the whole block in one
|
|
# condition, so defining only the first hides the second.
|
|
-DCLOCK_REALTIME=0 -DCLOCK_MONOTONIC=4
|
|
# Assertions are compiled out. This SDK declares a standard BSD assert - it
|
|
# expands to __assert(__func__, __FILE__, __LINE__, #e) - and __assert lives
|
|
# only in libc.a, which this pipeline does not link: the title's C library is
|
|
# runtime/libc.prx, loaded at run time, and the executable links the SDK's
|
|
# weak stubs. An assert that fires would therefore be an undefined symbol at
|
|
# link time rather than a message at run time. NDEBUG is what a release build
|
|
# of RetroArch uses in any case, so nothing is lost that was meant to ship.
|
|
-DNDEBUG
|
|
# Screenshots, which is the only capture path this port has. RetroArch's own
|
|
# `--max-frames=N --max-frames-ss --max-frames-ss-path=FILE` writes one at the end
|
|
# of a fixed number of frames, and the goal this round is working toward asks for
|
|
# frames *reaching the screen*: a picture read back from the frame the console was
|
|
# handed is that evidence without a camera pointed at the display. The flag is off
|
|
# in the configured tree (the whole block is `#ifdef HAVE_SCREENSHOTS`), so the
|
|
# options do not exist and the run exits with nothing to read. Nothing else in the
|
|
# build changes: the readback is the Vulkan driver's own path.
|
|
-DHAVE_SCREENSHOTS
|
|
# zstd enables its tracing hooks whenever it sees GNUC, ELF and an x86-64
|
|
# target, and a tracing hook is emitted as a weak undefined symbol on the
|
|
# promise that the linker may leave it unresolved. This title's eboot.bin
|
|
# goes through tools/build.sh, which builds a stub table from the symbols the
|
|
# executable imports and refuses to write one for a symbol no public SDK stub
|
|
# exports - so a hook nobody calls still stops the build, with
|
|
# ZSTD_trace_decompress_end as the message. A stronger check than the linker's
|
|
# weak-symbol rule, and the right answer is to not emit the hook: ZSTD_TRACE=0
|
|
# is zstd's own switch for a platform without weak symbols, which is what this
|
|
# is as far as the stub table is concerned.
|
|
-DZSTD_TRACE=0
|
|
# RetroArch's own mbedTLS (deps/mbedtls) for HTTPS: configure records the choice in
|
|
# config.h, but libretro-common's net_socket_ssl_mbed.c does not read config.h and
|
|
# picks the bundled headers only on this define, which upstream's console
|
|
# makefiles (Makefile.ctr) pass by hand. Without it the file looks for a system
|
|
# mbedTLS this SDK does not have.
|
|
-DHAVE_BUILTINMBEDTLS
|
|
# platform_unix appends /assets to ASSETS_DIR, so its prefix must be /app0.
|
|
# Where this title's own files live. configure baked the /user/homebrew
|
|
# prefix from tools/retroarch-sources.sh's --prefix, and tools/retroarch-flags.sh
|
|
# drops those four flags rather than passing a path that does not exist here:
|
|
# a PS5 title sees its own folder mounted at /app0 and cannot write into the
|
|
# system's homebrew tree. Asset paths point at /app0 so the menu reads the
|
|
# copy shipped inside the title; the paths RetroArch would write to point at
|
|
# /app0 as well, because that is the only place it may write.
|
|
-DGLOBAL_CONFIG_DIR='"/app0"'
|
|
-DASSETS_DIR='"/app0"'
|
|
-DFILTERS_DIR='"/app0/filters"'
|
|
-DCORE_INFO_DIR='"/app0/info"'
|
|
)
|
|
|
|
# XMB must cache its numeric context before allocation can fail. Keep these
|
|
# hooks in the same opt-in mode as the title observer; the flag is fingerprinted.
|
|
case "${PS5_MEMORY_DIAGNOSTICS:-0}" in
|
|
0) ;;
|
|
1) defines+=(-DPS5_MEMORY_DIAGNOSTICS) ;;
|
|
*) echo "PS5_MEMORY_DIAGNOSTICS must be 0 or 1" >&2; exit 2 ;;
|
|
esac
|
|
|
|
includes=(
|
|
# The tree that is compiled comes first, both for its headers and for
|
|
# config.h: every -I below names a directory inside it, so a quoted include
|
|
# and a bracketed one resolve to the same copy of every file. That is what
|
|
# makes "the patched video_driver.c was compiled" true by construction rather
|
|
# than by remembering to keep two lists in step.
|
|
-I"$tree"
|
|
-I"$tree/libretro-common/include" -I"$tree/deps"
|
|
-I"$tree/deps/7zip" -I"$tree/deps/stb" -I"$tree/deps/ibxm"
|
|
# libchdr includes <zstd.h>, and RetroArch vendors zstd in deps/zstd. Its
|
|
# public headers sit one level deeper than the package root, which is the
|
|
# difference between this source compiling and the whole CHD path being
|
|
# absent from the frontend.
|
|
-I"$tree/deps/zstd/lib"
|
|
# The vendored zlib's public headers are RetroArch's compatibility copy.
|
|
-I"$tree/libretro-common/include/compat/zlib"
|
|
-I"$tree/deps/libz"
|
|
-I"$tree/libretro-db" -I"$tree/deps/rcheevos/include"
|
|
# Vulkan needs a GLSL-to-SPIR-V compiler; RetroArch vendors glslang under
|
|
# deps/glslang/glslang, and its headers are included as
|
|
# <glslang/Public/ShaderLang.h>, so the include root is that inner directory.
|
|
-I"$tree/deps/glslang/glslang"
|
|
# RetroArch vendors the Vulkan headers it compiles against in gfx/include.
|
|
-I"$tree/gfx/include"
|
|
# Vulkan's shader path includes SPIRV-Cross as <spirv_cross.hpp>.
|
|
-I"$tree/deps/SPIRV-Cross"
|
|
-I"$root/src"
|
|
)
|
|
|
|
mapfile -t sources < <("$root/tools/retroarch-sources.sh")
|
|
|
|
if [[ ${1:-} == --list ]]; then
|
|
printf '%s\n' "${sources[@]}"
|
|
printf '==> [ra] %s sources\n' "${#sources[@]}" >&2
|
|
exit 0
|
|
fi
|
|
|
|
# The port's changes to RetroArch's own sources are applied to the configured copy
|
|
# in build/ra-conf by tools/apply-port-patches.py, which matches each change on a
|
|
# stable anchor line rather than a diff's line numbers. vendor/retroarch is never
|
|
# edited, so "upstream plus a named, re-runnable change" stays true.
|
|
#
|
|
# The patcher's decision is remembered, and that is not bookkeeping. A patch that is
|
|
# already applied reports `present` and writes nothing, so the patched file's
|
|
# timestamp does not move - and the compile loop below, which skips a source older
|
|
# than its object, then keeps an object that was compiled BEFORE the patch existed.
|
|
# That is not hypothetical: after the rename of upstream's `main` was added, the
|
|
# build kept a retroarch.c.o with no runloop_iterate in it, because the object was
|
|
# newer than the already-patched source. The build looked correct and the frontend
|
|
# had no main loop in it. So the patch list is stamped, and when it changes the
|
|
# objects belonging to patched files are deleted rather than trusted.
|
|
patch_stamp="$obj/.patches"
|
|
patched_files=()
|
|
if [[ -f $root/patches/series ]]; then
|
|
echo "==> [ra] applying the port's changes to build/ra-conf"
|
|
patch_report=$(python3 "$root/tools/apply-port-patches.py" "$root/build/ra-conf") || {
|
|
echo "$patch_report"
|
|
echo "error: a port change could not be applied; the frontend would build" >&2
|
|
echo " against unpatched sources and silently ignore the driver." >&2
|
|
exit 2
|
|
}
|
|
printf '%s\n' "$patch_report"
|
|
mapfile -t patched_files < <(printf '%s\n' "$patch_report" |
|
|
sed -n 's/^ \([^:]*\): .*/\1/p')
|
|
fi
|
|
|
|
mkdir -p "$obj"
|
|
|
|
# Up-to-date is not "newer than the source". A flag changed here, or a feature
|
|
# configure flipped, changes what every object should contain while every source
|
|
# file stays put, and the mtime test above would then keep 225 objects that were
|
|
# compiled against a different feature set. The flags and the configured header
|
|
# are hashed into a stamp; when it moves, the objects are rebuilt from scratch.
|
|
# Patched headers can change shared layouts (for example file_list_t). Every
|
|
# consumer must rebuild even when its own source file was not patched.
|
|
fingerprint=$({
|
|
printf '%s\n' "${defines[@]}" "${includes[@]}"
|
|
cat "$configured"
|
|
# The menu drivers include this port's WebUI renderer headers directly.
|
|
cat "$root"/src/ps5_webui_qr*.h
|
|
for name in "${patched_files[@]}"; do
|
|
case "$name" in
|
|
*.h|*.hpp) cat "$root/build/ra-conf/$name" ;;
|
|
esac
|
|
done
|
|
} | sha256sum | cut -d' ' -f1)
|
|
stamp="$obj/.fingerprint"
|
|
if [[ ! -f $stamp || $(<"$stamp") != "$fingerprint" ]]; then
|
|
if [[ -f $stamp ]]; then
|
|
echo "==> [ra] the flags or config.h changed; rebuilding the objects"
|
|
rm -f "$obj"/*.o
|
|
fi
|
|
printf '%s\n' "$fingerprint" > "$stamp"
|
|
fi
|
|
|
|
# The set of patched files, as of now. When it differs from last run's, the objects
|
|
# for exactly those files go, so the patch is compiled rather than assumed.
|
|
patch_list=$(printf '%s\n' "${patched_files[@]:-}" | sort)
|
|
if [[ ! -f $patch_stamp || $(<"$patch_stamp") != "$patch_list" ]]; then
|
|
if [[ -f $patch_stamp ]]; then
|
|
removed=0
|
|
for name in "${patched_files[@]}"; do
|
|
target="$obj/${name//\//_}.o"
|
|
[[ -f $target ]] && { rm -f "$target"; removed=$((removed + 1)); }
|
|
done
|
|
(( removed )) && echo "==> [ra] the port's changes moved; rebuilding $removed object(s)"
|
|
fi
|
|
printf '%s\n' "$patch_list" > "$patch_stamp"
|
|
fi
|
|
|
|
# Frame generation (patches/series, 0119): the generator and the driver's shim are PS5_FrameGen's,
|
|
# checked out beside this repository. The shim's header goes into the configured tree, where
|
|
# vulkan.c includes it by name. Its objects are built here and join the archive below.
|
|
fg="${PS5_FRAMEGEN:-$root/../PS5_FrameGen}"
|
|
fg_objects=()
|
|
if [[ -f $fg/framegen/include/framegen.h ]]; then
|
|
cmp -s "$fg/integrations/retroarch/gfx/vulkan_framegen.h" "$tree/gfx/drivers/vulkan_framegen.h" ||
|
|
cp "$fg/integrations/retroarch/gfx/vulkan_framegen.h" "$tree/gfx/drivers/vulkan_framegen.h"
|
|
fi
|
|
compiled=0
|
|
skipped=()
|
|
failed=()
|
|
|
|
for source in "${sources[@]}"; do
|
|
src="$tree/$source"
|
|
[[ -f $src ]] || { skipped+=("$source (absent in 1.22.2)"); continue; }
|
|
target="$obj/${source//\//_}.o"
|
|
if [[ -f $target && $target -nt $src ]]; then
|
|
compiled=$((compiled + 1)); continue
|
|
fi
|
|
# The language follows the source, not a preference: RetroArch's object list
|
|
# holds C and C++, and glslang and slang are C++.
|
|
#
|
|
# Exceptions are ON for the frontend's C++, and that is a measured requirement
|
|
# rather than a preference. With -fno-exceptions (which stays right for the
|
|
# title's own src/) ten sources of the Vulkan shader path do not compile at all:
|
|
# SPIRV-Cross's spirv_cross.cpp and its companions, and
|
|
# gfx/drivers_shader/{shader_vulkan,slang_process,slang_reflection}.cpp - every
|
|
# one reporting "cannot use 'throw' with exceptions disabled". The build then
|
|
# archived 266 of 276 objects and linked a title anyway, so the Vulkan shader
|
|
# path was silently half-present. That is precisely the partial build this loop
|
|
# prints a count for, and reading that count is what found it.
|
|
#
|
|
# RTTI stays off: nothing in this object list needs dynamic_cast or typeid.
|
|
standard=-std=c11
|
|
[[ $source == *.c ]] || standard=-std=c++20
|
|
extra=()
|
|
[[ $source == *.c ]] || extra=(-fexceptions -fno-rtti)
|
|
if PS5_CLANG=/usr/bin/clang PS5_PAYLOAD_SDK="$sdk" \
|
|
sh "$root/tooling/prospero-clang18" "$standard" -O2 -w \
|
|
"${extra[@]}" -ffunction-sections -fdata-sections \
|
|
"${defines[@]}" "${includes[@]}" -c "$src" -o "$target" 2>"$out/last-error.txt"; then
|
|
compiled=$((compiled + 1))
|
|
else
|
|
failed+=("$source: $(grep -m1 'error:' "$out/last-error.txt" | sed 's/^.*error: //')")
|
|
fi
|
|
done
|
|
|
|
# The generator's units: C++20 with exceptions (as the frontend's C++), through RetroArch's Vulkan
|
|
# symbol wrapper, so every vk call goes through the pointers RetroArch loads.
|
|
fg_sources=("$fg/framegen/src/fg_common.cpp" "$fg/framegen/src/fg_vulkan.cpp"
|
|
"$fg/framegen/src/fg_neural.cpp" "$fg/integrations/retroarch/gfx/vulkan_framegen.cpp")
|
|
for src in "${fg_sources[@]}"; do
|
|
[[ -f $src ]] || { failed+=("$src: absent (PS5_FRAMEGEN=$fg)"); continue; }
|
|
target="$obj/framegen_$(basename "${src%.cpp}").o"
|
|
if ! { [[ -f $target && $target -nt $src ]] &&
|
|
[[ -z $(find "$fg/framegen" -newer "$target" \( -name '*.h' -o -name '*.cpp' \) -print -quit) ]]; }; then
|
|
PS5_CLANG=/usr/bin/clang PS5_PAYLOAD_SDK="$sdk" \
|
|
sh "$root/tooling/prospero-clang18" -std=c++20 -O2 -w -fexceptions -fno-rtti \
|
|
-ffunction-sections -fdata-sections -include vulkan/vulkan_symbol_wrapper.h \
|
|
"${defines[@]}" "${includes[@]}" -I"$fg/framegen/include" -I"$fg/framegen/src" \
|
|
-I"$fg/integrations/retroarch/gfx" -c "$src" -o "$target" 2>"$out/last-error.txt" ||
|
|
{ failed+=("$src: $(grep -m1 'error:' "$out/last-error.txt" | sed 's/^.*error: //')"); continue; }
|
|
fi
|
|
fg_objects+=("$target")
|
|
compiled=$((compiled + 1))
|
|
done
|
|
|
|
echo "==> [ra] compiled $compiled of ${#sources[@]} sources"
|
|
if (( ${#skipped[@]} )); then
|
|
printf '==> [ra] %s sources absent in 1.22.2:\n' "${#skipped[@]}"
|
|
printf ' %s\n' "${skipped[@]:0:10}"
|
|
fi
|
|
if (( ${#failed[@]} )); then
|
|
printf '==> [ra] %s sources did not compile:\n' "${#failed[@]}"
|
|
printf ' %s\n' "${failed[@]:0:15}"
|
|
fi
|
|
|
|
# The objects are collected into one archive, which is how the title's own build
|
|
# receives the frontend: tools/build.sh links what APP_STATIC_ARCHIVES names, and
|
|
# its source discovery reads src/ only.
|
|
#
|
|
# The members are the objects this run knows about, in the same order and under
|
|
# the same names the loop above produced them, rather than every .o sitting in the
|
|
# directory. That distinction is not tidiness: when configure stops building a
|
|
# source, the loop skips it and its object stays on disk, so a directory sweep
|
|
# would link a feature that is switched off - which is how a stale
|
|
# gfx/video_crt_switch.o kept calling switchres after the feature was disabled.
|
|
# An object is in the archive only if its source is in the current list and
|
|
# compiled in this run.
|
|
archive="$out/libretroarch.a"
|
|
members=()
|
|
for source in "${sources[@]}"; do
|
|
target="$obj/${source//\//_}.o"
|
|
[[ -f $target ]] && members+=("$target")
|
|
done
|
|
members+=("${fg_objects[@]}")
|
|
ar=$(command -v llvm-ar || command -v ar)
|
|
ranlib=$(command -v llvm-ranlib || command -v ranlib)
|
|
[[ -n $ar && -n $ranlib ]] || { echo "error: no archiver found" >&2; exit 2; }
|
|
(( ${#members[@]} > 0 )) || { echo "error: no objects to archive" >&2; exit 2; }
|
|
rm -f "$archive"
|
|
"$ar" rc "$archive" "${members[@]}"
|
|
"$ranlib" "$archive"
|
|
echo "==> [ra] archived ${#members[@]} objects into build/ra/libretroarch.a"
|