mirror of
https://github.com/mihawk-99/PS5_RetroArch.git
synced 2026-10-18 23:09:29 -05:00
RPCS3: God of War HD reaches gameplay at 100% and 300%; LLVM 98b45cc
The fork (4560e6b) resolves paths as the Qt application does, answers the image and photo callbacks, releases a waiter's mutex around its wait callback in the atomic wait engine (EDEADLK on the console's error-checking mutexes), and gives the RetroPads the handler's own bindings. The LLVM fork (98b45cc) lays out TrailingObjects classes as other targets do: the PS5 ABI ignores alignas on their empty aligner base, and each allocation came up 4 bytes short (SPU compiles crashed in MachineInstr::hasOrderedMemoryRef). The title's crash report now prints every register, the heap words around them, and the code addresses on the crashing stack up to its top. God of War HD boots to its first level at 100% and 300%, every 10 s window after the boot at 100% (docs/PHASE_LOG.md).
This commit is contained in:
1 parent
19ae1659d2
commit
3aa85c449b
6 files changed
+216
-14
No files matched your search
+13
-10
@@ -1,6 +1,6 @@
|
||||
# Active work
|
||||
|
||||
_Updated: 2026-09-28_
|
||||
_Updated: 2026-09-29_
|
||||
|
||||
## Now: v0.5.0-alpha.5 released, the first on RADV; Dolphin's first start next
|
||||
|
||||
@@ -37,15 +37,18 @@ checkout without the sibling forks builds from there.
|
||||
|
||||
**Now: RPCS3 as a libretro core** (docs/RPCS3_PORT.md). The goal is God of
|
||||
War HD (NPUA80490, a PSN package plus its RAP) booting from `content/PS3/`
|
||||
at a 300% scale on RADV, with the firmware from `system/RPCS3/`. Ladder steps
|
||||
1 to 5 are done (docs/PHASE_LOG.md, 2026-09-28): the core builds from my fork
|
||||
PS5_RPCS3 (`main`, published) with the pinned LLVM, FFmpeg and libiconv, loads
|
||||
on the console, installs the firmware, and runs RPCS3's `gs_gcm_hello_world`
|
||||
test with the LLVM recompilers on RADV, picture and full-speed audio, exiting
|
||||
cleanly. Next is step 6: the game's package install and boot at 100%, then
|
||||
300%, the battery and the ten-minute run. Known costs: the shader
|
||||
interpreter's 6,650 pipelines compile before a first boot (about 100 s, then
|
||||
cached), with no loading screen yet.
|
||||
at a 300% scale on RADV, with the firmware from `system/RPCS3/`. Steps 1 to 5
|
||||
are done, and step 6 has reached gameplay (docs/PHASE_LOG.md, 2026-09-28): the
|
||||
package installs behind the core's loading screen (31.5 minutes for 6.3 GB,
|
||||
held to a title's ~2 MiB/s write budget), and the game boots to its first
|
||||
level at 100% and at 300%, every 10 s window after the boot at 100% speed.
|
||||
Getting there took the platform's `realpath`, a layout fix in the LLVM fork's
|
||||
`TrailingObjects` (the PS5 ABI ignores `alignas` on an empty base), a mutex
|
||||
held across a wait callback in RPCS3's atomic wait engine, the frontend's path
|
||||
and image callbacks, and the RetroPads' bindings. Still to do for step 6: the
|
||||
ten-minute run, the Close Content + reload battery (122 core threads were
|
||||
still running at quit), and my acceptance of the 300% screenshot. After that:
|
||||
installing a package's large files without copying them.
|
||||
|
||||
**Dolphin transitions (paused 2026-09-28 for RPCS3):** Rogue Leader's dips are
|
||||
not shader compiles. The emulation thread spins in `sched_yield` inside a
|
||||
|
||||
@@ -4346,3 +4346,103 @@ the core unloads cleanly. The CTS run was paused for these runs and resumed
|
||||
afterwards.
|
||||
|
||||
Next: God of War HD from its package and licence, first at 100%.
|
||||
|
||||
## 2026-09-28 — RPCS3: God of War HD installs behind a loading screen and boots to its SPU compiles (ladder step 6, begun)
|
||||
|
||||
**The package installs.** RPCS3's installer normalises its path with
|
||||
`std::filesystem::weakly_canonical`, which libc++ builds on `realpath`, and the
|
||||
console refuses a title's `realpath` with EPERM, even for `/app0` (the files
|
||||
probe, measured). The platform answers it now (`ps5_realpath`, SDK 8160289):
|
||||
the path made absolute from `ps5_getcwd`, `.` and `..` resolved by name, each
|
||||
component checked with `stat`. The title links it too (`--wrap=realpath`), and
|
||||
the cores' imports are aliased to it.
|
||||
|
||||
God of War HD's 6.3 GB package then installed in 31.5 minutes: 13 MB/s for the
|
||||
first 2.7 GB, 2.2 MB/s after that. The sampler put the writing thread in
|
||||
`write()` for almost every sample, not in AES (AES-NI) or the reads. The SDK's
|
||||
new sustained write probe (`writes`, SDK c769422) measured why: a title writes
|
||||
242 MiB/s for about 1.3 GiB, even with `O_DIRECT`, then 2.0-2.1 MiB/s however
|
||||
it writes, while the FTP server wrote the same folder at 22 MiB/s at the same
|
||||
moment. The storage is not the limit; a title's writes are (the SDK's
|
||||
docs/PROBE.md, "Sustained writes"). Installing a package's large files without
|
||||
copying them, read and decrypted from the package the way RPCS3 reads an ISO,
|
||||
would remove both the wait and the second copy; that is the next step for
|
||||
installs.
|
||||
|
||||
**A loading screen.** The install ran inside `retro_load_game`, so RetroArch
|
||||
showed a black screen for as long as it took. The fork (78dfa8f) installs the
|
||||
firmware and packages on a worker thread and shows a loading screen until
|
||||
RPCS3's first frame (docs/RPCS3_PORT.md, "Content loading and the loading
|
||||
screen"): the game's own art tinted violet, its icon, the title in Inter, and a
|
||||
violet-to-orchid bar with the sizes, speed and time left, then "Starting" with
|
||||
RPCS3's compile progress, or what failed. RetroArch's screenshot on the console,
|
||||
one minute into the install, showed it at 36% (2.32 GB of 6.32 GB). Along the
|
||||
way:
|
||||
|
||||
- the screen called `vkCreateImage` before RPCS3 had loaded any device
|
||||
function through volk (a jump to 0); it loads them for RetroArch's device;
|
||||
- a package header's `title_id` is the content ID (`UP9000-NPUA80490_00-...`),
|
||||
so the check for an installed game never matched and each load installed the
|
||||
package again, overwriting the installed game (which then had to be
|
||||
installed again); the title ID now comes from its PARAM.SFO, and a finished
|
||||
install is recorded in `system/RPCS3/libretro/installed/`;
|
||||
- closing content during the install waited for the whole 6 GB file: RPCS3's
|
||||
installer saw an abort only between files. It checks after every block now,
|
||||
and a file left part way counts the package as aborted.
|
||||
|
||||
The resolution scale became a core option (100% to 300%, 300% by default).
|
||||
|
||||
**The boot.** With the game installed, the boot got through the PPU executable
|
||||
and then stopped at RPCS3's boot music: the overlay plays SND0.AT3 through the
|
||||
frontend's video source, and the core made none. It makes a silent one now.
|
||||
Next, the shader interpreter's 6,650 pipelines (115 s the first time, a few
|
||||
seconds from RADV's cache after that), then SPU compiles, where every run
|
||||
crashed within seconds in LLVM's `MachineInstr::hasOrderedMemoryRef`, reading
|
||||
`0x2_xxxx_xxxx`. The crash report now prints every register and the heap words
|
||||
around them, which showed the instruction's two memory operands with the
|
||||
second one's upper 32 bits replaced by 2: the next allocation's first field.
|
||||
LLVM's `TrailingObjects` takes its alignment from an empty `alignas` base,
|
||||
which the PS4/PS5 ABI ignores, so on the console such classes were allocated
|
||||
4 bytes short (docs/RPCS3_PORT.md, "LLVM"). The LLVM fork (98b45cc) fixes it;
|
||||
the PS5 target now lays such a class out as the host does.
|
||||
|
||||
**To gameplay.** Four more stops, each found from the console's own report:
|
||||
|
||||
- *The game's folder lost its first letter.* Emulator::Load cuts the folder
|
||||
name after the resolved `<hdd0>/game` plus a separator of its own, and the
|
||||
core had left the path callbacks at their default, which returns a path as
|
||||
given, trailing separator and all: the game booted as
|
||||
`/dev_hdd0/game/PUA80490/` and found none of its files. The core resolves
|
||||
paths as the Qt application does (`QFileInfo::canonicalFilePath`), through
|
||||
`std::filesystem` on the platform's `realpath`.
|
||||
- *Three frontend callbacks were unset* (`get_scaled_image`, `get_image_info`,
|
||||
`get_photo_path`). The first overlay with an icon called an empty
|
||||
`std::function`, and a core built without exceptions terminates on the
|
||||
`std::bad_function_call`. They are stb_image's now. RPCS3's terminate
|
||||
handler also names an uncaught exception and prints its message, and the
|
||||
title's crash report lists the code addresses on the crashing stack, up to
|
||||
its top: the throw site survives, since nothing unwinds before terminate.
|
||||
- *"mutex lock failed: Resource deadlock avoided".* On FreeBSD and the PS5
|
||||
the atomic wait engine waits on a `std::condition_variable`, and the waiting
|
||||
thread held its own mutex while running its wait callback; an SPU thread's
|
||||
callback notifies lv2 objects, the notification locks every waiter's mutex,
|
||||
its own among them, and the console's default mutex checks for that
|
||||
(EDEADLK). The fork releases the mutex around the callback; a notification
|
||||
meanwhile sets `sync` first, which is checked again under the mutex.
|
||||
- *No button reached the game.* The core named the RetroPads in the input
|
||||
configuration but left RPCS3's default bindings, which are empty. Each pad
|
||||
gets the RetroPad handler's own now (`pad_thread::InitPadConfig`).
|
||||
|
||||
With those, God of War HD boots through its logo video to its menu, and a
|
||||
scripted Cross on "New Game" and the difficulty reaches the first level, the
|
||||
ship in the Aegean, with RPCS3's Vulkan renderer on RADV:
|
||||
|
||||
| Scale | Evidence | 10 s windows after the boot |
|
||||
| --- | --- | --- |
|
||||
| 100% (720p) | gameplay screenshot, 3840×2160 | 17 of 17 at 100% |
|
||||
| 300% (2160p) | gameplay screenshot, 3840×2160 | 17 of 17 at 100% |
|
||||
|
||||
The two windows after the 300% run's last (7%, 0%) are RetroArch encoding its
|
||||
5.8 MB screenshot and quitting after the frame limit, when the core no longer
|
||||
runs. At that quit the loader found 122 core threads still running and kept
|
||||
the core mapped; the Close Content and reload battery is what tests that.
|
||||
+15
-2
@@ -353,8 +353,21 @@ Where the build and the first code differ from the audit above.
|
||||
|
||||
- *LLVM* is my fork `../PS5_LLVM` (github.com/mihawk-99/PS5_LLVM), pinned in
|
||||
`tools/build-llvm.sh`. The PS4/PS5 ABI ignores `alignas` on an empty base
|
||||
class, so `SmallVector`'s layout assertions fail for this target; the fork
|
||||
aligns `SmallVector` itself under `__SCE__`. The build uses host tablegen
|
||||
class, and LLVM relies on it twice. `SmallVector<T, 0>`'s layout assertions
|
||||
failed at build time; the fork aligns `SmallVector` itself under `__SCE__`.
|
||||
`TrailingObjects` failed silently: its empty aligner base is what raises a
|
||||
class to its trailing objects' alignment, so on the PS5 a header of an `int`
|
||||
and some `bool`s stayed 4-aligned and 12 bytes, and two trailing pointers
|
||||
were given 28 bytes while `getTrailingObjects` read them from 16 to 32. The
|
||||
next allocation's first field overwrote the last pointer's upper half
|
||||
(`MachineInstr::ExtraInfo`'s memory operands: God of War HD's SPU compiles
|
||||
crashed in `MachineInstr::hasOrderedMemoryRef` on `0x2_xxxx_xxxx`, heap
|
||||
pointers with their upper 32 bits replaced by the count 2). The fork gives
|
||||
the aligner a zero-length member there, a non-empty base without storage;
|
||||
the PS5 target then lays such a class out as the host does (8-aligned, 16
|
||||
bytes, 32 allocated, trailing objects at 16). The other empty `alignas`
|
||||
classes in LLVM and RPCS3 are standalone objects, whose alignment the ABI
|
||||
keeps. The build uses host tablegen
|
||||
tools (`LLVM_NATIVE_TOOL_DIR`) and builds only the libraries
|
||||
(`LLVM_INCLUDE_TOOLS=OFF`), since no LLVM program links for the console.
|
||||
- *FFmpeg* 8.1.1 comes from its signed release tarball (`tools/build-ffmpeg.sh`),
|
||||
|
||||
@@ -23,10 +23,13 @@
|
||||
#include <initializer_list>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <pthread.h>
|
||||
#include <pthread_np.h>
|
||||
#include <ucontext.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <ps5platform/context.h>
|
||||
#include <ps5platform/heap.h>
|
||||
|
||||
extern "C" int sceKernelAvailableFlexibleMemorySize(std::size_t *size);
|
||||
|
||||
@@ -82,6 +85,89 @@ void report(int signal, siginfo_t *info, void *context_pointer)
|
||||
}
|
||||
(void)!write(fd, "\n", 1);
|
||||
}
|
||||
/* The return addresses above the fault: every word from rsp to the top of
|
||||
* the thread's stack (32 KiB at most) that points into the title's code or
|
||||
* a core's. Without frame pointers this is how a crash ending in abort()
|
||||
* (an uncaught exception, which terminates before unwinding) shows where
|
||||
* it began; the loader's base turns a core's into offsets. */
|
||||
if (fd >= 0 && rsp >= 4096 && (rsp & 7) == 0)
|
||||
{
|
||||
pthread_attr_t attributes;
|
||||
void *stack = nullptr;
|
||||
std::size_t stack_size = 0;
|
||||
if (pthread_attr_init(&attributes) == 0)
|
||||
{
|
||||
if (pthread_attr_get_np(pthread_self(), &attributes) == 0)
|
||||
pthread_attr_getstack(&attributes, &stack, &stack_size);
|
||||
pthread_attr_destroy(&attributes);
|
||||
}
|
||||
const std::uintptr_t stack_top = reinterpret_cast<std::uintptr_t>(stack) + stack_size;
|
||||
if (stack && rsp < stack_top)
|
||||
{
|
||||
const std::uintptr_t end = stack_top - rsp > 32768 ? rsp + 32768 : stack_top;
|
||||
(void)!write(fd, "crash: frames", 13);
|
||||
int found = 0;
|
||||
for (std::uintptr_t at = rsp; at + 8 <= end && found < 96; at += 8)
|
||||
{
|
||||
const std::uintptr_t word = *reinterpret_cast<const std::uintptr_t *>(at);
|
||||
const bool title = word >= 0x400000 && word < 0x1100000;
|
||||
const bool core = word >= 0x400000000ull && word < 0x500000000ull;
|
||||
if (!title && !core)
|
||||
continue;
|
||||
char text[24];
|
||||
const int n = std::snprintf(text, sizeof(text), " %llx",
|
||||
static_cast<unsigned long long>(word));
|
||||
(void)!write(fd, text, static_cast<std::size_t>(n));
|
||||
++found;
|
||||
}
|
||||
(void)!write(fd, "\n", 1);
|
||||
}
|
||||
}
|
||||
/* Every general register, then 64 bytes around each one that points into
|
||||
* the title heap: a bad pointer read from memory shows beside the good
|
||||
* ones it was stored with. A dump that faults ends the report there, after
|
||||
* the lines above are written. */
|
||||
if (fd >= 0)
|
||||
{
|
||||
const mcontext_t &mc = context->uc_mcontext;
|
||||
const std::uintptr_t registers[] = {
|
||||
std::uintptr_t(mc.mc_rax), std::uintptr_t(mc.mc_rbx), std::uintptr_t(mc.mc_rcx),
|
||||
std::uintptr_t(mc.mc_rdx), std::uintptr_t(mc.mc_rsi), std::uintptr_t(mc.mc_rdi),
|
||||
std::uintptr_t(mc.mc_rbp), std::uintptr_t(mc.mc_r8), std::uintptr_t(mc.mc_r9),
|
||||
std::uintptr_t(mc.mc_r10), std::uintptr_t(mc.mc_r11), std::uintptr_t(mc.mc_r12),
|
||||
std::uintptr_t(mc.mc_r13), std::uintptr_t(mc.mc_r14), std::uintptr_t(mc.mc_r15)};
|
||||
static const char *const names[] = {"rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp", "r8",
|
||||
"r9", "r10", "r11", "r12", "r13", "r14", "r15"};
|
||||
(void)!write(fd, "crash: registers", 16);
|
||||
for (std::size_t i = 0; i < sizeof(registers) / sizeof(registers[0]); ++i)
|
||||
{
|
||||
char word[32];
|
||||
const int n = std::snprintf(word, sizeof(word), " %s=%llx", names[i],
|
||||
static_cast<unsigned long long>(registers[i]));
|
||||
(void)!write(fd, word, static_cast<std::size_t>(n));
|
||||
}
|
||||
(void)!write(fd, "\n", 1);
|
||||
for (std::size_t i = 0; i < sizeof(registers) / sizeof(registers[0]); ++i)
|
||||
{
|
||||
const std::uintptr_t at = (registers[i] & ~std::uintptr_t(7)) - 32;
|
||||
if (!ps5_heap_owns(reinterpret_cast<const void *>(at)) ||
|
||||
!ps5_heap_owns(reinterpret_cast<const void *>(at + 63)))
|
||||
continue;
|
||||
char head[48];
|
||||
const int n = std::snprintf(head, sizeof(head), "crash: near %s %llx:", names[i],
|
||||
static_cast<unsigned long long>(at));
|
||||
(void)!write(fd, head, static_cast<std::size_t>(n));
|
||||
for (int w = 0; w < 8; ++w)
|
||||
{
|
||||
char word[24];
|
||||
const int m = std::snprintf(word, sizeof(word), " %llx",
|
||||
static_cast<unsigned long long>(
|
||||
reinterpret_cast<const std::uintptr_t *>(at)[w]));
|
||||
(void)!write(fd, word, static_cast<std::size_t>(m));
|
||||
}
|
||||
(void)!write(fd, "\n", 1);
|
||||
}
|
||||
}
|
||||
if (fd >= 0)
|
||||
close(fd);
|
||||
/* Not async-signal-safe, and worth the risk: the process is ending anyway,
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ set -euo pipefail
|
||||
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
cd "$root"
|
||||
|
||||
revision=f8d662cce7162d70bfb315344623eca7ab9eef88 # PS5_LLVM main: llvmorg-22.1.8 and the port
|
||||
revision=98b45cc22e98844b7d49bbedefdfee04a740650a # PS5_LLVM main: llvmorg-22.1.8 and the port
|
||||
prefix="$root/.deps/native/llvm-ps5"
|
||||
[[ -f $prefix/.revision && $(<"$prefix/.revision") == "$revision" ]] && {
|
||||
echo "==> [llvm] $revision already built in $prefix"; exit 0; }
|
||||
|
||||
@@ -26,7 +26,7 @@ core_stamp_skip rpcs3 \
|
||||
"$root/.deps/native/libiconv-ps5/.stamp"
|
||||
[[ $# == 0 ]] || { echo "usage: ${0##*/}" >&2; exit 2; }
|
||||
|
||||
revision=78dfa8f28fac3138df6274f3ec8576eb5e18a1f3 # ../PS5_RPCS3 main
|
||||
revision=4560e6b2ab2c44d1ea61be03505756c39c127dc4 # ../PS5_RPCS3 main
|
||||
core_fork_setup
|
||||
core_fork_checkout PS5_RPCS3 "$revision"
|
||||
core_info="$root/tooling/rpcs3/rpcs3_libretro.info"
|
||||
|
||||
Reference in new issue
Block a user