mirror of
https://github.com/mihawk-99/PS5_RetroArch.git
synced 2026-10-18 23:09:29 -05:00
Markdown files are no longer committed
The docs, the README, plans and notes are kept and updated on this machine only: .gitignore ignores *.md, and the 37 tracked files leave the index (they stay on disk). No build or test reads them.
This commit is contained in:
1 parent
a655e10b3e
commit
8396c1bf5d
38 files changed
+4
-13121
No files matched your search
@@ -42,3 +42,7 @@ __pycache__/
|
||||
# configured copy under build/, which is not committed; an older build script also
|
||||
# copied it here, and a generated file must never be able to dirty the tree.
|
||||
config.h
|
||||
|
||||
# Markdown (the docs, the README, plans and notes) stays on this machine: it is
|
||||
# not committed or published. The files are kept and updated locally.
|
||||
*.md
|
||||
@@ -1,138 +0,0 @@
|
||||
# Agent instructions
|
||||
|
||||
PS5 RetroArch is a native RetroArch homebrew application for jailbroken
|
||||
PlayStation 5 consoles. It gives the console a libretro frontend it was never
|
||||
shipped with, so the library of emulators and games that already runs through
|
||||
libretro also runs here.
|
||||
|
||||
## Read order
|
||||
|
||||
Read in this order and stop as soon as you have what you need:
|
||||
|
||||
1. This file. Frozen.
|
||||
2. `docs/PLAN.md` — the gates, the milestone map, the invariants that constrain
|
||||
the code, and the index of the reference files. About 150 lines, once per
|
||||
session.
|
||||
3. `docs/ACTIVE.md` — the volatile state: the current step, the next actions,
|
||||
the blockers, the last verified runs. Read last, before you start work.
|
||||
4. On demand only, when the task needs it: `docs/REFERENCE.md` (the step ladder,
|
||||
the workflow, the environment), `docs/FINDINGS.md` (the evidence behind each
|
||||
invariant), `docs/TESTING.md`, `docs/DEPLOYMENT.md`,
|
||||
`docs/TROUBLESHOOTING.md`, `docs/GPU_PATH_CRITERIA.md` (the acceptance
|
||||
criteria for the Vulkan route), `docs/PHASE_LOG.md` (append-only run logs),
|
||||
`docs/RELEASING.md` (what a release carries and the order of its steps).
|
||||
|
||||
Never read a long log end to end to answer a status question: `docs/ACTIVE.md`
|
||||
and each log's own summary say what passed.
|
||||
|
||||
## Volatility contract
|
||||
|
||||
| File | Rule |
|
||||
| --- | --- |
|
||||
| `AGENTS.md` | Frozen. Change only when the workflow itself changes. |
|
||||
| `docs/PLAN.md` | Static. Edit only when a gate, a milestone or an invariant changes. Never record progress here, and keep it short: it is read every session. |
|
||||
| `docs/ACTIVE.md` | Volatile. Rewrite in place; keep it under about 120 lines. |
|
||||
| `docs/PHASE_LOG.md`, `docs/FINDINGS.md` | Append-only. New dated entries at the end; never rewrite an existing one. |
|
||||
| `docs/REFERENCE.md`, `docs/TESTING.md`, `docs/DEPLOYMENT.md`, `docs/TROUBLESHOOTING.md` | Stable. Edit when the specification or the procedure changes. |
|
||||
| Any other `docs/*.md` | Stable. Normal edits. |
|
||||
|
||||
Progress, run results and "done" markers belong in `docs/ACTIVE.md` or
|
||||
`docs/PHASE_LOG.md`, never in the plan.
|
||||
|
||||
## Prompt caching
|
||||
|
||||
The agent's context is cached by exact prefix: only what sits *before* the new
|
||||
material matters. Keep that part byte-identical between turns.
|
||||
|
||||
- Append, don't edit. Adding to the end of a file or a conversation is cheap;
|
||||
changing text already in context invalidates everything after it.
|
||||
- Never put a date, a version or a "last updated" line at the top of a file that
|
||||
is read first. A date belongs in the volatile file, which is read last.
|
||||
- Keep the read order fixed and never reflow, renumber or bulk-rename: a
|
||||
reorder or a reformat changes the prefix exactly as a rewrite does. Add a new
|
||||
on-demand document to the list when it is created, once.
|
||||
- Keep `docs/ACTIVE.md` small: it is the only file expected to change every
|
||||
session, so its size is paid on every session.
|
||||
- One fact, one home. Never copy status into the plan; link to `docs/ACTIVE.md`
|
||||
or a log instead.
|
||||
- Batch documentation edits. One write-up at the end of a step beats continuous
|
||||
small edits, which invalidate the cache repeatedly.
|
||||
- Long files are appended to and searched, never rewritten, never read whole.
|
||||
|
||||
## Your task
|
||||
|
||||
The task in your prompt is the only task. `docs/ACTIVE.md` describes what the
|
||||
project is doing; it is context, not an assignment, and its "Next" list is not a
|
||||
queue. When the prompt and `docs/ACTIVE.md` disagree, the prompt wins: do not
|
||||
start the active file's next step, and say in your report that you noticed.
|
||||
|
||||
## Work loop
|
||||
|
||||
One step per commit, and no step is done until it is verified.
|
||||
|
||||
1. Read the prompt, then `docs/ACTIVE.md`. Inspect the workspace: it is
|
||||
authoritative. An earlier turn's narration, a summary or a compacted
|
||||
conversation is a claim, not a fact.
|
||||
2. Choose the smallest step that makes real progress, and say what would prove
|
||||
it before building it.
|
||||
3. Implement it, then run the gates.
|
||||
4. Record the evidence: the command, the result, and the artifact it produced.
|
||||
5. Commit it with that evidence, and write the step up once, in
|
||||
`docs/ACTIVE.md`, plus a dated entry in `docs/PHASE_LOG.md` when it lands.
|
||||
|
||||
Work that cannot be verified here is never committed as if it were: park it in
|
||||
`parked/` with the plan that would finish it, and say so.
|
||||
|
||||
## Gates
|
||||
|
||||
| Gate | Purpose |
|
||||
| --- | --- |
|
||||
| `format` | Format, lint and static checks |
|
||||
| `unit` | Unit tests |
|
||||
| `build` | Build every shipped target |
|
||||
| `integration` | Integration or end-to-end tests |
|
||||
| `evidence` | Replay and compare recorded evidence |
|
||||
|
||||
`tools/verify.sh` holds the commands — its only home — and runs them in that
|
||||
order, failing fast. A failing gate is fixed before the next step starts; a red
|
||||
gate is never carried forward. What each gate means, and how to add a test:
|
||||
`docs/TESTING.md`.
|
||||
|
||||
## Evidence
|
||||
|
||||
- Evidence is a committed artifact, not a claim: a machine-readable capture from
|
||||
the console (a `klog` run record, an ELF symbol/import dump, a staged-tree
|
||||
manifest) plus the expected result it is compared against. Every step's
|
||||
acceptance names the artifact and the command that reproduces it, and the
|
||||
commit message carries both.
|
||||
- An unexplained failure keeps the gate red. A tolerated one is named in
|
||||
`docs/ACTIVE.md` with its reason, and removed when the reason is gone.
|
||||
- Write logs factually: what was run, what it returned, what it proves, with
|
||||
failures at the same detail as successes.
|
||||
|
||||
## Long-running work
|
||||
|
||||
Keep one completion objective for work that spans many turns, and let it drive
|
||||
the rounds.
|
||||
|
||||
- State it as something finishable and checkable, not a direction: for example
|
||||
"a release ZIP whose `eboot.bin` loads as a title on the 6.02 console, draws the RGUI
|
||||
menu and returns the driver identity recorded in `docs/FINDINGS.md`".
|
||||
- Each round: inspect the workspace, make concrete progress, verify it, report
|
||||
what remains. Never restart finished work.
|
||||
- Mark it complete only with the evidence it names. A blocker is a concrete
|
||||
condition that persisted across several rounds with the same cause; difficulty,
|
||||
uncertainty or remaining work is not a blocker.
|
||||
- After a summary or a compaction, re-read `docs/ACTIVE.md` and inspect the
|
||||
workspace before acting.
|
||||
|
||||
## Working rules
|
||||
|
||||
- Commit finished, verified work. Never push and never rewrite published history
|
||||
unless the human asks.
|
||||
- Keep generated and environment files out of the diff: `vendor/`, `build/`,
|
||||
`dist/`, `.deps/`, `.env`, `*.elf`, `*.so`, `klog/`.
|
||||
- Do not add a toolchain flag, a dependency or a version pin to a shared script
|
||||
without saying why in the write-up. Never commit a secret, a key or a client's
|
||||
data. The console address, the FTP credentials and anything read from the
|
||||
console stay in the ignored `.env` and in a git-ignored capture directory.
|
||||
@@ -1,889 +0,0 @@
|
||||
# PPSSPP native Vulkan core: technical implementation and handoff plan
|
||||
|
||||
## Table of contents
|
||||
|
||||
1. [Decision and completion objective](#1-decision-and-completion-objective)
|
||||
2. [Scope, permissions and branch handoff](#2-scope-permissions-and-branch-handoff)
|
||||
3. [Source identities and evidence discipline](#3-source-identities-and-evidence-discipline)
|
||||
4. [Existing RetroArch foundation](#4-existing-retroarch-foundation)
|
||||
5. [Platform capability matrix](#5-platform-capability-matrix)
|
||||
6. [CPU, JIT and memory integration](#6-cpu-jit-and-memory-integration)
|
||||
7. [Native core build and loader contract](#7-native-core-build-and-loader-contract)
|
||||
8. [Libretro Vulkan interface and ownership](#8-libretro-vulkan-interface-and-ownership)
|
||||
9. [Confirmed current driver blockers](#9-confirmed-current-driver-blockers)
|
||||
10. [Additional graphics risks requiring investigation](#10-additional-graphics-risks-requiring-investigation)
|
||||
11. [Driver artifact selection and concurrent development](#11-driver-artifact-selection-and-concurrent-development)
|
||||
12. [Implementation milestones and acceptance gates](#12-implementation-milestones-and-acceptance-gates)
|
||||
13. [Diagnostics, measurements and failure recovery](#13-diagnostics-measurements-and-failure-recovery)
|
||||
14. [Console test matrix](#14-console-test-matrix)
|
||||
15. [Files, commands and proposed deliverables](#15-files-commands-and-proposed-deliverables)
|
||||
16. [Open questions and first-session checklist](#16-open-questions-and-first-session-checklist)
|
||||
17. [References and limits of this assessment](#17-references-and-limits-of-this-assessment)
|
||||
|
||||
## 1. Decision and completion objective
|
||||
|
||||
**The environment is promising enough to start PPSSPP integration. The currently
|
||||
inspected PS5_Vulkan implementation cannot run the normal upstream PPSSPP Vulkan
|
||||
renderer unchanged.** There are known rendering requirements that it explicitly
|
||||
refuses, including a combined depth/stencil attachment needed during device
|
||||
initialization. This is more specific than saying the workload is untested.
|
||||
|
||||
The user requests a native PPSSPP libretro core using the existing PS5 RetroArch
|
||||
pipeline and their Vulkan driver. This document is a technical handoff, not a
|
||||
claim that a port has been built or tested. No PPSSPP build, console launch or
|
||||
driver modification was performed while preparing it.
|
||||
|
||||
The implementation objective is:
|
||||
|
||||
> A reproducibly built, ABI-checked native `ppsspp_libretro.so` with matching
|
||||
> metadata and required assets, loaded by the existing native core loader, using
|
||||
> verified x86-64 JIT execution and PPSSPP's Vulkan hardware renderer against the
|
||||
> exact statically linked PS5_Vulkan artifact identified in the report; selected
|
||||
> PSP workloads render correct gameplay with working audio/input, no unexpected
|
||||
> GPU refusals or command-buffer failures, and return to XMB and another core
|
||||
> without corruption, deadlock or a crash. The console owner confirms the result.
|
||||
|
||||
A bounded initial gameplay milestone is acceptable; document the exact tested
|
||||
content and unsupported cases. Do not label the core generally compatible or
|
||||
full-speed from a boot screen or one simple workload.
|
||||
|
||||
Distinguish three achievements in all reports:
|
||||
|
||||
- **Core/platform bring-up:** native loading, dependencies, memory, CPU execution.
|
||||
- **Software-rendered PPSSPP with Vulkan presentation:** potentially useful as an
|
||||
explicitly labelled diagnostic, but does not satisfy hardware-rendered PSP.
|
||||
- **PPSSPP Vulkan hardware rendering:** GPU commands originate in PPSSPP's renderer
|
||||
and produce PSP graphics; RetroArch presents/composites the resulting image.
|
||||
|
||||
Finishing an entire Vulkan 1.0 milestone is not a necessary scheduling dependency
|
||||
if the PPSSPP-specific subset can be implemented and verified sooner. Conversely,
|
||||
a command-count audit or a milestone label is not proof of PPSSPP compatibility.
|
||||
Vulkan 1.3 is not a prerequisite established by this investigation.
|
||||
|
||||
## 2. Scope, permissions and branch handoff
|
||||
|
||||
Read `AGENTS.md`, `docs/PLAN.md`, then `docs/ACTIVE.md` in this repository before
|
||||
implementation. The active file's previous thumbnail/input work is context, not
|
||||
an instruction to resume that unrelated work. This handoff concerns PPSSPP.
|
||||
|
||||
The next implementation agent should create a RetroArch branch **before code
|
||||
changes**, preferably `codex/ppsspp-native-vulkan`. Inspect the working tree and
|
||||
existing branches first; do not overwrite changes, reset history or assume the
|
||||
branch does not exist. Example, only after inspection:
|
||||
|
||||
```bash
|
||||
git status --short --branch
|
||||
git branch --list codex/ppsspp-native-vulkan
|
||||
git switch -c codex/ppsspp-native-vulkan
|
||||
```
|
||||
|
||||
The planning task itself does not create that implementation branch. Preserve
|
||||
this document if it is still uncommitted when switching branches.
|
||||
|
||||
The user previously authorized driver fixes provided PS5_Vulkan documentation
|
||||
is updated and changes are committed in that repository. They also explicitly
|
||||
warned that they are actively modifying PS5_Vulkan. Therefore:
|
||||
|
||||
- Inspect its working tree before any driver work; coordinate overlapping files.
|
||||
- Do not absorb the user's unrelated modifications into an agent commit.
|
||||
- Prefer an isolated driver worktree when implementing an independent fix, after
|
||||
deciding which source state must be included. An ordinary worktree from HEAD
|
||||
does **not** contain the user's uncommitted changes.
|
||||
- Driver changes and their evidence belong in PS5_Vulkan; frontend/core changes
|
||||
belong here. Track the dependency by exact commit and artifact hashes.
|
||||
- Never silently replace the known-good frontend driver snapshot with whichever
|
||||
archive happens to be newest.
|
||||
- Do not push unless the user asks.
|
||||
|
||||
Current deployment policy: uploads are authorized, but check that the console is
|
||||
idle and preserve user data. A new launch requires the user's intervention or a
|
||||
new explicit authorization. Historical permissions for earlier tests are not an
|
||||
instruction to launch now. Never terminate another title. Start capture before
|
||||
an authorized launch and preserve logs before restarting after a failure.
|
||||
|
||||
Keep XMB as the default, RGUI selectable, and `video_ps5` registered/selectable as
|
||||
the CPU fallback. Do not claim a completed Vulkan step by silently selecting a
|
||||
software renderer or suppressing a driver refusal. Preserve the existing logging
|
||||
policy: useful logs, no periodic synchronous logging that reintroduces hitches.
|
||||
|
||||
## 3. Source identities and evidence discipline
|
||||
|
||||
Assessment snapshot, recorded during the September 2026 handoff:
|
||||
|
||||
| Component | Inspected identity |
|
||||
| --- | --- |
|
||||
| PS5_RetroArch HEAD | `b1dced92108a70ff56c20d0e97f5cfe87797901a` |
|
||||
| PS5_Vulkan HEAD | `9d3a025f68ad8c0b608b6814bec844aa252f6710` |
|
||||
| Driver working tree | Modified `driver/ps5vk_image.c`, `src/diagnostics.cpp`, `tools/check-mip-layout.sh`; untracked `jobs/v0-wide2/` |
|
||||
| Upstream PPSSPP source reviewed | `f293b10fb2d9dc0c2bc10281444ee3d3e932e6ad` in `hrydgard/ppsspp` |
|
||||
| Local temporary upstream excerpts | `/tmp/ppsspp-current-review/`, with `REVISION`; disposable, not a build input or committed evidence |
|
||||
|
||||
The PPSSPP revision above is an **assessment reference**, not an approved release
|
||||
pin. Confirm which upstream repository/revision will be used for the actual
|
||||
libretro build. Compare it with the user's older PS5 PPSSPP port before choosing.
|
||||
Pin sources, submodules, metadata and assets reproducibly; record digests.
|
||||
|
||||
Driver restrictions below are working-tree source observations. Recheck them
|
||||
because development is concurrent. Local source presence, archive contents and
|
||||
console execution are three different facts. Do not substitute one for another.
|
||||
|
||||
Use these evidence classifications:
|
||||
|
||||
- **Measured:** named console probe or accepted run, with its scope and artifact.
|
||||
- **Source-confirmed:** explicit code path or rejection in the inspected revision.
|
||||
- **Inference:** likely integration consequence that still needs a reproducer.
|
||||
- **Unverified:** no relevant workload result exists yet.
|
||||
|
||||
The Eden probe documents contain earlier failed approaches followed by newer
|
||||
successful direct-memory results. Read the dated amendments and current evidence;
|
||||
do not repeat the old anonymous-memory ceiling as the total available RAM.
|
||||
Some project plan text also predates the accepted Vulkan frontend. Consult current
|
||||
source and evidence rather than turning stale prose into a new implementation.
|
||||
|
||||
## 4. Existing RetroArch foundation
|
||||
|
||||
Already accepted by the console owner, within the tested workloads:
|
||||
|
||||
- Native frontend title, XMB and selectable RGUI.
|
||||
- RetroArch Vulkan presentation through statically linked PS5_Vulkan.
|
||||
- Native stereo audio, controller input, analog menu navigation and remapping.
|
||||
- Filesystem browsing, configuration loading/saving and writable title folders.
|
||||
- Native dynamic core loading, metadata discovery and failed-load recovery.
|
||||
- FCEUmm, mGBA, Snes9x, FBNeo and Genesis Plus GX gameplay in recorded tests.
|
||||
- Corrected software-frame colour handling and clean close-content transitions.
|
||||
- Fixed XMB large-list allocation handling and removed periodic logging hitches.
|
||||
- Latest accepted thumbnail RGBA handling and controller recovery after defaults.
|
||||
|
||||
This establishes a reusable frontend, **not** hardware-core Vulkan compatibility.
|
||||
All currently accepted emulator cores produce software-rendered frames.
|
||||
|
||||
Relevant implementation files:
|
||||
|
||||
| File | Purpose |
|
||||
| --- | --- |
|
||||
| `tools/build-snes9x.sh` | C++ native core build example, source/info pinning, local destructor registry, ABI report |
|
||||
| `tools/build-mgba.sh`, `tools/build-fbneo.sh`, `tools/build-genesis-plus-gx.sh` | Other native port/build precedents |
|
||||
| `tools/build-title.sh` | Builds current cores, generates import surface, links driver archives and stages title |
|
||||
| `tools/check-core.py` | ELF ABI, imports, exports and segment checks |
|
||||
| `tools/core-imports.py` | Core import aggregation for the title |
|
||||
| `src/core_loader_ps5.cpp` | Native shared-object mapping, relocation, constructors, teardown |
|
||||
| `src/core_imports_ps5.cpp` | Runtime import resolution surface |
|
||||
| `src/core_frame_ps5.cpp` | Existing software core-frame integration; inspect before changing format handling |
|
||||
| `tooling/native/ps5-core.ld` | Native core linker layout |
|
||||
| `tooling/native/core_cxx_runtime.cpp` | Core-local destructor handling |
|
||||
| `tooling/native/app-symbols.map` | Application symbol export policy |
|
||||
| `patches/series` | Ordered RetroArch frontend patches |
|
||||
|
||||
Do not blindly carry existing XRGB8888 software-output conversion patches into
|
||||
PPSSPP's hardware-image path. Native Vulkan images have their own format, layout
|
||||
and synchronization contract.
|
||||
|
||||
## 5. Platform capability matrix
|
||||
|
||||
Evidence resides in the sibling Eden repository under `evidence/` and is indexed
|
||||
by `docs/PROBING.md`, `docs/CAPABILITY_PROBE.md` and `docs/ACTIVE.md` there.
|
||||
|
||||
| Capability | Observed result | Consequence / limit |
|
||||
| --- | --- | --- |
|
||||
| Host CPU ABI | Native x86-64 code runs | Suitable architecture for PPSSPP's AMD64 recompiler; no speed guarantee |
|
||||
| Page size | 16,384 bytes | Use actual page size for allocation/protection boundaries |
|
||||
| Anonymous RW then RX | Generated function executes | W^X-style JIT path exists |
|
||||
| Anonymous RW then `mprotect` RWX | Generated function executes | Writable/executable mapping can be obtained through protection change |
|
||||
| Direct `mmap(RWX)` | Mapping returned, execution faults | Success return does not prove executable permissions |
|
||||
| JIT patch cycles | 64 protection/write/execute cycles pass | Basic rewriting works; not a PPSSPP self-modifying-code test |
|
||||
| Executable region | 16 MiB, 1,024 generated functions pass | More than a one-instruction-page demonstration |
|
||||
| Multithreaded generated code | Four worker threads pass | Basic concurrent execution works; not proof of PPSSPP cache synchronization |
|
||||
| Signals | SIGSEGV/SIGBUS, alternate stack, `si_addr`, `siglongjmp` exercised | Fault delivery/recovery primitives work; instruction-context backpatching remains untested |
|
||||
| Flexible memory | 448 MiB configured, 442 MiB available in one budget run | Ordinary anonymous/heap allocations remain constrained |
|
||||
| Anonymous allocation walk | About 432 MiB before ENOMEM in earlier runs | Process footprint and allocation route matter |
|
||||
| Direct-memory pool | 12,288 MiB reported | Separate pool accessible through native APIs |
|
||||
| Direct-memory allocation | Up to 4 GiB mapped, written, released | Large backing allocations possible |
|
||||
| Simultaneous layout | 4 GiB guest + 512 MiB code + 1 GiB table = 5,632 MiB | Large multi-region allocation and executable cache coexist in that probe |
|
||||
| Executable direct memory | Map as buffer, then `sceKernelMprotect(0x07)` works | Do not pass execute to initial direct mapping and assume equivalent behavior |
|
||||
| Direct mapping with execute | Refused with `0x80020016` in layout probe | Allocation/protection order is material |
|
||||
| Virtual reservation | `sceKernelReserveVirtualRange`: 4 GiB succeeds; 64 GiB fails `0x8002000C` | Intermediate sizes/alignment guarantees, including PPSSPP's 8 GiB request, not established |
|
||||
| Page-table accounting | Allowance of 16,384 entries reported | Respect accounting; do not equate it uncritically with one generic CPU PTE per page |
|
||||
|
||||
The layout result does not prove every byte was stress-tested for hours, that the
|
||||
entire reported pool is always available, or that graphics/CPU contention is
|
||||
acceptable. Consult the actual probe implementation before making stronger claims.
|
||||
|
||||
The user reports an older PS5 RetroArch+PPSSPP build with working JIT but no GPU
|
||||
support. Its source/build artifacts were not located or reviewed during this
|
||||
assessment. Obtain or locate them: they may already solve critical allocator,
|
||||
ABI, timing, thread or emulator-memory problems. Do not assume its SDK or runtime
|
||||
objects can be linked unchanged into this project.
|
||||
|
||||
## 6. CPU, JIT and memory integration
|
||||
|
||||
### 6.1 Upstream entry points to audit
|
||||
|
||||
At the reviewed revision:
|
||||
|
||||
- `Common/MemoryUtil.cpp`: `AllocateExecutableMemory`, `AllocateMemoryPages`,
|
||||
`ProtectMemoryPages`, `FreeExecutableMemory`; page rounding and platform policy.
|
||||
- `Common/MemArenaPosix.cpp`: backing allocation, `CreateView`, `ReleaseView`,
|
||||
`Find4GBBase`; `shm_open`, `ftruncate`, `mmap`, `MAP_FIXED`, shared views.
|
||||
- `Core/MemMap.cpp`: guest memory views and arena initialization.
|
||||
- The selected AMD64 JIT and fault-handler code: follow actual callers of the
|
||||
functions above in the chosen revision, including code-cache resets/patches.
|
||||
|
||||
**Confirmed mismatch:** the normal POSIX executable allocator starts with
|
||||
`PROT_READ | PROT_WRITE | PROT_EXEC`, unless `PlatformIsWXExclusive()` changes that
|
||||
policy. Direct RWX mapping is specifically the console path that failed to
|
||||
execute. Audit the complete allocate/write/protect/execute lifetime; simply
|
||||
changing one platform boolean is not sufficient without proving all callers.
|
||||
|
||||
**Confirmed address-space risk:** `MemArena::Find4GBBase()` on the ordinary 64-bit
|
||||
POSIX path requests an **8 GiB PROT_NONE anonymous mapping** to find a 4 GiB-aligned
|
||||
base. If it fails, the inspected source falls back to `0x2300000000`. Anonymous
|
||||
PROT_NONE reservations consume the constrained pool in the probes. Do not permit
|
||||
that hardcoded-address fallback to overwrite unknown mappings in the title.
|
||||
|
||||
### 6.2 Proposed PS5 memory adapter
|
||||
|
||||
Keep platform-specific allocation/protection code small and testable. Reuse the
|
||||
older working port where compatible; otherwise adapt the appropriate upstream
|
||||
memory abstraction rather than globally intercepting all `mmap` behavior.
|
||||
|
||||
Native APIs worth evaluating, using the installed SDK declarations and Eden
|
||||
probe calls as the source of exact signatures/flags:
|
||||
|
||||
- `sceKernelGetDirectMemorySize`
|
||||
- `sceKernelAllocateDirectMemory`
|
||||
- `sceKernelMapDirectMemory`
|
||||
- `sceKernelMprotect`
|
||||
- `sceKernelReleaseDirectMemory`
|
||||
- `sceKernelMunmap` or the correctly matched SDK unmap interface
|
||||
- `sceKernelReserveVirtualRange`
|
||||
- `sceKernelVirtualQuery`
|
||||
- `sceKernelGetPageTableStats`
|
||||
- `sceKernelConfiguredFlexibleMemorySize`
|
||||
- `sceKernelAvailableFlexibleMemorySize`
|
||||
- POSIX `mmap`, `mprotect`, `munmap`, `sysconf`/`getpagesize` where proven appropriate
|
||||
|
||||
Names above are an investigation checklist, not a ready-to-compile signature list.
|
||||
Confirm import availability in this title's SDK and loader. Do not copy Eden's
|
||||
numeric protection values without preserving their allocation context. The
|
||||
observed buffer mapping uses `0x33`; adding CPU RWX uses `0x07` through the native
|
||||
protection call. Derive production constants from the actual headers/probes.
|
||||
|
||||
Track each owned region's reservation base/size, mapped base/size, direct-memory
|
||||
offset/size, current protection, purpose and lifetime. Check size addition and
|
||||
alignment overflow. Ensure every failed stage unwinds earlier successful stages
|
||||
and that load/unload cycles return accounting to a stable baseline.
|
||||
|
||||
Plan memory by purpose:
|
||||
|
||||
| Allocation class | Initial approach | Verification |
|
||||
| --- | --- | --- |
|
||||
| Small host objects / runtime stacks | Existing native runtime | Aggregate frontend+core flexible-memory peak |
|
||||
| Guest RAM / aliases | PPSSPP arena adapter, direct backing if required | Address constraints, alias coherence, release behavior |
|
||||
| JIT cache | Explicit supported protection sequence | Generated calls, patch cycles, near-call reachability, protection error paths |
|
||||
| Large CPU caches | Direct memory when justified by measurements | Alignment, ownership and API compatibility |
|
||||
| Vulkan resources | Existing driver allocation APIs | Do not bypass Vulkan object ownership or confuse CPU direct memory with valid GPU resources |
|
||||
|
||||
PSP physical RAM is much smaller than Eden's guest RAM, but PPSSPP's **host
|
||||
virtual address layout** is a separate constraint. Verify aliases required by
|
||||
`Core/MemMap.cpp`; several distinct allocations containing copied data are not
|
||||
substitutes for views of the same backing. An 8 GiB reservation is not 8 GiB of
|
||||
PSP RAM. Investigate aligned native reservation or a supported alternate arena
|
||||
path; do not allocate multi-GiB physical memory merely to satisfy an address hint.
|
||||
|
||||
The reviewed `NO_MMAP` path uses a different allocation strategy, but enabling it
|
||||
is only a candidate: validate pointer translation, JIT assumptions, mirrors,
|
||||
performance and cleanup before selecting it. Do not assume it is a free fallback.
|
||||
|
||||
### 6.3 JIT correctness and threading
|
||||
|
||||
- Use 16 KiB page boundaries, with explicit checked rounding. Avoid changing the
|
||||
permissions of unrelated allocations sharing a page.
|
||||
- x86-64 generally has coherent instruction/data caches, but code patching still
|
||||
needs correct publication and synchronization. Keep upstream synchronization;
|
||||
never race a running thread against patching/freeing its instructions.
|
||||
- Check relative branch/displacement range assumptions between generated code,
|
||||
dispatch stubs, helper functions and mapped core text. Allocation success alone
|
||||
does not validate a JIT's placement requirements.
|
||||
- Verify POSIX threading, condition variables, mutexes, thread-local state,
|
||||
atomics and the monotonic/high-resolution timer actually used by the core.
|
||||
- The probe's signal handler resumes through `siglongjmp`; it does not establish
|
||||
that PPSSPP's fault handler can inspect/update every required machine-context
|
||||
field for fast-memory backpatching. Treat that as a separate integration gate.
|
||||
- Do not install a broad signal handler that hides native loader/driver faults.
|
||||
Define handler chaining/restoration and which address ranges the core owns.
|
||||
- Exercise repeated cache invalidation, reset, content unload and a second game.
|
||||
Threads must stop before executable mappings or core text are unmapped.
|
||||
|
||||
CPU-only or software-renderer diagnostics may isolate these problems, but report
|
||||
them as intermediate results. Preserve the accepted frontend GPU path.
|
||||
|
||||
## 7. Native core build and loader contract
|
||||
|
||||
### 7.1 Reproducible build shape
|
||||
|
||||
Proposed deliverables are `tools/build-ppsspp.sh`, `patches/ppsspp/` and narrowly
|
||||
scoped platform support under `tooling/ppsspp/` if needed. Follow existing scripts'
|
||||
logic, not another PS5 project's binary pipeline or flags copied wholesale.
|
||||
|
||||
Use `.deps/native/ps5-payload-sdk/bin/prospero-*` wrappers explicitly for C, C++,
|
||||
archive creation and linking. Existing scripts set `PS5_PAYLOAD_SDK`, `PS5_CLANG`,
|
||||
`CC`, `CXX`, `AR` and `LD`, then repeat compiler selections in upstream build
|
||||
arguments to prevent a hardcoded host compiler from winning.
|
||||
|
||||
Audit the selected PPSSPP build system for a libretro shared target and its
|
||||
platform probes. Cross-compilation must not execute PS5 test binaries on Linux.
|
||||
Separate any host code-generation tools from target libraries. Record versions,
|
||||
source/submodule pins and generated inputs. Avoid network fetches hidden in build
|
||||
steps after the dependency set has been established.
|
||||
|
||||
Inventory the actual enabled dependency graph: C++ runtime, compression/archive
|
||||
libraries, image/audio codecs, glslang/SPIR-V components, Vulkan headers and any
|
||||
thread/atomic libraries. Do not assume the desktop executable's entire dependency
|
||||
set is needed, or that headers-only Vulkan dispatch removes shader compilation
|
||||
requirements. PPSSPP's reviewed libretro Vulkan callback explicitly initializes
|
||||
glslang. All target dependencies must be built for this SDK/ABI.
|
||||
|
||||
### 7.2 ELF/loader restrictions already in force
|
||||
|
||||
`tools/check-core.py` checks:
|
||||
|
||||
- ELF64, little-endian, x86-64 ET_DYN, FreeBSD/PS5 ABI identification.
|
||||
- The required libretro API exports in the dynamic symbol table, including
|
||||
initialization, content lifecycle, callbacks, serialization and memory APIs.
|
||||
- `libkernel_web.sprx` required; allowed dependencies currently limited to it,
|
||||
`libSceLibcInternal.sprx` and `libScePosixForWebKit.sprx`.
|
||||
- No `libkernel_sys.sprx`, host Linux libc/libstdc++ or accidental host objects.
|
||||
- 16 KiB-compatible load segments; no writable+executable ELF load segment.
|
||||
|
||||
The last restriction concerns the **ELF image**, not an intentionally allocated
|
||||
runtime JIT cache. Do not weaken ELF validation to enable runtime JIT.
|
||||
|
||||
`src/core_loader_ps5.cpp` currently rejects PT_TLS and PT_INTERP; supports a
|
||||
limited relocation set including `R_X86_64_NONE`, `RELATIVE`, `64`, `GLOB_DAT` and
|
||||
`JUMP_SLOT`; supports init/fini arrays but rejects legacy initialization paths,
|
||||
text relocations and unsupported preinitialization. General C++ unwind
|
||||
registration is absent. Recheck exact implementation before changing it.
|
||||
|
||||
Therefore PPSSPP-specific load risks include:
|
||||
|
||||
- TLS emitted by PPSSPP, shader compiler libraries or the C++ runtime.
|
||||
- Exceptions/unwinding, RTTI assumptions and thread-local destructors.
|
||||
- Unsupported relocation forms, weak imports and symbol visibility.
|
||||
- Static constructors/destructors that require unavailable imports or outlive
|
||||
the core after unload.
|
||||
- Duplicate runtimes or globally interposed C++/Vulkan symbols.
|
||||
|
||||
Existing Snes9x builds a core-local destructor registry and uses
|
||||
`-fno-exceptions -fno-rtti` for that support object. This is a precedent, **not**
|
||||
proof PPSSPP and all dependencies can globally disable either feature. Inspect
|
||||
uses first. If loader features are added, write focused malformed-ELF and
|
||||
lifetime tests; do not turn rejection into silent acceptance.
|
||||
|
||||
An ABI-check pass does not resolve every symbol or prove the dynamic loader
|
||||
supports every section. Inspect `readelf -lW`, `-dW`, `-rW`, `--dyn-syms -W`,
|
||||
undefined-symbol reports and actual load results.
|
||||
|
||||
### 7.3 Packaging, metadata and assets
|
||||
|
||||
Proposed core staging matches existing conventions:
|
||||
|
||||
```text
|
||||
build/cores/stage/cores/ppsspp_libretro.so
|
||||
build/cores/stage/info/ppsspp_libretro.info
|
||||
dist/PPSA99169/cores/ppsspp_libretro.so
|
||||
dist/PPSA99169/info/ppsspp_libretro.info
|
||||
```
|
||||
|
||||
Current title staging additionally places a metadata copy beside each core for
|
||||
older configurations with an empty info-directory setting. Preserve that behavior
|
||||
when adding PPSSPP to `core_names`, import generation and runtime identity inputs.
|
||||
Do not add the unfinished core unconditionally to the shipped build before its
|
||||
build/load gate is ready.
|
||||
|
||||
Pin matching metadata from libretro-core-info. Validate display name, supported
|
||||
extensions and archive handling against the chosen core; do not copy another
|
||||
core's `.info` or treat discovery as successful loading. PPSSPP typically needs a
|
||||
support asset tree under the system directory (commonly `system/PPSSPP/`);
|
||||
verify exact path, capitalization, contents and licenses for the selected revision.
|
||||
A PSP BIOS dump is not a substitute for that support tree.
|
||||
|
||||
In-title `/app0` corresponds to FTP `/data/homebrew/PPSA99169/`. Preserve content,
|
||||
configuration, saves and existing assets during deployment. Start with a known
|
||||
supported uncompressed content format for isolation, then test archive handling
|
||||
explicitly; never dismiss an archive regression by silently changing the user's
|
||||
content workflow. Keep copyrighted games and private paths out of committed logs.
|
||||
|
||||
## 8. Libretro Vulkan interface and ownership
|
||||
|
||||
### 8.1 Reviewed upstream flow
|
||||
|
||||
`libretro/LibretroGraphicsContext.cpp` chooses a `LibretroVulkanContext` when
|
||||
requested and negotiates `RETRO_ENVIRONMENT_SET_HW_RENDER`.
|
||||
|
||||
`libretro/LibretroVulkanContext.cpp` at the reviewed revision:
|
||||
|
||||
1. Advertises `RETRO_HW_CONTEXT_VULKAN` (the constructor names Vulkan 1.0.18).
|
||||
2. Provides `RETRO_ENVIRONMENT_SET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE` with
|
||||
`RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN` and its interface version.
|
||||
3. In `create_device`, calls `VulkanLoadFromGetInstanceProcAddr` with the frontend's
|
||||
function pointer and adopts the frontend instance via `CreateInstanceExternal`.
|
||||
4. Selects the physical device and calls `VulkanContext::CreateDevice` with the
|
||||
frontend's required extensions/features.
|
||||
5. Marks the created device externally owned: the frontend eventually destroys it.
|
||||
6. Chooses a graphics queue without owning a WSI surface.
|
||||
7. Obtains `RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE` during context reset.
|
||||
|
||||
The context code was fetched at the reference commit; associated presentation
|
||||
implementation must still be read in full before porting. Do not follow an old
|
||||
web result for `libretro_vulkan.cpp`: the reviewed revision uses the context and
|
||||
presentation classes named here, and upstream architecture changes over time.
|
||||
|
||||
### 8.2 Required integration audit
|
||||
|
||||
The core should consume the frontend-provided dispatch path. The frontend already
|
||||
links libps5vk statically. Avoid a second independently initialized driver/runtime
|
||||
inside the core and avoid desktop `dlopen("libvulkan.so.1")` assumptions.
|
||||
|
||||
Verify:
|
||||
|
||||
- All Vulkan handles passed between core and frontend belong to the same driver
|
||||
and compatible device/instance ownership model.
|
||||
- `vkGetInstanceProcAddr` / device dispatch return every actually required entry
|
||||
point; advertised optional extensions do not conceal NULL function pointers.
|
||||
- The frontend implements the negotiated interface version and required features.
|
||||
- Context reset/destroy, device loss and failed creation have balanced ownership.
|
||||
- The core-created logical device is handed back according to the interface;
|
||||
no double destroy and no use after frontend teardown.
|
||||
- Presentation uses the libretro image handoff, expected layout and queue family,
|
||||
frontend synchronization callbacks and semaphores/fences as defined by the
|
||||
exact `libretro_vulkan.h` version. Audit `set_image`, queue locking and sync
|
||||
callbacks where present; do not invent a separate swapchain in the core.
|
||||
- Any negotiated device replacement preserves RetroArch's own menu/presentation
|
||||
resources or correctly recreates them.
|
||||
- PPSSPP workers cannot submit concurrently outside the interface's queue contract.
|
||||
- Core unload drains work and destroys dependent objects before mappings or
|
||||
dispatch storage disappear.
|
||||
|
||||
Do not assume existing software-core load/recovery tests cover any of this.
|
||||
A small native hardware-core canary may be worthwhile: negotiate the interface,
|
||||
render a known offscreen pattern, present it, open/close the menu, unload, reload.
|
||||
That isolates frontend bridge problems from emulation and shader complexity.
|
||||
|
||||
## 9. Confirmed current driver blockers
|
||||
|
||||
The following were directly inspected in the current local source. Line numbers
|
||||
are navigation aids and may move; search the named symbols/refusal strings.
|
||||
|
||||
### 9.1 Combined depth/stencil: startup blocker
|
||||
|
||||
`Common/GPU/Vulkan/VulkanContext.cpp`, `CreateDevice`, around line 655, selects the
|
||||
first attachment-capable format from:
|
||||
|
||||
```cpp
|
||||
VK_FORMAT_D24_UNORM_S8_UINT
|
||||
VK_FORMAT_D32_SFLOAT_S8_UINT
|
||||
VK_FORMAT_D16_UNORM_S8_UINT
|
||||
```
|
||||
|
||||
It asserts that a usable format was found. The libretro device callback actually
|
||||
calls this function, so this is not merely a standalone desktop path.
|
||||
|
||||
`PS5_Vulkan/driver/ps5vk_image.c` advertises depth attachment capability for
|
||||
`VK_FORMAT_D32_SFLOAT`, but none of those combined formats in the inspected table.
|
||||
`driver/ps5vk_draw.c` also explicitly refuses stencil attachments and accepts
|
||||
only the supported D32 depth-target shape.
|
||||
|
||||
Required work: implement at least one compatible combined format end-to-end:
|
||||
allocation/layout, views/aspects, attachment state, clear/load/store, depth and
|
||||
stencil testing/writing, transfers needed by the selected renderer and truthful
|
||||
feature reporting. Advertising a format or substituting depth-only D32 does not
|
||||
implement stencil and must not be used to bypass startup.
|
||||
|
||||
### 9.2 Fixed 4K attachments: fundamental framebuffer blocker
|
||||
|
||||
`driver/ps5vk_draw.c` defines target width 3840 and height 2160. Colour and depth
|
||||
attachment checks reject other extents; the render area must cover the full
|
||||
target. Related inherited/secondary rendering checks enforce the same shape.
|
||||
|
||||
PPSSPP uses offscreen PSP framebuffers and intermediate targets. Native PSP output
|
||||
is 480x272, but internal framebuffer stride/extent and effect targets vary; do
|
||||
not special-case only 480x272. A 4K frontend swapchain does not change this need.
|
||||
|
||||
Required work: parameterize target layout, pitch, allocation size, surface register
|
||||
encoding, viewport/scissor and render area handling. Validate multiple small,
|
||||
non-power-of-two and differently sized targets. Reuse the driver's AddrLib/oracle
|
||||
checks and existing pixel-readback probes. Audit primary, secondary, legacy
|
||||
render-pass and dynamic-rendering paths, not only one size check.
|
||||
|
||||
### 9.3 Culling, stencil and dynamic state
|
||||
|
||||
`driver/ps5vk_pipeline.c`, `ps5vk_draw_refusal`, around lines 800-840:
|
||||
|
||||
- Accepts dynamic viewport/scissor and selected depth states only.
|
||||
- Rejects non-NONE face culling, depth clamp/bias, rasterizer discard and non-fill
|
||||
polygons in this path.
|
||||
- Rejects enabled stencil or depth-bounds tests.
|
||||
|
||||
PPSSPP's `GPU/Vulkan/PipelineManagerVulkan.cpp` constructs PSP-dependent cull mode,
|
||||
stencil operations and dynamic `STENCIL_WRITE_MASK`, `STENCIL_COMPARE_MASK` and
|
||||
`STENCIL_REFERENCE`. These are genuine renderer paths, though individual games
|
||||
may exercise different subsets. Implement state encoding and reset between
|
||||
pipelines/draws, with tests that deliberately alternate contrasting states.
|
||||
|
||||
Do not infer all rejected optional features are unconditional PPSSPP requirements.
|
||||
For example, inspect advertised-feature fallback before declaring depth clamp or
|
||||
logic operations a universal startup blocker.
|
||||
|
||||
### 9.4 Blend constants and selective colour writes
|
||||
|
||||
The same pipeline function rejects dynamic `BLEND_CONSTANTS` and any colour-write
|
||||
mask other than RGBA (`0xf`) or none (`0`). PPSSPP pipelines use a PSP-derived write
|
||||
mask and enable dynamic blend constants when their blend factors require them.
|
||||
|
||||
The driver has separately verified constant blend-factor functionality; that is
|
||||
not equivalent to accepting Vulkan's dynamic blend-constant command/state path.
|
||||
Implement actual dynamic updates and per-channel masks with correct format
|
||||
component ordering. Test alpha-only, RGB-only and individual-channel writes over
|
||||
a known destination, including alternating draw states and menu transitions.
|
||||
|
||||
### 9.5 Descriptor sets beyond set zero: conditional limitation
|
||||
|
||||
`ps5vk_draw_refusal` rejects pipeline layouts containing descriptor sets beyond
|
||||
set 0. PPSSPP's draw engine uses combined image samplers and dynamic uniform
|
||||
buffers; inspect the chosen renderer's complete pipeline layouts, tessellation
|
||||
and optional paths to establish whether set >0 is actually needed.
|
||||
|
||||
This is a **confirmed driver restriction, not a confirmed universal PPSSPP
|
||||
blocker**. Do not spend time broadening it before recording an actual required
|
||||
layout or selecting an unavoidable source path.
|
||||
|
||||
## 10. Additional graphics risks requiring investigation
|
||||
|
||||
These need focused inspection/tests, not unsupported declarations of failure:
|
||||
|
||||
| Area | Audit / probe |
|
||||
| --- | --- |
|
||||
| Shader generation and compiler | PPSSPP-generated vertex/fragment SPIR-V, UBO accesses, varying locations, integer operations, loops, discard, texture operations |
|
||||
| Repeated compilation | Driver ACTIVE reports ACO SIGFPE/abort after particular shader sequences; reproduce in one process, including alternating shader families |
|
||||
| Descriptor churn | Dynamic UBO offsets/alignment/range, sampler counts, updates, pool rollover and lifetime under in-flight work |
|
||||
| Image formats | Actual texture/render-target/depth format feature combinations, component mapping, packed PSP formats and conversion paths |
|
||||
| Mipmaps | Multiple levels, non-square extents, view ranges, level selection and filtering; current driver changes touch format/layout work |
|
||||
| Render-to-texture | Render, transition, sample and render again; PSP framebuffer feedback/copy paths |
|
||||
| Depth/stencil lifecycle | Independent clears, stencil preservation, write masks and transitions between passes |
|
||||
| Transfers and readback | Row pitch, offsets, subregions, buffer/image copies, resolves/blits and CPU-visible completion |
|
||||
| Synchronization | Barriers, visibility, queue submission order, semaphores/fences, upload-buffer reuse and framebuffer reuse |
|
||||
| Render-pass compatibility | Attachment formats/samples, load/store, secondary command inheritance and render areas |
|
||||
| Vulkan memory allocator | Memory types/heaps, requirements, alignment, mapped ranges, coherent/noncoherent handling and allocation churn |
|
||||
| Cache persistence | Pipeline/shader caches, invalidation across driver builds, malformed or incompatible cache recovery |
|
||||
| Threading | Parallel shader compilation and submission safety; transient workaround must be explicit and measured |
|
||||
|
||||
The driver's ACTIVE notes describe an unsigned texture shader that leaves ACO
|
||||
unable to compile again in that process; running that case last in a separate
|
||||
probe battery avoids the sequence in diagnostics, not in a real emulator.
|
||||
PPSSPP compiles many pipelines during play. A stable one-shader demonstration is
|
||||
insufficient. The precise failing sequence may or may not occur in PPSSPP; capture
|
||||
real shaders before assuming equivalence.
|
||||
|
||||
Shader/frontend compilation and PS5 driver compilation are distinct stages:
|
||||
PPSSPP generates shader source/SPIR-V using its dependencies; PS5_Vulkan lowers
|
||||
that shader to GPU code through its compiler. A successful glslang result says
|
||||
nothing about the latter stage's coverage.
|
||||
|
||||
Optional unsupported features must be reported accurately so PPSSPP can choose
|
||||
its own fallbacks. Do not set feature bits to make negotiation succeed before
|
||||
semantics exist. Likewise, an audit that accounts for all entry points does not
|
||||
prove that every valid command argument/state combination is implemented.
|
||||
|
||||
## 11. Driver artifact selection and concurrent development
|
||||
|
||||
RetroArch uses prebuilt static archives. Rebuilding the sibling driver alone does
|
||||
not update an already linked title, and a source revision alone does not identify
|
||||
the executable driver inside that title.
|
||||
|
||||
Current `tools/build-title.sh` consumes:
|
||||
|
||||
```text
|
||||
build/driver/ps5/libps5vk.ps5.a
|
||||
.deps/native/vulkan-runtime/lib/libvk_runtime.ps5.a
|
||||
build/driver/ps5/libpsbc_driver.ps5.a
|
||||
.deps/native/psbc/lib/libpsbc_support.ps5.a
|
||||
```
|
||||
|
||||
It also links Mesa utility objects through `tools/build-mesa-util.sh`. Record their
|
||||
identity and the relevant headers/toolchain. The accepted recent frontend build
|
||||
retained its normal XMB driver inputs; do not assume it was linked to today's
|
||||
sibling working tree. Read the corresponding `driver-archives.json` evidence.
|
||||
|
||||
The title build has a diagnostic archive snapshot path that verifies source hash
|
||||
before copying, copied hash, and source hash after copying. Use an equally explicit
|
||||
stable-input policy for PPSSPP development rather than copying archives during a
|
||||
concurrent link. Do not enable costly memory diagnostics just to obtain snapshots.
|
||||
|
||||
For a driver change: build driver archives in its repository using its documented
|
||||
procedure; run its relevant gates; record commit plus dirty-diff status; snapshot
|
||||
all dependent artifacts; relink RetroArch; record frontend/core/driver hashes;
|
||||
then deploy and read back identity. Do not silently link older archives after a
|
||||
new driver build fails. Keep the known-good package available for recovery.
|
||||
|
||||
## 12. Implementation milestones and acceptance gates
|
||||
|
||||
Each milestone is a bounded step, not permission to skip required gates or console
|
||||
authorization. Record failed attempts as carefully as successful ones. Commit
|
||||
only what the evidence supports; unverified runtime work stays explicitly pending.
|
||||
|
||||
### M0 — Baseline, source choice and branch
|
||||
|
||||
- Create the implementation branch, inventory both repositories and accepted
|
||||
artifacts, locate the old JIT port, choose a source/submodule/asset pin.
|
||||
- Produce a dependency/ABI inventory and map old platform adaptations to this SDK.
|
||||
- Acceptance: reproducible inputs and written delta; no claim of a working core.
|
||||
|
||||
### M1 — Native shared core build and safe lifecycle
|
||||
|
||||
- Add the build script, metadata and required runtime imports/platform adaptations.
|
||||
- Validate ELF segments, relocations, undefined imports, constructors and teardown.
|
||||
- Test missing assets, rejected imports and failed initialization without corrupting
|
||||
the frontend. A test configuration may defer hardware initialization explicitly.
|
||||
- Acceptance: native load/init/deinit and repeated lifecycle capture. If the chosen
|
||||
upstream path initializes Vulkan immediately, record that dependency rather than
|
||||
artificially claiming this step can run before driver startup support exists.
|
||||
|
||||
### M2 — PPSSPP memory/JIT integration
|
||||
|
||||
- Implement the minimum arena/executable-memory adaptation and verify aliasing,
|
||||
placement, patching, worker synchronization and failure cleanup.
|
||||
- Prove the active CPU mode through logs and execution evidence, not the saved
|
||||
option string alone. Disable JIT intentionally once as a comparison if useful.
|
||||
- Acceptance: bounded CPU workload and repeated reset/load/unload with stable memory
|
||||
accounting. Software-rendered diagnostics do not close the final GPU objective.
|
||||
|
||||
### M3 — Frontend hardware interface canary
|
||||
|
||||
- Exercise the actual libretro Vulkan negotiation, device/queue ownership and image
|
||||
presentation lifecycle using a minimal offscreen render where needed.
|
||||
- Acceptance: known pixels, zero unexpected refusals, correct menu overlay,
|
||||
unload/reload and restored software-core presentation.
|
||||
|
||||
### M4 — Minimum driver subset for PPSSPP
|
||||
|
||||
- Implement variable target sizes, a combined depth/stencil format, required culling,
|
||||
stencil/dynamic state and colour-write behavior in independently tested steps.
|
||||
- Order implementation by the first unavoidable failure, with small driver probes.
|
||||
- Acceptance: exact pixel/readback comparisons and alternating-state tests; all
|
||||
claimed advertised capabilities match real behavior. Track remaining gaps.
|
||||
|
||||
### M5 — PPSSPP Vulkan first content
|
||||
|
||||
- Boot a redistributable PSP homebrew/test workload, then an owner-provided game at
|
||||
native rendering resolution with conservative settings.
|
||||
- Capture shaders, first failing Vulkan call/state and graphics timing summaries.
|
||||
- Acceptance: actual PPSSPP Vulkan GPU work, correct visible content, audio/input,
|
||||
no command-buffer-ending errors or unexpected driver refusals.
|
||||
|
||||
### M6 — Representative gameplay and transitions
|
||||
|
||||
- Add workloads exercising 2D/textures, 3D depth/culling, stencil/blend effects,
|
||||
framebuffer feedback/readback and repeated shader creation.
|
||||
- Test Quick Menu, Close Content, another PSP game, a different existing core and
|
||||
native Quit; test cold/warm caches and asset/config failure recovery.
|
||||
- Acceptance: owner confirms correct colours/effects and clean transitions, with
|
||||
matching log evidence and bounded memory growth.
|
||||
|
||||
### M7 — Performance and release decision
|
||||
|
||||
- Measure CPU emulation, render preparation, shader compilation, submission and
|
||||
presentation separately. Record game target rate, emulation speed, frame-time
|
||||
percentiles, audio underruns and memory use.
|
||||
- Do not confuse a 30 FPS PSP game with half-speed emulation or RetroArch's rolling
|
||||
display refresh estimate with instantaneous throughput.
|
||||
- Only then consider resolution scaling and optional features. Full-speed/4K is
|
||||
not an initial acceptance promise.
|
||||
- Ship only the verified configuration, with limitations and recoverable defaults.
|
||||
|
||||
## 13. Diagnostics, measurements and failure recovery
|
||||
|
||||
Keep `RetroArch.log`, driver trace and kernel logs useful. Capture these identities
|
||||
once at startup: frontend hash, core hash/source revision, driver archive hashes,
|
||||
SDK/build identifiers, CPU mode, renderer, negotiated interface and memory policy.
|
||||
|
||||
Use aggregate counters and first-failure records instead of per-draw/texture logs:
|
||||
|
||||
- Allocations/frees, current/peak bytes by owner and allocation route.
|
||||
- Guest/JIT/CPU cache/driver/frontend budgets and allocation-failure request size.
|
||||
- Pipeline/shader count, compile duration and repeated-compile failures.
|
||||
- Descriptor pools/sets and transient upload memory high-water marks.
|
||||
- Frames, CPU emulation time, GPU preparation time, submit/present/wait duration.
|
||||
- First unsupported Vulkan format/state/command and its relevant parameters.
|
||||
- Content/menu/context lifecycle transitions with a monotonic timestamp.
|
||||
|
||||
On first failure capture the error code, allocation owner, requested bytes,
|
||||
current/peak totals, frame number, shader/pipeline identifiers, content lifecycle
|
||||
state and queue/fence context. Preserve the first causal error before secondary
|
||||
cleanup errors overwrite it. Diagnostics must avoid allocation-heavy error paths
|
||||
or synchronous periodic flushes during navigation/gameplay.
|
||||
|
||||
Native core loading can reject unsupported objects safely; it cannot isolate an
|
||||
arbitrary fault in a core sharing the frontend process. Do not call that process
|
||||
isolation. Preserve crash logs, ask whether an unexplained title exit was manual,
|
||||
and do not automatically restart over evidence.
|
||||
|
||||
Raw captures stay in ignored `klog/`; credentials/console addresses stay in ignored
|
||||
`.env`. Commit sanitized machine-readable evidence and expectations under an
|
||||
appropriate `evidence/ppsspp-*` directory. Never publish owner ROM filenames,
|
||||
private paths or console identifiers inadvertently.
|
||||
|
||||
## 14. Console test matrix
|
||||
|
||||
| Test | Evidence needed | Pass condition |
|
||||
| --- | --- | --- |
|
||||
| Core discovery | Metadata, core hash, log | Correct core identity; no false success from `.info` alone |
|
||||
| Native load | ELF report + live loader log | All imports/relocations resolve; constructors complete |
|
||||
| Missing asset/content | Failure log + continued navigation | Useful error; frontend remains usable |
|
||||
| JIT microtest | Generated function results, protection transitions | Correct execution/patching, explicit errors on unsupported paths |
|
||||
| Guest memory | Alias/address tests, resource accounting | Correct mirrors, alignment and matched cleanup |
|
||||
| Vulkan negotiation | Device/interface/dispatch log | Shared driver and balanced ownership |
|
||||
| Small framebuffer | Known pattern/readback | Correct extents, pitch, clipping and colours |
|
||||
| Depth/stencil | Alternating clear/test/write cases | Expected depth rejection and stencil effects |
|
||||
| Blend/masks | Known destination and component comparisons | Correct constant blending and selective writes |
|
||||
| Shader churn | Many sequential/repeated compiles | No ACO crash, stale shader or leaked compilation state |
|
||||
| Gameplay | Owner observation + logs | Correct visuals, audio/input and identified renderer/CPU mode |
|
||||
| Menu overlay | Open/close repeatedly | Correct colours, synchronization and no persistent corruption |
|
||||
| Close Content | Menu then another game/core | Clean state restoration and bounded resource recovery |
|
||||
| Quit | Exit log + klog | Deliberate clean shutdown, no late worker submissions |
|
||||
| Long run | Timings, accounting and owner report | No progressive memory growth/stalls; scope/duration recorded |
|
||||
|
||||
Retain existing FCEUmm/mGBA/Snes9x/FBNeo/Genesis regressions when changing shared
|
||||
upload, loader or driver code. Existing menu colour fixes and screenshot/thumbnail
|
||||
fixes are distinct paths: test both hardware gameplay and saved thumbnails.
|
||||
|
||||
## 15. Files, commands and proposed deliverables
|
||||
|
||||
Existing verification entry point: `tools/verify.sh`, in the established order
|
||||
`format`, `unit`, `build`, `integration`, `evidence`. Its build gate currently
|
||||
builds the shipped cores/title; PPSSPP must be explicitly integrated before that
|
||||
gate can prove anything about it. Keep command definitions in that script and
|
||||
its existing subordinate tooling, rather than inventing a second gate runner.
|
||||
|
||||
Useful read-only baseline commands:
|
||||
|
||||
```bash
|
||||
git status --short --branch
|
||||
git log -5 --oneline
|
||||
git -C ../PS5_Vulkan status --short --branch
|
||||
git -C ../PS5_Vulkan rev-parse HEAD
|
||||
bash tools/verify.sh --list
|
||||
rg -n 'ps5vk_draw_refusal|stencil attachments|PS5VK_TARGET_WIDTH' ../PS5_Vulkan/driver
|
||||
```
|
||||
|
||||
After implementation, proposed commands (the PPSSPP build script does not yet
|
||||
exist at the time of this handoff):
|
||||
|
||||
```bash
|
||||
bash tools/build-ppsspp.sh
|
||||
python3 tools/check-core.py build/cores/stage/cores/ppsspp_libretro.so --report build/cores/ppsspp/abi.json
|
||||
readelf -lW build/cores/stage/cores/ppsspp_libretro.so
|
||||
readelf -dW build/cores/stage/cores/ppsspp_libretro.so
|
||||
readelf -rW build/cores/stage/cores/ppsspp_libretro.so
|
||||
bash tools/verify.sh
|
||||
```
|
||||
|
||||
Consult `docs/DEPLOYMENT.md` and the current `tools/run-title.sh` help before an
|
||||
authorized console run. Do not paste a guessed launch command from this plan.
|
||||
|
||||
Proposed evidence files, adaptable to the existing evidence schema:
|
||||
|
||||
```text
|
||||
evidence/ppsspp-native/abi.json
|
||||
evidence/ppsspp-native/build.json
|
||||
evidence/ppsspp-native/loader.json
|
||||
evidence/ppsspp-native/driver-archives.json
|
||||
evidence/ppsspp-native/capture.json
|
||||
evidence/ppsspp-native/expectation.json
|
||||
```
|
||||
|
||||
Add focused host tests for new loader/memory ownership behavior and meaningful
|
||||
console probes for graphics semantics. Do not count a test that only repeats a
|
||||
constant from its implementation as evidence of runtime correctness.
|
||||
|
||||
## 16. Open questions and first-session checklist
|
||||
|
||||
Open questions, in priority order:
|
||||
|
||||
1. Where is the user's previous working PS5 PPSSPP JIT source/build, and which
|
||||
memory/ABI adaptations can be reused without importing its old pipeline?
|
||||
2. Which PPSSPP/libretro revision is the port target, and how does its hardware
|
||||
interface differ from the assessed current upstream reference?
|
||||
3. Does its dependency graph emit TLS or require unwind features the loader lacks?
|
||||
4. What exact guest-memory aliasing/alignment and JIT placement does that build
|
||||
require? Can the native reservation API meet it without hardcoded addresses?
|
||||
5. Which PS5_Vulkan changes have landed since this snapshot? Which source state
|
||||
and tested archive set will the frontend consume?
|
||||
6. Which combined depth/stencil format is the best supported hardware/layout path?
|
||||
7. Does the current RetroArch hardware-core interface work with this driver's
|
||||
dispatch/device lifecycle, independently of PPSSPP?
|
||||
8. Which renderer paths require additional descriptor sets, transfers or optional
|
||||
features beyond the four confirmed graphics blockers?
|
||||
9. Which redistributable workload and owner-provided games define first acceptance?
|
||||
10. When will the owner authorize a launch after the first reviewed build is ready?
|
||||
|
||||
First session sequence:
|
||||
|
||||
- Read the required repository context and this plan; inspect both working trees.
|
||||
- Create/reuse the implementation branch without disturbing pending changes.
|
||||
- Locate the old port and record a source comparison before writing new adapters.
|
||||
- Inventory the chosen build's dependencies, ELF/loader requirements and memory
|
||||
assumptions. Read the actual libretro presentation implementation and headers.
|
||||
- Recheck current driver blockers, then select one bounded implementation step.
|
||||
- State its proof before building; run appropriate gates; record evidence and
|
||||
update `docs/ACTIVE.md` plus an append-only phase-log entry when it lands.
|
||||
|
||||
Do not begin by advertising unsupported driver features, globally disabling error
|
||||
checks, forcing a 4K PSP framebuffer, replacing the frontend renderer, or trying
|
||||
random compiler flags until the binary links. Preserve the causal error and fix
|
||||
the smallest demonstrated requirement.
|
||||
|
||||
## 17. References and limits of this assessment
|
||||
|
||||
### Local project references
|
||||
|
||||
- [Current RetroArch state](docs/ACTIVE.md)
|
||||
- [Project gates and invariants](docs/PLAN.md)
|
||||
- [Testing](docs/TESTING.md)
|
||||
- [Deployment](docs/DEPLOYMENT.md)
|
||||
- [GPU-path acceptance criteria](docs/GPU_PATH_CRITERIA.md)
|
||||
- [Native core build example](tools/build-snes9x.sh)
|
||||
- [Native loader](src/core_loader_ps5.cpp)
|
||||
- [Core ABI checker](tools/check-core.py)
|
||||
|
||||
Sibling paths below assume the existing `PS5_Homebrews` layout. They are local
|
||||
research references, not vendored dependencies or portable public documentation:
|
||||
|
||||
- `../PS5_Eden/docs/PROBING.md`
|
||||
- `../PS5_Eden/docs/CAPABILITY_PROBE.md`
|
||||
- `../PS5_Eden/docs/ACTIVE.md`
|
||||
- `../PS5_Eden/evidence/capability-probe/`
|
||||
- `../PS5_Eden/evidence/capability-probe-memory/`
|
||||
- `../PS5_Eden/evidence/capability-probe-budget/`
|
||||
- `../PS5_Eden/evidence/capability-probe-layout/`
|
||||
- `../PS5_Vulkan/docs/VULKAN_PROBE_ACTIVE.md`
|
||||
- `../PS5_Vulkan/driver/ps5vk_image.c`
|
||||
- `../PS5_Vulkan/driver/ps5vk_pipeline.c`
|
||||
- `../PS5_Vulkan/driver/ps5vk_draw.c`
|
||||
|
||||
### Public primary sources
|
||||
|
||||
- [PPSSPP upstream](https://github.com/hrydgard/ppsspp)
|
||||
- [Reviewed libretro Vulkan context](https://github.com/hrydgard/ppsspp/blob/f293b10fb2d9dc0c2bc10281444ee3d3e932e6ad/libretro/LibretroVulkanContext.cpp)
|
||||
- [Reviewed Vulkan device initialization](https://github.com/hrydgard/ppsspp/blob/f293b10fb2d9dc0c2bc10281444ee3d3e932e6ad/Common/GPU/Vulkan/VulkanContext.cpp)
|
||||
- [Reviewed pipeline states](https://github.com/hrydgard/ppsspp/blob/f293b10fb2d9dc0c2bc10281444ee3d3e932e6ad/GPU/Vulkan/PipelineManagerVulkan.cpp)
|
||||
- [Reviewed executable-memory utilities](https://github.com/hrydgard/ppsspp/blob/f293b10fb2d9dc0c2bc10281444ee3d3e932e6ad/Common/MemoryUtil.cpp)
|
||||
- [Reviewed POSIX memory arena](https://github.com/hrydgard/ppsspp/blob/f293b10fb2d9dc0c2bc10281444ee3d3e932e6ad/Common/MemArenaPosix.cpp)
|
||||
- [Reviewed guest memory mapping](https://github.com/hrydgard/ppsspp/blob/f293b10fb2d9dc0c2bc10281444ee3d3e932e6ad/Core/MemMap.cpp)
|
||||
- [PS5_Vulkan public repository](https://github.com/mihawk-99/PS5_Vulkan)
|
||||
- [libretro core metadata](https://github.com/libretro/libretro-core-info)
|
||||
|
||||
No PPSSPP console compatibility, sustained performance, exact dependency list,
|
||||
complete shader feature coverage or full hardware-interface lifecycle is proven
|
||||
by this plan. Those are explicitly the implementation and verification work it
|
||||
hands off. The strong platform probes justify beginning that work; the source
|
||||
restrictions identify why a core build alone is insufficient.
|
||||
@@ -1,311 +0,0 @@
|
||||
# PPSSPP implementation plan: software core first, Vulkan renderer second
|
||||
|
||||
This is the implementation plan for adding PPSSPP to this title. It is the
|
||||
executable counterpart to [PPSSPP_Core_Plan.md](PPSSPP_Core_Plan.md), which holds
|
||||
the platform analysis, the driver findings and the acceptance gates this plan
|
||||
inherits. Read that document for *why* each constraint exists; this one says what
|
||||
gets built, in what order, and what proves each step.
|
||||
|
||||
The plan is stable. It records no progress, no dates and no results: the current
|
||||
step is in `docs/ACTIVE.md`, the run history is in `docs/PHASE_LOG.md`, and the
|
||||
measurements behind each decision are in `docs/FINDINGS.md`.
|
||||
|
||||
## 1. The shape of the work
|
||||
|
||||
Two milestones, one core binary:
|
||||
|
||||
| Milestone | What runs | Graphics | Depends on PS5_Vulkan |
|
||||
| --- | --- | --- | --- |
|
||||
| **Track A** (this plan's first half) | Real PSP emulation: MIPS JIT, software GPU core, audio, input, saves | PPSSPP's CPU rasterizer, 480x272 XRGB8888 handed to the frontend | **No** |
|
||||
| **Track C** (second half) | The same core on the Vulkan hardware renderer | PPSSPP's `DrawEngineVulkan` against the linked libps5vk | Yes: the driver subset in section 7 |
|
||||
|
||||
Track A is a prefix, not a detour. It proves the loader contract, the platform
|
||||
adaptation, the build pipeline, the memory policy, audio, input and content
|
||||
handling, and it produces a known-good PPSSPP to diff the Vulkan renderer
|
||||
against. Its only throwaway decision is the first memory policy (section 5),
|
||||
which is upgraded inside Track A anyway.
|
||||
|
||||
Do not describe a software-rendered run as hardware rendering. The plan's own
|
||||
three-achievement rule applies: core/platform bring-up, software-rendered PPSSPP,
|
||||
and PPSSPP Vulkan hardware rendering are separate claims with separate evidence.
|
||||
|
||||
### Why this order is available at all
|
||||
|
||||
`LibretroGraphicsContext::CreateGraphicsContext()` (`libretro/LibretroGraphicsContext.cpp`)
|
||||
tries GL, GLES and Vulkan in turn and ends unconditionally at
|
||||
`LibretroSoftwareContext`. The core option `ppsspp_backend = "none"` maps to
|
||||
`RETRO_HW_CONTEXT_NONE` (`libretro/libretro.cpp`), which matches none of those
|
||||
branches, so PPSSPP never calls `RETRO_ENVIRONMENT_SET_HW_RENDER` and never
|
||||
creates a Vulkan device. `GetGPUCore()` then returns `GPUCORE_SOFTWARE` and
|
||||
`retro_load_game` wires that into the emulator.
|
||||
|
||||
The frontend needs no change: the software frames arrive through the same
|
||||
`video_cb` XRGB8888 path that the five shipped cores already prove, with
|
||||
`video_driver = "vulkan"` presenting them. The one runtime consequence is a fixed
|
||||
480x272 output (`NATIVEWIDTH`/`NATIVEHEIGHT`, `SOFT_BMP_SIZE` in
|
||||
`libretro/LibretroGraphicsContext.h`), which is already a multiple of the
|
||||
frontend's 256-byte row padding, so no padded-width work is involved.
|
||||
|
||||
## 2. Decisions already made, with their evidence
|
||||
|
||||
- **Pin `hrydgard/ppsspp` at `f293b10fb2d9dc0c2bc10281444ee3d3e932e6ad`.** It is
|
||||
the revision `PPSSPP_Core_Plan.md` and `handoff/PPSSPP_UPSTREAM_LIBRETRO_RESEARCH.md`
|
||||
were written against, so every finding in them is true of the pinned tree. It is
|
||||
an assessment reference, not an approved release: M0 of the core plan still has
|
||||
to confirm the final pin against the owner's older PS5 port when that source is
|
||||
located.
|
||||
- **`thread_local` needs no patch.** The SDK toolchain compiles with
|
||||
`-femulated-tls`, so a `thread_local` variable becomes a `__emutls_v.*` object
|
||||
with no `.tdata`/`.tbss` and no `PT_TLS` segment. Verified by compiling and
|
||||
linking a TLS probe with `prospero-clang++` and `tooling/native/ps5-core.ld`:
|
||||
zero PT_TLS, zero TLS sections, `NEEDED` exactly `libkernel_web.sprx`,
|
||||
`libSceLibcInternal.sprx`, `libScePosixForWebKit.sprx`, and one undefined
|
||||
import, `__emutls_get_address`.
|
||||
**Consequence to protect:** the title must keep resolving `__emutls_get_address`
|
||||
at link time. It comes from clang's `libclang_rt.builtins-x86_64.a`, which
|
||||
`tools/build.sh` links only inside its `APP_VULKAN_ARCHIVES` branch. If a future
|
||||
change drops the Vulkan archives from the link, every core with a `thread_local`
|
||||
stops linking.
|
||||
- **One core binary, two runtime configurations.** The backend is chosen per
|
||||
content load from a core option (`retro_check_backend()` in `libretro.cpp`), so
|
||||
the software and Vulkan paths are the same `.so`. There is no fork to maintain
|
||||
and no separate build.
|
||||
- **The frontend does not change for Track A.** No new patch in `patches/series`,
|
||||
no change to `core_names` until the ABI gate passes, and `video_driver` stays
|
||||
`"vulkan"`.
|
||||
|
||||
## 3. Files this plan adds
|
||||
|
||||
| Path | Purpose |
|
||||
| --- | --- |
|
||||
| `tools/build-ppsspp.sh` | the reproducible build: pinned fetch, zero-fuzz patches, cross configure, build, ABI check, staging, `build.json` |
|
||||
| `tooling/ppsspp/ps5-toolchain.cmake` | FreeBSD/x86-64 cross toolchain on `prospero-*`, no host library search |
|
||||
| `tooling/ppsspp/CMakeLists.txt` | wrapper project: adds the pinned tree, forces the libretro target and the PS5 feature set |
|
||||
| `patches/ppsspp/*.patch` | named source changes, one concern per file, applied with `--fuzz=0` |
|
||||
| `build/cores/ppsspp/` | the fetched tree, its build directory and `abi.json`/`build.json` (ignored) |
|
||||
| `build/cores/stage/{cores,info}/ppsspp_libretro.{so,info}` | the staging convention every other core uses |
|
||||
| `evidence/ppsspp-native/` | committed sanitized evidence once console runs exist |
|
||||
|
||||
## 4. Step ladder
|
||||
|
||||
Each step states its acceptance before it is built, and one step is one commit.
|
||||
|
||||
### A1 — the platform branch
|
||||
|
||||
PPSSPP has no platform for this ABI: `ppsspp_config.h` is an `#elif` chain over
|
||||
Windows, Apple, Switch, Android, Linux and OpenBSD with no `#else` and no
|
||||
`#error`. Compiled as-is, no `PPSSPP_PLATFORM_*` macro is defined and the tree
|
||||
silently takes generic paths — `MemoryMap_Setup` logs "Hit a wrong path" and
|
||||
returns false, and `Common/CPUDetect.cpp` leaves its core count uninitialised.
|
||||
|
||||
Add the branch on the macro the toolchain actually defines (`__PROSPERO__`), then
|
||||
fix what the compiler reports. The census is small: `PPSSPP_PLATFORM(LINUX)`
|
||||
appears in only nine files and `PPSSPP_PLATFORM(OPENBSD)` in one, so PS5 is a
|
||||
genuinely new platform rather than a Linux alias. Where a BSD-shaped path is
|
||||
right, follow the OpenBSD precedent.
|
||||
|
||||
- Acceptance: the pinned tree configures and compiles past every
|
||||
`PPSSPP_PLATFORM`-guarded file with no undefined platform macro; the diff to
|
||||
upstream is one named patch file.
|
||||
|
||||
### A2 — the cross build
|
||||
|
||||
`tools/build-ppsspp.sh` follows `tools/build-mgba.sh`: pinned revision, fresh
|
||||
extraction, patches with zero fuzz, `prospero-*` compilers named explicitly, and
|
||||
a local destructor registry object compiled with `-fno-exceptions -fno-rtti`
|
||||
(`tooling/native/core_cxx_runtime.cpp`). PPSSPP needs git submodules, so the
|
||||
fetch is a pinned `git` clone plus `git submodule update --init --recursive`
|
||||
rather than a codeload tarball; the script records the commit and every submodule
|
||||
SHA in `build/cores/ppsspp/build.json` so the input set is identified even though
|
||||
it is not a single digest.
|
||||
|
||||
The wrapper project must keep host discovery out of the build: `find_package`
|
||||
restricted to the SDK root, `OPENGL_LIBRARIES` preset empty so
|
||||
`find_package(OpenGL REQUIRED)` is never reached, `X11_LIBRARIES` empty, optional
|
||||
dependencies (FFmpeg, Discord, miniUPnPc, SDL) off, and `VULKAN` left on since the
|
||||
Vulkan path is compiled regardless. Linking matches every other core:
|
||||
`-nostdlib -nodefaultlibs -Wl,-z,undefs -Wl,-T,tooling/native/ps5-core.ld` with
|
||||
`-lkernel_web -lSceLibcInternal -lScePosixForWebKit`.
|
||||
|
||||
- Acceptance: `tools/check-core.py build/cores/ppsspp/ppsspp_libretro.so` passes —
|
||||
25 libretro exports, `libkernel_web.sprx` present, imports limited to the three
|
||||
allowed modules, 16 KiB load segments, no writable+executable segment, and no
|
||||
`PT_TLS`. `readelf -lW` and the ABI report are the evidence.
|
||||
|
||||
### A3 — the import surface
|
||||
|
||||
`tools/core-imports.py` derives the title's native binding table from the staged
|
||||
cores' undefined symbols. Adding PPSSPP grows that table, and every new symbol
|
||||
must be exported by a stub of a module the title already links, or the title link
|
||||
fails with `no public SDK stub exports required symbol <name>`
|
||||
(`tooling/native/sce_module_writer.cpp`).
|
||||
|
||||
Expected new imports, all previously confirmed present in `libkernel_web.so` or
|
||||
`libSceLibcInternal.so`: `mprotect`, `pthread_*`, `sigaction`, `sigaltstack`,
|
||||
`shm_open`, `__cxa_*`, `_Unwind_*`, `__emutls_get_address`.
|
||||
|
||||
- Acceptance: the title links with PPSSPP in `core_names`, the five existing cores
|
||||
still pass `check-core.py`, and the added symbol list is recorded as evidence.
|
||||
The staged core is **not** added to `core_names` before this point.
|
||||
|
||||
### A4 — first load on the console
|
||||
|
||||
Deploy the staged core beside the five shipped ones and load it with
|
||||
`ppsspp_backend = "none"`. This is the first proof that the loader accepts a
|
||||
20 MB C++ core with hundreds of thousands of dynamic symbols and that the title
|
||||
stays alive when it does.
|
||||
|
||||
- Acceptance: the loader log shows the module loaded, initializers ran and the
|
||||
core reported its identity; the frontend remains navigable; `klog` captured.
|
||||
- Failure handling: the loader can refuse a malformed or unsupported core, but it
|
||||
cannot isolate a fault inside core code. A crash is evidence to preserve, not a
|
||||
reason to relaunch.
|
||||
|
||||
### A5 — a game boots
|
||||
|
||||
Assets first: PPSSPP needs `system/PPSSPP/` (its own asset files, the `lang`
|
||||
folder and `flash0` fonts) and `ppge_atlas.zim`; the firmware-derived parts come
|
||||
from the owner and are never committed. Content starts with a redistributable PSP
|
||||
homebrew, then an owner-provided game.
|
||||
|
||||
- Acceptance: a PSP title reaches gameplay with the software GPU core, audio and
|
||||
input working, and the run captured. Frame timing is recorded but not yet a
|
||||
gate; the software rasterizer is expected to be slow on 3D content.
|
||||
|
||||
### A6 — lifecycle and the memory policy
|
||||
|
||||
Repeated load/unload, content change, menu open/close, and a second game. Then
|
||||
the memory upgrade (section 5). The `NO_MMAP` bring-up policy caps guest RAM near
|
||||
32 MiB, so a 64 MiB title is the natural forcing case for the arena work.
|
||||
|
||||
- Acceptance: bounded memory growth across cycles, no leak visible in the
|
||||
title's allocation diagnostics, and a 64 MiB title running on the upgraded
|
||||
policy.
|
||||
|
||||
### A7 — Track A acceptance
|
||||
|
||||
The plan's M2 milestone, reached with the software renderer: bounded CPU
|
||||
workloads, repeated reset/load/unload with stable accounting, the active CPU mode
|
||||
proven by logs and execution evidence rather than by the saved option string, and
|
||||
one deliberate interpreter run as a comparison.
|
||||
|
||||
## 5. Memory policy, in two steps
|
||||
|
||||
The console's numbers are fixed: a 448 MiB flexible pool that **every** anonymous
|
||||
mapping draws on, including `PROT_NONE` reservations; a 12,288 MiB direct pool
|
||||
reachable only through the `sceKernel*` APIs; a 16,384-entry page-table
|
||||
allowance; execute granted by `mprotect` and never at mapping time; and `mprotect`
|
||||
rounding a non-aligned address instead of refusing it.
|
||||
|
||||
PPSSPP's normal POSIX path cannot work in that environment.
|
||||
`MemArena::Find4GBBase()` needs an 8 GiB `PROT_NONE` anonymous mapping to succeed
|
||||
before it returns a 4 GiB-aligned base, and on failure it returns the hardcoded
|
||||
`0x2300000000`; `MemArena::CreateView` then maps each guest view with `MAP_FIXED`
|
||||
at `base + virtual_address`, which silently replaces whatever occupies that
|
||||
address. The 8 GiB probe cannot succeed inside a 448 MiB pool, so the dangerous
|
||||
fallback is the *expected* path, not an edge case.
|
||||
|
||||
**Step one, bring-up: `USE_NO_MMAP`.** CMake adds `-DNO_MMAP -DMASKED_PSP_MEMORY`;
|
||||
`CreateView` returns the pointer without mapping anything, `Find4GBBase` becomes a
|
||||
single 160 MiB allocation, and guest pointers are `base + (addr & 0x3FFFFFFF)`.
|
||||
No shm object, no `MAP_FIXED`, no multi-GiB reservation. The guest RAM ceiling
|
||||
this implies is a known limitation, not a surprise: it is the reason step two
|
||||
exists.
|
||||
|
||||
**Step two, correctness: a PS5 arena adapter.** A small platform memory file that
|
||||
reserves address space with `sceKernelReserveVirtualRange`, backs guest RAM,
|
||||
VRAM and the JIT cache with `sceKernelAllocateDirectMemory` +
|
||||
`sceKernelMapDirectMemory`, obtains execute with `sceKernelMprotect`, tracks each
|
||||
region's reservation base, mapped base, direct offset, protection and lifetime,
|
||||
and unwinds every failed stage. The exact prototypes are not in the SDK headers —
|
||||
they are exported by the kernel stubs but undeclared — so they come from the
|
||||
probe code in the sibling Eden repository, which already exercised them on this
|
||||
console.
|
||||
|
||||
Both steps are backend-independent: they apply unchanged when the Vulkan renderer
|
||||
is switched on.
|
||||
|
||||
## 6. The JIT and executable memory
|
||||
|
||||
The MIPS recompilers allocate a 16 MiB code space (`AllocCodeSpace(1024*1024*16)`
|
||||
in each backend); `GetJitCacheSize` no longer exists upstream, so that constant is
|
||||
the whole code-cache budget. `AllocateExecutableMemory` asks for
|
||||
`PROT_READ|PROT_WRITE|PROT_EXEC` unless `PlatformIsWXExclusive()` is true, and a
|
||||
direct RWX mapping is exactly the case this console refuses to execute.
|
||||
|
||||
So the PS5 platform must report `PlatformIsWXExclusive() == true`, which routes
|
||||
allocation through RW and adds execute later with `mprotect`, with
|
||||
`Common/CodeBlock.h` bracketing writes with protect/unprotect. The platform branch
|
||||
in A1 owns that. The cost is measured and known: 108 microseconds per
|
||||
map/write/RX/call/munmap cycle on this console against 3.5 on the development
|
||||
host, so code-cache churn is expensive and worth keeping out of steady state.
|
||||
|
||||
PPSSPP's fast-memory handling installs a `SIGSEGV` handler with `SA_SIGINFO` and
|
||||
`SA_ONSTACK`, reads the machine context and rewrites the faulting program counter
|
||||
(`Common/ExceptionHandlerSetup.cpp`, `Core/MemFault.cpp`). The SDK exports
|
||||
`sigaction`, `sigaltstack`, `getcontext`, `makecontext` and `swapcontext`, so the
|
||||
primitives exist; the loader must not be covering those signals, and the SDK's
|
||||
machine-context layout is the thing to verify with a focused probe before fast
|
||||
memory is trusted.
|
||||
|
||||
## 7. The upgrade path to the Vulkan renderer
|
||||
|
||||
Nothing in Track A is discarded except the memory policy's first step. Switching
|
||||
on hardware rendering means:
|
||||
|
||||
1. Set `ppsspp_backend = "vulkan"` (and keep `video_driver = "vulkan"`).
|
||||
2. Land the driver subset PPSSPP actually requires, in this order: attachments at
|
||||
arbitrary extents; a combined depth/stencil format; cull mode and front face;
|
||||
triangle strips and fans; then colour write mask, stencil state, dynamic blend
|
||||
constants and meaningful pipeline barriers.
|
||||
3. Prove the frontend's `retro_hw_render_interface_vulkan` bridge with a minimal
|
||||
hardware-core canary before PPSSPP is the thing being debugged, because
|
||||
PPSSPP's device creation asserts when the driver has no combined depth/stencil
|
||||
format and the bridge has otherwise never been exercised.
|
||||
4. Keep `ppsspp_backend = "none"` as the documented recovery configuration. A
|
||||
driver regression then costs a core option, not a rebuild.
|
||||
|
||||
The driver details, including what **not** to build because PPSSPP never asks for
|
||||
it — descriptor sets beyond zero, geometry and tessellation shaders, dynamic
|
||||
rendering, logic operations, depth clamp, dual-source blend — are in section 9 of
|
||||
`PPSSPP_Core_Plan.md` as amended by this plan's findings. The honest rule for
|
||||
both sides: advertise a capability only when its semantics exist, because PPSSPP
|
||||
takes the fast path whenever the feature bit is set and a silently dropped
|
||||
feature is a correctness bug, not a fallback.
|
||||
|
||||
## 8. Evidence and gates
|
||||
|
||||
- `tools/verify.sh` gains nothing new. The PPSSPP build becomes part of the
|
||||
`build` gate only when step A3 lands, by adding `ppsspp` to `core_names` in
|
||||
`tools/build-title.sh`; before that the build script is run directly and its
|
||||
ABI report is the evidence.
|
||||
- Every step's evidence is an artifact: the ABI report for A2, the title link and
|
||||
the import delta for A3, loader and frontend logs for A4, gameplay capture for
|
||||
A5, allocation accounting for A6.
|
||||
- Raw captures stay in the ignored `klog/`; sanitized machine-readable results go
|
||||
to `evidence/ppsspp-native/`. Owner content, ROM filenames, console addresses
|
||||
and firmware-derived assets never enter the repository.
|
||||
- The five shipped cores are regression-tested whenever the import surface, the
|
||||
loader or shared upload code changes. A PPSSPP step that breaks one of them is
|
||||
not landed.
|
||||
|
||||
## 9. Open items to close before or during A1-A3
|
||||
|
||||
| Item | Why it matters | Where it closes |
|
||||
| --- | --- | --- |
|
||||
| Final upstream pin | the assessment pin is not an approved release, and the owner's older PS5 port may carry reusable adaptations | before the first public build |
|
||||
| The owner's older JIT port | may already solve arena, ABI and timing problems; must be located or declared lost | A1 |
|
||||
| PSP content and `system/PPSSPP` assets | acceptance impossible without them; firmware-derived files come from the owner | A5 |
|
||||
| SDK machine-context layout | fast memory rewrites the faulting PC from `ucontext_t` | A4, by probe |
|
||||
| `__emutls_get_address` retention | every core with a `thread_local` depends on the title's clang builtins archive | A3, by the title link |
|
||||
|
||||
## 10. Non-goals
|
||||
|
||||
- Building or vendoring PSP firmware, games or PPSSPP's firmware-derived assets.
|
||||
- A second graphics backend, a private GPU path, or driver code in this
|
||||
repository: the driver lives in PS5_Vulkan and is consumed as released
|
||||
archives.
|
||||
- Claiming hardware rendering, full-speed gameplay or general PSP compatibility
|
||||
from a software-rendered run, a boot screen or a single title.
|
||||
- Changing the shipped default away from the five accepted cores until PPSSPP
|
||||
passes its own acceptance steps.
|
||||
@@ -1,412 +0,0 @@
|
||||
# PS5 RetroArch 🎮
|
||||
|
||||
**Native RetroArch for jailbroken PlayStation 5 consoles, rendering through
|
||||
[PS5_Vulkan](https://github.com/mihawk-99/PS5_Vulkan)'s port of Mesa's RADV
|
||||
Vulkan driver.**
|
||||
|
||||
Maintained by [Mihawk](https://github.com/mihawk-99). Based on
|
||||
[RetroArch / libretro](https://github.com/libretro/RetroArch), with a native PS5
|
||||
application foundation derived from
|
||||
[ProsperoLight](https://github.com/blackbearreloaded/ProsperoLight).
|
||||
|
||||
The current build launches as a homebrew title, presents XMB through RetroArch's
|
||||
Vulkan video driver, and runs eight native libretro cores, three of them (PSP,
|
||||
GameCube/Wii and PlayStation 2) rendering on the PS5's GPU. Input, stereo audio,
|
||||
configuration persistence and content browsing have been verified on a console.
|
||||
This is an active development project; the tested paths below do not imply
|
||||
complete core compatibility or Vulkan conformance.
|
||||
|
||||
**Latest release: v0.5.0-alpha.5** — see the [release notes](docs/releases/v0.5.0-alpha.5.md).
|
||||
It is the first release on RADV; [v0.4.0-alpha.4](docs/releases/v0.4.0-alpha.4.md)
|
||||
was the last on ps5vk, the project's first driver.
|
||||
|
||||
## Table of contents
|
||||
|
||||
- [Current status](#current-status)
|
||||
- [Available cores](#available-cores)
|
||||
- [Graphics and native runtime](#graphics-and-native-runtime)
|
||||
- [Roadmap](#roadmap)
|
||||
- [Build from source](#build-from-source)
|
||||
- [Install and file locations](#install-and-file-locations)
|
||||
- [Testing and troubleshooting](#testing-and-troubleshooting)
|
||||
- [Documentation](#documentation)
|
||||
- [Authors and acknowledgements](#authors-and-acknowledgements)
|
||||
- [License and third-party terms](#license-and-third-party-terms)
|
||||
|
||||
## Current status
|
||||
|
||||
| Feature | Status |
|
||||
| --- | --- |
|
||||
| Native title startup and Quit | ✅ Working, including splash dismissal and clean native exit |
|
||||
| Vulkan video output | ✅ Menu and software-core frames presented through RADV, Mesa's Vulkan driver, linked into the title (PS5_Vulkan's port, reporting Vulkan 1.4). ps5vk remains a build option |
|
||||
| XMB | ✅ Default menu, with icons, fonts and background rendering |
|
||||
| RGUI | ✅ Alternative menu |
|
||||
| Native controller input | ✅ Buttons, left-stick menu navigation and button/axis binding capture |
|
||||
| Native audio | ✅ `audio_ps5` stereo PCM output, audible channel test and buffering diagnostics |
|
||||
| Filesystem and configuration | ✅ Directory browsing, configuration loading/saving and FTP-writable application folders |
|
||||
| Core loading | ✅ Native shared-core loader, official `.info` discovery and recovery from rejected loads |
|
||||
| Content loading | ✅ Tested games and archives with the cores below. Load Content opens two roots: **INTERNAL**, the title's folder, and **EXTERNAL**, the console's `/mnt`, where USB drives and extended storage mount. The title's sandbox hides `/mnt` for now, so EXTERNAL is empty |
|
||||
| Colour and menu transitions | ✅ Corrected pixel uploads; Quick Menu/Close Content/next-game transitions I verified on the console |
|
||||
| Hardware-rendered cores | ✅ PPSSPP, Dolphin, LRPS2, Beetle PSX HW, Mupen64Plus-Next (ParaLLEl-RDP) and Azahar render on RADV through their Vulkan renderers, with JITs where they have them, at up to 18× internal resolution; PPSSPP's MSAA works |
|
||||
| Shader cache | ✅ RADV keeps compiled pipelines in `radv-shader-cache/`: a game's next start reads them back instead of compiling |
|
||||
| Save states and fast-forward | ✅ Save/load states (including `--entryslot`) and fast-forward, tested with PPSSPP and mGBA |
|
||||
| 120 Hz output | ✅ 120 Hz by default where the display offers it; the refresh is measured, and a display that stays at 60 Hz gets 60 Hz |
|
||||
| Stability | ✅ On RADV: every core with a game through boot, the menu, Close Content and a reload, with and without Threaded Video, and a 10-minute PPSSPP soak with 25 menu toggles: no crash ([release checks](docs/PHASE_LOG.md)) |
|
||||
| CPU video fallback | ✅ `video_ps5` remains registered and selectable |
|
||||
| Development diagnostics | ✅ `retroarch.log`, startup/GPU trace, kernel captures and optional buffered frame timing |
|
||||
|
||||
The Alpha 5 checks recorded no crash and no kernel fatal signal, and full-speed
|
||||
audio windows once each game had booted. These results apply to the captured
|
||||
tests, not every possible workload. See
|
||||
[active state](docs/ACTIVE.md) and [committed evidence](evidence/) for exact builds,
|
||||
test coverage and known exceptions.
|
||||
|
||||
## Available cores
|
||||
|
||||
The title build includes these cores and their official metadata. FCEUmm,
|
||||
mGBA, Snes9x, FBNeo, Genesis Plus GX, Beetle Saturn, VICE, MAME and DeSmuME
|
||||
**render emulated games in software**; RetroArch uploads their frames and presents
|
||||
them through Vulkan. PPSSPP, Dolphin, LRPS2, Beetle PSX HW, Mupen64Plus-Next and
|
||||
Azahar **render on the GPU** through RADV, with their Vulkan renderers.
|
||||
|
||||
Each core's PS5 defaults are its highest graphical settings that hold full speed
|
||||
in the games I tested: the most internal resolution the core offers, unless a
|
||||
lower one is the most that keeps full speed (DeSmuME).
|
||||
|
||||
| Core | Systems covered by the core | Console verification in this port |
|
||||
| --- | --- | --- |
|
||||
| [FCEUmm](https://github.com/libretro/libretro-fceumm) | NES / Famicom | ✅ Gameplay, audio and input; subsequent shared menu-transition fixes verified. [Evidence](evidence/native-core-loading/) |
|
||||
| [mGBA](https://github.com/libretro/mgba) | Game Boy, Game Boy Color, Game Boy Advance | ✅ GB/GBC/GBA loading, corrected colours and clean menu/next-game transitions. [Evidence](evidence/mgba-native/) |
|
||||
| [Snes9x](https://github.com/libretro/snes9x) | SNES / Super Famicom | ✅ Tested gameplay, colours, audio/input and menu transitions; not every special chip or video mode. [Evidence](evidence/snes9x-native/) |
|
||||
| [FinalBurn Neo](https://github.com/libretro/FBNeo) | Supported arcade boards, including Neo Geo and Sega System 16/32 | ✅ Tested arcade games using both native 32-bit and converted 16-bit output; not every board or ROM set. [Evidence](evidence/fbneo-native/) |
|
||||
| [Genesis Plus GX](https://github.com/libretro/Genesis-Plus-GX) | Mega Drive / Genesis, Master System, Game Gear, SG-1000, Sega CD | ✅ Genesis gameplay and clean transitions, which I confirmed on the console. Other Sega systems and disc/BIOS paths still need separate acceptance. [Evidence](evidence/genesis-plus-gx-native/) |
|
||||
| [PPSSPP](https://github.com/hrydgard/ppsspp) v1.20.4 | PlayStation Portable | ✅ God of War: Ghost of Sparta and Yu-Gi-Oh! GX Tag Force at 10× internal resolution (4800×2720), 16× anisotropy: correct picture, full speed at 120 Hz, save states, fast-forward, and closing and reopening games. MSAA renders on RADV (it needs render pass 2, which ps5vk lacked). |
|
||||
| [Dolphin](https://github.com/libretro/dolphin) 2609 | GameCube, Wii | ✅ Wind Waker (an hour), Resident Evil 4 (30 minutes), Super Smash Bros. Melee, Mario Kart Wii and Rogue Leader, with the JIT and fast memory, save states and closing and reopening games. Rogue Leader's attract sequence still dips to 72–85% (see the release notes). |
|
||||
| [LRPS2](https://github.com/libretro/LRPS2) (PCSX2) | PlayStation 2 | ✅ The God of War II and Final Fantasy X demos and GTA San Andreas at 6× internal resolution on the Vulkan hardware renderer, full speed, with multi-threaded VU1 and save states. Needs your own BIOS in `system/pcsx2/bios/`. |
|
||||
| [Beetle PSX HW](https://github.com/libretro/beetle-psx-libretro) | PlayStation | ✅ Crash Bandicoot at 16× internal resolution on the Vulkan renderer, 32-bit colour, PGXP (no wobbling polygons), full speed, and closing and reopening the game. The disc image is read into memory at load. It runs with its built-in OpenBIOS; your own BIOS (`scph5501.bin` and the others its metadata lists) in `system/` is used when present. |
|
||||
| [Mupen64Plus-Next](https://github.com/libretro/mupen64plus-libretro-nx) | Nintendo 64 | ✅ Mario Kart 64 with ParaLLEl-RDP at 8× upscaling and ParaLLEl-RSP, both JITs on, full speed after boot, and closing and reopening the game. |
|
||||
| [Beetle Saturn](https://github.com/libretro/beetle-saturn-libretro) | Sega Saturn | ⚠️ Loads, then needs your own BIOS in `system/`: `mpr-17933.bin` (US/EU) or `sega_101.bin` (JP). Without it the game refuses to load and the menu stays usable. Gameplay not yet tested. |
|
||||
| [VICE](https://github.com/libretro/vice-libretro) x64sc | Commodore 64 | ✅ A `.d64` disk game at full speed, and closing and reopening it. |
|
||||
| [MAME](https://github.com/libretro/mame) 0.289 | Arcade | ✅ Metal Slug 3 from a 0.289 non-merged set, BIOS in the same folder: full speed, and closing and reopening it. Raster games render at their native size and are scaled on the GPU. Vector games are drawn at 4K by MAME's alternate renderer (not yet tested on the console). Sets must match 0.289. |
|
||||
| [DeSmuME](https://github.com/libretro/desmume) | Nintendo DS | ✅ Pokémon Diamond at 5× (1280×960) with the JIT and eight rasterizer threads, full speed, and closing and reopening it. 6× measured 93–95%. |
|
||||
| [Azahar](https://github.com/azahar-emu/azahar) | Nintendo 3DS | ✅ Mario & Luigi: Superstar Saga + Bowser's Minions at 18× internal resolution (the most Azahar offers) on Vulkan, with asynchronous shader compilation and the JIT, full speed after boot, and closing and reopening it. Decrypted games only. |
|
||||
|
||||
Use **FBNeo for Sega System 16/32 arcade sets**, rather than Genesis Plus GX.
|
||||
FBNeo needs compatible arcade sets and receives its ZIP/7z archives intact.
|
||||
Archive support and BIOS requirements vary by core.
|
||||
|
||||
Core binaries must be built for **this native pipeline and SDK**. A desktop `.so`
|
||||
or a core from a different PS5 RetroArch distribution is not automatically
|
||||
compatible. No games or BIOS files are bundled.
|
||||
|
||||
## Graphics and native runtime
|
||||
|
||||
```text
|
||||
Software core → video callback → RetroArch Vulkan video driver
|
||||
→ RADV, linked into the title → PS5 display
|
||||
XMB / RGUI ──────────────────────┘
|
||||
PPSSPP, Dolphin, LRPS2 (hardware cores) → Vulkan through RetroArch's HW context → RADV
|
||||
```
|
||||
|
||||
[PS5_Vulkan](https://github.com/mihawk-99/PS5_Vulkan), which I also maintain,
|
||||
is the separate GPU-driver project used here. Since v0.5.0-alpha.5 the title
|
||||
links its port of RADV: Mesa's Vulkan driver and ACO compiler, unchanged but
|
||||
where the console differs, over a PS5 winsys, built from my Mesa fork
|
||||
[PS5_Mesa](https://github.com/mihawk-99/PS5_Mesa). It reports Vulkan 1.4, and
|
||||
its conformance is that project's milestone: the full Khronos CTS runs on the
|
||||
console. Up to v0.4.0-alpha.4 the title linked ps5vk, the project's first
|
||||
driver, which `PS5_VULKAN_DRIVER=ps5vk` still builds.
|
||||
|
||||
The driver is **linked into the title**. Updating a driver checkout does not
|
||||
update the linked code: rebuild and redeploy the RetroArch title against the
|
||||
intended driver artifacts. A `libvulkan.so.1` left in the title folder by an
|
||||
earlier release is ps5vk's and is not used.
|
||||
|
||||
This repository supplies the frontend/platform integration, native audio and
|
||||
input backends, core loader, build scripts and console validation. Core-side
|
||||
pixel adapters preserve the renderer's buffers while matching the frontend's
|
||||
upload format. The CPU video backend remains available as a fallback; the
|
||||
current default is `video_driver = "vulkan"`, `menu_driver = "xmb"`.
|
||||
|
||||
PPSSPP's JIT runs: its code memory is mapped read-write and then made
|
||||
executable, and its fast-memory fault handler reads the console's own signal
|
||||
context layout. The port builds PPSSPP v1.20.4 with one patch
|
||||
(`patches/ppsspp/ps5-port.patch`) and FFmpeg 3.0.2 for game videos. It starts
|
||||
with the settings I test with (10× internal resolution, 16× anisotropy, auto
|
||||
max-quality filtering, hardware transform, software skinning, no frameskip, no
|
||||
speed hacks); an existing `PPSSPP.opt` is set aside once as
|
||||
`PPSSPP.opt.before-ps5-profile`. The native loader has explicit limits, including
|
||||
no TLS or general exception-unwind registration, and it waits for a core's
|
||||
threads to finish before unmapping the core. See the
|
||||
[runtime contract](docs/REFERENCE.md#native-in-process-core-loader).
|
||||
|
||||
## Roadmap
|
||||
|
||||
**✅ = verified for the stated scope. ❌ = pending implementation or acceptance
|
||||
in this port, even if upstream RetroArch already offers the feature.**
|
||||
|
||||
### Frontend and platform
|
||||
|
||||
- ✅ Native startup and clean exit.
|
||||
- ✅ GPU presentation through PS5_Vulkan; selectable CPU fallback.
|
||||
- ✅ XMB, with RGUI retained.
|
||||
- ✅ Native input, analog menu navigation and remapping.
|
||||
- ✅ Native stereo audio and buffering diagnostics.
|
||||
- ✅ Filesystem browsing, configuration persistence and content loading.
|
||||
- ✅ Native core loading, metadata and failed-load recovery.
|
||||
- ❌ Save RAM and save-state persistence verified across restarts and core changes.
|
||||
- ❌ Core-option persistence and per-game/per-core overrides fully validated.
|
||||
- ❌ BIOS/system-file coverage, disc swapping and multi-disc acceptance tests.
|
||||
- ❌ RetroAchievements and netplay; networking is disabled in the current frontend build.
|
||||
- ❌ User Slang shader presets and multipass effects validated on PS5_Vulkan.
|
||||
- ✅ 120 Hz output where the display offers it, with a 60 Hz fallback chosen by measuring the refresh.
|
||||
- ❌ No core losing speed while a shader compiles. On RADV (Alpha 5), PPSSPP's compiles on its own threads no longer cost it speed, and a game's second start reads its pipelines from the cache; Dolphin's first start of a game still compiles its ubershaders at a cost (see the release notes).
|
||||
- ✅ Save states and fast-forward, including PPSSPP.
|
||||
- ❌ 4K output/upscaling, VRR and HDR validated in this application.
|
||||
- ❌ Low-latency features, runahead and sustained per-core performance measurements.
|
||||
- ❌ Broader compatibility testing and release qualification.
|
||||
|
||||
### Cores
|
||||
|
||||
| Status | Core / milestone |
|
||||
| --- | --- |
|
||||
| ✅ | FCEUmm — NES |
|
||||
| ✅ | mGBA — GB / GBC / GBA |
|
||||
| ✅ | Snes9x — SNES |
|
||||
| ✅ | FBNeo — tested arcade games |
|
||||
| ✅ | Genesis Plus GX — tested Genesis gameplay |
|
||||
| ❌ | Beetle PCE — PC Engine / TurboGrafx-16, SuperGrafx and CD; next proposed addition |
|
||||
| ❌ | Stella — Atari 2600; candidate |
|
||||
| ❌ | PicoDrive — add Sega 32X coverage; candidate |
|
||||
| ✅ | MAME 0.289 — arcade; tested games only |
|
||||
| ✅ | Beetle PSX HW — PlayStation, Vulkan renderer at 16× |
|
||||
| ✅ | Mupen64Plus-Next — Nintendo 64, ParaLLEl-RDP at 8× |
|
||||
| 🚧 | Beetle Saturn — Sega Saturn; needs a gameplay test with a BIOS |
|
||||
| ✅ | VICE x64sc — Commodore 64 |
|
||||
| ✅ | DeSmuME — Nintendo DS at 5× |
|
||||
| ✅ | Azahar — Nintendo 3DS, Vulkan at 18× |
|
||||
| ❌ | EXTERNAL storage (USB, extended storage) readable from inside the title's sandbox |
|
||||
| ✅ | PPSSPP — PSP, Vulkan rendering and JIT; tested games only |
|
||||
| ✅ | PPSSPP MSAA — render pass 2 and depth/stencil resolve, on RADV |
|
||||
| ✅ | Dolphin — GameCube and Wii, Vulkan rendering and JIT; tested games, long play and the enhancement profiles |
|
||||
| ✅ | LRPS2 — PlayStation 2, Vulkan hardware renderer at 4K; tested games only |
|
||||
| 🚧 | LRPS2 — upstream PCSX2's newer renderer fixes, 8× internal resolution and texture replacement |
|
||||
|
||||
Future entries are development targets, not a promised release order. Hardware
|
||||
rendering introduces new Vulkan requirements beyond presenting software frames;
|
||||
PPSSPP is the first core that exercises them.
|
||||
|
||||
## Build from source
|
||||
|
||||
The current build uses Linux host tools and the project's cached public PS5 SDK.
|
||||
Start with `bash tools/doctor.sh` for host-tool checks. You also need `curl`,
|
||||
`patch`, `pkg-config`, ELF utilities such as `readelf`, and working host C/C++
|
||||
compilers with sanitizer support for the tests. The target scripts currently use
|
||||
`/usr/bin/clang` through the SDK wrappers.
|
||||
|
||||
Prepare and build [PS5_Vulkan](https://github.com/mihawk-99/PS5_Vulkan) following
|
||||
its own instructions, normally as a sibling directory:
|
||||
|
||||
```text
|
||||
workspace/
|
||||
├── PS5_RetroArch/
|
||||
├── PS5_Vulkan/ # The RADV release archive, its link recipe and dependencies
|
||||
├── PS5_Mesa/ # My Mesa fork, which PS5_Vulkan builds RADV from
|
||||
└── PS5_PayloadSDK/ # My payload SDK fork and its platform layer, at a pinned revision
|
||||
```
|
||||
|
||||
The cores that needed changes for the console build from my forks of them
|
||||
(PS5_LRPS2, PS5_BeetlePSX, PS5_Mupen64Plus, PS5_BeetleSaturn, PS5_VICE,
|
||||
PS5_MAME, PS5_DeSmuME, PS5_Azahar with PS5_Dynarmic), each pinned by revision in
|
||||
its build script. The script uses the sibling checkout when there is one, and
|
||||
`github.com/mihawk-99/<fork>` otherwise.
|
||||
|
||||
The title build consumes PS5_Vulkan's RADV release archive
|
||||
(`tools/build-radv.sh release` there, built from PS5_Mesa at the revision it
|
||||
pins) and links it with that project's `tools/radv-link.sh`; it does not build
|
||||
the driver for you. `PS5_VULKAN_DRIVER=ps5vk` links ps5vk's driver, Vulkan
|
||||
runtime and shader-compiler archives instead, and `RADV_ARCHIVE` names another
|
||||
RADV archive. `PS5_VULKAN_DIR` can select an alternative checkout for the
|
||||
build; some host tests currently require the sibling layout above. Driver
|
||||
dependencies and setup requirements are documented in the driver repository.
|
||||
|
||||
From the RetroArch repository root:
|
||||
|
||||
```bash
|
||||
make deps
|
||||
export PS5_PAYLOAD_SDK="$PWD/.deps/native/ps5-payload-sdk"
|
||||
export PS5_CLANG=/usr/bin/clang
|
||||
bash tools/fetch-retroarch.sh
|
||||
bash tools/build-title.sh # Create the artifacts inspected by the host tests
|
||||
bash tools/verify.sh
|
||||
```
|
||||
|
||||
The five gates are **format → unit → build → integration → evidence**. The build
|
||||
pins RetroArch 1.22.2, fetches core sources/metadata with checked hashes, builds the
|
||||
frontend and all fifteen cores, and stages the native title in `dist/PPSA99169/`.
|
||||
The initial dependency/source fetch requires network access.
|
||||
|
||||
For an already configured checkout:
|
||||
|
||||
```bash
|
||||
bash tools/build-title.sh # Build/stage the frontend and all shipped cores
|
||||
make genesis-plus-gx # Build and ABI-check one core only
|
||||
# Other core targets: fceumm, mgba, snes9x, fbneo
|
||||
bash tools/build-ppsspp.sh # The larger cores have scripts of their own:
|
||||
# build-ppsspp.sh, build-dolphin.sh, build-lrps2.sh,
|
||||
# build-beetle-psx.sh, build-mupen64plus.sh,
|
||||
# build-beetle-saturn.sh, build-vice.sh,
|
||||
# build-mame.sh, build-desmume.sh, build-azahar.sh
|
||||
```
|
||||
|
||||
When adding or updating a core, rebuild the title too: the frontend's native
|
||||
import table and build identity depend on the shipped core binaries. Source
|
||||
patches live in `patches/`; fetched and generated trees stay in ignored
|
||||
`vendor/`, `.deps/`, `build/` and `dist/` directories.
|
||||
|
||||
## Install and file locations
|
||||
|
||||
The verified deployment is a **homebrew title folder**, not a retail package.
|
||||
Copy the complete `dist/PPSA99169/` tree to the title location used by your
|
||||
configured homebrew launcher. The current validation setup uses
|
||||
`/data/homebrew/PPSA99169/`. This project does not install a jailbreak or launcher.
|
||||
|
||||
`/app0` is the running application's mount point. Over FTP, use the corresponding
|
||||
title folder instead:
|
||||
|
||||
| Purpose | Under `/data/homebrew/PPSA99169/` | In RetroArch |
|
||||
| --- | --- | --- |
|
||||
| Native cores | `cores/` | `/app0/cores/` |
|
||||
| Core metadata | `info/`, with compatibility copies in `cores/` | `/app0/info/` |
|
||||
| Games | `content/` | `/app0/content/` |
|
||||
| BIOS/system data | `system/` | `/app0/system/` |
|
||||
| PS2 BIOS (your own dump) | `system/pcsx2/bios/` | `/app0/system/pcsx2/bios/` |
|
||||
| Live configuration | `config/retroarch.cfg` | `/app0/config/retroarch.cfg` |
|
||||
| Save RAM | `savefiles/` | `/app0/savefiles/` |
|
||||
| Save states | `savestates/` | `/app0/savestates/` |
|
||||
| RADV shader cache | `radv-shader-cache/` | `/app0/radv-shader-cache/` |
|
||||
|
||||
RADV creates `radv-shader-cache/` itself, open to FTP like the other folders;
|
||||
deleting it only makes the next start compile again. `ps5vk-shader-cache/` and
|
||||
`sce_module/libvulkan.so.1`, left by releases up to v0.4.0-alpha.4, are not used
|
||||
since v0.5.0-alpha.5 and can be deleted.
|
||||
|
||||
For FBNeo, use `system/fbneo/` for its system files. Genesis Plus GX's Sega CD BIOS
|
||||
filenames belong in the configured `system/` root, as listed by its metadata.
|
||||
|
||||
The application creates its managed writable folders and seeds live settings
|
||||
only when no live configuration exists. Ordinary scripted updates preserve user
|
||||
content and saved settings. Existing settings can therefore keep RGUI selected
|
||||
even though XMB is the packaged default. Back up user files before any clean
|
||||
removal; `--clean` removes the entire title folder.
|
||||
|
||||
See [deployment](docs/DEPLOYMENT.md) for FTP setup, verified readback and test runs.
|
||||
Console details belong in the ignored `.env`, based on `.env.example`.
|
||||
|
||||
## Testing and troubleshooting
|
||||
|
||||
A successful build proves neither gameplay nor correct rendering. Core acceptance
|
||||
includes native loading, gameplay, colour checks, audio/input, Quick Menu →
|
||||
Close Content, and loading another game. I record my own visual confirmation on
|
||||
the console alongside the logs; a camera can miss refresh-synchronous flicker.
|
||||
|
||||
The v0.5.0-alpha.5 release passed all five host gates with 74 Python tests;
|
||||
52 recorded captures replay successfully. Exact results and limitations are in
|
||||
[ACTIVE](docs/ACTIVE.md), rather than implied by a core's upstream feature list.
|
||||
|
||||
For reports, include the core, game-file format, relevant settings, reproduction
|
||||
steps and whether the application was closed manually. Preserve `retroarch.log`
|
||||
and `trace.txt` before reopening: the frontend log is replaced on a new launch.
|
||||
The test tools retain kernel captures in ignored `klog/`. Redact private paths,
|
||||
console addresses and credentials before sharing logs.
|
||||
|
||||
Old `gpu-buffers-*.bin`, `gpu-stages-*.bin` and `gpu-tables-*.bin` files are temporary
|
||||
rendering diagnostics from earlier investigations. They are not required runtime
|
||||
assets and can be removed. Keep the normal development logs when reporting bugs.
|
||||
|
||||
## Documentation
|
||||
|
||||
| Document | Purpose |
|
||||
| --- | --- |
|
||||
| [Active state](docs/ACTIVE.md) | Current verified build, known errors and acceptance limits |
|
||||
| [Reference](docs/REFERENCE.md) | Native loader, core contracts, source pins and platform details |
|
||||
| [Testing](docs/TESTING.md) | Host gates and console acceptance procedures |
|
||||
| [Deployment](docs/DEPLOYMENT.md) | Build staging, FTP locations, updates and capture workflow |
|
||||
| [Troubleshooting](docs/TROUBLESHOOTING.md) | Recorded symptoms, causes and fixes |
|
||||
| [Findings](docs/FINDINGS.md) | Technical observations and evidence behind decisions |
|
||||
| [Phase log](docs/PHASE_LOG.md) | Dated development and console-test history |
|
||||
| [Releasing](docs/RELEASING.md) | What a release carries (licences, component manifest, source archives) and the order that keeps tag, ZIP and source in step; [what each past release was built from](docs/releases/SOURCE_CORRESPONDENCE.md) |
|
||||
| [Evidence](evidence/) | Machine-readable captures and expected results |
|
||||
| [Contributor/agent instructions](AGENTS.md) | Scope, verification, attribution and commit rules |
|
||||
|
||||
## Authors and acknowledgements
|
||||
|
||||
This port builds on substantial upstream and PS5 homebrew work. Credits below
|
||||
identify project authors and teams; their repositories retain the full contributor
|
||||
lists and original notices.
|
||||
|
||||
### Frontend, platform and graphics
|
||||
|
||||
| Project / author | Contribution |
|
||||
| --- | --- |
|
||||
| [Mihawk](https://github.com/mihawk-99) — [PS5_RetroArch](https://github.com/mihawk-99/PS5_RetroArch), [PS5_Vulkan](https://github.com/mihawk-99/PS5_Vulkan), [PS5_Mesa](https://github.com/mihawk-99/PS5_Mesa), [PS5_PayloadSDK](https://github.com/mihawk-99/PS5_PayloadSDK) | This native RetroArch port and core integration; the PS5 Vulkan drivers (the RADV port and ps5vk); the Mesa fork with the PS5 winsys; the payload SDK fork and its platform layer |
|
||||
| [RetroArch / libretro contributors](https://github.com/libretro/RetroArch) | Frontend, libretro API, menus, video pipeline and shared libraries |
|
||||
| [BlackBearReloaded — ProsperoLight](https://github.com/blackbearreloaded/ProsperoLight) | Project starting point and reference for native PS5 input and audio integration |
|
||||
| [BlackBearReloaded — PS5 Native App Boilerplate](https://github.com/blackbearreloaded/ps5-native-app-boilerplate) | Underlying native title tooling, ELF/FSELF conversion and runtime-shim foundation |
|
||||
| [John Törnblom and ps5-payload-dev contributors](https://github.com/ps5-payload-dev/sdk) | Public PS5 Payload SDK, toolchain and API stubs; [PacBrew](https://github.com/ps5-payload-dev/pacbrew-repo) ports infrastructure |
|
||||
| [John Törnblom / ps5-payload-dev — websrv](https://github.com/ps5-payload-dev/websrv) | Reference for per-core fetch/build/stage scripts; this port uses a separate native title pipeline |
|
||||
| [BlackBearReloaded — ps5-opengl](https://github.com/blackbearreloaded/ps5-opengl) | Shader-compiler and graphics foundations consumed by PS5_Vulkan; not the active RetroArch video backend |
|
||||
| [Mesa contributors](https://gitlab.freedesktop.org/mesa/mesa) | RADV, the Vulkan driver the title renders through, with its ACO compiler, NIR, the Vulkan runtime and utilities |
|
||||
| [Khronos Group](https://github.com/KhronosGroup/Vulkan-Headers) | Vulkan API headers and [specification](https://github.com/KhronosGroup/Vulkan-Docs) |
|
||||
| [RetroArch assets contributors](https://github.com/libretro/retroarch-assets) and the [M+ Fonts project](https://mplusfonts.github.io/) | Packaged XMB assets and font; original notices retained |
|
||||
| [LLVM / Clang contributors](https://github.com/llvm/llvm-project), [zlib authors Jean-loup Gailly and Mark Adler](https://github.com/madler/zlib) | Compilation and compression tooling |
|
||||
|
||||
### Emulator cores
|
||||
|
||||
| Core | Authors / maintainers credited by upstream |
|
||||
| --- | --- |
|
||||
| [FCEUmm](https://github.com/libretro/libretro-fceumm) | FCEU Team, CaH4e3 and contributors |
|
||||
| [mGBA](https://github.com/libretro/mgba) | endrift and contributors |
|
||||
| [Snes9x](https://github.com/libretro/snes9x) | Snes9x Team and contributors |
|
||||
| [FinalBurn Neo](https://github.com/libretro/FBNeo) | Team FBNeo and contributors |
|
||||
| [Genesis Plus GX](https://github.com/libretro/Genesis-Plus-GX) | Charles MacDonald, Eke-Eke and contributors |
|
||||
| [PPSSPP](https://github.com/hrydgard/ppsspp) | Henrik Rydgård and contributors |
|
||||
| [Dolphin](https://github.com/dolphin-emu/dolphin), [libretro/dolphin](https://github.com/libretro/dolphin) | Dolphin Emulator Project and contributors; libretro core maintainers |
|
||||
| [PCSX2](https://github.com/PCSX2/pcsx2), [LRPS2](https://github.com/libretro/LRPS2) | PCSX2 Dev Team and contributors; libretro LRPS2 maintainers |
|
||||
| [libretro core-info](https://github.com/libretro/libretro-core-info) | Metadata maintainers and contributors |
|
||||
|
||||
## License and third-party terms
|
||||
|
||||
This repository's own code is **GPL-3.0-or-later** ([LICENSE](LICENSE)). Most source
|
||||
files carry a copyright and SPDX notice; the ones that do not (for example
|
||||
`src/memory_ps5.cpp` and the build scripts in `tools/`) are under the same licence.
|
||||
Code inherited from BlackBearReloaded's ps5-native-app-boilerplate and ProsperoLight
|
||||
is Copyright (C) 2026 BlackBearReloaded, GPL-3.0-or-later. The generated
|
||||
`sce_module/libc.prx` is described in [runtime/README.md](runtime/README.md).
|
||||
|
||||
Every built title folder carries `licenses/`: the licence texts each part requires,
|
||||
and `components.json`, which ties every executable file to the source revision it
|
||||
was built from ([tooling/notices/components.json](tooling/notices/components.json),
|
||||
[docs/RELEASING.md](docs/RELEASING.md)). Releases up to v0.5.0-alpha.5 were
|
||||
published without it; [docs/releases/SOURCE_CORRESPONDENCE.md](docs/releases/SOURCE_CORRESPONDENCE.md)
|
||||
records what each was built from.
|
||||
|
||||
The cores keep their own licences, and they differ:
|
||||
|
||||
| Licence | Cores |
|
||||
| --- | --- |
|
||||
| GPL-2.0-or-later | FCEUmm, PPSSPP, Dolphin, Beetle PSX HW, Beetle Saturn, Mupen64Plus-Next (with MIT and LGPL parts), VICE, DeSmuME, Azahar (Dynarmic is 0BSD), MAME (as a whole; many files BSD-3-Clause) |
|
||||
| GPL-3.0-or-later | LRPS2 (PCSX2) |
|
||||
| GPL-2.0-only | RPCS3, in development here and in no release |
|
||||
| MPL-2.0 | mGBA |
|
||||
| Non-commercial licences | Snes9x, FinalBurn Neo, Genesis Plus GX: they may not be sold or used commercially, and FBNeo's forbids asking for donations for a project that uses its code |
|
||||
|
||||
Assets and fonts keep their licences too: the XMB theme is CC-BY-4.0 with the M+
|
||||
font licence, PPSSPP's fonts are OFL-1.1, and Dolphin's `Sys` files carry theirs.
|
||||
|
||||
This is an independent homebrew project, not affiliated with or endorsed by Sony
|
||||
Interactive Entertainment, the Khronos Group or the libretro project. PlayStation
|
||||
and PS5 are Sony trademarks. Vulkan is a registered trademark of the Khronos Group
|
||||
Inc.; the RADV port this title uses is not a Khronos-conformant product (see
|
||||
[PS5_Vulkan](https://github.com/mihawk-99/PS5_Vulkan)). RetroArch is the libretro
|
||||
project's name and logo, used here to name the frontend this port is built from.
|
||||
@@ -1,30 +0,0 @@
|
||||
XMB Monochrome Theme
|
||||
====================
|
||||
|
||||
Guidelines
|
||||
----------
|
||||
|
||||
### Palette
|
||||
|
||||
* The icons have only one color, pure white (#ffffff).
|
||||
* No semi transparent parts except for anti-aliasing purpose.
|
||||
|
||||
### Layout
|
||||
|
||||
* The shape should be easily recognizable.
|
||||
* The shapes should be, when possible, aligned to a 64x64 grid, but exported as 256x256 PNGs, to downscale nicely.
|
||||
* The shapes should be included in a circle of radius 31px (except when the shape is itself a circle, then we make it smaller), to insure consistent sizes.
|
||||
* Some special icons have a superelliptic shape with a substracted symbol. They have an associated content icon figuring the same symbol. Symbols should match.
|
||||
|
||||
### Style
|
||||
|
||||
* The border radius are either 1px, or 2px.
|
||||
* Lines are 2px.
|
||||
* The level of detail is reduced, we try to represent every button, but we can void representing stickers or color changes.
|
||||
|
||||
### Export
|
||||
|
||||
The PNGs are better exported by the old Inkscape v0.48.5. Later versions causes a premultiplied alpha issue with our mipmaping and blending code.
|
||||
|
||||
### Theme Font
|
||||
* This theme uses the [M+ P Type-1 Regular](http://mplus-fonts.osdn.jp/design.html#mplus_p1) typeface by the [M+ Fonts Project](http://mplus-fonts.osdn.jp/) covered under the [Free License](http://mplus-fonts.osdn.jp/about-en.html#license).
|
||||
-131
@@ -1,131 +0,0 @@
|
||||
# Active work
|
||||
|
||||
_Updated: 2026-09-29_
|
||||
|
||||
## Now: v0.5.0-alpha.5 released, the first on RADV; Dolphin's first start next
|
||||
|
||||
**Released.** Build `f96bdb0e` (release notes `docs/releases/v0.5.0-alpha.5.md`):
|
||||
the title links ../PS5_Vulkan's RADV release archive by default (PS5_Mesa
|
||||
`cedb774`, built by ../PS5_Vulkan `5d8f37d`), SDK fork `95c08f2`, LRPS2 `6d14775` with
|
||||
its port patch, the same tree as the fork's `main` at `9c2eea4` (6x). `PS5_VULKAN_DRIVER=ps5vk` still builds ps5vk,
|
||||
which v0.4.0-alpha.4 (build `870bd1bb`) shipped. On RADV the release battery
|
||||
(every core with a game, the menu, Close Content and a reload, Threaded Video on
|
||||
half) passed three times as the driver changed, and PPSSPP's ten-minute soak once
|
||||
(docs/PHASE_LOG.md, 2026-09-28). PPSSPP's MSAA works, RADV's shader cache is in
|
||||
`radv-shader-cache/`, and the title heap has an arena for each thread. The
|
||||
console's installed title is still v0.4.0-alpha.4's build.
|
||||
|
||||
**Dolphin round (after v0.4.0-alpha.4, on ps5vk):** the PS5 defaults are 6x, Async
|
||||
(UberShaders), 16x AF and GPU texture decoding (patches/dolphin, e905c4e): RE4,
|
||||
Melee, Wind Waker and Mario Kart Wii at 100% after boot. ../PS5_Vulkan R94
|
||||
compiles pipelines on six threads at once; R95 writes tiled uploads a run at a
|
||||
time. Rogue Leader still dips (attract 71-87%, gameplay 93-96%): Dolphin's MMU
|
||||
emulation and its threads waiting on each other, not the driver.
|
||||
|
||||
**Seven more systems (2026-09-28, docs/PHASE_LOG.md):** Beetle PSX HW (16x),
|
||||
Mupen64Plus-Next (ParaLLEl-RDP 8x), Beetle Saturn (needs a BIOS), VICE x64sc,
|
||||
MAME 0.289 (vector screens at 4K, raster native), DeSmuME (5x) and Azahar (18x),
|
||||
each from my fork at a pinned revision. They all pass the battery, and every
|
||||
tested game runs at full speed. Load Content lists INTERNAL and EXTERNAL
|
||||
(patch 0097). The installed title is build `4be9e814`.
|
||||
|
||||
Still open for them: EXTERNAL (`/mnt`) is empty inside the title's sandbox
|
||||
(USB and extended storage need the title jailbroken; etaHEN's jailbreak call is
|
||||
one way), Saturn gameplay with a BIOS and a vector game at 4K in MAME. The
|
||||
forks are published on github.com/mihawk-99 at the pinned revisions, so a
|
||||
checkout without the sibling forks builds from there.
|
||||
|
||||
**Now: RPCS3 as a libretro core** (docs/RPCS3_PORT.md). The game under test
|
||||
is GTA IV (BLES00229 1.00, kept as `dev_hdd0/game/GTA4`) since 2026-09-29, and
|
||||
the goal is 4K at a stable, stutter-free 60 fps, measured from a save state in
|
||||
its first playable scene. It boots (a FIFO in local memory, the ZCULL late
|
||||
fault, its folder's name), installs 3.3 GB to `dev_hdd0` on its first start,
|
||||
saves and loads states, and with the fork's frame-rate patch for 1.00 runs at
|
||||
50-51 fps at 300% (54 at 100%) where it was held to 30; the guest RSX is 98%
|
||||
busy and the SPU threads meet on `vm::writer_lock`, which is next. Every game
|
||||
in RPCS3's patch database now starts with its frame-rate patch ("Frame-rate
|
||||
patches", on by default), and RPCS3's settings are core options by section. A
|
||||
title's writes are held to about 2 MiB/s once a 1.3 GiB burst is spent; RPCS3's
|
||||
files in `/app0` then go through the console's FTP server, another process
|
||||
(about 6 MiB/s for GTA IV's install, 8x faster, byte for byte the same). Earlier
|
||||
(2026-09-28): God of War HD (NPUA80490) reached gameplay at 100% and 300% and
|
||||
passed the ten-minute run and the Close Content + reload battery; my acceptance
|
||||
of its 300% screenshot is still open.
|
||||
|
||||
**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
|
||||
libkernel lock for about half its slow time, and runs the MMU slow path for
|
||||
the rest (docs/PHASE_LOG.md). Next: name that lock with the sampler's stack
|
||||
scan, then compile-thread priority, the boot INI save, the GPU-thread cost and
|
||||
the MMU fast path.
|
||||
|
||||
**Open:**
|
||||
|
||||
1. Dolphin's first start of a game on RADV, from an empty shader cache: Wind
|
||||
Waker's first two 10 s windows at 83% and 92%, where ps5vk had 86% and 99%.
|
||||
The time is Mesa's NIR optimisation of Dolphin's ubershaders
|
||||
(../PS5_Vulkan/docs/RADV_PHASE.md).
|
||||
2. Rogue Leader in Dolphin (measured on ps5vk; not yet on RADV): I chose to make Dolphin itself faster on the PS5 --
|
||||
its MMU slow path, where 16 KiB host pages limit page-table fastmem, and its
|
||||
GPU-thread cost -- rather than underclock the emulated CPU (75% reached
|
||||
83-100% but changes the game's timing). Queued while the driver's Vulkan 1.4
|
||||
plan is decided (../PS5_Vulkan/docs/VULKAN_1_4_PLAN.md).
|
||||
3. The CPU's transfers on the GPU (CP DMA for linear copies, vk_meta for tiled
|
||||
ones), with the golden comparisons updated for the new submission shapes.
|
||||
4. The 60 Hz fallback on a display that stays at 60 Hz (the tester's trace).
|
||||
5. LRPS2: the upstream PCSX2 port in ../PS5_LRPS2's working tree, 8x as the PS5
|
||||
default, the lighting compared with the native picture.
|
||||
|
||||
**Test runs and core options.** RetroArch here uses per-core options
|
||||
(`global_core_options = false` in my config), so a run's `core_options_path`
|
||||
is ignored and each core reads `config/<core>/<core>.opt`. A test that needs
|
||||
its own options sets `global_core_options = "true"` with its own path; my
|
||||
per-core files are never edited.
|
||||
|
||||
## Accepted baselines
|
||||
|
||||
- Native byte order on driver `8b311e3`: I accepted the colours with the red/blue
|
||||
compensation gone (`evidence/driver-1.0-native-byte-order/`).
|
||||
- XMB: large-list allocation failures fixed (`601e575`), crash-free navigation
|
||||
(`docs/XMB_LIST_SAFETY.md`, `evidence/xmb-safe-list-run/`).
|
||||
- Gameplay: Genesis Plus GX colours, sound, controls and transitions accepted;
|
||||
FCEUmm, mGBA, Snes9x and FBNeo evidence in `native-core-loading/`, `mgba-native/`,
|
||||
`snes9x-native/`, `fbneo-native/`.
|
||||
- Thumbnail and configuration-reset fixes (patches 0081/0082).
|
||||
- PPSSPP (God of War: Ghost of Sparta, Yu-Gi-Oh! GX Tag Force at 10x): picture,
|
||||
full speed at 120 Hz, save states, fast-forward, close and reopen.
|
||||
|
||||
## Named errors and remaining limits
|
||||
|
||||
- First frontend snapshot has five intentional missing-core recovery errors and two
|
||||
archive-extraction failures; I identified the content as Sega System 16 & 32,
|
||||
which belongs to FBNeo. No general archive fix is inferred.
|
||||
- Console verification does not cover every supported system, BIOS/disc/CHD, NTSC and
|
||||
interlace options, SRAM/state round trips, long-run A/V or performance. Zero backend
|
||||
errors is not proof of uninterrupted audio across all workloads.
|
||||
- Upstream compiler warnings (old zlib prototypes, Tremor's long-to-int abs conversion,
|
||||
an unused blip helper's missing return) are neither suppressed nor treated as
|
||||
acceptance.
|
||||
- The loader still rejects TLS, legacy init/fini, nonempty preinit and unsupported
|
||||
relocations/dependencies; cores share the frontend process, so a rejected load is safe
|
||||
but an arbitrary core fault is not isolated.
|
||||
- The withdrawn path has the console history; the native byte order does not yet. A
|
||||
colour regression would show as red and blue exchanged in the menu, in core frames or
|
||||
in thumbnails, and is the first thing to check on the next run.
|
||||
|
||||
## Preserved platform and file locations
|
||||
|
||||
XMB stays default and RGUI selectable. Vulkan remains the presentation route; video_ps5
|
||||
stays selectable as CPU fallback. Native audio/input/binding, config saving, FTP
|
||||
permissions and directory browsing remain. Deployments preserve live settings/games.
|
||||
|
||||
FTP base `/data/homebrew/PPSA99169/` corresponds to in-title `/app0`. Use content/,
|
||||
system/, cores/, config/retroarch.cfg, savefiles/, savestates/. Genesis Plus GX Sega CD
|
||||
BIOS files use system/ root; FBNeo uses system/fbneo/. See `docs/DEPLOYMENT.md`.
|
||||
|
||||
## Operating notes
|
||||
|
||||
Uploads are authorized; check console idle before deployment. A launch without args.txt needs my
|
||||
intervention. Start klog first, preserve old logs, and capture allocation, frontend and
|
||||
trace logs. Never interrupt another title. No new core or unrelated driver work is
|
||||
assigned by this file.
|
||||
@@ -1,240 +0,0 @@
|
||||
# Deployment
|
||||
|
||||
How code reaches the place it runs, and how the run is observed. Two rules shape
|
||||
everything here: **deployment is not verification** — shipping is safe because
|
||||
the same artifact was already verified, not because the console looks fine
|
||||
afterwards — and **this repository does not configure a console**. It builds a
|
||||
homebrew title folder and uploads it to a console that is already set up.
|
||||
|
||||
## Environments
|
||||
|
||||
| Environment | What it is | Who may change it |
|
||||
| --- | --- | --- |
|
||||
| Local | the development machine: host tests, cross-build, staging | anyone |
|
||||
| Console | a jailbroken PS5 on the local network, with its own loader | the agent, when the prompt names the console |
|
||||
| Release | the ZIP attached to a tag, and the console a stranger installs it on | humans, deliberately |
|
||||
|
||||
The agent never writes outside `/data/homebrew/` on a console it was pointed at,
|
||||
never installs or removes a title registration, and never touches a console whose
|
||||
address is not in the ignored `.env`. Firmware, exploit and loader configuration
|
||||
are the console owner's, not this project's.
|
||||
|
||||
## The artifact
|
||||
|
||||
Build once, stage once, promote the same tree. The artifact is
|
||||
`dist/<TITLE_ID>/`: the signed application image `eboot.bin`, the runtime the
|
||||
title carries in `sce_module/libc.prx`, and the metadata under `sce_sys/`
|
||||
(`param.json`, `icon0.png`, `pic0.dds`, `pic1.dds`, `snd0.at9`) — exactly what the
|
||||
console's loader reads, and nothing that only exists on this host. There is no
|
||||
separate payload file: this is a title, and the console runs its `eboot.bin`.
|
||||
|
||||
The build is reproducible from a clean checkout at the committed revision:
|
||||
`tools/fetch-retroarch.sh` names the upstream revision and checks the fetched tree
|
||||
against that commit, `patches/series` is committed, and the artifact's file list,
|
||||
sizes and sha256 digests are recorded in `dist/<TITLE_ID>/manifest.sha256`, which
|
||||
`tools/check-manifest.sh` verifies. Anything that cannot be reproduced is not part
|
||||
of the artifact (`vendor/`, `build/`, `.deps/`, `klog/`).
|
||||
|
||||
## Shipping a change
|
||||
|
||||
1. All gates green at the revision being shipped (`tools/verify.sh`).
|
||||
2. `bash tools/build-title.sh` produces `dist/<TITLE_ID>/` and records its
|
||||
manifest; `bash tools/check-manifest.sh` proves the folder is that manifest —
|
||||
every file present and unchanged, nothing extra, `eboot.bin` a fake self, and
|
||||
`param.json` naming the folder it sits in.
|
||||
3. `python3 tools/deploy-title.py` uploads the tree over the console's FTP
|
||||
service, each file under a temporary name and promoted only after its transfer
|
||||
completes, the image and the metadata last; it then reads every file back and
|
||||
compares digests.
|
||||
4. `tools/console-run.sh <TITLE_ID>` starts the title through the resident control
|
||||
payload and captures the console's log from a marked position.
|
||||
5. The run's distilled capture becomes the step's evidence under
|
||||
`evidence/<step>/`.
|
||||
|
||||
Selecting a console is configuration, never a committed value:
|
||||
|
||||
| Variable | Default | Purpose |
|
||||
| --- | --- | --- |
|
||||
| `PS5_HOST` | required | console address, from the environment or `.env` |
|
||||
| `FTP_PORT` | `2121` | the console's FTP service |
|
||||
| `KLOG_PORT` | `3232` | the console's kernel-log service |
|
||||
| `PS5_CTL_PORT` | `9111` | the resident control payload's port |
|
||||
| `PS5_FTP_USER`, `PS5_FTP_PASSWORD` | unset | credentials, if the console's FTP service wants them |
|
||||
| `DEPLOY_DRY_RUN` | `0` | `1` builds and prints the target without networking |
|
||||
|
||||
`tools/deploy-title.py` refuses to run without a host and refuses a host that is
|
||||
not an address or hostname. It publishes `dist/<TITLE_ID>/` and then reads every
|
||||
file back and compares its sha256 with the file here, because this console's FTP
|
||||
service has served a file's old bytes under its new name and listed a stale size
|
||||
for a file it had already replaced: a size check passed on content that was not
|
||||
there. Rollback and removal are the same command family: `python3
|
||||
tools/deploy-title.py --clean` removes only this title's directory.
|
||||
|
||||
## Rollback
|
||||
|
||||
`tools/deploy-title.py --clean` removes `/data/homebrew/<TITLE_ID>/` and leaves
|
||||
the previous release ZIP as the fallback: extracting the previous release over the
|
||||
same path restores it in one step. A change that cannot be rolled back this way —
|
||||
anything that writes outside the title folder — is not shipped without a written
|
||||
reason in `docs/PHASE_LOG.md`.
|
||||
|
||||
Removal is deliberately named **undeploy**, not uninstall: FTP removal does not
|
||||
unregister a title from the console's shell database, and this project does not
|
||||
do that either.
|
||||
|
||||
## Configuration and secrets
|
||||
|
||||
- Configuration lives in `retroarch.cfg` inside the title folder, seeded from
|
||||
`config/retroarch.cfg` in this repository. Keys that a step adds are recorded
|
||||
in `docs/REFERENCE.md` with their default.
|
||||
- The console's address and credentials live in the ignored `.env`, never in a
|
||||
committed file, never echoed into a log, and never pasted into `docs/`. The
|
||||
committed `.env.example` holds placeholders and the dry-run defaults.
|
||||
- Nothing in this repository is signed, and no key, ticket or account credential
|
||||
is ever created here.
|
||||
|
||||
## Observing a target run
|
||||
|
||||
- `tools/console-run.sh <TITLE_ID>` captures the console's kernel log for the run:
|
||||
it checks what the console is already running, starts the listener, records its
|
||||
position in the stream, launches, and judges only the lines after that mark. The
|
||||
raw capture stays in the ignored `klog/`; the distilled record is what gets
|
||||
committed.
|
||||
- The resident control payload comes from the console tooling already in use in
|
||||
`../PS5_Vulkan`: it answers one command per connection (`ping`, `procs`,
|
||||
`launch <TITLE_ID>`, `kill <TITLE_ID>`, `restart <TITLE_ID>`) so a run is
|
||||
scripted rather than clicked. Keeping it out of this repository is deliberate:
|
||||
one console agent, one owner.
|
||||
- A run is identified by the console's own boot session plus the git revision
|
||||
being deployed. Record both next to the result: the same run id recurs across
|
||||
restarts and means nothing on its own.
|
||||
- A capture is only kept when the run it describes is named by the step's
|
||||
acceptance line. Anything else is a scratch run and belongs in `klog/`.
|
||||
|
||||
## Sharing the console
|
||||
|
||||
The console runs one title at a time, and two sessions cannot both use it: a
|
||||
launch from a second session replaces whatever the first was running. This
|
||||
machine's console is shared with the PS5_Vulkan work, so **ask before every
|
||||
launch and every upload** — the rule the console's owner set, and the reason
|
||||
`tools/console-run.sh` prints what it is about to do before it does it, and
|
||||
refuses to launch into a busy console with `0x80940010`.
|
||||
|
||||
Two habits follow from that:
|
||||
|
||||
- Prepare everything that can be prepared offline — the build, the staged tree,
|
||||
the checks — and touch the console only for the part that genuinely needs it.
|
||||
- Treat a run as a claim about the revision it was launched from. The launch
|
||||
records the revision in `evidence/<step>/capture.json`, so a run that belongs
|
||||
to a different revision is visible rather than implied.
|
||||
|
||||
## Buffered GPU timing with development logs
|
||||
|
||||
`tools/run-title.sh --gpu-profile 60 --watch 80` opts into a bounded in-memory
|
||||
capture, then retrieves the profile and the current `retroarch.log` alongside
|
||||
its kernel/trace capture. A normal run omits `--gpu-profile`. The runner checks
|
||||
console availability before upload and again before launch, and validates the
|
||||
frontend log's build identity to reject stale logs. See `docs/GPU_TIMING.md` for
|
||||
measurement definitions and `tools/analyze-gpu-profile.py` for replay.
|
||||
|
||||
## Native audio test
|
||||
|
||||
`tools/run-title.sh --no-build --audio-test --watch 45` uses the ordinary build
|
||||
with a one-shot console control file; no diagnostic binary is left installed.
|
||||
It requires at least a 20-second watch window, announces alternating low-level
|
||||
left/right tones, captures and validates `audio-test.json`, then captures the
|
||||
normal frontend log after the test's port is closed and reopened by RetroArch.
|
||||
The owner must separately confirm audible playback. Omit `--audio-test` for
|
||||
ordinary launches; the runner clears any stale audio control file in either case.
|
||||
|
||||
## Where to put configurations, cores and content
|
||||
|
||||
`/app0` is the running title's mount path, not an FTP directory. For this title,
|
||||
FTP uses `/data/homebrew/PPSA99169/` as the corresponding base directory:
|
||||
|
||||
| Use | FTP path | In RetroArch |
|
||||
| --- | --- | --- |
|
||||
| Cores | `/data/homebrew/PPSA99169/cores/` | `/app0/cores/` |
|
||||
| ROMs/content | `/data/homebrew/PPSA99169/content/` | `/app0/content/` |
|
||||
| BIOS/system files | `/data/homebrew/PPSA99169/system/` | `/app0/system/` |
|
||||
| Live settings | `/data/homebrew/PPSA99169/config/retroarch.cfg` | `/app0/config/retroarch.cfg` |
|
||||
| Save files/states | `savefiles/`, `savestates/` under the FTP base | `/app0/savefiles/`, `/app0/savestates/` |
|
||||
|
||||
The native frontend creates these directories. It installs the packaged
|
||||
`retroarch.cfg` seed only when the live config does not exist. Ordinary updates
|
||||
replace packaged files but do not delete the user's live config, cores, content
|
||||
or saves. Full `--clean` deployment/removal is destructive to the entire title
|
||||
folder, including these user files: back them up before explicitly requesting it.
|
||||
|
||||
“Load Content” starts at `/app0`, while “Load Core” starts at `/app0/cores`.
|
||||
A directory can correctly be empty: this filesystem step installs no emulator
|
||||
cores or ROMs. A core must be built for this PS5 port; copying a desktop `.so`
|
||||
does not make it loadable. USB/data roots appear only when the title can open
|
||||
them; FTP visibility alone does not prove title access to an external mount.
|
||||
|
||||
The seven app-managed directories use owner-authorized `0777`; `0775` still
|
||||
denied actual FTP uploads on this console. Starting the current build repairs
|
||||
older `0755`/`0775` directories.
|
||||
`python3 tools/check-ftp-write.py` verifies upload/readback/cleanup in every managed
|
||||
folder with uniquely named disposable files and records the result under `klog/`.
|
||||
It does not overwrite cores, ROMs, saved settings or other existing files.
|
||||
|
||||
|
||||
## Built-in core artifacts
|
||||
|
||||
The title build stages FCEUmm under `cores/fceumm_libretro.so` and its metadata
|
||||
under `info/fceumm_libretro.info`, with an identical copy beside the core for
|
||||
saved configurations whose core-info path is empty. The manifest and FTP
|
||||
readback cover all three files.
|
||||
`make fceumm` builds the core alone without uploading or running anything.
|
||||
Runtime acceptance remains separate from the artifact checks; see
|
||||
`docs/ACTIVE.md` for the current console result. No game or BIOS is bundled.
|
||||
|
||||
After uploading each `.info`, deployment writes and reads back RetroArch's
|
||||
`core_info.refresh` marker in the same directory. RetroArch consumes it when
|
||||
rebuilding its metadata cache; it is not a shipped manifest file. This also
|
||||
refreshes entries previously cached as having no metadata.
|
||||
|
||||
|
||||
For a no-game native loader/recovery diagnostic, use
|
||||
`tools/run-title.sh --no-build --core-test --watch 30` after the host gates pass.
|
||||
For manual core/game testing, launch with `--no-build --watch 180` (also
|
||||
`--no-deploy` if the same build is already verified on the console). The owner
|
||||
selects the game; collect `retroarch.log`, `trace.txt` and the kernel capture.
|
||||
The loader's supported ELF/runtime contract is in `docs/REFERENCE.md`.
|
||||
|
||||
|
||||
`make mgba` builds and checks the mGBA shared core without uploading it. The title
|
||||
build includes `mgba_libretro.so` alongside FCEUmm and installs its `.info` in both
|
||||
metadata locations. Deploy the rebuilt title with the new core: its import table
|
||||
covers all shipped cores. Use `--core-test=mgba` for the optional loader diagnostic.
|
||||
Place user GB/GBC/GBA games in `content/` (or another browsable user folder), select
|
||||
mGBA manually, and capture the gameplay log before closing/reopening the title.
|
||||
No ROM or BIOS is included in the build.
|
||||
|
||||
|
||||
`make snes9x` builds and ABI-checks Snes9x without uploading. The title build
|
||||
stages snes9x_libretro.so and official metadata alongside the existing cores;
|
||||
rebuild the frontend too, since its import table includes C++ runtime bindings.
|
||||
Use `tools/run-title.sh --no-build --core-test=snes9x --watch 180` for loader and
|
||||
recovery diagnostics followed by manual SNES archive loading. Preserve existing
|
||||
config/content; verify gameplay colours, audio/input, Quick Menu, Close Content
|
||||
and a subsequent game before accepting the run.
|
||||
|
||||
`make fbneo` builds and ABI-checks the full native FBNeo core. Rebuild/deploy the
|
||||
frontend alongside it so the new native imports are available. Official metadata
|
||||
is staged in info/ and cores/; user-supplied `.info` files are references only.
|
||||
Use `tools/run-title.sh --no-build --core-test=fbneo --watch 240` for diagnostics
|
||||
and a manual gameplay/colour-transition run. Place matching user arcade archives
|
||||
in content/ and BIOS files in system/fbneo/ under the FTP base above. Keep each
|
||||
ZIP/7z archive intact: FBNeo loads it directly. Deployment preserves those user
|
||||
folders and the live configuration. No BIOS, ROM or sample data is bundled.
|
||||
|
||||
`make genesis-plus-gx` builds and ABI-checks Genesis Plus GX. Rebuild/deploy the
|
||||
frontend too: it binds the core's imports and includes the core in build identity.
|
||||
Use `tools/run-title.sh --no-build --core-test=genesis_plus_gx --watch 240` after
|
||||
host gates pass. Select Genesis Plus GX and a user game/archive manually; verify
|
||||
colours, sound, controls, Quick Menu, Close Content and the next game/core.
|
||||
Use content/ for games and the configured system/ root for Sega CD BIOS filenames
|
||||
listed by the official metadata (not system/fbneo/). Existing games, BIOS and live
|
||||
configuration are preserved. Other supported Sega systems need separate tests.
|
||||
-2933
File diff suppressed because it is too large.
Load diff
@@ -1,98 +0,0 @@
|
||||
# The GPU path: acceptance criteria
|
||||
|
||||
Scoped before any code is written, because the previous path taught this project
|
||||
what "done" has to mean before the work starts rather than after. Every criterion
|
||||
names how it is verified, and the verification is a command or an artifact - not an
|
||||
opinion.
|
||||
|
||||
## What this replaces, and what it must not break
|
||||
|
||||
The title renders today through `video_ps5` (`src/video_ps5.cpp` +
|
||||
`src/display.cpp`): RGUI's 320x240 RGB565 framebuffer is converted and scaled on the
|
||||
CPU, one pixel at a time, into a tiled 1920x1080 framebuffer in direct memory, and
|
||||
handed to the display with `sceVideoOutSubmitFlip`. The menu is visible on the
|
||||
console. **That path stays working throughout.** Every criterion below is additive:
|
||||
if the GPU path is not yet better, `video_ps5` is still the driver that runs, and
|
||||
the menu is still on screen.
|
||||
|
||||
## A - the 40 refusals, cleared
|
||||
|
||||
The linked libps5vk refuses this frontend's commands, and a refusal ends recording
|
||||
with an error at `vkEndCommandBuffer`, so the command buffers never submit and the
|
||||
screen stays black while every call returns success. The maintainer identified all
|
||||
three sources and the frontend-side fix for each.
|
||||
|
||||
| # | Criterion | Where | How it is verified |
|
||||
| --- | --- | --- | --- |
|
||||
| A1 | No pipeline is created with `VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP` | `vendor/retroarch/gfx/drivers/vulkan.c:2620`, `:2649`, `:2765` - the three sites that select a strip topology | `grep -c TRIANGLE_STRIP` over the configured `gfx/drivers/vulkan.c` is 0 in any `input_assembly.topology` assignment, and the trace has **zero** `only triangle lists without primitive restart` lines |
|
||||
| A2 | The RGUI upload does not use a storage image | `vulkan_copy_staging_to_dynamic` chooses compute when `dynamic->format != staging->format` (`:1099-1104`); the compute path writes a `VK_DESCRIPTOR_TYPE_STORAGE_IMAGE` at binding 3 (`:1150-1156`) | the trace has **zero** `descriptor type 3 has no proven table entry` lines |
|
||||
| A3 | Every sampler the frontend creates uses clamp-to-edge on all three axes | `vendor/retroarch/gfx/drivers_shader/shader_vulkan.cpp:2034-2036` (hardcoded REPEAT) and `:2087-2108` (preset-driven, mapping to repeat / mirrored-repeat / clamp-to-border / mirror-clamp) | the trace has **zero** `sampler address modes` lines |
|
||||
| A4 | The refusal count is zero, not merely lower | all of the above | `grep -c '^vulkan: ' /app0/trace.txt` is **0** over a full run |
|
||||
|
||||
A2 is settled as the RGB8888 route. RGUI itself still produces packed RGBA4444
|
||||
for the Vulkan driver; the port expands that data to full-range R,G,B,A bytes and
|
||||
allocates both staging and dynamic textures as `VK_FORMAT_R8G8B8A8_UNORM`.
|
||||
Matching formats select `vkCmdCopyBufferToImage`. The unused storage-image compute
|
||||
upload shader is not compiled, since its descriptor is unsupported. This preserves
|
||||
the agreed 32-bit texture route without claiming RGUI produces native 8-bit colour.
|
||||
|
||||
## B - the menu reaches the screen through the GPU
|
||||
|
||||
| # | Criterion | How it is verified |
|
||||
| --- | --- | --- |
|
||||
| B1 | The title runs on `video_vulkan`, not `video_ps5` | `/app0/trace.txt` shows RetroArch's Vulkan driver initialising, and `ps5_init entered` (this project's own driver) is **absent**, because only one video driver runs |
|
||||
| B2 | The frame is presented through libps5vk's swapchain | `vkQueuePresentKHR` returns `VK_SUCCESS` and the driver's flip marker is reached; a presentation count appears in the trace |
|
||||
| B3 | No command buffer ends in error | the trace carries no refusal line (A4) **and** no `vkEndCommandBuffer` failure is reported; this is the specific fault that produced a black screen with successful presents before |
|
||||
| B4 | The console owner confirms RGUI is visible, rendered through the GPU | the owner's confirmation, which is the only witness for "on the screen" this project accepts |
|
||||
|
||||
## C - it is actually better, measured
|
||||
|
||||
The reason for doing this at all. A GPU path that renders the menu no faster than
|
||||
the CPU path is not worth the swap, so it has to be shown to be faster.
|
||||
|
||||
| # | Criterion | How it is verified |
|
||||
| --- | --- | --- |
|
||||
| C1 | The per-frame CPU cost of presentation is gone | the per-pixel loop in `src/video_ps5.cpp`'s `ps5_frame` does not run under Vulkan; evidenced by the absence of the driver's frame marks and by the frontend's own frame timing |
|
||||
| C2 | Frame rate at the menu is at least as good as the CPU path | measured frame count over a fixed `--watch` window, compared against the CPU path's number for the same window |
|
||||
| C3 | A real core runs at its target rate | load a libretro core, run it for a fixed window, and record frames presented versus frames expected. This is the number the whole exercise is for |
|
||||
| C4 | The result is reversible | `video_ps5` remains registered and selectable, so a regression in the GPU path can be undone by one line in `tools/apply-port-patches.py` |
|
||||
|
||||
C3 needs a core in the title folder. If no core is available when the rest is done,
|
||||
C3 is recorded as **not measured** rather than inferred - the previous rounds
|
||||
established what happens when an unverified assumption is treated as a result.
|
||||
|
||||
## D - the invariants hold
|
||||
|
||||
| # | Criterion |
|
||||
| --- | --- |
|
||||
| D1 | Driver changes require explicit user authorization and are documented and committed in `../PS5_Vulkan`. The user granted that authorization on 2026-09-19 for this rendering defect. |
|
||||
| D2 | `tools/verify.sh` passes: format, unit, build, integration, evidence. |
|
||||
| D3 | The port's changes to upstream remain named edits in `patches/series`, applied by `tools/apply-port-patches.py` to `build/ra-conf` only; `vendor/retroarch` is never edited. |
|
||||
| D4 | Every step is verified by a command or an artifact, committed with its evidence, and written up in `docs/ACTIVE.md` and `docs/FINDINGS.md`. |
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Audio. The frontend already reports `Failed to initialize audio driver. Will
|
||||
continue without audio.` and that is unchanged by this work.
|
||||
- Shaders, filters, overlays and the XMB menu, beyond what comes free with the
|
||||
Vulkan driver. A1-A4 gate on the menus this project actually renders.
|
||||
- Input. The pad driver already opens and delivers presses; nothing here changes it.
|
||||
- Asking the maintainer for the `V0-sampler` or `V0-topology` probes. Every
|
||||
criterion above is reachable from this side. If A3 turns out to need repeat or
|
||||
mirrored-repeat sampling rather than clamp-to-edge, that is a scope change and
|
||||
comes back to the `docs/ACTIVE.md` open list rather than being assumed.
|
||||
|
||||
## Order of work
|
||||
|
||||
1. A1, A2, A3 as named patches, then one run. A4 is the gate: zero refusals.
|
||||
2. Only then B - switch the compiled default to `vulkan`, one run, owner confirms.
|
||||
3. Only then C - measure, because measuring before the path works measures nothing.
|
||||
|
||||
If A4 cannot be reached, the work stops there, the CPU path stays, and what was
|
||||
learned is recorded. The menu on the screen is not put at risk for an optimisation.
|
||||
|
||||
## Current evidence
|
||||
|
||||
Current acceptance and artifacts live in `docs/ACTIVE.md`; dated runs are in
|
||||
`docs/PHASE_LOG.md`. Successful Vulkan calls establish completion, while captured
|
||||
pixels and the console owner's confirmation establish visible rendering.
|
||||
@@ -1,93 +0,0 @@
|
||||
# Buffered Vulkan frame timing and development logs
|
||||
|
||||
Normal launches keep logging enabled but do not run the profiler. Keep
|
||||
`/app0/retroarch.log` for frontend/core/menu lifecycle, INFO/WARN/ERROR messages;
|
||||
`/app0/trace.txt` for build identity, startup milestones, assertions and driver
|
||||
refusals/API errors; and host `klog/` captures for kernel failures. Errors are
|
||||
not buffered away. Successful button transitions and periodic frame/render
|
||||
markers are routine chatter and no longer write files. Startup probes remain
|
||||
bounded to their initial calls.
|
||||
|
||||
Patch 0058 reasserts the frontend file logger after config/argument processing:
|
||||
this port's argument rebuilding previously lost the launcher's logging request.
|
||||
Each frontend startup/core reload records the title's input-derived build identity
|
||||
in `retroarch.log`. The run tool refuses to accept a log without the expected
|
||||
identity. This is distinct from the identity check on the uploaded executable.
|
||||
|
||||
## Capture and reproduce
|
||||
|
||||
Build with `bash tools/verify.sh`, then:
|
||||
|
||||
```sh
|
||||
bash tools/run-title.sh --no-build --gpu-profile 60 --watch 80
|
||||
python3 tools/analyze-gpu-profile.py klog/gpu-profile-<run-stamp>.tsv
|
||||
```
|
||||
|
||||
The runner requires the shared console to be idle before uploading, checks again
|
||||
before launching, clears stale capture controls, and closes its title afterward.
|
||||
It saves `retroarch-<stamp>.log`, `gpu-profile-<stamp>.tsv` and the kernel capture
|
||||
in ignored `klog/`. Trace output stays in the run transcript. Extract only the
|
||||
last `bss check=` substring from appended traces and verify its build identity.
|
||||
|
||||
`--gpu-profile` accepts 1..60 seconds. Allow at least 15 extra watch seconds for
|
||||
startup and reporting. The runner writes `/app0/gpu-profile.txt`; title startup
|
||||
consumes this one-shot opt-in. The runner also removes stale controls on later
|
||||
runs, including runs without profiling. Neither control nor results are deployed
|
||||
as part of the normal title. There is no image readback hook.
|
||||
|
||||
The profiler skips 120 completed callbacks, then collects nanosecond records in
|
||||
a bounded 8,192-frame array and aggregates five-second windows **without file
|
||||
I/O**. When the requested interval finishes, it stops sampling and writes one
|
||||
buffered `gpu-profile.tsv` report. That reporting pause is outside the sample
|
||||
and occurs only in an opted-in diagnostic run. A killed/failed run may have no
|
||||
report; a full buffer is explicitly flagged and rejected by the analyzer rather
|
||||
than being presented as a complete measurement.
|
||||
|
||||
## Meaning of the measurements
|
||||
|
||||
`src/vulkan_trace.cpp` uses `CLOCK_MONOTONIC` around real Vulkan function pointers
|
||||
and around `vulkan_frame`, from its initial diagnostic marker through the return
|
||||
from the context's swap callback. Arguments and results remain unchanged.
|
||||
Measurements assume the title's single-threaded video loop and ordinary menu
|
||||
path, not threaded video or BFI/multiple presentations per callback.
|
||||
|
||||
Each `gpu timing:` window in the result gives completed frames, measured seconds,
|
||||
FPS, and average/maximum milliseconds per frame. TSV records preserve every
|
||||
frame's phase times and presentation-completion interval in nanoseconds.
|
||||
|
||||
| Field | Measurement |
|
||||
| --- | --- |
|
||||
| `interval` | Previous completed callback through this completed callback. |
|
||||
| `outside` | Time between callbacks excluding the menu texture-update callback: menu/input/pacing work and the previous callback's tail. |
|
||||
| `texture` | Menu `vulkan_set_texture_frame` between video callbacks, including resource creation/reuse, conversion and memory operations. |
|
||||
| `prepare` | Video callback wall time minus the four explicitly timed API categories below; includes command recording, uploads and other calls. |
|
||||
| `end` | Real `vkEndCommandBuffer` calls inside the callback. |
|
||||
| `submit` | Real `vkQueueSubmit` calls, including synchronous driver work/waits. |
|
||||
| `present` | Real `vkQueuePresentKHR` calls, including display waits. |
|
||||
| `wait` | Real `vkWaitForFences` and `vkAcquireNextImageKHR` calls. |
|
||||
| `present_interval` | Time between successful synchronous `vkQueuePresentKHR` returns. |
|
||||
|
||||
`outside + texture + prepare + end + submit + present + wait` equals `interval`
|
||||
for each recorded frame. Maxima can belong to different frames and cannot be
|
||||
added. An incomplete callback invalidates the next interval instead of charging
|
||||
an unmeasured gap to it.
|
||||
|
||||
These are CPU-observed elapsed times, not CPU utilization or GPU timestamps.
|
||||
This driver's present call returns after its flip marker reaches VideoOut, so
|
||||
return intervals provide useful presentation-completion cadence. They are not
|
||||
hardware scanout timestamps or exact missed-vblank counts. The analyzer reports
|
||||
intervals above 20/25 ms and rounded estimates of extra 60 Hz intervals, explicitly
|
||||
labelled as estimates. Small scheduling jitter around 16.67 ms is not proof of a
|
||||
missed refresh. A cadence near 16.683 ms corresponds to about 59.94 Hz.
|
||||
|
||||
The menu's "estimated screen refresh rate" averages observed frame intervals
|
||||
(`video_monitor_fps_statistics`); startup delays can initially depress it. It
|
||||
is separate from the driver's declared 60 Hz display mode. This measurement
|
||||
cannot guarantee other cores/workloads, nor compare 4K Vulkan with the 1080p CPU
|
||||
fallback as if they rendered the same number of pixels.
|
||||
|
||||
Fake-clock tests cover phase partitions, presentation spacing, warmup, delayed
|
||||
output, capacity limits, stop behavior and dispatch/result preservation. Analyzer
|
||||
tests reject truncated/error captures and inconsistent timing partitions. Run
|
||||
`bash tools/verify.sh` for the complete gates. Current results and remaining
|
||||
limitations belong in `docs/ACTIVE.md` and the committed evidence.
|
||||
@@ -1,126 +0,0 @@
|
||||
# LRPS2 in the PS5 title: requirements, gaps and plan
|
||||
|
||||
LRPS2 is libretro's PCSX2 core. The goal is a production-quality PS2 core in
|
||||
this title (PPSA99169), rendering through ../PS5_Vulkan. It must run God of War
|
||||
II (demo), Ratchet & Clank (demo) and Final Fantasy X (demo, Besaid) correctly
|
||||
at 4K internal resolution (6x) with maximum settings, at full speed, and stay
|
||||
stable over long sessions. This page is Phase 0: what the pinned core needs,
|
||||
what the console and driver give it today, and the order of the work. Results
|
||||
go to docs/PHASE_LOG.md as they come. The Vulkan half of the gap list and the
|
||||
driver rounds are in ../PS5_Vulkan/docs/LRPS2_GAPS.md.
|
||||
|
||||
## Source and workflow
|
||||
|
||||
- **Pinned revision:** my fork ../PS5_LRPS2 (github.com/mihawk-99/PS5_LRPS2),
|
||||
main at `9c2eea4cbd76ad6a4fb2340c95a354eb9a63500f` (2026-09-26). It is
|
||||
libretro's LRPS2 with an AArch64 port and a C89 x86 emitter, among other
|
||||
work; the x86-64 recompilers are upstream PCSX2's.
|
||||
- **Every change to the core is made in the fork**, committed on its `main`
|
||||
branch and pushed. `tools/build-lrps2.sh` clones the pinned revision (from
|
||||
../PS5_LRPS2 when it is beside this repository, from GitHub otherwise) and
|
||||
builds it as it is: there is no patch on top. After a change in the fork,
|
||||
the pin moves to the new revision. Until 2026-09-28 the port was a branch of
|
||||
the fork (`ps5-port`) carried here as `patches/lrps2/ps5-port.patch` against
|
||||
`6d14775`; that tree is the same as `9c2eea4`'s.
|
||||
`LRPS2_DEV=1` builds the fork's working tree directly while I edit it.
|
||||
- The build follows the other native cores: `tooling/lrps2/ps5-toolchain.cmake`
|
||||
(FreeBSD x86-64 through the SDK's prospero-clang), libc shims where the core
|
||||
calls what the console lacks, `tools/check-core.py`'s ABI check,
|
||||
`tools/core-imports.py`'s import table, and the core stamp.
|
||||
|
||||
## What the core needs
|
||||
|
||||
### Rendering
|
||||
|
||||
- **Hardware renderer (Vulkan)**, the shipping path. It needs a Vulkan 1.1
|
||||
instance and device, `VK_KHR_sampler_mirror_clamp_to_edge` (enabled
|
||||
unconditionally), R16G16B16A16_UNORM as a colour target, and a sampled
|
||||
D32_SFLOAT_S8_UINT. It relies on barriers inside render passes for its
|
||||
feedback loops and uses a dozen optional extensions. The details and the
|
||||
driver rounds are in ../PS5_Vulkan/docs/LRPS2_GAPS.md.
|
||||
- **paraLLEl-GS**: a Vulkan compute renderer needing most of the 1.2 feature
|
||||
set. It is used for Profile 5's compute stress only, after the rest.
|
||||
- **Software renderer**: "Software (SW)" rasterises on the CPU and asks the
|
||||
frontend for no hardware context (the core's `libretro_select_hw_render`).
|
||||
Phase 2 boots with it, so the CPU side is proven apart from the driver. Its
|
||||
rasteriser is JIT-compiled with xbyak into the code area below.
|
||||
- The GS thread is the frontend's own: `retro_run` drains the GS ring
|
||||
(`MTGS::MainLoop`) and every Vulkan call happens there.
|
||||
- Shaders are GLSL, compiled at run time by the bundled glslang, with a
|
||||
pipeline and SPIR-V cache in `system/pcsx2/cache`.
|
||||
|
||||
### CPU emulation and the platform
|
||||
|
||||
| Area | What the pinned core does |
|
||||
|---|---|
|
||||
| Recompilers | x86-64: EE (R5900), IOP (R3000A), microVU0/1, the VIF unpack dynarec, and the software GS's xbyak JIT. The multi-ISA sources are built for SSE4.1, AVX and AVX2 and chosen at run time by CPUID; the console's Zen 2 has all three. |
|
||||
| Addressing from JIT code | The C89 emitter encodes a global either RIP-relative or as a sign-extended 32-bit absolute, and aborts on anything else (`c89emit.h`, `E_MODRM_ABS`). The code area, the core's own data and guest main memory must therefore be within 2 GiB of each other. Upstream finds such a spot near the executable on Windows only; elsewhere it takes whatever `mmap` returns. |
|
||||
| Main memory | 320 MiB (`HostMemoryMap::MainSize`): EE RAM, scratchpad and ROMs; IOP memory; VU memory; a bump allocator. It is a shared-memory object (`memshm_create`, i.e. `shm_open`) so the fastmem window can map its pages. |
|
||||
| Code area | 305 MiB, read-write-execute (`CodeSize`): EE 64, IOP 32, VIF0/1 8 each, mVU0 64, mVU1 64, VIF unpack 1, software GS 64. |
|
||||
| Fastmem | A 4 GiB window (`memshm_area_create`) into which guest pages of main memory are mapped at their guest virtual addresses, with `mprotect` on single pages. Faults are backpatched to slow paths. |
|
||||
| Code protection | EE RAM pages holding recompiled code are made read-only (`mmap_MarkCountedRamPage`), and a write fault invalidates their blocks. |
|
||||
| Page size | `__pagesize` is 4 KiB on x86 and 16 KiB on Apple AArch64. The vtlb coalesces four 4 KiB guest pages into one host page when the host page is 16 KiB (`vtlb.cpp`, the `__pagesize != VTLB_PAGE_SIZE` paths). |
|
||||
| GS memory | 4 MiB of GS local memory through `memfd_create`/`shm_open`, mapped several times back to back so reads wrap (`GS.cpp`). |
|
||||
| Faults | libretro-common's faulthandler on SIGSEGV (and SIGBUS on Apple and AArch64); FreeBSD x86-64 reads `mc_rip`. |
|
||||
| Threads | The EE and IOP on the core's CPU thread (created at load, paused and resumed around `retro_run`, save states and option changes); MTVU (VU1) on its own thread by default; the GS on the frontend thread; the CDVD reader thread; software GS worker threads. |
|
||||
| Audio | SPU2 produced on the CPU thread; batched to the frontend at the end of `retro_run`. |
|
||||
| Files | libretro VFS. BIOS in `system/pcsx2/bios`. Memory cards in `system/pcsx2/memcards` (shared) or per content. Cache in `system/pcsx2/cache`. Discs as ISO, CHD, CSO, ZSO, GZ, CUE and ELF, with M3U lists. `block_extract` is set, so the frontend never unpacks an archive for it. |
|
||||
| Bundled dependencies | Everything is in the tree: libretro-common (CHD, zstd, LZMA, deflate, LZ4, FLAC, PNG, YAML, rthreads, fastjmp, faulthandler, memmap), glslang, the Vulkan headers and xbyak. The built-in GameDB is generated at build time. OpenGL (glad), DEV9 networking and USB are not needed. |
|
||||
|
||||
## The console against those needs
|
||||
|
||||
| Need | The console, from the Dolphin and PPSSPP ports | Plan |
|
||||
|---|---|---|
|
||||
| Shared memory for main memory, fastmem and GS memory | Every view of a shared-memory object is charged to the title's flexible memory (about 450 MB for the whole title), which Wind Waker's mirrors exhausted. Direct memory is a separate pool of several GiB, and one allocation maps at any number of addresses (Dolphin's MemArena). | A PS5 backend for libretro-common's `memshm_*`, `memshm_area_*` and `memreserve*` in the fork: direct memory for the objects, and `sceKernelReserveVirtualRange` for the reservations. The fastmem window and GS memory both come from it. |
|
||||
| Address layout | `0x2_0000_0000`-`0x2_FFFF_FFFF` is the driver's GPU window. An unhinted `mmap` lands in it. Dolphin's arena is at `0x10_0000_0000` and PPSSPP's range at `0x6_0000_0000`. The title's core loader maps a core wherever `mmap(nullptr)` puts it (Dolphin landed at `0x2_0199_8000`). | One 2 GiB neighbourhood above the GPU window for the core image, the 305 MiB code area and the 320 MiB main memory. That needs the loader to place a core at a chosen base, which is a general loader change, and hints in the core for the two areas. The 4 GiB fastmem window goes anywhere outside the GPU window (it is reached through a register). |
|
||||
| Executable memory | A mapping asked for with `PROT_EXEC` does not execute; one mapped read-write and then `mprotect`ed executable does (Dolphin and PPSSPP). | The code area is mapped that way at its hint. |
|
||||
| Flexible memory | About 450 MB for the whole title, with Dolphin running leaving roughly 238 MB free. Anonymous mappings are charged in full when made. | 305 MiB of code does not fit beside RetroArch. I will measure what a game really uses, then size the areas to fit (they are upstream's generous defaults) or place code in direct memory if the console executes it. Either way the result is in the log. |
|
||||
| Page size | 16 KiB. | `__pagesize` 16 KiB for the PS5, using the vtlb's coalescing path. That path so far has run only on AArch64 Apple, so the x86 recompilers' page-protection users (`mmap_MarkCountedRamPage`, the mVU dispatcher's size) need checking against it. |
|
||||
| Faults | A fault on a reserved, unmapped page can arrive as SIGBUS. The console's machine context has 48 bytes before `uc_mcontext` that FreeBSD's lacks (measured by the platform probe). | Take both signals. My SDK fork's `sys/_ucontext.h` declares the 48 bytes, so the FreeBSD branch's `uc_mcontext.mc_rip` is right as written. |
|
||||
| Threads, timers, audio, input, FTP-visible files, 120 Hz pacing | Solved by the title for Dolphin and PPSSPP: `core_threads_ps5` (sampler registration and stack sizes), `audio_ps5`, `input_ps5`, `permissions_ps5`, and the pacer. | Reused as they are; the CPU thread and MTVU get the sampler hooks. |
|
||||
| Content | The Ratchet & Clank demo is a `.7z`, which LRPS2 does not open and the frontend will not unpack for it. The FFX demo is the European release, so PAL at 50 Hz. God of War II's demo is an ISO. | I convert the 7z once, on the console's own storage, beside the original and leaving it untouched. Full speed for FFX is 50 fps, and its pacing on the 120 Hz output gets its own check. |
|
||||
|
||||
## Plan
|
||||
|
||||
1. **Phase 1 — build:** `tools/build-lrps2.sh`, the toolchain file, libc shims,
|
||||
the import table, ABI check and stamp; packaged into the title with its info
|
||||
file. Loading it on the console is the first test.
|
||||
2. **Phase 2 — platform, on the software renderer:** memory map and the
|
||||
`memshm` backend, the loader's placement, JIT memory, the fault path,
|
||||
threads, audio and input; then the BIOS; then each game to a playable scene,
|
||||
with a save state there as the baseline (in a test directory; my own states
|
||||
and memory cards are never touched); then memory cards and save states.
|
||||
3. **Driver rounds R81-R84** (see the Vulkan page), started in parallel with
|
||||
Phase 2, since they are what stands between the hardware renderer and its
|
||||
first frame.
|
||||
4. **Phase 3 — hardware renderer:** first frames, then correctness against
|
||||
desktop LRPS2 at the same revision on the same states (GS dumps where a frame
|
||||
needs bisecting), then R85-R87 as the profiles make them matter.
|
||||
5. **Phase 4 — Profiles 0-12**, with any missing option exposed cleanly. The
|
||||
upscale option listed 1x, 2x, 4x and 8x; the fork adds 3x, 5x and 6x.
|
||||
Sharpening, anti-aliasing and TV effects are the frontend's slang shaders:
|
||||
LRPS2 has none of its own.
|
||||
|
||||
## Status (2026-09-26)
|
||||
|
||||
Phase 1 is done: the core builds from the pinned revision and the committed
|
||||
patch, passes the ABI check and is staged in the title. Phase 2's platform work
|
||||
is done: the code area and guest memory come from my SDK fork's platform layer,
|
||||
and the recompilers have upstream's 305 MiB again (docs/PHASE_LOG.md, the SDK
|
||||
fork's adoption). The BIOS boots on both renderers.
|
||||
|
||||
Phase 3 has started. The hardware renderer draws on the driver's R81-R91 and
|
||||
runs God of War II's demo, Final Fantasy X's demo and San Andreas at 6x
|
||||
(2880x2160) at full speed, with MTVU on by default on the PS5
|
||||
(docs/PHASE_LOG.md, "LRPS2's hardware renderer at 4K"). What is left of it:
|
||||
|
||||
- the comparison with desktop LRPS2 at the same revision and settings, which is
|
||||
where San Andreas' doubled radiosity haze at 6x is settled;
|
||||
- FFX's pacing on the 120 Hz output: a PAL game presents at 50 Hz, two and
|
||||
three refreshes a frame. VideoOut offers no 50 or 100 Hz output to switch to
|
||||
(../PS5_Vulkan R92: only 59.94 and 119.88 Hz), so the cadence is the pacer's
|
||||
to keep even;
|
||||
- the Ratchet & Clank demo, once its .7z is converted on the console's own
|
||||
storage;
|
||||
- memory cards and save states in a test directory (Profile 10), then Phase 4's
|
||||
profiles.
|
||||
@@ -1,179 +0,0 @@
|
||||
# XMB allocation diagnostic build
|
||||
|
||||
The opt-in observer records allocation lifetimes without changing allocation
|
||||
policy or suppressing Vulkan errors. Allocation-policy fixes apply independently
|
||||
of this switch; the accepted XMB list fix is described in
|
||||
`docs/XMB_LIST_SAFETY.md`. PS5_Vulkan is read only for this work.
|
||||
|
||||
## Build and host verification
|
||||
|
||||
```sh
|
||||
PS5_MEMORY_DIAGNOSTICS=1 bash tools/verify.sh
|
||||
```
|
||||
|
||||
The normal build (variable absent or `0`) compiles out the observer. The diagnostic
|
||||
adds `PS5_MEMORY_DIAGNOSTICS` to the title and frontend definitions and
|
||||
`--wrap=posix_memalign` to the existing malloc/calloc/realloc/free wrappers because
|
||||
Mesa's default Vulkan host allocator uses aligned native allocations. Mode and
|
||||
archive contents participate in the runtime build identity.
|
||||
|
||||
The diagnostic build copies the four driver archives into
|
||||
`build/memory-diagnostic-inputs/` and links those copies. `archives.json` records
|
||||
their SHA-256 digests. A copy that changes during the read is rejected. This does
|
||||
not rebuild or write into the sibling project; it also does not imply the copied
|
||||
archives are the same driver as an earlier console run. Mesa utility sources are
|
||||
still read from the configured driver source tree, and the resulting object
|
||||
bytes participate in the identity.
|
||||
|
||||
Output is `dist/PPSA99169/`. Preserve `build/llvm-pie.elf`, `build/title.map`,
|
||||
`build/title_build_identity.h`, the manifest, and the archive digests before any
|
||||
subsequent build. Caller addresses must be decoded against the exact matching ELF.
|
||||
|
||||
Host tests exercise allocation/free accounting, mapped/native realloc migration,
|
||||
failed realloc retention, foreign pointers, aligned allocation semantics,
|
||||
concurrency, metadata saturation, five-second sampling with an injected clock,
|
||||
logging while the wrapped allocator fails, and 10,006 repeated failures producing
|
||||
only five failure records with the clock controlled by the test. Vulkan mocks check that image and
|
||||
queue-idle observers preserve arguments, return values and repeat registration.
|
||||
These tests do not establish console stability or identify the memory consumer.
|
||||
|
||||
The five-second report is synchronous in the presentation path: caller aggregation
|
||||
scans 131,072 tracking slots while holding the observer mutex, then writes the
|
||||
summary and caller rows. It can disturb frame pacing. Use the normal default
|
||||
`PS5_MEMORY_DIAGNOSTICS=0` build for responsiveness comparisons after capturing the
|
||||
allocation evidence; RetroArch.log, trace logging and passive klog remain available.
|
||||
The option controls the observer, not the XMB allocation fix.
|
||||
|
||||
## Manual console test — only when the owner is ready
|
||||
|
||||
Uploads are authorized after checking the console is idle. A launch requires
|
||||
owner intervention; do not change live settings or stop another title automatically.
|
||||
Start a passive klog listener before the owner's reproduction and preserve the
|
||||
existing logs first. Deploy the diagnostic using the project's normal folder
|
||||
procedure, preserving live configuration and user content. The owner launches it.
|
||||
|
||||
1. Leave XMB idle for 30 seconds.
|
||||
2. Browse rapidly for 30 seconds, then leave it idle for 30 seconds. Repeat.
|
||||
3. Repeat the RGUI to XMB transition that triggered the crash. Note the approximate
|
||||
elapsed time of each phase, including whether switching required restarting.
|
||||
4. On a crash, leave the title closed until logs have been downloaded.
|
||||
|
||||
Download `/data/homebrew/PPSA99169/memory-diagnostics.log`, `retroarch.log`,
|
||||
`trace.txt`, and the live configuration, along with the kernel capture. Keep raw
|
||||
files under ignored `klog/`; they may contain user paths and process information.
|
||||
In the title the same new log is `/app0/memory-diagnostics.log`. It appends a
|
||||
`session` line with build identity and PID each launch rather than erasing the
|
||||
previous run. No runner or deployment behavior is changed by this diagnostic.
|
||||
|
||||
## Reading the capture
|
||||
|
||||
`initial`, `sample`, `failure-summary` and `final` rows contain:
|
||||
|
||||
- `native_bytes/count/peak`: requested live bytes, live count and peak live bytes
|
||||
from observed ordinary native allocations.
|
||||
- `aligned_bytes/count/peak`: the same for `posix_memalign`; this is also native
|
||||
memory, not a separate heap. Native pressure includes both native and aligned.
|
||||
- `mapped_bytes/count/peak`: requests routed through large-buffer mappings and the menu-object slabs.
|
||||
These exclude mapping headers, page rounding, slab size-class padding and
|
||||
allocator overhead; they are requested bytes, not mapped physical footprint.
|
||||
- `failures`: cumulative failures; the first four `failure` rows immediately
|
||||
record operation, requested bytes, alignment (or element count for calloc
|
||||
overflow), error and caller return address (`pc`). Further details and failure
|
||||
summaries are limited to one per five seconds, even when presentation stops.
|
||||
`failure_records` and `failure_suppressed` expose that limit. The `error` field
|
||||
is a native error return or observed errno; a failed allocator may leave stale
|
||||
errno, so a NULL result alone does not establish a particular error code. `calloc-overflow` is an arithmetic rejection,
|
||||
not measured heap exhaustion.
|
||||
- `dropped`: allocation records omitted because the fixed table was full. Any
|
||||
nonzero value makes live totals incomplete for the rest of that session.
|
||||
- `foreign_frees/reallocs`: pointers whose allocation was not observed (including
|
||||
records dropped on saturation). Never inspect private libc allocation headers.
|
||||
- `image_create/destroy/failed`: successful frontend Vulkan image creations,
|
||||
non-null destruction calls and failed creations. These include images other
|
||||
than menu textures and exclude calls internal to the driver.
|
||||
- `idle_begin/end/failed`: queue-idle calls started, returned and returned failure.
|
||||
A missing completion in a failure summary helps locate the failing operation.
|
||||
|
||||
Every five seconds while Vulkan presents, a summary and up to eight largest live
|
||||
allocation caller groups are emitted. Groups contain `pc`, requested live bytes
|
||||
and count; `site_records_omitted` reports overflow of the separate 2,048-site
|
||||
aggregation table. They are not full stack traces. No frame means no periodic
|
||||
sample; rate-limited allocation failures still write synchronously. Clean frontend return
|
||||
writes `final`; a crash need not.
|
||||
|
||||
### First-failure allocation owners and XMB state
|
||||
|
||||
The first observed allocation failure additionally emits, once per process:
|
||||
|
||||
- `first-failure-caller`: up to 16 largest live caller groups **per route**,
|
||||
with `route=0` native, `1` mapped and `2` aligned. These separate rankings keep
|
||||
large mappings from hiding the native consumers. Counts/bytes are live requests,
|
||||
not cumulative allocation traffic. Groups beyond the top 16 are not printed;
|
||||
`site_records_omitted` separately indicates aggregation-table overflow.
|
||||
- `first-failure-xmb`: the last cached numeric context and insertion progress,
|
||||
current node counters, and native requested bytes/count at failure.
|
||||
- `first-failure-xmb-history`: the last eight boundary events, oldest first,
|
||||
including native requested bytes/count as observed at each event. Navigation
|
||||
itself only updates a fixed in-memory ring: there is no per-entry file I/O.
|
||||
|
||||
`tab` is the horizontal category index, `kind` the configured source's
|
||||
`XMB_SYSTEM_TAB_*` enum (UINT_MAX for a custom tab). Optional compiled features
|
||||
affect enum numbering; decode against the matching configured source. `current`,
|
||||
`old` and `horizontal` are the latest observed selection-list, animation-list and
|
||||
horizontal-list sizes. `list_size` refers to the list at the recorded operation.
|
||||
`index` is the insertion offset, copied-entry count for copy phases, or selection
|
||||
index for cache/populate phases. No strings, entry names or content paths are
|
||||
captured. A zero phase means no XMB context has been observed yet.
|
||||
|
||||
Phases: 1 cache begin, 2 destination selected, 3 copy begin, 4 copy end,
|
||||
5 clear begin, 6 clear end, 7 before insert, 8 populated. Clear boundaries
|
||||
surround XMB node cleanup; callbacks/entry strings are freed subsequently by
|
||||
the menu layer. Sizes are observations, not pointers reread from an allocation
|
||||
failure callback. In particular, clear-end size can still be the old entry
|
||||
count, and current/old sizes remain cached until the next context hook.
|
||||
`ms` is the last stage observation's timestamp; the enclosing `failure` row
|
||||
gives the actual failure time. For failure on another thread this is the last
|
||||
observed frontend state, not an assertion that that thread was rebuilding XMB.
|
||||
|
||||
`nodes` counts successful XMB node allocations/copies minus observed node frees;
|
||||
`created`, `copied` and `freed` are cumulative. `unmatched_frees` marks incomplete
|
||||
node accounting. This supplements allocator ownership; it does not replace it
|
||||
or classify GPU allocations. The failure path never calls frontend code or
|
||||
dereferences cached frontend pointers. Existing failure rate limits still apply;
|
||||
the expanded snapshot is not repeated during the failure storm.
|
||||
|
||||
Host coverage wraps the eight-event ring, injects insertion context, separates
|
||||
same-address callers across all three routes, caps output at 48 owner rows, and
|
||||
checks that 10,006 failures emit only one expanded snapshot and five ordinary
|
||||
failure records (<20 KiB total in the test). It also verifies that navigation
|
||||
hooks write no bytes and normal-build C macros do not evaluate their arguments.
|
||||
The linked diagnostic inspection checks that the actual frontend XMB object
|
||||
references all three hooks. These checks are not console acceptance.
|
||||
|
||||
The first console run showed why failure logging must be bounded: menu-entry
|
||||
allocation retries produced 12,793 failure records and summaries (5.4 MB) before
|
||||
the owner closed a frozen title. That run is not a valid performance measurement,
|
||||
and rate limiting alone does not resolve the underlying allocation failures.
|
||||
|
||||
Logging uses stack formatting, a preopened descriptor and `write`, without stdio
|
||||
or heap allocation. A fixed 131,072-entry pointer table plus state bytes uses
|
||||
about 4.13 MiB of static storage. Mutexes, table operations, scans and synchronous
|
||||
writes add overhead, so use this build to diagnose lifetimes, not benchmark FPS.
|
||||
The allocation route and payload size remain unchanged.
|
||||
|
||||
Coverage is title/core/static-library references intercepted by the linker.
|
||||
Allocations performed internally by system libraries, direct mappings outside the
|
||||
existing large-buffer wrapper, GPU direct memory, and allocator bookkeeping are
|
||||
not measured. Requested live bytes are not the console's total memory usage.
|
||||
|
||||
For a `pc` from this title, subtract the image load base recorded in klog (normally
|
||||
`0x400000`) and one byte for a return address, then use
|
||||
`llvm-addr2line -C -f -e <matching-symbols.elf> <offset>`. A fault instruction
|
||||
address is decoded without the one-byte adjustment. Keep symbols locally; never
|
||||
publish raw memory addresses or local paths as sanitized evidence by accident.
|
||||
|
||||
Compare settled idle samples from the same session. Sustained growth is a reason
|
||||
to investigate retained allocations; it alone does not prove a leak. Stable
|
||||
observed totals plus allocation failures leave unobserved native usage,
|
||||
fragmentation, heap limits and corruption open. The crash's Mesa null dereference
|
||||
while reporting an allocation failure remains unfixed by this instrumentation.
|
||||
-4552
File diff suppressed because it is too large.
Load diff
-105
@@ -1,105 +0,0 @@
|
||||
# PS5 RetroArch: the plan
|
||||
|
||||
A native RetroArch homebrew application for jailbroken PlayStation 5 consoles.
|
||||
This file is the top-level plan: the gates that decide whether a milestone is
|
||||
done, the milestone map, the invariants that constrain the code, and the index
|
||||
of the documents that hold the detail. It is read every session, so it stays
|
||||
short and never records progress — the state is in `docs/ACTIVE.md` and the runs
|
||||
are in `docs/PHASE_LOG.md`.
|
||||
|
||||
## The gates
|
||||
|
||||
A milestone is done when all four hold for the step that closes it. They are
|
||||
ordered: a later gate never excuses an earlier one.
|
||||
|
||||
1. **It runs.** `tools/verify.sh` is green on this host, in order: the sources
|
||||
are formatted and linted, the host unit tests pass, and the PS5 target builds
|
||||
from a clean tree.
|
||||
2. **It is correct.** The cross-built artifact is structurally what the console's
|
||||
loader and RetroArch's own dynamic loader require: PS5 objects that import
|
||||
`libkernel_web.sprx` and never `libkernel_sys.sprx`, libretro cores that
|
||||
export `retro_api_version`, `retro_init`, `retro_run`, `retro_load_game` and
|
||||
`retro_get_system_info` in the dynamic symbol table, and no host Linux object
|
||||
staged by mistake.
|
||||
3. **It works on the target.** The staged tree is uploaded to the console,
|
||||
launched from the console's own homebrew launcher, and the run reaches the
|
||||
step's definition of working — the menu drawn, the core loaded, the frame
|
||||
presented — with the console's `klog` captured for the whole run.
|
||||
4. **It is provable.** The console capture is distilled into a committed
|
||||
artifact under `evidence/<step>/`, and `tools/verify.sh evidence` replays it
|
||||
against the committed expectation on this host and exits non-zero on a
|
||||
difference. A claim with no artifact is not a pass.
|
||||
|
||||
## Milestone map
|
||||
|
||||
| Milestone | What it delivers | Detail |
|
||||
| --- | --- | --- |
|
||||
| M0 | The contract: filled documentation, the pinned environment, a green gate runner, and the bootstrapper that fetches and cross-builds the pinned upstream RetroArch | `docs/PHASE_LOG.md` |
|
||||
| M1 | The console shell: a RetroArch payload ELF that loads on the console, takes over the screen, shows its identity and exits cleanly | `docs/REFERENCE.md` |
|
||||
| M2 | Video and input: RetroArch draws through our own platform context driver onto the graphics backend, and the controller reaches its input driver | `docs/REFERENCE.md` |
|
||||
| M3 | Cores and storage: core scan and load from the application's own directories, content loading, saves, save states and configuration persistence | `docs/REFERENCE.md` |
|
||||
| M4 | Audio, mapping and release: audio output, input mapping, menus usable at 1080p, and a release ZIP with an evidence-backed acceptance run | `docs/REFERENCE.md` |
|
||||
|
||||
Each milestone is a sequence of steps, one commit each, in `docs/REFERENCE.md`.
|
||||
A step that turns out to be two steps is split before it is started, not after
|
||||
it is half-built.
|
||||
|
||||
## Invariants
|
||||
|
||||
These constrain every change. Breaking one is a design decision, and a design
|
||||
decision is written down in `docs/REFERENCE.md` and `docs/FINDINGS.md` before
|
||||
the code that depends on it.
|
||||
|
||||
- **Upstream stays upstream.** RetroArch is fetched at a pinned version and
|
||||
digest into `vendor/`, which is never committed and never hand-edited; every
|
||||
change to it is a file in `patches/`, ordered in `patches/series`, and
|
||||
regenerated by `tools/fetch-retroarch.sh`. A fix that cannot be expressed as a
|
||||
patch belongs in `platform/`, not in `vendor/`.
|
||||
- **The toolchain is named everywhere.** Every compilation of PS5 code goes
|
||||
through `$PS5_PAYLOAD_SDK/bin/prospero-*` — in the environment *and* as make
|
||||
variables — so a makefile that hardcodes `CC = gcc` cannot silently produce a
|
||||
host Linux object that stages cleanly and loads nowhere.
|
||||
- **The loader contract is checked, not assumed.** Every shipped ELF and core is
|
||||
verified after the build: it imports `libkernel_web.sprx`, it does not import
|
||||
`libkernel_sys.sprx`, and its exported libretro entry points are present in
|
||||
the dynamic symbol table. Linking is not loading.
|
||||
- **No direct hardware access.** RetroArch code talks to the console through the
|
||||
public payload SDK, and to the GPU only through a released graphics backend:
|
||||
the OpenGL 3.3 Core stack in `../ps5-opengl-sdk-0.2.0` until the Vulkan driver
|
||||
in `../PS5_Vulkan` reaches its 1.0 rung, then that driver. No AGC register
|
||||
writes, no `sceGnm*` command submission, no firmware offsets, and no private
|
||||
GPU structures in this repository.
|
||||
|
||||
## Where the truth lives
|
||||
|
||||
| Question | Document |
|
||||
| --- | --- |
|
||||
| What is being built, and why | this file |
|
||||
| What is happening right now | `docs/ACTIVE.md` |
|
||||
| What a step is, and how it is accepted | `docs/REFERENCE.md` |
|
||||
| What was measured, and what it forces | `docs/FINDINGS.md` |
|
||||
| What happened, run by run | `docs/PHASE_LOG.md` |
|
||||
| How to verify, ship and debug | `docs/TESTING.md`, `docs/DEPLOYMENT.md`, `docs/TROUBLESHOOTING.md` |
|
||||
|
||||
If a question has no home, add it to the table and create the document; do not
|
||||
answer it in two places.
|
||||
|
||||
## What is deliberately not planned
|
||||
|
||||
- The exploit, the kernel payload and the console's configuration. This project
|
||||
builds an application; it does not jailbreak a console, and it never writes to
|
||||
a console it was not pointed at.
|
||||
- The GPU driver. Graphics on this console are separate projects in
|
||||
`../PS5_Vulkan` and `../ps5-opengl-sdk-0.2.0`; this project consumes their
|
||||
released artifacts and reports what RetroArch needs from them.
|
||||
- The homebrew launcher, the payload loader and the title registration flow. The
|
||||
console's own loader starts this application; this repository does not install
|
||||
or configure a console.
|
||||
- Retail-style packaging: signing keys, `sce_sys` beyond a homebrew title's own
|
||||
metadata, and store submission.
|
||||
- A software-rendered fallback as the product. The headless and null drivers
|
||||
exist to prove the frontend underneath the graphics work; the shipped
|
||||
application renders through a graphics backend named in `docs/REFERENCE.md`.
|
||||
|
||||
Scope grows by changing this file, in its own commit, with the reason in the
|
||||
message. It never grows by a step quietly doing more than its milestone says.
|
||||
@@ -1,123 +0,0 @@
|
||||
# The 10.01 system modules: what they tell us
|
||||
|
||||
I have an unpacked copy of a console's system modules on this machine,
|
||||
outside every repository (a folder named for firmware 10.01). This page records
|
||||
what comparing their export tables with our SDK and our shims established, and
|
||||
whether the copy is worth using. It names exported functions and modules only.
|
||||
Nothing from the copy is in any repository, and the reader I used lives in my
|
||||
scratch space.
|
||||
|
||||
## What is there
|
||||
|
||||
332 files, all x86-64 ELF images with the FreeBSD ABI and readable dynamic
|
||||
tables:
|
||||
|
||||
| Folder | Contents |
|
||||
|---|---|
|
||||
| `common/lib` | 277 modules: libkernel in three variants (`libkernel`, `libkernel_web`, `libkernel_sys`), `libSceLibcInternal`, and the Sce libraries (VideoOut, AudioOut, Pad, Agc, Sysmodule, ...) |
|
||||
| `priv/lib` | 26 system-private modules |
|
||||
| `sys` | system processes |
|
||||
| `vsh` | system applications |
|
||||
|
||||
Each module's symbols are named by NID: an 11-character hash of the function
|
||||
name, the same derivation our own ELF writer uses for our imports
|
||||
(tooling/native/sce_module_writer.cpp). With the module's library table, an
|
||||
export reads as "NID, library, module". I computed NIDs for every name in the
|
||||
payload SDK's stub libraries, the SDK headers,
|
||||
tooling/native/runtime/imports.txt and the functions our shims replace, and
|
||||
looked each up.
|
||||
|
||||
**Versions.** The copy is 10.01. My console runs 12.09:
|
||||
`sceKernelGetSystemSwVersion` reports `12.090.001` in our own capability probe
|
||||
(../PS5_PayloadSDK, platform/evidence/probe-2026-09-25), and
|
||||
`kern.sdk_version` reports
|
||||
0x10010000. So everything below is a hint, and each behaviour that matters was
|
||||
measured on the console.
|
||||
|
||||
## What it shows that the SDK does not
|
||||
|
||||
**The SDK's stubs are almost all real.** Every one of the 3,015 names in the
|
||||
SDK's `libSceLibcInternal` stub is exported by the console's libc. Of the 1,111
|
||||
names in the `libkernel_web` stub, all but `sceKernelGetPrefixVersion` and
|
||||
`sceKernelIccGetThermalAlert` are exported (the same two are missing from the
|
||||
other variants). The other stubs we could link are complete except for
|
||||
system-only functions nobody here calls: six system-cursor functions in
|
||||
VideoOut, 17 crash-report functions in SystemService, 40 settings getters in
|
||||
UserService and two device-control functions in AudioOut.
|
||||
`libScePosixForWebKit` and `libSceGLSlimVSH` are not in the copy, so their
|
||||
stubs cannot be judged from it; the SDK routes `arc4random`,
|
||||
`arc4random_buf`, `getaddrinfo`, `freeaddrinfo`, `opendir` and `readdir`
|
||||
through the former, and our titles measured `arc4random` resolving to nothing.
|
||||
|
||||
**Why our libc gaps are gaps.** For the functions our shims replace
|
||||
(src/libc_shims.c, src/ps5_directory.cpp, tooling/lrps2/ps5-libc-shims.cpp,
|
||||
the cores' patches):
|
||||
|
||||
| Function | Exported by | So |
|
||||
|---|---|---|
|
||||
| `gmtime_r`, `localtime_r`, `utimensat`, `futimens`, `dirfd`, `faccessat`, `readlinkat`, `clock_nanosleep`, `if_nameindex`, `arc4random`, `arc4random_buf`, `arc4random_uniform` | no module in the copy | no stub fix is possible: shims |
|
||||
| `umask`, `statfs`, `fstatfs`, `openat`, `unlinkat`, `fchmodat`, `fstatat`, `mkdirat`, `renameat`, `fchownat`, `linkat`, `symlinkat` | `libkernel_sys` only | our titles import `libkernel_web` by design (docs/PLAN.md), so these resolve to nothing; shims |
|
||||
| `statvfs`, `fstatvfs` | libc | exported, but libc builds them on `statfs`, which only `libkernel_sys` carries; our titles measured `statvfs` faulting inside libc (2026-09-24): shim |
|
||||
| `opendir`, `fdopendir`, `readdir`, `closedir`, `rewinddir` | libc | exported; our titles measured the console refusing `opendir`, so enumeration goes through `getdents`, which `libkernel_web` exports |
|
||||
| `gmtime`, `posix_memalign`, `aligned_alloc` | libc | real exports |
|
||||
|
||||
So the question has a clear answer: no gap is a stub routing mistake that
|
||||
a stub fix would replace. Each is either absent from the system, present only
|
||||
in the variant our titles do not import, or exported but faulting or refused.
|
||||
The shims stay, once, in the platform layer.
|
||||
|
||||
**The memory interface.** Everything the platform layer needs is exported by
|
||||
all three libkernel variants and named in the SDK's libkernel stubs, but not
|
||||
one `sceKernel` function is declared in the SDK's headers:
|
||||
|
||||
- direct memory: `sceKernelAllocateDirectMemory`,
|
||||
`sceKernelAllocateMainDirectMemory`, `sceKernelReleaseDirectMemory`,
|
||||
`sceKernelCheckedReleaseDirectMemory`, `sceKernelMapDirectMemory`,
|
||||
`sceKernelMapDirectMemory2`, `sceKernelMapNamedDirectMemory`,
|
||||
`sceKernelGetDirectMemorySize`, `sceKernelAvailableDirectMemorySize`,
|
||||
`sceKernelDirectMemoryQuery`, `sceKernelGetDirectMemoryType`;
|
||||
- protection: `sceKernelMprotect`, `sceKernelMtypeprotect`,
|
||||
`sceKernelQueryMemoryProtection`, `sceKernelVirtualQuery`, and POSIX
|
||||
`mprotect`;
|
||||
- virtual ranges: `sceKernelReserveVirtualRange`,
|
||||
`sceKernelSetVirtualRangeName`, `sceKernelMmap`, `sceKernelMunmap`,
|
||||
`sceKernelBatchMap`, `sceKernelBatchMap2`;
|
||||
- memory pools: `sceKernelMemoryPoolExpand`, `...Reserve`, `...Commit`,
|
||||
`...Decommit`, `...Batch`, `...Move`, `...GetBlockStats`;
|
||||
- flexible memory: `sceKernelMapFlexibleMemory`,
|
||||
`sceKernelMapNamedFlexibleMemory`, `sceKernelReleaseFlexibleMemory`,
|
||||
`sceKernelAvailableFlexibleMemorySize`,
|
||||
`sceKernelConfiguredFlexibleMemorySize`;
|
||||
- shared memory for JIT code: `sceKernelJitCreateSharedMemory`,
|
||||
`sceKernelJitCreateAliasOfSharedMemory`, `sceKernelJitMapSharedMemory`,
|
||||
`sceKernelJitGetSharedMemoryInfo`. In `libkernel_web` these are exported
|
||||
only under the `libkernel_jvm`, `libkernel_ps2emu` and `libkernel_psmkit`
|
||||
libraries. Our probe measured `sceKernelJitCreateSharedMemory` refused to
|
||||
the title (0x80020001), so executable code does not go this way.
|
||||
|
||||
Across our repositories the kernel functions we call are all among these, and
|
||||
nine files plus two core patches declared their own prototypes for them. The
|
||||
machine-context offset was carried in five places: the crash reporter, the
|
||||
sampler, and the Dolphin, PPSSPP and LRPS2 ports.
|
||||
|
||||
## Verdict
|
||||
|
||||
Useful, as an index, for three things: deciding which exported functions are
|
||||
worth probing, explaining why an import resolves to nothing (which module and
|
||||
variant exports it), and confirming that the SDK stubs are not the problem.
|
||||
Not useful for behaviour: the copy is two firmware versions behind my console,
|
||||
and an export says nothing about whether a title may use it --
|
||||
`sceKernelJitCreateSharedMemory` is exported and refused, `opendir` is
|
||||
exported and refused. Every behaviour the platform layer relies on is
|
||||
established by our own probe instead (../PS5_PayloadSDK, platform/docs/PROBE.md).
|
||||
|
||||
## Where the platform layer lives
|
||||
|
||||
In my fork of the payload SDK, `../PS5_PayloadSDK`, which every project pins by
|
||||
revision. The fork's `sys/_ucontext.h` declares the console's 48 extra bytes,
|
||||
so `uc_mcontext.mc_rip` is the faulting instruction and no project carries an
|
||||
offset. Its `platform/` builds `libps5platform.a` and installs
|
||||
`include/ps5platform` with the SDK. That covers the kernel declarations, the
|
||||
libc gaps above, the executable-code allocator and direct-memory shared memory,
|
||||
with host tests. Its `platform/tools/setup-sdk.sh` installs the fork's revision
|
||||
over the v0.42 release binaries.
|
||||
@@ -1,657 +0,0 @@
|
||||
# PS5 RetroArch: reference
|
||||
|
||||
Stable reference. Edit when the specification changes. This file holds the step
|
||||
ladder, the workflow that turns a step into a commit, and the environment facts
|
||||
every session needs. It does not hold progress, run results or next actions:
|
||||
those are `docs/ACTIVE.md` and `docs/PHASE_LOG.md`.
|
||||
|
||||
## M0: the contract
|
||||
|
||||
| Step | What | Acceptance |
|
||||
| --- | --- | --- |
|
||||
| M0.1 | Fill the documentation contract: this file's ladder, the gates in `docs/PLAN.md`, the environment table below, and the procedures in `docs/TESTING.md`, `docs/DEPLOYMENT.md` and `docs/TROUBLESHOOTING.md` | `grep -rno '{{[A-Z0-9_]*}}' AGENTS.md docs tools` returns nothing, and every command named in a gate exists as a file in `tools/` |
|
||||
| M0.2 | Pin the toolchain and the upstream source: `tools/doctor.sh` proves the SDK and every host tool, `tools/fetch-retroarch.sh` fetches the pinned revision and checks the tree it got against that commit | `tools/doctor.sh` exits 0 and names each resolved path; `tools/fetch-retroarch.sh --check` prints the pin and what is present, and a second run is a no-op |
|
||||
| M0.3 | Compile the frontend's own sources, to prove the toolchain end to end: `tools/retroarch-sources.sh` asks RetroArch's build which objects a link needs, `tools/build-retroarch.sh` compiles them | `tools/build-retroarch.sh` reports how many sources it compiled and archives them into `build/ra/libretroarch.a`; every source it could not compile is named in its report |
|
||||
| M0.4 | Stage the title: `sce_sys/param.json`, the presentation assets, the runtime and the signed image in the layout the loader reads | `bash tools/build-title.sh` writes `dist/<TITLE_ID>/` with `eboot.bin`, `sce_sys/` and `sce_module/libc.prx`, and records `manifest.sha256`; `bash tools/check-manifest.sh` verifies it and `tools/verify.sh integration` passes on it |
|
||||
| M0.5 | The gate runner: `tools/verify.sh` runs format, unit, build, integration, evidence in order and fails fast | `tools/verify.sh` exits 0 on a clean tree and exits 1 at the first red gate; `tools/verify.sh --list` prints the five commands |
|
||||
|
||||
## M1: the console shell
|
||||
|
||||
| Step | What | Acceptance |
|
||||
| --- | --- | --- |
|
||||
| M1.1 | A minimal PS5 payload that prints its identity through the SDK's stdio and exits | The ELF loads on the console and the captured `klog` holds the identity line; `evidence/m1.1/` carries the capture and the expected line |
|
||||
| M1.2 | The platform driver that owns a title's lifetime: user service, splash screen, and an exit path that does not return from `main` | Console run shows the splash hidden by our call and a clean close from the home screen; the capture is committed |
|
||||
| M1.3 | VideoOut bring-up behind a project-owned seam: acquire buffers, present a solid frame, release on shutdown | A frame is presented on the console and the run's capture names the resolution it received (`docs/DEPLOYMENT.md`, "Observing a target run") |
|
||||
| M1.4 | RetroArch's frontend enters the platform driver and reaches its own main loop in a headless configuration | The console run logs the frontend's own version and driver identity, and the host build of the same step passes `tools/verify.sh` |
|
||||
|
||||
## M2: video and input
|
||||
|
||||
| Step | What | Acceptance |
|
||||
| --- | --- | --- |
|
||||
| M2.1 | Choose the backend with evidence: build against the installed OpenGL 3.3 Core package and list the entry points RetroArch's GL driver resolves against what the package exports | `tools/check-gl-exports.sh` prints the resolved and the missing lists; either the missing list is empty, or it is quoted in full in `docs/FINDINGS.md` and the step closes as a decision to wait for PS5_Vulkan's rung |
|
||||
| M2.2 | A platform video driver and its matching display context: the console's display is opened, the swapchain or context is created from the backend's public entry points, and nothing else in this repository touches the GPU | The console run shows RetroArch's own driver identity line and the resolution it received; the capture is committed under `evidence/m2.2/` |
|
||||
| M2.3 | The menu is drawn: the first visible interface, on whichever backend the driver provides | A console run presents a menu frame; the capture holds the driver name, the swapchain extent, the present count and a frame digest. A framebuffer-only driver cannot satisfy this — `docs/FINDINGS.md` records why the display context is what draws a menu |
|
||||
| M2.4 | Controller input through the payload SDK's pad API into RetroArch's input driver | The capture records the buttons pressed and the menu's reaction, from the mapping table written in this file |
|
||||
| M2.5 | A libretro core loads and runs one frame of content, headless first and then presented | The core's own identification line, the frame digest and the present count sit in one capture under `evidence/m2.5/` |
|
||||
|
||||
## M3: cores and storage
|
||||
|
||||
| Step | What | Acceptance |
|
||||
| --- | --- | --- |
|
||||
| M3.1 | Core discovery and installation: the scan path, the `.info` metadata and the load path | A console run lists the staged cores and loads one by name; the capture names the core and its version |
|
||||
| M3.2 | Save and save-state paths on the console's own storage, with the write-through discipline `docs/DEPLOYMENT.md` describes | A state written on the console is read back after a relaunch, and both runs are captured |
|
||||
| M3.3 | Configuration persistence: `retroarch.cfg` round-trips through the console's storage without losing keys | The diff between the seeded config and the config read back after a settings change names only the changed key |
|
||||
| M3.4 | A second core of a different class (a non-dynarec, non-GL core) loads, runs and exits | Two core names appear in two captures, each with its own content digest |
|
||||
|
||||
## M4: audio, mapping and release
|
||||
|
||||
| Step | What | Acceptance |
|
||||
| --- | --- | --- |
|
||||
| M4.1 | Audio output through the payload SDK's audio API, with the buffer discipline documented in `docs/FINDINGS.md` | The console run reports the audio format it opened and the frames written; a captured run holds no underrun line |
|
||||
| M4.2 | Input mapping: buttons, sticks, the menu combo and the PlayStation confirm/cancel convention | A capture records each mapped button's effect once, from a written mapping table in this file |
|
||||
| M4.3 | The menus at 1080p: readable text, bounded frame time, and no layout that depends on a mouse | A console run at the target resolution with the frame-time record committed |
|
||||
| M4.4 | Release: the ZIP, its `SHA256SUMS`, and one acceptance run end to end | The release artifact's digest, the run's capture and the expected lines are committed under `evidence/m4.4/` |
|
||||
|
||||
Acceptance is the contract. A step is done when its acceptance line is satisfied
|
||||
word for word, and the evidence it names is committed. A step whose acceptance
|
||||
was wrong is corrected in this file, in its own commit, before the code that
|
||||
depends on it lands.
|
||||
|
||||
## The workflow
|
||||
|
||||
| Phase | What it means |
|
||||
| --- | --- |
|
||||
| Spike | Time-boxed, throwaway, never committed as product code. What it learned goes to `docs/FINDINGS.md`. |
|
||||
| Step | One commit, one acceptance line, verified before it lands. |
|
||||
| Milestone | Its steps are all done and its gate in `docs/PLAN.md` holds. |
|
||||
|
||||
### Turning a step into a commit
|
||||
|
||||
1. Restate the acceptance line, and name the artifact that will prove it.
|
||||
2. Build the smallest thing that can satisfy it.
|
||||
3. Run the gates in `AGENTS.md`; fix what fails.
|
||||
4. Commit with the evidence: what was run, what it returned, what it proves.
|
||||
5. Write the step up once in `docs/ACTIVE.md`; append a dated entry to
|
||||
`docs/PHASE_LOG.md`.
|
||||
|
||||
### Splitting and parking
|
||||
|
||||
- If a step needs two independent proofs, it is two steps. Split it in
|
||||
`docs/PLAN.md`/this file first, then build the first one.
|
||||
- If a step cannot be verified in this environment (it needs the console, a
|
||||
loader the console does not have yet, or the Vulkan driver before its rung is
|
||||
reached), implement it and park it in `parked/` as a patch plus the
|
||||
verification plan. It is not merged and it is not "done".
|
||||
|
||||
## The environment
|
||||
|
||||
*Verified on this host with `tools/doctor.sh`. Every session otherwise
|
||||
re-derives these, at full price.*
|
||||
|
||||
| Thing | Value |
|
||||
| --- | --- |
|
||||
| Host | CachyOS (Arch-based) Linux, x86-64, bash 5.3, Python 3.14 |
|
||||
| Host compiler and formatter | clang / clang-format / clang-tidy 22.1.8, `llvm-ar`, `ccache` |
|
||||
| PS5 SDK | `$PS5_PAYLOAD_SDK`, default `/home/mihawk/ps5-payload-sdk`, unpacked 2026-09-17; environment from `$PS5_PAYLOAD_SDK/toolchain/prospero.sh` |
|
||||
| PS5 compiler | `prospero-clang` 22.1.8, target `x86_64-sie-ps5`; sysroot `$PS5_PAYLOAD_SDK/target` |
|
||||
| Upstream source | RetroArch 1.22.2 tarball, fetched into the ignored `vendor/`, patched per `patches/series` |
|
||||
| Build | `bash tools/build-title.sh` → `dist/<TITLE_ID>/eboot.bin`, signed and manifested; `bash tools/verify.sh build` runs the same command |
|
||||
| Test | `tools/verify.sh unit` (host-native, GoogleTest) and `tools/verify.sh integration` (shell and Python against the staged tree) |
|
||||
| Format and lint | `tools/verify.sh format` (`clang-format --dry-run --Werror`, `bash -n`, `python3 -m py_compile`, attribution and JSON checks) |
|
||||
| Run locally | host-native frontend against the headless driver: `build/host/retroarch --features` and the unit suite; no console is touched |
|
||||
| Target | A PS5 title folder: a signed fake-self `eboot.bin` (x86-64) with `sce_module/libc.prx` and `sce_sys/`, which is what the console's loader runs |
|
||||
| Graphics on the target | the relocatable OpenGL 3.3 Core package from `../ps5-opengl-sdk-0.2.0` today, and the Vulkan driver from `../PS5_Vulkan` once it reaches rung 1.0 — see "The graphics backend" below; neither is rebuilt here |
|
||||
| Vulkan headers for our build | `../ps5-opengl-sdk-0.2.0/third_party/Vulkan-Headers/include`, the vendored copy the local driver builds against; the payload SDK's sysroot ships none |
|
||||
| Console tools | the resident control payload and `ps5_console.py` from `../PS5_Vulkan` (`launch`, `kill`, `klog` on port 3232); address and credentials come from the ignored `.env` |
|
||||
| Source of truth for deps | `pin/sources.txt` (versions and SHA-256) and `patches/series` (the patch order) |
|
||||
|
||||
## Versions and pins
|
||||
|
||||
Anything pinned is pinned here, with the reason. Nothing is pinned in two
|
||||
places, and nothing is upgraded without a line in `docs/PHASE_LOG.md`.
|
||||
|
||||
| Pin | Version | Why |
|
||||
| --- | --- | --- |
|
||||
| RetroArch source | 1.22.2, `sha256 245ef18c8fa8fbd9fbb5eb25cf43e17c6aace2f95c1ed99873cbd794012bb232` | The newest upstream tag that carries the Vulkan context driver and the frontend the port needs; the digest is the tarball's own, so a moved tag is caught |
|
||||
| PS5 payload SDK | `$PS5_PAYLOAD_SDK`, unpacked 2026-09-17 | The only toolchain that produces PS5 payload ELFs; the version is recorded in `pin/sources.txt` so a build is reproducible from a checkout |
|
||||
| Vulkan driver | from `../PS5_Vulkan` at the revision recorded in `pin/sources.txt` | The driver is this project's graphics backend; a revision that changes a driver entry point must be a deliberate, recorded bump |
|
||||
| Graphics backend (current) | `../ps5-opengl-sdk-0.2.0`, release 0.2.0, consumed through its installed package | It is the only graphics stack with a recorded hardware acceptance run on this machine; the driver package is never rebuilt here |
|
||||
| GoogleTest | host-only, fetched and digest-verified into `.deps/test/` | Unit tests run on the host; it is never linked into a PS5 artifact |
|
||||
|
||||
## The graphics backend
|
||||
|
||||
Two local projects provide graphics; this repository builds neither. Which one a
|
||||
step uses is recorded in the step's acceptance line, and the choice is evidence
|
||||
driven, not stylistic.
|
||||
|
||||
| Stage | Backend | How this repository consumes it | What it decides |
|
||||
| --- | --- | --- | --- |
|
||||
| Now | OpenGL 3.3 Core from `../ps5-opengl-sdk-0.2.0` | its relocatable package, through `ps5-opengl-core33.mk` or its `ps5-opengl-core33.pc`; headers are EGL, GL and KHR | RetroArch's OpenGL path, with the frontend rendering through our own platform context driver |
|
||||
| After PS5_Vulkan rung 1.0 | the Vulkan driver from `../PS5_Vulkan` | its released static archive; Vulkan headers come from the SDK's vendored copy | RetroArch's `vulkan` context driver, which is the target backend for the shipped application |
|
||||
|
||||
Rules that hold either way:
|
||||
|
||||
- The application never opens a GPU device directly. It issues EGL/GL or Vulkan
|
||||
calls through RetroArch's own context driver, so the backend can be swapped
|
||||
without touching the frontend.
|
||||
- Whichever backend is in use is named in the run's capture, and a step that
|
||||
changes backend is its own step with its own acceptance run.
|
||||
- A backend that fails the step is a finding in `docs/FINDINGS.md` with the exact
|
||||
call that failed, not a reason to add private GPU access here.
|
||||
|
||||
The first M2 step settles the OpenGL question with evidence rather than
|
||||
assumption: RetroArch's GL driver resolves its entry points through `glsym`, and
|
||||
a backend that exports fewer functions than the driver asks for fails at load
|
||||
time. `tools/check-gl-exports.sh` compares the backend's exported symbol list
|
||||
with the driver's request list and reports the difference; if the difference is
|
||||
not empty, that difference is the M2 finding and PS5_Vulkan's rung becomes the
|
||||
blocking dependency for a presented frame.
|
||||
|
||||
## Shipping as a PPSA title
|
||||
|
||||
The console needs an `eboot.bin` in a title folder with `sce_sys/param.json`
|
||||
carrying a `PPSA#####` id. This project produces one through
|
||||
`../ps5-native-app-boilerplate-main`'s pipeline, which is the path already proven
|
||||
on this console: ProsperoLight, built the same way, starts when placed.
|
||||
|
||||
| Piece | Where it comes from |
|
||||
| --- | --- |
|
||||
| the tooling that links and signs `eboot.bin` | `tooling/`, taken from the native pipeline and committed as part of this project |
|
||||
| the loader-visible runtime module | `runtime/libc.prx`, checked against its own digest manifest |
|
||||
| the title's identity and launcher assets | `sce_sys/`, written by the scaffold from `title/` |
|
||||
| the application | RetroArch's sources, compiled by `tools/build-retroarch.sh`, linked with `src/` |
|
||||
|
||||
The pipeline's builder compiles `src/**/*.{c,cc,cpp}` with fixed flags
|
||||
(`-std=c11`, `-std=c++20`, `-O2 -Wall -Wextra -ffunction-sections
|
||||
-fdata-sections`) and links the result with its own CRT, C++ runtime, AGC link
|
||||
stubs and version script. Two consequences shape this project:
|
||||
|
||||
- **RetroArch is reached by include path, not copied in.** Its tree is dozens of
|
||||
directories with its own configure step, so `tools/build-retroarch.sh` compiles
|
||||
it separately with the same compiler and the generated `config.h`, and `src/`
|
||||
holds only this project's code — the entry point and the video driver.
|
||||
- **Extra link inputs arrive through `APP_STATIC_ARCHIVES`.** That is how a
|
||||
graphics backend is added when a step needs one; the Vulkan driver's archive is
|
||||
the intended first use.
|
||||
|
||||
## Reference material
|
||||
|
||||
Two directories hold material this project did not write, and they are different
|
||||
kinds of thing:
|
||||
|
||||
| Path | Committed | Rule |
|
||||
| --- | --- | --- |
|
||||
| `reference/` | yes, with a `PROVENANCE.txt` naming the source, the revision and every file's digest | read-only. It is what a claim was measured against; a change to it is its own step, and the digests are re-recorded in that step |
|
||||
| `vendor/` | no, ignored | build-time only. `tools/fetch-retroarch.sh` recreates it from the pin on every clean build, so nothing in it can be relied on to persist |
|
||||
|
||||
Neither is edited to get a step unblocked. A change we want in either one is a
|
||||
patch in `patches/` or a documented decision, never a quiet edit.
|
||||
|
||||
## Style
|
||||
|
||||
- C and C++ follow the upstream RetroArch style inside `platform/` and the
|
||||
project's own `clang-format` configuration: 4-space indent, 80 columns, braces
|
||||
on the same line for control flow. Run `tools/verify.sh format` before staging.
|
||||
- Shell scripts start with `#!/usr/bin/env bash` and `set -euo pipefail`, quote
|
||||
every expansion, and exit 2 on a usage error and 1 on a check failure — the
|
||||
convention the console tools in `../PS5_Vulkan` already use.
|
||||
- Generated code, vendored dependencies and build output are never edited by
|
||||
hand and never reformatted: `vendor/`, `build/`, `dist/`, `.deps/`, `*.elf`,
|
||||
`*.so`, and everything under a git-ignored capture directory. `reference/` is
|
||||
committed but equally read-only: it is the baseline a measurement was taken
|
||||
against.
|
||||
|
||||
## The port
|
||||
|
||||
RetroArch on this console is a video driver of this repository's own, presented
|
||||
through the display layer in `src/display.cpp`. Two menus are available and the
|
||||
choice is a backend choice, not a preference:
|
||||
|
||||
| Menu | Display context | What it needs |
|
||||
| --- | --- | --- |
|
||||
| RGUI | none — `menu/drivers/rgui.c` references `dispctx` **zero** times | `poke->set_texture_frame`, `viewport_info`, `video_driver_supports_rgba` |
|
||||
| XMB, Ozone, MaterialUI | required — XMB dereferences `dispctx` 72 times | a GPU backend: every entry in `gfx_display_ctx_drivers[]` is a GPU API and there is no software one |
|
||||
|
||||
So RGUI is the first target: it rasterises the menu into its own framebuffer and
|
||||
hands that one texture to the video driver, which means it runs over VideoOut with
|
||||
no GPU stack at all. XMB comes after, over `../PS5_Vulkan`
|
||||
(284 `ps5vk_` symbols in `libps5vk.ps5.a`) once the frontend is already proven.
|
||||
|
||||
The mandatory surface is small, and the rest is optional:
|
||||
|
||||
| Interface | Mandatory | Notes |
|
||||
| --- | --- | --- |
|
||||
| `video_driver_t` | `init`, `suppress_screensaver`, `alive`, `frame`, `ident`, `poke_interface` | `suppress_screensaver` is called with no NULL check; the rest are NULL-guarded |
|
||||
| `video_poke_interface_t` | `set_texture_frame` | RGUI pushes its menu framebuffer through it |
|
||||
| `gfx_ctx_driver_t` | not needed at all | `video_driver_init_internal` never touches one |
|
||||
|
||||
Field order matters: the struct is initialised positionally, and `overlay_interface`
|
||||
sits inside `#ifdef HAVE_OVERLAY` before `poke_interface`, so an initialiser has to
|
||||
account for it.
|
||||
|
||||
## XMB menu assets and build selection
|
||||
|
||||
XMB uses RetroArch's existing Vulkan display driver; no new graphics backend or
|
||||
runtime dependency is added. Configure enables XMB and retains RGUI; upstream's
|
||||
default selection prefers XMB when Ozone/MaterialUI are disabled. The seed config
|
||||
matches that selection. Configure arguments are fingerprinted before feature
|
||||
flags are derived so an existing configured tree cannot silently retain RGUI-only
|
||||
objects. `ASSETS_DIR` is `/app0`, since platform_unix appends `/assets` itself.
|
||||
The title currently selects the null platform frontend, so that Unix initializer
|
||||
does not execute: patch 0063 seeds `/app0/assets` directly in configuration
|
||||
defaults and records the resolved XMB paths and load result at context reset.
|
||||
|
||||
`assets/xmb/source.json` pins the official libretro/retroarch-assets revision
|
||||
73106363e14e34c08a5854b4cfbc29f184e3b783 and hashes the shipped subset: all 120
|
||||
fixed menu icons named by RetroArch 1.22.2's xmb_texture_path, the monochrome M+ 1p
|
||||
font, attribution and licenses. This pin makes the required menu artwork
|
||||
reproducible without downloading assets at build/run time. Per-system playlist
|
||||
icons are not shipped yet; standard default/content icons are included.
|
||||
|
||||
The XMB ribbon retains its two-float shader layout but uploads a zero-padded
|
||||
16-byte uniform block (named patch 0060), matching libps5vk's current whole-record
|
||||
restriction. This is frontend compatibility padding, not general support for
|
||||
arbitrary UBO ranges in the driver. Named patch 0061 expands each triangle strip
|
||||
into list vertices with alternating winding and submits the expanded count for
|
||||
both effect and icon draws. `tests/test_xmb_assets.py` executes the C index mapping
|
||||
for strips through the full 8,064-vertex ribbon and checks bounds and winding.
|
||||
|
||||
Named patch 0062 also binds the existing white texture and nearest sampler for
|
||||
untextured menu effects. The ribbon shader does not sample them, but libps5vk
|
||||
currently validates all stage-visible descriptor layout entries rather than just
|
||||
statically used shader bindings. This uses the same descriptor-fill compatibility
|
||||
path as textured draws (including patch 0026's second sampler slot).
|
||||
|
||||
Named patch 0065 keeps texture coordinates consistent with the physical image
|
||||
width required by the driver's row alignment. RGBA8 rows need 64-texel alignment;
|
||||
R8 font rows need 256-texel alignment. Display UVs are scaled to the logical image
|
||||
region and font atlas offsets are normalized by the physical width. Padding only
|
||||
the image width changes what a normalized coordinate samples.
|
||||
|
||||
Named patch 0066 keeps static/menu textures at one mip level. XMB's full mipmapped
|
||||
icons rendered as repeated/cropped fragments in the tested libps5vk path; the
|
||||
single-level images render correctly. This is a compatibility limit, not proof
|
||||
that the driver's general mip-chain storage/sampling is correct. RGUI and the
|
||||
CPU video driver remain compiled and selectable. The retired screenshot recipe
|
||||
is `parked/xmb-readback/README.md`; screenshots are not taken by normal builds.
|
||||
|
||||
## Native PS5 audio
|
||||
|
||||
`src/audio_ps5.cpp` adapts ProsperoLight's `sceAudioOutInit/Open/Output/Close`
|
||||
sequence to RetroArch's `audio_driver_t`. Patch 0067 registers `audio_ps5` and
|
||||
selects `ps5` by default; the staged config agrees. The backend opens the system
|
||||
user's main output at 48,000 Hz, signed 16-bit interleaved stereo, 256 frames per
|
||||
native output call. `new_rate` tells RetroArch to resample other source rates.
|
||||
Only the default device is supported. No SDL or Opus dependency is introduced.
|
||||
|
||||
A dedicated worker feeds AudioOut, whose synchronous output call paces playback.
|
||||
The producer and consumer share a bounded ring guarded by a mutex/condition;
|
||||
no lock is held while calling AudioOut. Requested latency sizes the ring in
|
||||
256-frame multiples, with a 512-frame minimum and 8,192-frame maximum. A zero
|
||||
latency request uses 64 ms (3,072 frames). An additional native block can be in
|
||||
flight. `write`, `write_avail` and `buffer_size` use bytes, matching RetroArch's
|
||||
call sites. Blocking writes wait for space; nonblocking writes return the bytes
|
||||
accepted, including zero when full. Empty or partial blocks are zero-filled.
|
||||
|
||||
Pause drops queued samples, waits for the in-flight block, then drains the native
|
||||
port. Resume uses the same worker. Free wakes and joins the worker before draining
|
||||
and closing the port. Output errors make the driver inactive and wake blocked
|
||||
writers. Startup, failures and close summaries are logged; successful blocks are
|
||||
not individually logged. Silence includes normal idle/menu output and padding,
|
||||
so it is not by itself evidence of a streaming underrun.
|
||||
|
||||
The opt-in backend test is documented in `docs/TESTING.md`. Core integration,
|
||||
long-duration A/V synchronization and streaming underrun behavior need their own
|
||||
content-based acceptance runs; native test tones do not prove those properties.
|
||||
|
||||
## Native platform frontend and paths
|
||||
|
||||
`frontend_ctx_ps5` supplies platform initialization, a startup environment callback
|
||||
and a drive-list callback (patch 0068). Its non-null environment callback keeps the
|
||||
initial command line intact. Without it, task_content substitutes
|
||||
`menu_content_environment_get`, reconstructs argv from empty startup state and
|
||||
loses `-c`. Subsequent menu-initiated content loads retain upstream's menu callback.
|
||||
|
||||
The frontend seeds `g_defaults` with paths under `/app0`, creates the title's own
|
||||
config/core/content/system/save/playlist directories, and copies the packaged seed
|
||||
to `/app0/config/retroarch.cfg` only when that file is absent. A temporary file and
|
||||
rename avoid installing a partial copy. Existing configs are left intact, including
|
||||
when a newer packaged seed is uploaded. `docs/DEPLOYMENT.md` maps these internal
|
||||
paths to FTP. Platform defaults apply when an existing config leaves paths unset.
|
||||
|
||||
Patch 0069 returns `/app0/eboot.bin` for application-path discovery. The generic
|
||||
Unix procfs/getpid/readlink path is inappropriate for a PS5 title; configuration
|
||||
saving invokes it while abbreviating paths. Patch 0070 routes RetroArch's VFS
|
||||
`opendir/readdir/closedir` calls through `src/ps5_directory.cpp`. The adapter uses
|
||||
public SDK `open(O_DIRECTORY)`, `getdents`, and `close`, validates variable-length
|
||||
FreeBSD records, skips deleted entries and returns directory types/names. Reads
|
||||
use a 64 KiB buffer: 4 KiB failed with EINVAL on the tested `/app0` mount even
|
||||
though it worked on `/`; 64 KiB succeeded. The minimum accepted size was not
|
||||
determined. It does
|
||||
not change file reads/writes or bypass the title's filesystem permissions.
|
||||
|
||||
The title compiles against the configured libretro-common include tree. Its
|
||||
headers resolve relative `config.h` includes there; using the pristine vendor
|
||||
include tree fails as soon as the native frontend includes the menu interfaces.
|
||||
No new dependency, SDK version or graphics-driver change is involved.
|
||||
|
||||
Managed directories (`config`, `cores`, `content`, `system`, `savefiles`,
|
||||
`savestates`, `playlists`) use mode `0777`. Startup applies `chmod` after `mkdir`,
|
||||
including when the directory already exists, so an inherited umask cannot remove
|
||||
FTP write access and older `0755` directories are repaired. Mode `0775` was
|
||||
applied but still denied real FTP uploads on this console, despite matching
|
||||
groups in directory listings; the owner authorized `0777`. This policy
|
||||
changes only those directory modes; it does not recursively rewrite user files.
|
||||
|
||||
### Native joypad and binding interface
|
||||
|
||||
`input_ps5` pairs with `ps5_joypad`, registered before the null joypad. The joypad
|
||||
owns the single initial-user pad handle and exposes 16 raw buttons plus six axes
|
||||
(left X/Y, right X/Y, L2/R2). The built-in `PS5 Controller` profile maps them to
|
||||
RetroPad defaults through RetroArch's normal autoconfiguration. Existing saved
|
||||
configs with an empty joypad driver select it automatically; new seeds name
|
||||
`ps5`. Explicit user bindings override the profile.
|
||||
|
||||
The input interface adds no raw gamepad state: OR-ing the old direct mapping into
|
||||
RetroArch's mapped state would make reassigned buttons remain active. Menu analog
|
||||
navigation and binding capture use the joypad callbacks. Sticks span -32767 to
|
||||
32767 centered on byte 128; triggers span 0 to 32767. RetroArch owns deadzone and
|
||||
axis-threshold handling. A zero-sample read preserves the previous sample (the
|
||||
binding screen may poll twice); errors/disconnects and shell interception suppress
|
||||
input. No per-frame or per-button logging is added. Rumble and multiple controllers
|
||||
remain unsupported in this backend.
|
||||
|
||||
|
||||
## FCEUmm core build
|
||||
|
||||
`make fceumm` (or `tools/build-fceumm.sh`) cross-builds the pinned upstream
|
||||
FCEUmm revision and its pinned core-info metadata. The script verifies both
|
||||
input SHA-256 digests, extracts fresh sources into `build/cores/fceumm`, and
|
||||
uses this project's `.deps/native/ps5-payload-sdk` wrappers for CC/CXX/AR/LD.
|
||||
It does not modify the fetched upstream source. `platform=unix` selects upstream
|
||||
source/features, while explicit compiler and linker make variables prevent a
|
||||
host build. The linker is the compiler driver because upstream passes `-Wl`
|
||||
options; `-nostdlib -nodefaultlibs` excludes payload CRT/static libc defaults.
|
||||
The `--build-id=sha1` override replaces Clang's random PS5 UUID with a
|
||||
content-derived ELF ID so repeated builds have stable hashes.
|
||||
`-T tooling/native/ps5-core.ld` separates executable, read-only and writable
|
||||
segments on 16 KiB boundaries; the loader never maps writable executable pages.
|
||||
The core checker rejects a writable executable load segment.
|
||||
Math and libc imports come from the native runtime stubs, with `libkernel_web`
|
||||
rather than the payload `libkernel_sys`. No SDL or decoder is added.
|
||||
|
||||
Outputs are `build/cores/stage/cores/fceumm_libretro.so` and
|
||||
`build/cores/stage/info/fceumm_libretro.info`. `tools/build-title.sh` builds and
|
||||
copies both after assembling the title, before recording its manifest. Native
|
||||
core metadata defaults to `/app0/info`. The same `.info` file is also staged
|
||||
beside the core because older saved configs set `libretro_info_path = ""`;
|
||||
upstream then searches the core directory. Existing settings are preserved.
|
||||
`build/cores/fceumm/build.json` records
|
||||
source/metadata pins, hashes, ELF imports, exported callbacks and compiler-wrapper
|
||||
identity. First use requires network access; verified cached inputs allow offline
|
||||
rebuilds. `JOBS` controls build parallelism.
|
||||
|
||||
The reference was john-tornblom's websrv
|
||||
[core build script](https://github.com/ps5-payload-dev/websrv/blob/master/homebrew/RetroArch/build-snes9x2010.sh).
|
||||
Only its fetch/build/stage pattern applies here: this application uses the native
|
||||
title pipeline and its own SDK. A shared ELF passing the ABI gate is not proof
|
||||
that the native title's dynamic loader can load it. FCEUmm renders NES frames in
|
||||
software; RetroArch presents those frames through the existing Vulkan driver.
|
||||
|
||||
After uploading each `.info`, deployment writes and reads back RetroArch's
|
||||
`core_info.refresh` marker in the same directory. RetroArch consumes it when
|
||||
rebuilding its metadata cache; it is not a shipped manifest file. This also
|
||||
refreshes entries previously cached as having no metadata.
|
||||
|
||||
|
||||
## Native in-process core loader
|
||||
|
||||
`src/core_loader_ps5.cpp` supplies the dynamic-library operations used by the
|
||||
frontend. It loads this pipeline's ELF64 x86-64 FreeBSD-ABI shared cores into
|
||||
anonymous memory, resolves their imports against explicit title bindings,
|
||||
applies checked RELA relocations, then protects each segment as RX, R or RW.
|
||||
File reads use bounded POSIX reads into a separate mapped buffer. Failed opens
|
||||
release allocations and report the failing operation; successful handles are
|
||||
reference counted and unmapped on the final close.
|
||||
|
||||
`tools/core-imports.py` derives the union of required native bindings from the
|
||||
explicitly shipped FCEUmm, mGBA, Snes9x, FBNeo and Genesis Plus GX ELFs; bindings and all cores participate in the title build
|
||||
identity. Directory imports use this port's directory adapters, including `rewinddir`.
|
||||
The `localtime_r` binding uses RetroArch's existing locked `rtime_localtime`
|
||||
helper, initialized before gameplay. The
|
||||
runtime dependencies are limited to the public kernel_web, libc and Posix stubs.
|
||||
This route does not depend on websrv's payload loader hooks or publish native
|
||||
module exports through the title converter.
|
||||
|
||||
The initial supported contract is deliberately narrower than a general dynamic
|
||||
linker: no TLS, ELF interpreter, legacy DT_INIT/DT_FINI, C++ unwind
|
||||
registration, REL/RELR, or additional dependent shared libraries. Bounded
|
||||
DT_INIT_ARRAY callbacks are supported: every relocated pointer must target this
|
||||
module's executable segment, and all callbacks are checked before any run.
|
||||
Initializers run once per new mapping after protection, before exposing the
|
||||
handle; a reference-counted reopen does not rerun them. Unsupported imports and
|
||||
relocations fail explicitly. Adding another core requires reviewing this
|
||||
contract and extending both host tests and target diagnostics as needed.
|
||||
|
||||
Core selection checks load success before content startup tears down drivers.
|
||||
The main loop also refuses to poll drivers after frontend initialization fails.
|
||||
This protects against load failures; it cannot isolate a fault inside arbitrary
|
||||
core code running in the same process.
|
||||
|
||||
|
||||
Software core frames in libretro XRGB8888 are converted by
|
||||
`src/core_frame_ps5.cpp` to RGBA8 bytes with opaque alpha, respecting both row
|
||||
pitches and in-place framebuffers. The Vulkan frontend requests RGBA8 for both
|
||||
staging and sampled core textures. This extends the menu's matching-format
|
||||
upload rule to actual content, avoiding the RGB565-only compute branch. Frame
|
||||
scaling and presentation remain on the Vulkan GPU path; `video_ps5` stays
|
||||
registered as the selectable fallback. RGB565 core frames retain their existing
|
||||
path and require separate content acceptance.
|
||||
|
||||
|
||||
## mGBA core build
|
||||
|
||||
`make mgba` runs `tools/build-mgba.sh` with the same SDK and ELF linker layout as
|
||||
FCEUmm. The pinned source is the [official libretro mGBA tree](https://github.com/libretro/mgba/tree/7a12d6d4b9acb14c0ae62c9166b6a2f3d08007f6).
|
||||
Source and `.info` inputs have verified SHA-256 digests; a fresh extraction goes
|
||||
to `build/cores/mgba/`. CMake is required by this source revision. The wrapper in
|
||||
`tooling/mgba/` selects only the shared libretro target, both GB and GBA engines,
|
||||
software XRGB8888 output, and native kernel_web/libc/Posix imports. External
|
||||
frontend/media dependencies are disabled: RetroArch supplies presentation,
|
||||
audio, archive extraction and content services. No SDL, Qt or OpenGL backend is
|
||||
added. This core uses Vulkan for frontend presentation, not hardware emulation.
|
||||
|
||||
The toolchain restricts header/library/package searches to the SDK. Function
|
||||
probes link against the actual native stubs without a CRT and are never executed;
|
||||
static-library probes falsely reported unavailable locale functions. The SDK
|
||||
provides locale types/headers but lacks several locale runtime functions, so the
|
||||
small `patches/mgba/native-locale-type.patch` uses a private formatting type for
|
||||
upstream's string-locale fallback; `HAVE_XLOCALE` is removed from this target.
|
||||
The patch is applied with zero fuzz to the fresh source. `HAVE_LOCALTIME_R` names
|
||||
the frontend adapter, avoiding upstream's unimplemented fallback. `-z undefs`
|
||||
permits that frontend-provided import; the generated binding table must still
|
||||
resolve every core import when the title links.
|
||||
|
||||
`--build-id=sha1` keeps output repeatable. `GIT_CEILING_DIRECTORIES` prevents
|
||||
upstream's version generator from discovering RetroArch's parent Git checkout;
|
||||
the archive reports its own 0.11.0 version, with the exact source revision in
|
||||
`build/cores/mgba/build.json`. That report also contains the SDK wrapper, source,
|
||||
metadata and port-input hashes. No source checkout or environment is modified.
|
||||
|
||||
Outputs are `build/cores/stage/cores/mgba_libretro.so` and
|
||||
`build/cores/stage/info/mgba_libretro.info`. The title build stages all shipped cores,
|
||||
with metadata in both `info/` and `cores/` for existing saved configurations.
|
||||
Changing the shipped core list requires rebuilding the frontend's native import
|
||||
bindings. Console loading and manual gameplay remain separate target checks.
|
||||
|
||||
|
||||
`MGBA_PS5` selects `/app0/config/mgba` for mGBA's optional standalone config
|
||||
and disables `portable.ini` working-directory discovery. The zero-fuzz
|
||||
`native-config-path.patch` avoids native `getcwd`/Unix home discovery; core
|
||||
options continue to come from RetroArch's libretro environment.
|
||||
|
||||
## Large application allocations
|
||||
|
||||
`src/memory_ps5.cpp` maps allocations of at least 1 MiB with the native kernel
|
||||
and keeps smaller requests on libc. The title linker wraps `malloc`, `calloc`,
|
||||
`realloc` and `free` together, including the generated core binding references.
|
||||
This flag is required because a 16 MiB 7z extraction allocation returned null
|
||||
through the native libc import. The donor also uses mappings for large buffers;
|
||||
this implementation tracks mappings in a mutex-protected list and never reads
|
||||
headers before unknown pointers. Buffers allocated inside native libraries
|
||||
remain libc-owned and are forwarded to native free/realloc. Resize of a mapped
|
||||
buffer preserves its contents and leaves the old allocation intact on failure.
|
||||
Resize of a libc-owned buffer remains a libc operation, including growth beyond
|
||||
the threshold. Do not pass a mapped pointer to an API that privately frees or
|
||||
reallocates it inside an unwrapped native library.
|
||||
|
||||
Patch 0076 reports 7z allocation/header/extraction failures without filenames
|
||||
and checks allocation before copying extracted content. It logs errors only.
|
||||
|
||||
|
||||
The mGBA libretro callback converts native XBGR to its declared XRGB8888 format
|
||||
using `ps5-video.h` and a separate bounded video buffer. It leaves the renderer's
|
||||
pixels intact for duplicate/cached frames. Upstream's 16-bit-only colour
|
||||
correction/interframe-blending routines are not enabled by this 32-bit build.
|
||||
|
||||
Patch 0077 sets RGBA menu image decoding on every Vulkan context initialization
|
||||
and requests RGBA textures for those decoded pixels. This is separate from core
|
||||
XRGB frames. The Vulkan software-framebuffer callback returns false: callers
|
||||
must retain their own original pixels, since exposing RGBA staging memory as
|
||||
writable XRGB would let upload conversion mutate cached frames while paused.
|
||||
FCEUmm uses its own buffer when that optional callback is unavailable.
|
||||
|
||||
On normal frontend return, `catchReturnFromMain` requests
|
||||
`sceSystemServiceLoadExec("exit", nullptr)` and waits for asynchronous shell
|
||||
termination on success. RetroArch has already cleaned up drivers and saved its
|
||||
configuration. A failed request is logged and falls back to the existing CRT
|
||||
termination path; it must not be described as a successful clean exit.
|
||||
|
||||
### Padded core presentation
|
||||
|
||||
Patch 0078 extends the existing 256-byte texture-row workaround to software
|
||||
core presentation. The filter chain carries logical width separately from the
|
||||
sampled image width and crops both triangle UVs to that initialized region.
|
||||
Each pass owns one small vertex buffer per retired sync slot, so CPU updates do
|
||||
not overwrite another in-flight frame. Swapchain reconstruction releases these
|
||||
buffers. Hardware-provided images and intermediate pass outputs keep their own
|
||||
extents; sampled format substitution is applied before computing padded width.
|
||||
The host regression covers FCEUmm, the blank menu frame, GBA and GB widths across
|
||||
repeated transitions. Console flicker acceptance is recorded in `evidence/mgba-native/`. This is
|
||||
not validation of arbitrary Slang presets, history sampling or linear-filter
|
||||
edge behaviour; those remain separate milestones.
|
||||
|
||||
|
||||
## Snes9x core build and colour contract
|
||||
|
||||
`make snes9x` runs `tools/build-snes9x.sh` against pinned official libretro source
|
||||
`fae2fea08f74180759ef540ee94259213f503480` (Snes9x 1.63). Its own libretro Makefile
|
||||
selects the sources; CC/CXX/AR/LD are explicitly the project's SDK wrappers.
|
||||
LTO remains off as upstream defaults, and upstream's no-strict-aliasing,
|
||||
no-exceptions/no-RTTI flags remain. No alternate SDK, CRT, SDL or host library
|
||||
is linked. The existing native linker script separates RX/R/RW pages. Native
|
||||
imports and C++ SDK runtime references are bound by the frontend's explicit
|
||||
union table; `-z undefs` permits these at core link, while the title link must
|
||||
resolve every entry. Metadata uses the same official core-info revision as the
|
||||
other cores; its upstream display_version says 1.61, while the core reports 1.63
|
||||
plus its source revision. The metadata otherwise stays upstream unchanged.
|
||||
|
||||
The renderer retains RGB565 including its NTSC and hires paths. The callback
|
||||
patch converts to a separate bounded XRGB8888 buffer, reports XRGB8888 at both
|
||||
normal and subsystem load sites, and supplies the converted 32-bit pitch.
|
||||
The existing frontend XRGB-to-RGBA conversion then feeds matching staging and
|
||||
sampled textures. Cached native pixels remain unchanged. Tests cover every
|
||||
RGB565 value end to end, non-tight pitch and 256/512/602/1024-pixel widths through
|
||||
478 rows. These tests do not by themselves establish console acceptance of
|
||||
NTSC filtering, HD Mode 7 or interlaced games.
|
||||
|
||||
Snes9x has C++ globals. `tooling/native/core_cxx_runtime.cpp` is linked locally
|
||||
inside this core, hidden by upstream's version script. Its own __cxa_atexit list
|
||||
runs in reverse registration order via a fini-array callback, so unloaded code
|
||||
is never retained in the process-wide exit registry. The loader accepts bounded
|
||||
DT_FINI_ARRAY/DT_FINI_ARRAYSZ, validates all targets before any initializer, and
|
||||
runs finalizers in reverse array order on the last close before unmapping.
|
||||
Reload gets a new registry; a rejected load executes no callbacks. This does not
|
||||
add TLS, legacy init/fini functions or exception-unwind registration support.
|
||||
As with initializers, lifecycle callbacks must not reenter this bounded loader.
|
||||
|
||||
## FBNeo native core contract
|
||||
|
||||
`make fbneo` uses pinned official libretro/FBNeo source
|
||||
`6bb3167a044e19e7106a5110d5531aa9c6afa96f` and the project's native SDK wrappers.
|
||||
The full upstream source selection remains, including ZIP/7z and CHD support;
|
||||
no ROM, BIOS, samples or user-provided metadata is shipped. Official core-info
|
||||
revision `5a74858ab2f7a50cebb5a6330895bc38899531c0` supplies the `.info` file.
|
||||
Archive digests are verified before fresh extraction. The local make wrapper
|
||||
removes obsolete GCC `-fforce-addr`, selects C++11 for SDK libc++, and disables
|
||||
exceptions/RTTI because the native module loader has no unwind registration.
|
||||
The shared core-local destructor registry handles C++ globals before unmapping.
|
||||
Native imports are included in the title's explicit union table, so the core
|
||||
and updated frontend must be deployed together.
|
||||
|
||||
Native 32-bit FBNeo output is already XRGB8888 (0x00RRGGBB). Drivers flagged
|
||||
16-bit-only retain RGB565 internally; the video callback converts those frames
|
||||
into a separate XRGB8888 buffer with a 32-bit pitch. Source pixels are never
|
||||
mutated, and NULL duplicate-frame callbacks remain NULL. The existing frontend
|
||||
converts XRGB into RGBA for the Vulkan texture upload. This is CPU emulation and
|
||||
software rendering with Vulkan presentation, not Vulkan hardware emulation.
|
||||
|
||||
The MPEG layer 2/AMM decoder uses an exception solely to escape an exhausted
|
||||
bit reader. The native patch substitutes an instance-local setjmp/longjmp
|
||||
escape and the same false return. The traversed decoder frames have only
|
||||
trivial local variables; no C++ destructor is bypassed. This is not general
|
||||
exception support. Differential host tests compare the original and adapted
|
||||
real decoder on complete and truncated synthetic layer 2 and AMM input. Core metadata
|
||||
queries also use a bounded static version string, avoiding upstream's
|
||||
unowned duplicate on each query.
|
||||
|
||||
At runtime FBNeo sets need_fullpath=true and block_extract=true: RetroArch
|
||||
passes the whole archive to the core. Do not unpack arcade ZIP/7z collections
|
||||
as single-content archives. BIOS lookup includes the frontend's system/fbneo/
|
||||
and system/ directories and the content directory. User BIOS/ROM sets must
|
||||
match the pinned core's requirements; a successful build is not a compatibility
|
||||
claim for every supported board, disc format or subsystem.
|
||||
|
||||
FBNeo's writable driver catalogue uses two allocations: a bulk name store and
|
||||
its pointer tables. Thousands of individual sub-1-MiB allocations otherwise
|
||||
exhaust SceLibcInternal's small heap during BurnLibInit. The name store crosses
|
||||
the frontend allocator's existing 1 MiB mapping threshold; that allocator and
|
||||
all other cores stay unchanged. Catalogue construction validates lengths and
|
||||
allocations before changing driver pointers, and exit restores the original
|
||||
pointers before freeing storage. Repeated exit/reinitialization is safe.
|
||||
Initialization failure is logged and causes regular/subsystem content loads to
|
||||
return false. No partially initialized catalogue is used for game execution.
|
||||
|
||||
## Genesis Plus GX native core contract
|
||||
|
||||
`make genesis-plus-gx` uses official libretro/Genesis-Plus-GX source
|
||||
`c2838c7dc4236fc2fe94e5dbd08b41486067918e` (archive SHA-256
|
||||
`7ba2eab9d6dae71bb42e8208573300ad3475a4263e860178c4d19c36d85fc92b`).
|
||||
Official core-info comes from `5a74858ab2f7a50cebb5a6330895bc38899531c0`,
|
||||
SHA-256 `9793bff8d9e298a7ee0c94c0511dab242200ca60fbe87e3720eb9231a3e0166a`.
|
||||
The supplied .info is reference material; only the hash-checked official file is
|
||||
staged in info/ and cores/. The fresh source build uses this project's explicit
|
||||
SDK compiler/archiver wrappers and native linker script/import union, without a
|
||||
payload CRT. `HAVE_CDROM=0` disables Linux-host autodetection of physical CD-ROM
|
||||
access, retaining CHD and disc-image codecs. `ZSTD_TRACE=0` disables optional weak
|
||||
instrumentation hooks for which the title has no external provider. It does not
|
||||
disable core/frontend error logging. Source, metadata, SDK wrapper and port-input
|
||||
hashes are recorded in build/cores/genesis_plus_gx/build.json.
|
||||
|
||||
The software renderer, NTSC filters, cursors and Game Gear LCD persistence retain
|
||||
upstream RGB565 storage. A separate bounded XRGB8888 callback buffer preserves the
|
||||
cached source and the existing frontend's RGBA Vulkan upload contract. Conversion
|
||||
uses the actual 720-pixel input stride and byte viewport offset; output is packed
|
||||
at width*4 bytes per row. Rejected XRGB8888 negotiation rejects content through
|
||||
upstream cleanup. Invalid views are logged and never read out of bounds. Skipped
|
||||
frames still submit NULL. The patch normalizes libretro.c line endings only in
|
||||
the disposable extraction and corrects the unused-return update_geometry helper
|
||||
to void. Frontend video code and PS5_Vulkan require no change.
|
||||
|
||||
Upstream covers Mega Drive/Genesis, Master System, Game Gear, SG-1000 and Sega CD;
|
||||
this is a software core using Vulkan presentation, not a Vulkan hardware renderer.
|
||||
Its runtime uses need_fullpath=true and block_extract=false, allowing frontend
|
||||
archive extraction. Place region-specific Sega CD BIOS files in the configured
|
||||
system directory root, following the official .info names. No BIOS or ROM is
|
||||
bundled. Console acceptance is limited to the systems and transitions actually
|
||||
recorded in evidence; available build features do not establish full coverage.
|
||||
@@ -1,79 +0,0 @@
|
||||
# Releasing
|
||||
|
||||
What a published release must carry, and the order that makes the tag, the ZIP and
|
||||
the source agree. The history behind each rule is docs/releases/SOURCE_CORRESPONDENCE.md.
|
||||
|
||||
## What a release carries
|
||||
|
||||
1. **The title ZIP**, `PS5_RetroArch-<tag>.zip`, holding `PPSA99169/` with its
|
||||
`licenses/` folder: the licence texts each part requires, `components.json`
|
||||
(every part's licence and source revision, and the sha256 of every executable
|
||||
file) and `README.txt`. `tools/build-title.sh` writes it
|
||||
(`tools/stage-notices.py`, from `tooling/notices/components.json`).
|
||||
2. **The source archives** from `tools/source-bundle.py`, with their `SHA256SUMS` and
|
||||
`SOURCES.txt`, attached to the same release. The GPLv3 lets the source sit on
|
||||
another server with clear directions (section 6(d)), and GPLv2 counts access from
|
||||
the same place as the binary (section 3); attaching the archives satisfies both,
|
||||
and it covers the cores this project fetches from repositories it does not
|
||||
control. Genesis Plus GX's licence asks for the complete source of a modified
|
||||
redistribution, which the archives are.
|
||||
3. **The notes**, with a "Source" section naming the commit the ZIP was built from
|
||||
(it is the tag, see below) and the non-commercial parts (Snes9x, FBNeo, Genesis
|
||||
Plus GX): they may not be sold. FBNeo's licence also forbids asking for donations
|
||||
for a project that uses its code, so a release page carries no donation link.
|
||||
|
||||
## The order
|
||||
|
||||
1. **Commit and push everything that is linked in.** PS5_RetroArch itself, and every
|
||||
revision it pins or takes from a sibling: PS5_PayloadSDK (`sdk_revision`), each
|
||||
core fork, PS5_Vulkan (its `HEAD`) and the PS5_Mesa revision its RADV archive
|
||||
records. Check that each is on GitHub before building:
|
||||
|
||||
```bash
|
||||
gh api repos/mihawk-99/PS5_Mesa/commits/<revision> --jq .sha
|
||||
```
|
||||
|
||||
2. **Build from the clean tree**, naming the release:
|
||||
|
||||
```bash
|
||||
PS5_RELEASE_TAG=v0.6.0-alpha.6 bash tools/build-title.sh
|
||||
bash tools/verify.sh
|
||||
python3 tools/check-notices.py --release
|
||||
```
|
||||
|
||||
`--release` fails when any part was built from uncommitted source or has no
|
||||
published address.
|
||||
|
||||
3. **Make the source archives and the ZIP**:
|
||||
|
||||
```bash
|
||||
python3 tools/source-bundle.py dist/PPSA99169 dist/source-v0.6.0-alpha.6
|
||||
(cd dist && zip -r -X PS5_RetroArch-v0.6.0-alpha.6.zip PPSA99169)
|
||||
sha256sum dist/PS5_RetroArch-v0.6.0-alpha.6.zip
|
||||
```
|
||||
|
||||
An asset may be at most 2 GiB. The largest archives are PPSSPP (about 560 MB,
|
||||
its submodules included), MAME and the parts of LLVM RPCS3 links.
|
||||
|
||||
4. **Tag the built commit and push the tag before creating the release**, so GitHub
|
||||
does not make a tag from whatever `main` holds:
|
||||
|
||||
```bash
|
||||
git tag -a v0.6.0-alpha.6 -m "PS5 RetroArch v0.6.0-alpha.6" <built commit>
|
||||
git push PS5_RetroArch v0.6.0-alpha.6 # this repository's remote for github.com/mihawk-99/PS5_RetroArch
|
||||
gh release create v0.6.0-alpha.6 --verify-tag --prerelease \
|
||||
dist/PS5_RetroArch-v0.6.0-alpha.6.zip dist/source-v0.6.0-alpha.6/*
|
||||
```
|
||||
|
||||
5. **Read it back.** Download the ZIP from the release, compare its sha256 with step
|
||||
3, unpack it and run `python3 tools/check-notices.py <unpacked>/PPSA99169`.
|
||||
|
||||
## Parts that need a decision before they ship
|
||||
|
||||
- **RPCS3** is GPL-2.0-only and is combined with GPL-3.0-or-later code (this port's
|
||||
core runtime object is linked into it, and it runs against the title's GPL-3.0
|
||||
platform code). Do not ship it in a release until that is resolved; the audit's
|
||||
counsel packet sets out the options.
|
||||
- **Artwork whose origin is not recorded** (`sce_sys/pic0.dds`, `pic1.dds`) should be
|
||||
replaced with artwork whose licence is known, or its origin recorded, before the
|
||||
next release.
|
||||
@@ -1,679 +0,0 @@
|
||||
# RPCS3 as a libretro core: the port
|
||||
|
||||
This document is the audit behind porting RPCS3 (PlayStation 3) to the PS5
|
||||
title as `rpcs3_libretro.so`, and the plan it leads to. Progress and run
|
||||
results are recorded in `docs/ACTIVE.md` and `docs/PHASE_LOG.md`, not here.
|
||||
|
||||
## The objective
|
||||
|
||||
On my console, `rpcs3_libretro.so` boots God of War HD (the PSN release
|
||||
NPUA80490) through RetroArch's Load Content or `/app0/args.txt`. The game
|
||||
reaches gameplay, rendered by RPCS3's Vulkan renderer on RADV at a 300%
|
||||
resolution scale (3840×2160 internal for its 720p output). The evidence is:
|
||||
|
||||
- a RetroArch screenshot of gameplay whose picture I accept;
|
||||
- ten minutes of play with no crash;
|
||||
- the Close Content + reload battery passing;
|
||||
- each 10 s audio window's speed, recorded. Full speed is not part of the
|
||||
objective, but its measurement is.
|
||||
|
||||
What the console holds (read in place, never copied anywhere):
|
||||
|
||||
| Item | Where on the console |
|
||||
| --- | --- |
|
||||
| PS3 firmware | `/app0/system/RPCS3/PS3UPDAT.PUP` (206 MB) |
|
||||
| The game | `/app0/content/PS3/God of War.pkg` (6.3 GB), a PSN package |
|
||||
| Its licence | `/app0/content/PS3/UP9000-NPUA80490_00-GODOFWARHDUS0000.rap` |
|
||||
|
||||
Because the game is a package and not a disc folder, the first content path is
|
||||
"install the package and its RAP, then boot the installed game". The
|
||||
installed copy needs another 6.3 GB on the console's storage.
|
||||
|
||||
## The fork and the pin
|
||||
|
||||
`../PS5_RPCS3` is my fork of RPCS3/rpcs3 (github.com/mihawk-99/PS5_RPCS3,
|
||||
public), with an `upstream` remote. Its `main` starts at upstream
|
||||
**1707d7fc883e** (2026-09-28, "remove unnecessary #ifdef"): the newest master
|
||||
commit whose CI passed all thirteen checks, including RPCS3's FreeBSD build.
|
||||
The PS5's system software is FreeBSD-based, so that is the configuration
|
||||
closest to this port. Every port change is a commit on the fork's `main`, and
|
||||
`tools/build-rpcs3.sh` pins the fork by revision. Submodules RPCS3 names by
|
||||
relative URL (`../../llvm/llvm-project.git` and the rest) resolve from the fork
|
||||
as from upstream. A submodule that needs changes gets its own fork,
|
||||
`../PS5_<Name>`, referenced by relative URL, as PS5_Azahar does with
|
||||
PS5_Dynarmic.
|
||||
|
||||
## Licence
|
||||
|
||||
RPCS3 states its licence as GPL-2.0-only (its README; `LICENSE` is the GPLv2).
|
||||
The rest of the title is GPL-3.0-or-later (RetroArch, this port, the payload SDK
|
||||
fork and its platform layer), and GPL-2.0-only code and GPL-3.0 code cannot be
|
||||
combined into one work and distributed. Whether the core and the title are one
|
||||
work is not settled by the core being a separate `.so`; these are the facts that
|
||||
bear on it (2026-09-29):
|
||||
|
||||
- `rpcs3_libretro.so` statically links `tooling/native/core_cxx_runtime.cpp`
|
||||
(this repository, GPL-3.0-or-later by the repository's licence) through
|
||||
`PS5_CORE_LINK_INPUTS`, beside LLVM (Apache-2.0 WITH LLVM-exception, whose
|
||||
exception addresses GPLv2 combinations), FFmpeg and GNU libiconv
|
||||
(LGPL-2.1-or-later).
|
||||
- At load time the title's native loader binds the core's libc and file-system
|
||||
imports to the title's own wrappers and to the platform layer
|
||||
(`tools/core-imports.py`, `build/core_imports.inc`), and the core runs in the
|
||||
title's process, on the title's heap, through RetroArch's libretro and Vulkan
|
||||
context interfaces.
|
||||
|
||||
So it is not in a release. The ways out are a licence decision for the owner of
|
||||
the GPL-3.0 parts that touch the core (dual-licensing `core_cxx_runtime.cpp`
|
||||
and the platform layer's bound functions under a GPLv2-compatible licence, which
|
||||
is possible only for code whose authors agree), a change of interface, or
|
||||
counsel's view that the combination is not one work; the licence audit of
|
||||
2026-09-29 sets them out for that decision.
|
||||
|
||||
The title bundles no PS3 firmware, keys or games; RPCS3 installs the firmware
|
||||
the user supplies and decrypts packages with the user's own licence files
|
||||
(RAP); that audit covers those paths too.
|
||||
|
||||
## Phase 1: the audit
|
||||
|
||||
### 1. Dependencies
|
||||
|
||||
RPCS3 builds its emulator as the static library `rpcs3_emu`, and its Qt
|
||||
application links that. Upstream already builds `rpcs3_emu` without Qt for its
|
||||
Android port (`ANDROID` in `CMakeLists.txt`): Qt, the Qt UI and the OpenGL
|
||||
renderer are left out, OpenAL is compiled out (`WITHOUT_OPENAL`) and the
|
||||
bundled FFmpeg is not used. The libretro
|
||||
build follows that path, with a `LIBRETRO` switch of its own where Android's
|
||||
choices differ.
|
||||
|
||||
`3rdparty/` at the pin (submodule revisions in brackets):
|
||||
|
||||
| Item | What RPCS3 uses it for | Decision |
|
||||
| --- | --- | --- |
|
||||
| llvm (llvm-project `ca7933e4`, LLVM 22.1) | PPU and SPU recompilers | **Build**: a pinned static LLVM for `x86_64-sie-ps5`, X86 target only (below) |
|
||||
| asmjit (`416f7356`) | SPU ASMJIT recompiler, trampolines, JIT helpers | **Build**, its code memory through the platform layer (gap a) |
|
||||
| ffmpeg (RPCS3/ffmpeg-core `649d9d93`, prebuilt) | video and audio decoding (cellVdec, cellAdec, ATRAC3+, cellMusic) | **Build from source**: a pinned FFmpeg release, static, software decoders (below); the prebuilt archive has no PS5 build |
|
||||
| glslang (`f0bd0257`) | GLSL to SPIR-V for the Vulkan renderer's shaders | **Build** |
|
||||
| GPUOpen/VulkanMemoryAllocator (`1d8f600f`) | Vulkan memory | **Build** (header library) |
|
||||
| zlib (`da607da7`), zstd (`f8745da6`) | archives, save states, caches | **Build** |
|
||||
| libpng (`3061454d`), stblib (`013ac3be`), bcdec | images, texture decoding | **Build** (bcdec and stb are headers) |
|
||||
| yaml-cpp (`51a5d623`), pugixml (`c8033ce9`) | configuration, patches, XML | **Build** |
|
||||
| SoundTouch (`a0fba77b`) | audio time-stretch in the resampler | **Build** |
|
||||
| fusion (`93254240`) | motion-sensor fusion in `PadHandler.cpp` | **Build** (small C library) |
|
||||
| unordered_dense | hash maps | **Build** (header) |
|
||||
| protobuf (`edaa823d`) | RPCN (NP) messages | **Stub**: RPCN is compiled into `rpcs3_emu` unconditionally, so a `LIBRETRO` option leaves the NP network clients out with an offline implementation. Networking is dropped |
|
||||
| wolfssl (`3c5eead4`), curl (`01346829`) | RPCN and clans over TLS | **Drop**, with the network clients (above) |
|
||||
| miniupnp (`d66872e3`) | UPnP port mapping | **Drop**, with `upnp_handler` stubbed |
|
||||
| libusb (`87a55632`) | USB passthrough (`sys_usbd`, `usb_device`) | **Stub**: no PS5 backend; `sys_usbd` reports no devices |
|
||||
| hidapi | HID pads, only in `rpcs3/Input` | **Drop** (the libretro pad handler replaces `rpcs3/Input`) |
|
||||
| rtmidi (`1e5b4992`) | MIDI instruments (Rock Band) | **Build** with RtMidi's dummy API, or stub the MIDI devices |
|
||||
| OpenAL | microphone capture in cellMic | **Drop**: `WITHOUT_OPENAL`, as on Android |
|
||||
| cubeb, FAudio, XAudio2 | audio output backends | **Drop**: a libretro audio backend replaces them |
|
||||
| libsdl-org/SDL | input, camera, FAudio's dependency | **Drop** |
|
||||
| opencv | PS Move tracking in `rpcs3/Input` | **Drop** |
|
||||
| discord-rpc, feralinteractive (GameMode), pine | Discord, GameMode, PINE IPC | **Drop** |
|
||||
| 7zip | not used by `rpcs3_emu` at the pin | **Drop** |
|
||||
| MoltenVK, GL | macOS Vulkan, OpenGL headers | **Drop** (no OpenGL renderer, as on Android) |
|
||||
| Qt 6 | the UI | **Drop** |
|
||||
|
||||
CMake options (top-level `CMakeLists.txt`):
|
||||
|
||||
| Option | Default | Port |
|
||||
| --- | --- | --- |
|
||||
| `USE_NATIVE_INSTRUCTIONS` | ON | OFF; `-march=znver2` instead, the PS5's Zen 2 (AVX2, FMA, BMI2), set in the toolchain file |
|
||||
| `WITH_LLVM` | ON | ON |
|
||||
| `BUILD_LLVM` | OFF | OFF: LLVM is built separately and found through `LLVM_DIR` |
|
||||
| `STATIC_LINK_LLVM` | OFF | ON |
|
||||
| `USE_FAUDIO`, `USE_SDL`, `USE_LIBEVDEV`, `USE_DISCORD_RPC`, `USE_GAMEMODE` | ON/OFF | OFF |
|
||||
| `USE_VULKAN` | ON | ON |
|
||||
| `USE_PRECOMPILED_HEADERS` | OFF | OFF (ccache) |
|
||||
| `USE_SYSTEM_*` | mixed | OFF throughout: every library is built for the PS5 or dropped |
|
||||
| `HAS_MEMORY_BREAKPOINTS`, `BUILD_RPCS3_TESTS`, `RUN_RPCS3_TESTS` | OFF | OFF |
|
||||
| `USE_LTO` | ON | OFF: a 1 GiB-class shared object links in time without it; revisit when it runs |
|
||||
|
||||
**LLVM.** RPCS3 requires LLVM 18 or newer and builds 22.1 from its submodule.
|
||||
I build that same revision (`ca7933e4`) once, as a pinned static install
|
||||
(`tools/build-llvm.sh` into `.deps/native/llvm-ps5`), with `prospero-clang`,
|
||||
ccache, `LLVM_TARGETS_TO_BUILD=X86`, and no tools, tests, docs or examples, and
|
||||
point RPCS3 at it with `LLVM_DIR`. Keeping it out of RPCS3's build tree means a
|
||||
change to RPCS3 never rebuilds LLVM. LLVM's own `Support` layer assumes Unix
|
||||
facilities (`mmap`, `sysconf`, `dladdr`, signals) that the platform layer and
|
||||
the core loader's import table provide or refuse; the link shows which. The
|
||||
archive size, and what it adds to the core, are measured at step 3; LLVM with
|
||||
only the X86 target is on the order of a few hundred MB of archives, of which
|
||||
the core links the code generator, MCJIT and RuntimeDyld.
|
||||
|
||||
**FFmpeg.** RPCS3's prebuilt `ffmpeg-core` has no build for this console, so I
|
||||
build FFmpeg from a pinned source release: static, no programs, no network, no
|
||||
hardware acceleration. The decoders are the ones the PS3's media libraries
|
||||
need (H.264, MPEG-2, MPEG-4 Part 2, ATRAC3, ATRAC3+, AAC, MP3, AC-3, LPCM), with
|
||||
libavformat's MP4, MPEG-PS and raw demuxers, libswscale and libswresample. No
|
||||
encoders: video recording is dropped. Software decoding comes first. The
|
||||
console's hardware video decoder, which ProsperoLight uses, is a later option
|
||||
for H.264.
|
||||
|
||||
### 2. The frontend
|
||||
|
||||
RPCS3 drives `Emu` (the `Emulator` in `rpcs3/Emu/System.h`) from a frontend
|
||||
that fills `g_emu_callbacks` (`rpcs3/Emu/emu_callbacks.h`: 49 `std::function`
|
||||
hooks) and calls `Emu.Init()`. `headless_application` shows the minimum: it is
|
||||
a `QCoreApplication` whose callbacks create null renderers and handlers, and
|
||||
it refuses Vulkan. The libretro layer, `rpcs3/libretro/` in the fork, replaces
|
||||
it without Qt:
|
||||
|
||||
- **Entry points.** `retro_init` sets RPCS3's directories (below) and fills the
|
||||
callbacks. `retro_load_game` installs the firmware if it is missing, installs
|
||||
a package if the content is one, then boots. `retro_run` runs the queued
|
||||
main-thread calls (`call_from_main_thread`, which RPCS3 uses for UI-side work
|
||||
on the frontend's thread), presents the newest frame and pushes audio.
|
||||
`retro_unload_game` stops `Emu` and waits for its threads.
|
||||
- **Vulkan.** RPCS3 creates its own instance (API 1.2) and device
|
||||
(`vkutils/instance.cpp`, `vkutils/device.cpp`). Through RetroArch's
|
||||
context-negotiation interface (v2) the core creates the instance at the
|
||||
version RPCS3 asks for (`create_instance`), and the device with RPCS3's
|
||||
feature and extension choices (`create_device2`). RetroArch uses that
|
||||
instance and device. RetroArch's own instance is 1.1, which is why the core
|
||||
creates it. RPCS3's existing `native_swapchain_base` owns its presentable
|
||||
images. A `swapchain_libretro` on top of it presents by handing the finished
|
||||
image to RetroArch with `set_image` and `set_signal_semaphore`, in the layout
|
||||
RetroArch expects. Every `vkQueueSubmit` and present that RPCS3's RSX thread
|
||||
makes on the queue it shares with RetroArch goes between the interface's
|
||||
`lock_queue` and `unlock_queue`.
|
||||
- **Audio.** A `LibretroBackend : AudioBackend` takes cellAudio's output
|
||||
through the backend's write callback, resamples it to RetroArch's rate
|
||||
(48 kHz; RPCS3's audio is 48 kHz already, so resampling is a copy until a
|
||||
game asks otherwise), converts it to 16-bit stereo and queues it. `retro_run`
|
||||
hands it over with the batch callback.
|
||||
- **Input.** A libretro pad handler (a `PadHandlerBase`) maps the RetroPad to
|
||||
a DualShock 3: buttons, both sticks and the analogue triggers. Pressure
|
||||
sensitivity comes from the digital state. Keyboard, mouse, camera and music
|
||||
handlers are the null ones.
|
||||
- **Firmware.** The Qt application installs `PS3UPDAT.PUP` in
|
||||
`main_window::HandlePupInstallation` (about 320 lines). The libretro layer
|
||||
does the same work without Qt: `pup_object` (`Loader/PUP`), the update
|
||||
packages' TAR extraction and the SPRX decryption. It runs on first boot when
|
||||
`utils::get_firmware_version()` is empty and reports progress through the
|
||||
RetroArch message interface.
|
||||
- **Content.** The formats, in order:
|
||||
1. A PSN package plus its RAP. This is the game at hand. The layer installs
|
||||
it with `package_reader` (`Crypto/unpkg`), as
|
||||
`main_window::HandlePackageInstallation` does (about 380 lines), and
|
||||
copies the RAP to `dev_hdd0/home/<user>/exdata/`
|
||||
(`main_window::InstallFileInExData`), then boots the installed
|
||||
`dev_hdd0/game/<title>/USRDIR/EBOOT.BIN`. An already installed package is
|
||||
not installed again.
|
||||
2. A disc game's folder (`PS3_GAME/USRDIR/EBOOT.BIN`).
|
||||
3. An ISO, which the pinned revision supports (`Loader/ISO.cpp`).
|
||||
- **Where RPCS3 keeps its files.** RPCS3's emulator directory (`dev_flash`,
|
||||
`dev_hdd0`, config, caches) is `/app0/system/RPCS3/`, next to the firmware.
|
||||
Folders are 0777 and files at least 0666, so everything stays reachable over
|
||||
FTP.
|
||||
- **Core options and PS5 defaults.** Resolution scale 300%. Shader mode
|
||||
asynchronous with the shader interpreter (`async_with_interpreter`, already
|
||||
upstream's default), so no game waits for a shader compile or relies on a
|
||||
warm cache. PPU and SPU decoders LLVM (upstream's defaults). Options are the
|
||||
libretro v2 kind, written to RPCS3's configuration when content loads
|
||||
(Phase 2 below).
|
||||
- **Core info.** libretro-core-info has no RPCS3 file, so
|
||||
`tooling/rpcs3/rpcs3_libretro.info` is in this repository (name, extensions
|
||||
`pkg|iso|bin|elf|self|sfo`, firmware entry, GPLv2, `needs_fullpath`).
|
||||
- **Reference ports.** Dolphin's `Source/Core/DolphinLibretro/` (the Vulkan
|
||||
negotiation, its instance and device hooks and main-thread handling),
|
||||
PPSSPP's libretro port and Azahar's.
|
||||
|
||||
### 3. Platform gaps
|
||||
|
||||
Each gap is either already measured on the console (`docs/PROBE.md`, the
|
||||
platform layer's probes) or gets a probe step in the platform layer
|
||||
(`src/probe.c`, host tests in `tests/test_platform.c`), run inside the title
|
||||
with `/app0/platform-probe.txt`.
|
||||
|
||||
**(a) Code memory for LLVM and asmjit.** RPCS3 reserves code areas with
|
||||
`utils::memory_reserve(size, can_be_jit=true)` and commits them
|
||||
read-write-execute (`protection::wx`). Measured: direct memory mapped
|
||||
read-write and then given execute runs, read-write-execute included, and a
|
||||
protection change costs about 26 µs. `ps5_exec_allocate` has had no limit on
|
||||
live blocks since SDK d3e8625: 2,000 4 KiB blocks and 300 64 KiB regions ran on
|
||||
the console. LLVM uses the Small code model with PIC, so its code reaches
|
||||
RPCS3's helpers through RuntimeDyld stubs and needs no placement near the
|
||||
core. asmjit commits its 2 GiB area up front only under `CAN_OVERCOMMIT`
|
||||
(Linux), which the PS5 branch leaves undefined, so it grows on demand.
|
||||
*To measure:* a probe step that reserves a 2 GiB range, commits 64 KiB pieces
|
||||
read-write-execute on demand, and runs code in each, with the time a commit
|
||||
takes.
|
||||
|
||||
**(b) Address-space reservations.** RPCS3 reserves its guest memory in 4 GiB
|
||||
steps from `0x3_0000_0000` upward (`Emu/Memory/vm.cpp`):
|
||||
|
||||
| Area | Size |
|
||||
| --- | --- |
|
||||
| `g_base_addr` + `g_sudo_addr` | 8 GiB |
|
||||
| `g_exec_addr` | 12 GiB |
|
||||
| `g_hook_addr` | 32 GiB |
|
||||
| `g_stat_addr` | 4 GiB (`g_free_addr`, for SPU, starts right after it) |
|
||||
|
||||
That is 56 GiB, plus JIT areas (2 GiB for asmjit, 768 MiB for each LLVM
|
||||
memory manager) and RSX's mappings. Measured (`docs/PROBE.md`): the GPU window
|
||||
is `0x2_0000_0000`–`0x2_FFFF_FFFF`; from `0x4_0000_0000` at most 15 GiB; from
|
||||
`0x10_0000_0000` to `0x80_0000_0000` 16 GiB is reserved at the hint itself;
|
||||
nothing is granted at or above 1 TiB; and a hint whose free space is too small
|
||||
is refused, not moved. The PS5 branch of `memory_reserve` therefore starts at
|
||||
`0x10_0000_0000`, through `ps5_vrange_reserve`, which never draws on the
|
||||
title's 448 MiB flexible pool, where an anonymous `mmap` reservation would.
|
||||
*To measure:* one probe step reserving RPCS3's layout, 56 GiB in the sizes
|
||||
above from `0x10_0000_0000`, to confirm 16 GiB is not a per-reservation cap
|
||||
for 32 GiB.
|
||||
|
||||
**(c) Guest memory as views.** RPCS3's `utils::shm` creates a memory object
|
||||
(`memfd_create`, or `shm_open(SHM_ANON)` on FreeBSD) and maps it at several
|
||||
addresses: `g_base_addr` and `g_sudo_addr` see the same memory at different
|
||||
protections. The PS5 branch uses `ps5platform/shm.h`: `ps5_shm_create` (one
|
||||
direct-memory object), `ps5_shm_map` at fixed addresses inside a
|
||||
`ps5_vrange_reserve` range, and `ps5_memfd_create` where RPCS3 wants a
|
||||
descriptor. The platform tests already cover several views of one object.
|
||||
*To measure:* 4 GiB of direct memory as two views, written through one and
|
||||
read through the other, and the time a 64 KiB view takes to map.
|
||||
|
||||
**(d) 16 KiB host pages.** RPCS3 already handles hosts whose pages exceed
|
||||
4 KiB (Apple Silicon): `vm.cpp` stops protecting 4 KiB guest blocks one by one
|
||||
when `utils::get_page_size() > 4096` and maps them read-write, and every
|
||||
`mprotect` is rounded to the host page. The RSX texture cache still protects
|
||||
pages to catch CPU writes, now 16 KiB at a time, so a write near a cached
|
||||
texture invalidates a wider range. *To measure:* what `sysconf(_SC_PAGESIZE)`
|
||||
returns in the title (it must be 16384; the platform layer answers it
|
||||
otherwise), and the texture cache's protection changes per frame in the game,
|
||||
from RPCS3's own counters, at 26 µs each.
|
||||
|
||||
**(e) `thread_local` on emulated TLS.** The core loader has no PT_TLS support,
|
||||
so a core's `thread_local` goes through `__emutls_get_address`. RPCS3 has 93
|
||||
`thread_local` uses. The hot ones are `cpu_thread::g_tls_this_thread`,
|
||||
`vm::g_tls_locked` and atomic.cpp's wait callbacks, all read on every guest
|
||||
reservation and wait. *To measure:* the cost of an emulated-TLS read on the
|
||||
console (a probe step timing `__emutls_get_address` over a million reads),
|
||||
then RPCS3's rate of those reads in the game. If it matters, the fork keeps
|
||||
those few variables in a per-thread structure the thread already carries.
|
||||
|
||||
**(f) Atomic waits.** On FreeBSD, RPCS3's `atomic.cpp` uses its `USE_STD`
|
||||
path (`std::condition_variable`), and `Utilities/sync.h` emulates `futex()`
|
||||
off Linux, so it needs only pthread mutexes and condition variables, which the
|
||||
console exports. `_umtx_op` is exported too, if a faster path is wanted later.
|
||||
Nothing to add.
|
||||
|
||||
**(g) Faults and `ucontext`.** RPCS3's access-violation handler
|
||||
(`Utilities/Thread.cpp`) reads `uc_mcontext.mc_rip`, `mc_err` and the general
|
||||
registers on FreeBSD. The SDK's `sys/_ucontext.h` corrects the console's
|
||||
machine-context layout, and the platform probe already backpatches 200 fault
|
||||
sites from a handler. RPCS3 also installs `SIGSEGV`, `SIGBUS`, `SIGILL` and
|
||||
`SIGPIPE` handlers process-wide. In the title those would sit beside the
|
||||
title's own crash reporter. *To measure:* a guest page fault handled and
|
||||
resumed inside the core, with the title's reporter still catching a genuine
|
||||
crash.
|
||||
|
||||
**(h) Threads.** RPCS3's `named_thread` calls `pthread_create`, which the
|
||||
title binds to its factory (`src/core_threads_ps5.cpp`, 2 MiB minimum stack).
|
||||
RPCS3 creates a PPU thread for each guest thread, six SPU threads, the RSX
|
||||
thread and a pool of LLVM compile workers sized from
|
||||
`sysconf(_SC_NPROCESSORS_ONLN)`. Measured by the thread probe (SDK 9a57ba7):
|
||||
thirteen CPUs (0–12) are available to the title, and threads run at policy 1,
|
||||
priority 700, in a range of 256 to 767. A priority set on a running thread
|
||||
reads back. `pthread_getaffinity_np` with FreeBSD's `cpuset_t` returns ERANGE,
|
||||
and RPCS3's `set_thread_affinity_mask` uses exactly that form. The platform
|
||||
layer gets `ps5_pthread_setaffinity_np` and `ps5_pthread_getaffinity_np` (the
|
||||
exported `scePthreadSetaffinity`/`scePthreadGetaffinity` with a 64-bit mask,
|
||||
or the size the kernel accepts), bound through `tools/core-imports.py`.
|
||||
`sysconf(_SC_NPROCESSORS_ONLN)` must answer 13. *To measure:* the affinity
|
||||
calls round-tripping on the console, and what sysconf answers.
|
||||
|
||||
### 4. Risks
|
||||
|
||||
| Risk | How it is measured |
|
||||
| --- | --- |
|
||||
| LLVM does not build for the PS5 target, or its `Support` layer needs what the console lacks | the LLVM build (step 1) and the core's link and import report (step 3) |
|
||||
| The core is too large for the loader (1 GiB image cap) | `check-core.py` and the loader's `mapped_bytes` (step 4) |
|
||||
| Guest reservations do not fit below 1 TiB with the GPU window avoided | the reservation probe (gap b), then `Emu.Init` on the console |
|
||||
| Emulated TLS costs too much in the hottest paths | the TLS probe (gap e), then the sampler in the game |
|
||||
| RetroArch's Vulkan interface cannot give RPCS3 the device it needs (1.2 core, extensions) | device creation through `create_device2` (step 4), and RPCS3's own capability log |
|
||||
| Queue sharing with RetroArch stalls the RSX thread | the sampler on the RSX thread; frame timing |
|
||||
| Firmware install needs host facilities the title lacks | step 5's install run, file by file |
|
||||
| Shader compiles cost speed | windows during the game's first minutes with an empty cache |
|
||||
| The package install fills the console's storage | the install run's free-space check before it starts |
|
||||
| 16 KiB pages widen the texture cache's invalidations | RPCS3's texture-cache counters in the game |
|
||||
| The CPU cannot hold full speed (not part of the objective) | the 10 s windows, reported as they are |
|
||||
|
||||
### 5. The ladder
|
||||
|
||||
Each step ends with its evidence in `docs/ACTIVE.md` and a dated entry in
|
||||
`docs/PHASE_LOG.md`.
|
||||
|
||||
1. **Fork and build scripts.** The fork at the pin; `tools/build-llvm.sh` (the
|
||||
pinned static LLVM); `tools/build-ffmpeg.sh` (the pinned FFmpeg);
|
||||
`tools/build-rpcs3.sh` on `tools/core-fork.sh`; `tooling/rpcs3/ps5-toolchain.cmake`;
|
||||
`rpcs3` in `core_names`. *Evidence:* LLVM and FFmpeg archives with their sizes,
|
||||
and `rpcs3_emu` compiling for the PS5 target.
|
||||
2. **Host build (optional).** The same libretro core for the host, debugged in
|
||||
a desktop RetroArch if this host's Vulkan driver allows. *Evidence:* the
|
||||
host core reaching the firmware check.
|
||||
3. **Link and ABI.** The PS5 core links and passes `tools/check-core.py`;
|
||||
missing libc and kernel functions go into the platform layer with aliases
|
||||
in `tools/core-imports.py`. *Evidence:* `check-core.py` PASS and the import
|
||||
list.
|
||||
4. **Load.** The core loads on the console and `retro_init` returns; the probe
|
||||
steps for gaps (a)–(h) run inside the title. *Evidence:* the loader's
|
||||
`ready` line, the probe's checks.
|
||||
5. **Firmware, then a homebrew program.** `PS3UPDAT.PUP` installs from
|
||||
`/app0/system/RPCS3/`, then a small PS3 homebrew or test program boots
|
||||
before any commercial game. *Evidence:* the firmware version RPCS3 reports,
|
||||
and the program's output.
|
||||
6. **The game.** God of War HD installed from its package and RAP, booted at
|
||||
100%, then at 300%: screenshots, the speed of each window, the Close
|
||||
Content + reload battery and a ten-minute run. *Evidence:* the screenshot I
|
||||
accept, the windows, the battery lines and the ten-minute trace.
|
||||
|
||||
## Phase 2: what the port turned out to need
|
||||
|
||||
Where the build and the first code differ from the audit above.
|
||||
|
||||
**Dependencies.**
|
||||
|
||||
- *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, 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`),
|
||||
with RPCS3's own component list and NASM for its x86 assembly.
|
||||
- *GNU libiconv* 1.18, static, comes from its signed release tarball
|
||||
(`tools/build-libiconv.sh`). `cellL10n` converts the PS3's character sets
|
||||
with iconv, and the console's libc has none.
|
||||
- *OpenAL Soft* is built rather than compiled out: `cellMic` includes its
|
||||
headers outside `WITHOUT_OPENAL` off Android. Every backend is off, so it
|
||||
opens no device. Its sources are C++20 modules; CMake's dependency scan runs
|
||||
through `tooling/rpcs3/clang-scan-deps`, which gives the scanner the SDK's
|
||||
target and headers.
|
||||
- *zlib* is the frontend's pinned build. libpng's generated configuration does
|
||||
not see the submodule's headers.
|
||||
- *pkg-config* answers nothing in the toolchain file
|
||||
(`tooling/rpcs3/ps5-toolchain.cmake`). The host's packages would add the
|
||||
host's `/usr/include`.
|
||||
- The network clients (RPCN, clans, UPnP) still build, with wolfSSL and curl.
|
||||
They are not stubbed yet; the title has no network use for them.
|
||||
- The target defines `__FreeBSD__` as 9, so RPCS3's FreeBSD branches take their
|
||||
pre-13 paths (`shm_open(SHM_ANON)` for `memfd_create`). The PS5 branch below
|
||||
replaces them where memory is concerned.
|
||||
|
||||
**Vulkan** (`rpcs3/Emu/RSX/VK/vkutils/swapchain_libretro.*` in the fork).
|
||||
RetroArch creates the instance with the core version RPCS3 asks for (1.2), and
|
||||
the device through `create_device2`. The core runs RPCS3's own
|
||||
`render_device::create` there, so the device has RPCS3's features and
|
||||
extensions plus RetroArch's; when the renderer starts, `render_device::create`
|
||||
runs again and adopts that device instead of creating one. Features RPCS3
|
||||
enables only for some settings are the same whatever the game's configuration,
|
||||
so the device a boot needs is the one RetroArch made. Every submission and
|
||||
device-wide wait takes RetroArch's queue lock inside RPCS3's own submit lock.
|
||||
The swapchain is the core's own images, sampled by RetroArch:
|
||||
|
||||
- acquiring an image signals RPCS3's semaphore with an empty submission;
|
||||
- presenting waits on RPCS3's semaphore and signals a fence;
|
||||
- `retro_run` hands RetroArch the newest finished image with `set_image`, after
|
||||
its fence;
|
||||
- an image RetroArch showed is reused only after RetroArch's frame index that
|
||||
last read it comes around again, which is when RetroArch has waited for that
|
||||
frame.
|
||||
|
||||
The images are not RPCS3's allocations, so they outlive the renderer while
|
||||
RetroArch may still show one; they go when a newer frame replaces them or the
|
||||
context is destroyed. The game boots in the first `retro_run` after RetroArch's
|
||||
context is up, because RPCS3 builds its renderer while booting.
|
||||
|
||||
**Input** (`rpcs3/libretro/libretro_pad_handler.*`). RetroPads 1 to 4 are the
|
||||
PS3's controllers, through a pad handler on RPCS3's generic machinery (SDL's
|
||||
value conventions). The core compiles `rpcs3/Input/pad_thread.cpp` and what it
|
||||
needs, without the HID handlers (no hidapi on the console), and writes the
|
||||
input configuration that names the RetroPads before the pad thread loads it.
|
||||
|
||||
**Memory** (the `__PROSPERO__` branch of `rpcs3/util/vm_native.cpp`). RPCS3's
|
||||
reservations, commits and shared memory go through the platform layer:
|
||||
|
||||
- reservations are `ps5_vrange_reserve` and `ps5_vrange_reserve_at`;
|
||||
- commits and decommits are `ps5_vrange_commit` and `ps5_vrange_decommit`,
|
||||
direct memory in 64 KiB units (SDK 7f3d3ff);
|
||||
- `utils::shm` is a `ps5_shm` direct-memory object with its views.
|
||||
|
||||
Guest memory starts at 64 GiB (`0x10_0000_0000`). The 32 GiB hook area is only
|
||||
reserved: nothing reads it, and a 32 GiB copy-on-write view of zeros has no
|
||||
direct-memory form. The page size is the kernel's 16 KiB. The core's
|
||||
reservations are released, with the memory committed in them, when the core is
|
||||
unloaded, so the next load finds its addresses free.
|
||||
|
||||
**Faults.** In the libretro core, a fault RPCS3 does not handle goes back to the
|
||||
handler it replaced (the title's crash reporter), and the title's handlers
|
||||
return when the core is unloaded.
|
||||
|
||||
**Platform gaps closed in the SDK (8160289).** Each is aliased in
|
||||
`tools/core-imports.py`.
|
||||
|
||||
- No export at all: `accept4`, `getpagesizes`, `in6addr_any`.
|
||||
- Exported, but resolved to nothing in a title: `gai_strerror`, `statfs`,
|
||||
`fstatfs`, `umask`, `fork`, `setsid`, `wait4`.
|
||||
- Formerly taken from the SDK's static payload libc, whose system calls a title
|
||||
may not make: `syscall` (only `SYS_write`), `getpwnam_r`, `posix_madvise`,
|
||||
`strsignal`.
|
||||
- Answered differently on the console: `pthread_getaffinity_np` and
|
||||
`pthread_setaffinity_np` (the 64-bit mask), `sysconf` (13 CPUs, not 16),
|
||||
`pthread_exit` (a thread's `thread_local` destructors first).
|
||||
- Refused: `realpath` answers EPERM, even for `/app0`, so libc++'s
|
||||
`std::filesystem::canonical` and `weakly_canonical` came back empty and the
|
||||
package installer could not normalise its path. `ps5_realpath` makes the path
|
||||
absolute, resolves `.` and `..` by name and checks each component with
|
||||
`stat`; it keeps a symbolic link's name. The title links it too
|
||||
(`--wrap=realpath`).
|
||||
- Memory: committed ranges (`ps5_vrange_commit`), exact reservations, zeroing
|
||||
of reused direct memory, and placement up to 1 TiB.
|
||||
|
||||
The title's core loader answers a core's `dlopen(NULL)` with the process handle
|
||||
(`src/core_loader_ps5.cpp`), which LLVM's JIT needs.
|
||||
|
||||
**What the console run needed in the fork** is recorded in docs/PHASE_LOG.md
|
||||
(2026-09-28): RSX's start order, VMA's entry points under volk, the kqueue
|
||||
trigger RSX audio's timer uses, releasing LLVM's memory-manager ranges, the
|
||||
log listeners' lifetime, and quitting through `Emulator::Quit`.
|
||||
|
||||
**Content loading and the loading screen** (`rpcs3/libretro/libretro_loader.*`
|
||||
in the fork). `retro_load_game` returns at once: the firmware and a PSN package
|
||||
install on a worker thread while `retro_run` shows a loading screen, and the
|
||||
game boots when they are done. The screen is drawn on the CPU (the stb
|
||||
libraries RPCS3 already has) and handed to RetroArch as a Vulkan image, one
|
||||
per RetroArch frame index, with the copy submitted through
|
||||
`set_command_buffers`:
|
||||
|
||||
- a violet night behind the game's own art (its PIC1, blurred and tinted),
|
||||
its ICON0 on a glass card, the title in Inter, and a violet-to-orchid bar
|
||||
with a sweeping highlight;
|
||||
- "Installing firmware" with its packages, "Installing" with the bytes, the
|
||||
speed, the time left and the percentage, then "Starting" with RPCS3's
|
||||
compile progress, until RPCS3's first frame replaces it;
|
||||
- what failed, in rose, when something does.
|
||||
|
||||
Inter 4.1 (SIL Open Font License, its licence beside it) is fetched by
|
||||
`tools/build-rpcs3.sh`, pinned by digest, and staged in `system/RPCS3/fonts`;
|
||||
the firmware's Rodin is the fallback. A finished package install is recorded
|
||||
in `system/RPCS3/libretro/installed/<title ID>`, since an interrupted one leaves
|
||||
an `EBOOT.BIN` behind, and the record goes when an install starts. Closing
|
||||
content during an install stops it, inside a large file too (the fork's
|
||||
`unpkg.cpp`); a partial firmware is installed again next time.
|
||||
|
||||
**RPCS3's folder, as staged.** Besides the loading screen's fonts, the title
|
||||
stages RPCS3's own overlay images (`bin/Icons/ui` into `system/RPCS3/Icons/ui`),
|
||||
which its native dialogs draw. The configuration the core writes at load also
|
||||
turns RPCS3's GDB server off: no debugger connects to a core, and it would only
|
||||
open a socket on the console. Open question: the server's address did not
|
||||
match RPCS3's IPv4 `std::regex` on the console (it fell through to the Unix
|
||||
socket branch), which may mean `std::regex` misbehaves there; RPCS3 also parses
|
||||
patches and its game database with it, so it wants a probe.
|
||||
|
||||
**What loads.** A PSN package (`.pkg`, installed once, then booted at
|
||||
once), a game's `EBOOT.BIN`, an ISO, or a folder (RetroArch's "Use this
|
||||
directory"): the folder's `USRDIR/EBOOT.BIN` (an installed game, such as
|
||||
`system/RPCS3/dev_hdd0/game/NPUA80490`) or `PS3_GAME/USRDIR/EBOOT.BIN` (a
|
||||
disc game) boots. RPCS3 itself treats a bare folder as "build the PPU cache of
|
||||
everything in it", which compiled and stopped. Loading a folder also first
|
||||
crashed the title: canonicalising a relative path asks libc++ for the working
|
||||
directory, and the title's own `getcwd` resolves to nothing on the console
|
||||
(libc's calls `__getcwd`, which only libkernel_sys has). The title links it to
|
||||
the platform's `ps5_getcwd` (`--wrap=getcwd`), as the cores already were, and
|
||||
`tooling/native/app-symbols.map` keeps every `__wrap_` symbol local, since lld
|
||||
exported `__wrap_getcwd` to interpose on libc's stubs and a title publishes
|
||||
no exports.
|
||||
|
||||
**Core options** (`rpcs3/libretro/libretro_options.*` in the fork). Every RPCS3
|
||||
setting with a set of values is a libretro v2 core option, one category per
|
||||
section of RPCS3's configuration: CPU (Core), Virtual File System, GPU
|
||||
(Video, with its Vulkan, performance overlay and shader loading dialog
|
||||
subsections), Audio, Input, System, Network, Save States and Miscellaneous.
|
||||
They are built from the configuration tree itself, so their names, values
|
||||
(an enum's names; a number's range as a list: all of a small range, else the
|
||||
1-2-5 series, even steps, the bounds and the default), defaults and whether
|
||||
RPCS3 takes a change while a game runs are RPCS3's own. A change RPCS3 takes
|
||||
live applies at once; the rest are written to `config.yml` when content loads,
|
||||
as RPCS3's settings dialog writes them, so a game's custom configuration still
|
||||
overrides them. Left out: what the core fixes itself (the Vulkan renderer,
|
||||
RetroArch's audio backend, the RetroPads in place of keyboards, mice, cameras
|
||||
and Move), the Qt application's window behaviour (auto-start and auto-exit,
|
||||
fullscreen, focus pause, native UI, GameMode), settings for other hosts (Metal),
|
||||
VSync (RetroArch paces frames) and RPCS3's savestate folder limits. Paths and
|
||||
free-text settings have no set of values to offer. The resolution scale keeps
|
||||
100% to 300% (the frame is 720p at the scale, 2160p at most), 300% by default.
|
||||
|
||||
**Game patches** (`rpcs3/libretro/libretro_patches.*`). RPCS3 has no
|
||||
frame-rate unlock of its own: a game runs faster than it was made to only
|
||||
through the patches in RPCS3's game patch database. The fork keeps that
|
||||
database as RPCS3's patch manager downloads it (`bin/patches/patch.yml`, its
|
||||
source, date and hash in `bin/patches/SOURCE`; 815 executables, 502 of them
|
||||
with a frame-rate patch on 2026-09-29), and the title stages it where RPCS3
|
||||
reads it (`system/RPCS3/patches/`). The "Frame-rate patches" core option, on by
|
||||
default, turns each game's frame-rate patch on when content loads: "60 FPS"
|
||||
where the database has one, else "Unlock FPS", which RPCS3's frame limit holds
|
||||
at the display's rate; one per executable, serial and version, and another
|
||||
rate (30, 50, 120), a fallback variant or a "No User Input" one stays off.
|
||||
RPCS3's `patch_config.yml` is written only when that changes, and keeps the
|
||||
state of every other patch. A patch for a game the database lacks goes in
|
||||
`patches/<serial>_patch.yml`, which RPCS3 reads for that game.
|
||||
|
||||
**Save states** (`rpcs3/libretro/libretro_state.*`). RetroArch's save states
|
||||
are RPCS3 savestates. RPCS3 writes one to a file system in memory, a virtual
|
||||
device like its ISO one (`Emulator::savestate_path_override` points the
|
||||
savestate there), and the game boots again from it, as RPCS3's own "save state
|
||||
and continue" does; loading a state stops the game and boots the state in its
|
||||
place. Nothing goes through the console's storage but RetroArch's own state
|
||||
file. RetroArch asks for a state's size right before it serializes one, and
|
||||
before it loads one (to back up what runs for "Undo Load State"), so the core
|
||||
makes the state at the size query and keeps it until the next frame: one
|
||||
request costs one savestate. RPCS3 refuses a savestate while an SPU cannot be
|
||||
stopped (its "Compatible Savestate Mode" option), a video decoder context
|
||||
exists (LLE libvdec avoids it) or the game is saving; the game then runs on
|
||||
and RetroArch reports the failure. The core declares its states variable in
|
||||
size and platform-dependent, and its `.info` offers basic save states only (no
|
||||
rewind or run-ahead, which RPCS3 savestates cannot serve). The pad script's
|
||||
`SAVE_STATE` and `LOAD_STATE` actions save and load the current slot, so a
|
||||
test run can start from a known scene (and `SCREENSHOT` keeps the frame shown
|
||||
then as `/app0/pad-shot-<n>.png`, so one run shows each screen it walks
|
||||
through); RetroArch names the state after the
|
||||
content, so a game loaded as its folder (`dev_hdd0/game/GTA4`) has
|
||||
`GTA4.state`, where an `EBOOT.BIN` gives every game `EBOOT.state`.
|
||||
|
||||
**A title's write budget.** The console, not its NVMe drive, sets how fast a
|
||||
title writes: about 1.3 GiB at 242 MiB/s, then about 2 MiB/s, whether through
|
||||
`write()`, `O_DIRECT` or a shared mapping and with however many threads, and it
|
||||
refills while the title does not write. Its FTP server, another process, is
|
||||
held back less: about 7 MiB/s a connection and 22 MiB/s in all (the SDK's
|
||||
docs/PROBE.md, "Sustained writes" and "Writing through another process"). So
|
||||
RPCS3's files in the title's folder go through that server once the title's
|
||||
burst is spent (the fork's `Utilities/File.cpp`, on the SDK's
|
||||
`include/ps5platform/offload.h`): a file opened for writing in `/app0` times
|
||||
its writes, and once its first MiB is written and a MiB of the title's writes
|
||||
ran slower than 50 MiB/s, a write at the file's end starts a stream that
|
||||
carries the file's following writes through a loopback connection to the
|
||||
server; anything else done with the file (reading, seeking, its size,
|
||||
closing) first waits for the server to confirm the stream's bytes. The stream
|
||||
keeps what the server has not confirmed (16 MiB at most) and writes it itself
|
||||
if the server fails, so nothing is lost; with no server it never starts. The
|
||||
server is found by its greeting and the folder it sees `/app0` as by a marker
|
||||
file, and the answer is kept in `/app0/.ps5-offload`. GTA IV reinstalling
|
||||
1414 MiB of its install with the budget spent took 4 minutes, about 6 MiB/s,
|
||||
where its first install ran at 0.7 MiB/s once the budget was spent, and the
|
||||
nine files it wrote were byte for byte the ones it had written before
|
||||
(2026-09-29). Several files streamed at once would reach the server's 22 MiB/s;
|
||||
RPCS3 writes one at a time. Installing a package's large files without copying
|
||||
them, read and decrypted from the package the way RPCS3 reads an ISO through a
|
||||
virtual device, would remove the wait and the second copy.
|
||||
|
||||
## GTA IV (BLES00229 1.00): the test game, toward 4K at 60 fps
|
||||
|
||||
GTA IV is the RPCS3 core's test game from 2026-09-29; the target is 4K at a
|
||||
stable 60 fps, measured from a save state in its first playable scene (Roman's
|
||||
taxi at the docks, the first mission). What it took, all in the fork:
|
||||
|
||||
- **A FIFO in local memory** (`RSX: a FIFO whose command buffer is in local
|
||||
memory`). 1.00's libgcm allocates its context with system mode 0x210 and
|
||||
writes its first commands at local-memory offset 0x1000 with no IO memory
|
||||
mapped; RPCS3 read GET and PUT as IO offsets and stopped with "Dead FIFO
|
||||
commands queue state" (RPCS3 issue #14194, where 1.00 never boots). Mode 0x200
|
||||
now makes GET, PUT and the jump and call targets local-memory offsets.
|
||||
- **The ZCULL late fault** (`ZCULL: a late fault on a report page unlocks it`),
|
||||
RPCS3 issue #19610: on 16K-page hosts the RSX thread's semaphore labels share
|
||||
a page with the reports, and a declined late fault spun forever.
|
||||
- **Its folder** (`Disc games in dev_hdd0/game mount their folder whatever its
|
||||
name`, and `A disc game in a dev_hdd0 folder resumes from a savestate in that
|
||||
folder`): GTA IV is kept as `dev_hdd0/game/GTA4`, which the boot took as a
|
||||
9-character title ID, and a savestate's boot path carries a doubled separator.
|
||||
- **Save states** (`vm: memory restored from a savestate takes its block's page
|
||||
size`): loading a state over a game resumed from one stopped on "Memory
|
||||
inconsistency found" at the stack block. A save costs about 20 s of the game,
|
||||
and so does a load: RPCS3 resumes by booting the state.
|
||||
- **Its frame rate** (`libretro: GTA IV 1.00's frame-rate patch`): RPCS3's
|
||||
patch database unlocks 1.08 only. 1.00's render thread holds frames to the
|
||||
frame lock times 1/60 s, polling the timebase with 30 us sleeps (1.16 million
|
||||
of them in 15 s of gameplay), and skips that when the frame lock is 0; the
|
||||
fork's `bin/patches/BLES00229_patch.yml` sets it (0x0113b9f4) to 0, found in
|
||||
the game's code (a dump of the running executable, the `ppu-dump.txt` test
|
||||
hook). A higher vblank rate (120) left it at 30: it counts time, not vblanks.
|
||||
|
||||
The game installs 3.3 GB of its disc to `dev_hdd0/game/BLES00229` on its first
|
||||
start; see "A title's write budget" above.
|
||||
|
||||
Measured from the baseline state (70 s runs, `--appendconfig` test config,
|
||||
2026-09-29):
|
||||
|
||||
| Build | Resolution | Frames a second (10 s windows) |
|
||||
|---|---|---|
|
||||
| no frame-rate patch | 300% (2160p) | 27.9-30.0: the game's own 30 |
|
||||
| no patch, Vblank Rate 120 | 300% | 27.8-30.0 |
|
||||
| frame-rate patch | 300% | 50.4-51.5, loading screens 59.8 |
|
||||
| frame-rate patch | 100% (720p) | 53.4-54.8 |
|
||||
|
||||
The game keeps its speed (the audio windows are full), and the resolution
|
||||
costs little: the limit is the emulation, not the PS5's GPU. RPCS3's
|
||||
performance overlay puts the guest RSX at 98% busy, while its host CPU
|
||||
figures read nearly nothing on the console (5.5% PPU, 1.8% SPU: the thread
|
||||
times do not work there). The title's sampler, every thread sampled (its walk
|
||||
now bounded by each thread's stack), found one thread busy 85-89% of the time
|
||||
in recompiled code, most likely an SPU running GTA IV's SPURS work, and five
|
||||
more at 30-55% with `vm::writer_lock` their most frequent address, while the
|
||||
RSX thread ran about half the time. With RPCS3's accurate SPU reservations
|
||||
(the default, which stays), every SPU atomic write takes that lock, and a
|
||||
PS5's Zen 2 has no transactional memory to take it faster. That lock and the
|
||||
busiest SPU are where 60 fps is to be found next.
|
||||
-339
@@ -1,339 +0,0 @@
|
||||
# Testing and evidence
|
||||
|
||||
How this project decides that something is true, and what it keeps as proof.
|
||||
`AGENTS.md` lists the gate commands; this file says what they mean, how to add to
|
||||
them, and what counts as evidence.
|
||||
|
||||
## The four layers
|
||||
|
||||
| Layer | Runs | Answers | Must be |
|
||||
| --- | --- | --- | --- |
|
||||
| Format and static | every save | is it shaped right? | fast, no network |
|
||||
| Unit | every commit | is the logic right in isolation? | deterministic, milliseconds |
|
||||
| Integration | every commit | do the parts work together? | deterministic, seconds, no console |
|
||||
| Target | every step | does it work where it ships? | scripted, repeatable, captured |
|
||||
|
||||
The first three run on the development machine and are the agent's own feedback
|
||||
loop. The fourth runs on the console and is the only layer that can close a
|
||||
step. A step is never closed by a mock of the console.
|
||||
|
||||
## What each gate runs
|
||||
|
||||
| Gate | Command | What it proves |
|
||||
| --- | --- | --- |
|
||||
| format | `tools/verify.sh format` | `tools/lint-shell.sh` parses every script in `tools/` (`bash -n`) and every python tool (`ast.parse`), rejects CRLF and byte-order marks, and refuses a committed console address or credential; `tools/lint-format.sh` then runs `clang-format --dry-run --Werror` over `src/`, `tests/` and `tooling/native/` against the repository's `.clang-format`. Formatters are not installed for shell or python on this machine, so this gate is syntax and policy rather than style. |
|
||||
| unit | `tools/verify.sh unit` | `make test-unit` runs `tests/test_frontend.py`. Four kinds of check, none of which needs a console: the built `gfx_video_driver.c.o` really lists `video_ps5` before `video_null` in `video_drivers[]` (read from its relocations); the title and the frontend agree on `video_driver_t`, by compiling RetroArch's header with the frontend's own defines from `tools/retroarch-flags.sh` and comparing `sizeof` and the `poke_interface` offset against the object the title links - the fault that kept the menu off the screen, see `docs/FINDINGS.md`; the frame-layout function from `src/display.cpp` is compiled from the source by the host compiler, compared against `../PS5_Vulkan`'s own addressing over all 2,073,600 pixels, and pinned against a table of values so a change to it cannot be silent; and the built `dist/<TITLE_ID>/` is a fake self wrapping a 64-bit x86-64 ELF whose `param.json` names its own folder. |
|
||||
| build | `tools/verify.sh build` | `bash tools/build-title.sh` fetches verified core inputs on first use, compiles RetroArch's sources with the cross toolchain, archives them, compiles `src/` with the same `-D` flags the archive was compiled with, links it with the pipeline's CRT, signs the result and records the folder's manifest. It is not bare `make app`: the things the link needs, and the defines the two sides must share, are set by that script and by nothing else. |
|
||||
| integration | `tools/verify.sh integration` | `make test-integration` (the same tests as `unit`, from the other side of the build) followed by `tools/check-manifest.sh` on the built tree: every recorded file present and unchanged, nothing extra, `eboot.bin` a fake self, and `param.json` naming the folder it sits in. |
|
||||
| evidence | `tools/verify.sh evidence` | `tools/evidence.py compare evidence/` replays every committed capture against its expectation and exits non-zero on a difference. It never contacts the console. |
|
||||
|
||||
`tools/verify.sh` runs them in that order and stops at the first failure. The
|
||||
order is deliberate: a formatting failure is cheaper to fix than a staged
|
||||
artifact that cannot load.
|
||||
|
||||
## Evidence
|
||||
|
||||
Evidence is a committed artifact plus the command that reproduces it. The shape
|
||||
this project uses: **capture on the console, replay on the development machine,
|
||||
compare the two.**
|
||||
|
||||
- **Capture.** A console run is captured by the target-layer tooling — the run's
|
||||
own stdout through the SDK's stdio, plus the console's kernel log for the
|
||||
window of the run. Raw captures are large and console-specific, so they stay in
|
||||
the git-ignored `klog/`; the distilled artifact is committed under
|
||||
`evidence/<step>/`.
|
||||
- **Distil.** `tools/evidence.py distil` turns a raw capture into a small,
|
||||
machine-readable record: the ordered identity and event lines, a frame digest,
|
||||
a present count, and the driver and core names, with timestamps and process ids
|
||||
replaced by placeholders. A distilled record must let a stranger tell pass from
|
||||
fail by reading it.
|
||||
- **Replay.** `tools/evidence.py compare` re-runs the deterministic part on this
|
||||
host — the same argument builders, the same config round-trip, the same digest
|
||||
computation — and compares its output with the committed record.
|
||||
- **Compare.** A comparison is exact unless the record declares a tolerance, and
|
||||
a tolerance states its unit, its value and what makes it necessary, in
|
||||
`docs/FINDINGS.md`. A frame digest is compared exactly; a timing line is
|
||||
compared only when the record says which fields are bounded.
|
||||
|
||||
An artifact is only evidence if a stranger can tell pass from fail by reading it.
|
||||
A screenshot, a green check with no input recorded, or a log line that says "ok"
|
||||
is not evidence.
|
||||
|
||||
## Adding a test
|
||||
|
||||
- A bug fix lands with the test that fails without the fix. Write the test
|
||||
first, watch it fail, then fix — and say in the commit that it was seen
|
||||
failing.
|
||||
- A new test goes next to the code it covers, follows the naming of its
|
||||
neighbours, and is added to the gate that runs it.
|
||||
- A test that needs the console is split: the deterministic part runs in the
|
||||
integration gate, the console part becomes a captured artifact.
|
||||
- Never weaken, skip or special-case a test to make a gate pass. If the test is
|
||||
wrong, fix the test in its own commit with the reason.
|
||||
|
||||
## Determinism
|
||||
|
||||
Unit and integration tests are deterministic: no wall-clock reads, no sleeps to
|
||||
synchronise, no unseeded randomness, no network, no console, and no dependence on
|
||||
the order tests run in. Time, randomness and I/O enter through an injectable seam
|
||||
so the test can pin them. A test that cannot be made deterministic is a
|
||||
target-layer test, not a unit test.
|
||||
|
||||
A capture is the one artifact that comes from an uncontrolled environment, so its
|
||||
distillation removes everything that is not a decision: timestamps, pids, buffer
|
||||
addresses, and the console's own boot noise.
|
||||
|
||||
## Tolerance and flakiness
|
||||
|
||||
- A comparison with a tolerance states the tolerance, its unit, and what makes
|
||||
it necessary.
|
||||
- A flaky test is a bug in the test or in the code, and it is named in
|
||||
`docs/ACTIVE.md` under Open findings until it is fixed. It is never dealt with
|
||||
by retrying until green.
|
||||
- A known-benign console message (a service warning, a busy-device notice) is
|
||||
recorded once in `docs/TROUBLESHOOTING.md` with its exact text, and every later
|
||||
run may say "known benign" only if the text matches.
|
||||
|
||||
## Native audio backend
|
||||
|
||||
The unit gate compiles the real `src/audio_ps5.cpp` against an explicitly clocked
|
||||
AudioOut mock (`tests/audio_ps5_test.cpp`). It checks native arguments, 48 kHz rate
|
||||
negotiation, byte counts, FIFO order across wrap, full/partial nonblocking writes,
|
||||
blocking backpressure, zero-filled tails, pause/resume and output-error wakeup.
|
||||
Condition barriers coordinate the test; timeouts only bound a deadlock failure.
|
||||
|
||||
`tools/run-title.sh --no-build --audio-test --watch 45` arms a consumed
|
||||
`/app0/audio-test.txt` file. Before the frontend starts, the same `audio_ps5`
|
||||
callbacks play four one-second PCM tones: left 440 Hz, right 660 Hz, repeated, at
|
||||
12.5% peak with 10 ms boundary ramps. Announce the tones before launch and obtain
|
||||
an audible/channel-order confirmation from the console owner. The test uses
|
||||
1/255/257/1000-frame writes, drains and pauses/resumes between phases, and then
|
||||
checks that an oversized nonblocking write accepts exactly one queue capacity.
|
||||
It closes its port before RetroArch opens its own normal audio driver.
|
||||
|
||||
The runner retrieves `audio-test.json` into the ignored capture directory and
|
||||
rejects failure, stale build identity, wrong format, lost frames, native errors
|
||||
or queue-count mismatches. Expected: 48 kHz, 256-frame grains, four bytes/frame,
|
||||
1,536-frame queue, 193,536 accepted and played frames, zero errors, 6,144 bytes
|
||||
accepted from the oversized nonblocking write. The report cannot establish
|
||||
speaker audibility; that confirmation is recorded separately in the evidence.
|
||||
Normal launches remove leftover control files and never generate test tones.
|
||||
|
||||
## Platform paths and directory browsing
|
||||
|
||||
`tests/test_platform_paths.py` runs the actual native platform setup against an
|
||||
isolated host filesystem, checking first-install config seeding, preservation
|
||||
across a changed seed, startup argument preservation and accessible browser roots.
|
||||
The real SDK directory adapter receives synthetic FreeBSD records to test multiple
|
||||
batches, directory/file names and types, deleted records, EOF, denied opens,
|
||||
valid fd zero, truncated/oversized records and missing string terminators. The
|
||||
mounted-directory fixture rejects small reads; the previous 4 KiB variant fails
|
||||
and the 64 KiB adapter passes.
|
||||
Application-path tests compile the patched upstream function with procfs-related
|
||||
calls replaced by aborts, checking the known executable path and small buffers.
|
||||
|
||||
On the console, retain the current build identity, platform directory summaries,
|
||||
configuration load/save logs, GPU API results, and the owner's browser observation.
|
||||
Read back the live config over FTP and verify its selected paths/settings. Launch
|
||||
again without overwriting it to prove parsing/persistence across runs. No core or
|
||||
ROM execution is implied by seeing directory entries. Raw filenames, settings,
|
||||
crash dumps and console information stay in ignored captures; commit sanitized
|
||||
counts, known port paths, crash symbols and acceptance results only.
|
||||
|
||||
The platform fixture also creates existing `0755` cores and `0775` content
|
||||
directories and uses umask `0077`; every managed directory must become `0777` after initialization.
|
||||
This test fails against the former mkdir-only implementation. After console
|
||||
startup, `python3 tools/check-ftp-write.py` requires both mode `0777` and successful
|
||||
FTP upload/readback/cleanup in all seven managed directories. Mode listings alone
|
||||
are insufficient evidence that the FTP process can write.
|
||||
|
||||
The native input fixture compiles `src/input_ps5.cpp` against mocked pad services
|
||||
and the actual upstream `input_state_wrap` and analog helpers. It checks raw button
|
||||
capture, all stick directions, trigger scaling, invalid ports/axes/buttons,
|
||||
user binding priority without the old direct mapping, menu deadzone behavior,
|
||||
zero-sample repeat polling, interception/disconnect and driver teardown/reinit.
|
||||
ELF relocations verify joypad registration and the built-in profile in the shipped
|
||||
frontend. On console, confirm left-stick menu navigation, button/axis capture in
|
||||
Settings > Input > RetroPad Binds > Port 1 Controls, and saved bindings after a
|
||||
restart. Preserve the owner's live configuration when deploying.
|
||||
|
||||
|
||||
## Libretro core artifacts
|
||||
|
||||
`tools/check-core.py <core.so> --report <report.json>` requires an ELF64 x86-64
|
||||
FreeBSD/PS5 shared object, 16 KiB-compatible load segments, native runtime imports
|
||||
including `libkernel_web.sprx`, and all 25 libretro callbacks in the dynamic symbol
|
||||
table. It rejects Linux ELF ABI, payload kernel imports and absent callbacks.
|
||||
The report deliberately keeps `console_loading_verified` false: ELF structure
|
||||
cannot establish runtime loading. The title manifest check also checks the
|
||||
staged FCEUmm artifact and requires identical `.info` metadata in `info/` and
|
||||
`cores/`, covering both explicit and legacy empty core-info paths.
|
||||
|
||||
`tests/test_core_abi.py` builds real host fixture ELFs, marking only test fixtures
|
||||
with the PS5 OSABI. Positive and negative cases exercise the parser through
|
||||
readelf: missing callback, wrong runtime library, wrong ABI, 4 KiB segments and
|
||||
truncated header. These fixtures are never staged or run on the console.
|
||||
|
||||
For the target test, upload a verified build, open RetroArch normally, and let
|
||||
the owner select FCEUmm under Load Core and then a game under Load Content.
|
||||
Collect `retroarch.log`, trace and kernel log with the build identity. Record
|
||||
core load, content initialization and presented frames separately; a visible
|
||||
core entry or `.info` name is not proof of loaded executable code. Owner game
|
||||
paths remain in ignored logs. Keep normal XMB, Vulkan and audio startup working
|
||||
when no core is selected.
|
||||
|
||||
|
||||
## Native core loader diagnostics
|
||||
|
||||
`tests/test_core_loader.py` compiles and executes the real loader against a small
|
||||
host ELF fixture. It tests relocation/import execution, reference counts,
|
||||
reloads, once-per-mapping initializers, invalid initializer tables/callbacks,
|
||||
missing imports, malformed header/table bounds, forbidden segment flags,
|
||||
TLS, and invalid relocation targets/types. The fixture's OSABI byte is adjusted
|
||||
only for this host test; shipped cores must pass the actual cross-build checker.
|
||||
`tests/test_core_recovery.py` runs the upstream core-selection function before
|
||||
and after the patch and checks rejection without marking the core as selected.
|
||||
|
||||
`tools/run-title.sh --no-build --core-test --watch 30` arms an opt-in no-game
|
||||
console diagnostic. It executes eight FCEUmm load/symbol/API/identity/unload
|
||||
cycles, rejects a missing core/export, and loads the core again with the frontend
|
||||
resident. It then attempts missing-core selection/content startup through the
|
||||
actual task functions and checks that the initialized menu context survives.
|
||||
The runner collects `core-loader-test.json` and `core-recovery-test.json`, checks
|
||||
their build identity and pass status, captures logs, and closes the title. Normal
|
||||
runs clear the control file; diagnostics never run without the flag.
|
||||
|
||||
These diagnostics do not establish gameplay. The owner selects the core and
|
||||
content manually in a separate captured run and confirms video, audio, controls
|
||||
and return to XMB. Keep user content paths in ignored captures, not evidence.
|
||||
|
||||
|
||||
`tests/test_core_frame.py` executes the actual XRGB8888 upload conversion with
|
||||
red/green/blue and mixed colours, checks opaque alpha, distinct input/output row
|
||||
pitches, untouched padding and in-place conversion. Real-core acceptance also
|
||||
requires a trace with no Vulkan refusals or failed command buffers, rather than
|
||||
inferring correctness from the core's successful initialization.
|
||||
|
||||
|
||||
For mGBA use `tools/run-title.sh --no-build --core-test=mgba --watch 180`.
|
||||
`--core-test` without a value retains the FCEUmm diagnostic. Reports contain the
|
||||
selected core name, checked along with build identity and pass status. Only these
|
||||
two names are accepted. The owner selects content manually after startup.
|
||||
|
||||
`tests/test_core_imports.py` checks union/deduplication across two cores, object
|
||||
and untyped imports, directory/time adapters, and conflicting or TLS import
|
||||
rejection. The directory adapter fixture also checks rewind after EOF, rewind of
|
||||
a partially consumed batch and failed seek without discarding state. The loader
|
||||
constructor test was observed failing before support was added, then passing;
|
||||
it also rejects callbacks into writable data without executing them.
|
||||
|
||||
Record GB, GBC and GBA gameplay separately when tested. Core compilation and
|
||||
metadata support for a system do not substitute for console gameplay. RTC,
|
||||
optional BIOS files, saves/states, sensors, rumble and sustained performance
|
||||
require their own acceptance; do not infer them from one successful game.
|
||||
|
||||
|
||||
The native memory test covers 16/32 MiB allocation, zeroing, preserved data on
|
||||
resize, overflow/failure keeping the original buffer, mapped-to-small resize,
|
||||
foreign libc ownership and concurrent allocation/free. Target acceptance must
|
||||
also load a GBA archive larger than the native heap could previously allocate;
|
||||
a host allocation test alone does not prove native content loading.
|
||||
|
||||
|
||||
For core lifecycle acceptance, test coloured content, open Quick Menu with the
|
||||
core paused, close content to XMB, reload another game, and choose Quit. Inspect
|
||||
both the live frontend log and kernel capture. Native exit(0) followed by SIGSYS
|
||||
is a shutdown failure even when the user intentionally chose Quit. Check core
|
||||
XBGR-to-XRGB conversion and repeated immutable cached-frame uploads on the host;
|
||||
console colour/flicker confirmation remains required.
|
||||
|
||||
For patch 0078, manually exercise game -> Quick Menu -> Close Content -> another
|
||||
core/game, repeating with FCEUmm and mGBA. Check menu and gameplay for flicker
|
||||
and stale image regions at each transition, including the RGUI configuration
|
||||
in the owner's reproduction. Owner observation is required: a recording at a
|
||||
matching refresh cadence may hide alternating corrupted frames. Preserve the
|
||||
live frontend log before a relaunch replaces it; do not accept a clean loader
|
||||
probe alone as visual acceptance. Record hashes of linked driver archives when
|
||||
the sibling Vulkan repository is being developed concurrently.
|
||||
|
||||
|
||||
### Snes9x
|
||||
|
||||
`tests/test_snes9x_video.py` exhaustively checks 65,536 RGB565 values through the
|
||||
actual core conversion and frontend GPU upload helpers. It also covers padded
|
||||
input rows, source immutability, repeated cached frames, common/hires/NTSC/4x
|
||||
sizes, and rejection without writing on bad dimensions/pitch/capacity.
|
||||
`test_core_loader.py` loads a C++ ELF with this core-local destructor registry:
|
||||
construct once, defer destruction while another handle exists, destroy in reverse
|
||||
order on the last close, reload repeatedly, and reject malformed finalizer tables
|
||||
or non-executable callbacks before any constructor. The host fixture disables
|
||||
GNU ld's EH frame header because this PS5 layout is not a host unwind layout;
|
||||
exceptions/unwind registration are outside the loader contract.
|
||||
|
||||
Console acceptance uses `tools/run-title.sh --no-build --core-test=snes9x
|
||||
--watch 180`: eight loader cycles and live-menu recovery, then owner-loaded SNES
|
||||
archive with correct colours, sound and controls. Open Quick Menu, Close Content
|
||||
and load another game/core to check persistent colour/flicker regressions. Owner
|
||||
visual confirmation and matching build identity are required. Core-declared FPS
|
||||
and audio rate are metadata, not performance measurements. NTSC filters, special
|
||||
chips, subsystem BIOSes, interlace/hires modes, SRAM/state round trips and long-run
|
||||
A/V timing require separate coverage beyond an ordinary game acceptance run.
|
||||
|
||||
### FBNeo
|
||||
|
||||
`tests/test_fbneo_video.py` checks every RGB565 value through the actual native
|
||||
conversion and frontend upload helpers, immutable cached input, padded rows,
|
||||
bounds rejection and horizontal/vertical arcade dimensions. The pinned-source
|
||||
`test_fbneo_upstream.py` checks all 16,777,216 32-bit colours using upstream's
|
||||
actual HighCol32/HighCol16 functions and the frontend upload helper. It also
|
||||
compares the original MPEG decoder against the patched decoder at all 4,097
|
||||
bit limits of each synthetic layer 2 and AMM frame, with address/undefined-behaviour
|
||||
sanitizers. LeakSanitizer is disabled because the host sandbox cannot provide
|
||||
its process attachment; bounds and UB checks remain enabled. This source-based
|
||||
test requires the hash-verified archive downloaded by `make fbneo`; absence is
|
||||
reported as a skip and cannot establish FBNeo acceptance. The patched metadata
|
||||
function is also queried 10,000 times under these sanitizers, checking its
|
||||
name/version, full-path and no-extraction flags, and archive extensions.
|
||||
|
||||
After all host gates pass, use `tools/run-title.sh --no-build --core-test=fbneo
|
||||
--watch 240` for eight load/unload cycles, failed-load/menu recovery and manual
|
||||
arcade archive loading. Capture frontend/trace/kernel logs and both diagnostic
|
||||
reports, verify identity, and obtain owner confirmation of gameplay colours,
|
||||
audio/input, Quick Menu, Close Content and the next loaded game's colours.
|
||||
Record whether the observed game exercised native 32-bit or converted 16-bit
|
||||
output. Do not infer both paths or all supported boards from one game. ROM-set
|
||||
errors, BIOS errors, CHD, samples, rotation, save states and long-run performance
|
||||
need their own evidence when exercised. Private game names stay in ignored logs.
|
||||
|
||||
The FBNeo pinned-source test also exercises the patched catalogue with 30,000
|
||||
drivers under a simulated 1 MiB small-allocation budget: two bulk allocations,
|
||||
each allocation failing independently, repeated initialize/exit, double exit,
|
||||
original pointer restoration and oversized-name rejection. ASan/UBSan runs cover
|
||||
both narrow and Unicode variants. The shipped console build remains narrow.
|
||||
|
||||
### Genesis Plus GX
|
||||
|
||||
`tests/test_genesis_plus_gx_video.py` compiles the C output adapter and the actual
|
||||
frontend RGBA upload helper. It tests all 65,536 RGB565 values packed by the
|
||||
hash-verified upstream renderer macro; cropped views, 720-pixel input rows,
|
||||
resolution changes up to 720x576, repeated cached frames, source immutability,
|
||||
output canaries and invalid viewport rejection. Fetch the pinned source with
|
||||
`make genesis-plus-gx` first; a missing archive produces an explicit skip, which
|
||||
cannot establish acceptance. Host geometry coverage does not prove a particular
|
||||
core option or system on the console.
|
||||
|
||||
After all gates pass, `tools/run-title.sh --no-build
|
||||
--core-test=genesis_plus_gx --watch 240` checks eight load/export/API/name/unload
|
||||
cycles and failed-load/menu recovery. The owner then loads content manually and
|
||||
checks colours, audio/input, Quick Menu, Close Content and another game/core.
|
||||
Capture frontend/trace/kernel logs, match the exact build identity, and check for
|
||||
Vulkan refusals, GPU API failures, invalid bitmap views and unexpected runtime
|
||||
errors before committing. Record systems actually tested and preserve user
|
||||
confirmation separately from machine-readable loader and runtime evidence.
|
||||
|
||||
## XMB allocation diagnostics
|
||||
|
||||
The opt-in build, host allocator/Vulkan-dispatch tests, raw log fields, ownership
|
||||
coverage limits and manual reproduction protocol are specified in
|
||||
[MEMORY_DIAGNOSTICS.md](MEMORY_DIAGNOSTICS.md). Its build inspection is host-only;
|
||||
it must not be cited as a passing console reproduction or a fixed crash.
|
||||
@@ -1,79 +0,0 @@
|
||||
# Thumbnail colours and configuration reset
|
||||
|
||||
The owner reported swapped save-state thumbnail colours and loss of input after
|
||||
Reset to Defaults, then confirmed the uploaded fixes with “Perfect. Commit”.
|
||||
Build, deployment and acceptance evidence: `evidence/thumbnail-input-defaults/`.
|
||||
Uploads are authorized; do not launch or interrupt a title without owner intervention.
|
||||
|
||||
## Findings and changes
|
||||
|
||||
- The asynchronous image task receives `supports_rgba` but initializes its texture
|
||||
with false. Its completion conversion consequently leaves ARGB words unchanged,
|
||||
while this port's Vulkan texture uploader consumes RGBA bytes. Patch 0081 passes
|
||||
the requested flag into the image. PNG files/core framebuffer formats and the
|
||||
synchronous asset loader are unchanged. Existing correctly saved thumbnails
|
||||
should display correctly when loaded again; no file rewriting is needed.
|
||||
- Compiled input defaults already select ps5, but joypad defaults select null.
|
||||
Patch 0082 selects ps5 for the joypad default as well.
|
||||
- Reset to Defaults clears auto-binds and device identity without restarting input.
|
||||
The driver remembers its connected pad and would not announce it again. The reset
|
||||
command now marks that announcement stale; the next poll re-requests the built-in
|
||||
controller profile once, even with no fresh native pad sample. This avoids a full
|
||||
driver teardown or per-frame autoconfiguration tasks.
|
||||
- No PS5_Vulkan edits or logging changes. Build against the previously accepted
|
||||
four driver archives in build/xmb-normal-vulkan through the existing override.
|
||||
No new dependency, toolchain option or source pin is introduced.
|
||||
|
||||
## Host checks
|
||||
|
||||
`python3 -m unittest tests.test_thumbnail_defaults tests.test_input_ps5 -v`
|
||||
executes the real task initializer, upload callback and pixel conversion with
|
||||
stubbed file decode/scheduling. Known red, blue and partially transparent pixels
|
||||
must match both RGBA and the existing non-RGBA path. It executes the compiled
|
||||
default selectors and the Reset to Defaults command arm. Native input mocks verify
|
||||
one reannouncement with no reopen, including no new sample and no active driver.
|
||||
|
||||
All three new regressions were observed failing before the fixes: wrong red pixel,
|
||||
null joypad default, and absent binding refresh. All four focused tests now pass.
|
||||
Patch inventory grows from 176 to 179 edits, including a fifth configuration.c
|
||||
edit for the joypad default. The first full run stopped on the old four-edit
|
||||
configuration inventory assertion; that expected count now includes the added
|
||||
default. All 74 host tests pass. Full verification command:
|
||||
|
||||
```
|
||||
PS5_MEMORY_DIAGNOSTICS=0 PS5_VULKAN_DIR="$PWD/build/xmb-normal-vulkan" bash tools/verify.sh
|
||||
```
|
||||
|
||||
## Regression checklist
|
||||
|
||||
Preserve old logs, live config, and the exact ELF/map/build identity before upload.
|
||||
Check console idle, upload the verified eboot only, read back runtime identity and
|
||||
confirm live config unchanged. Start passive klog before the owner launches.
|
||||
|
||||
1. Load a game with recognizable red/blue colours; save a state and inspect its
|
||||
thumbnail. Revisit an existing thumbnail and confirm both match gameplay.
|
||||
2. Return to XMB; use Configuration File -> Reset to Defaults. Immediately check
|
||||
buttons and left-stick navigation, then input binding capture.
|
||||
3. Save the reset defaults and restart manually. Confirm native input still works.
|
||||
4. Confirm gameplay and Close Content/menu colours remain correct.
|
||||
|
||||
Collect retroarch.log/trace/klog for each run and match the uploaded identity.
|
||||
No console success is claimed by the host tests; no launch is authorized by this plan.
|
||||
|
||||
## Verified build and upload
|
||||
|
||||
All five gates passed: format, 74 host tests, build (278 frontend sources),
|
||||
integration (74 tests and manifest) and 30 replayed evidence captures. Normal
|
||||
identity: `e11018a79a62ce91fa723deaad6939c1e48a47bace21b3527ae84f4452148451`.
|
||||
Exact ELF/map/title/manifest/logs: klog/thumbnail-input-build/; host-report.json
|
||||
in `evidence/thumbnail-input-defaults/` records their hashes. The same four driver archives and all three Mesa
|
||||
utility objects match the previously accepted normal XMB build. The expensive
|
||||
memory observer is absent and the reset hook is linked into the frontend/title.
|
||||
|
||||
Upload: klog/thumbnail-input-upload-20260919-205531/. Console idle checked before
|
||||
upload; runtime identity read back; only eboot replaced. Live configuration is
|
||||
byte-identical. Its input backend is ps5 and joypad selection is empty (automatic),
|
||||
so no stale null selection needed repair. No title launch or kill was sent.
|
||||
Owner confirmation accepts the reported fixes. Individual checklist results were
|
||||
not separately reported, and no fresh console logs were captured after acceptance.
|
||||
Existing logs/settings were preserved.
|
||||
@@ -1,108 +0,0 @@
|
||||
# Troubleshooting
|
||||
|
||||
Symptom first, then the cause that was actually found, then the fix. Written for
|
||||
the agent that hits this at 3am with no memory of the last session, so the entry
|
||||
starts with the text the machine prints, not with the diagnosis.
|
||||
|
||||
Append new entries at the end. When a cause stops being true (a version bump, a
|
||||
platform change), append a short entry that says so rather than deleting the old
|
||||
one — the old entry is still what someone with an old lockfile will see.
|
||||
|
||||
## The title appears in the launcher, but nothing happens when it is selected
|
||||
|
||||
**Cause.** The ELF imports `libkernel_sys.sprx`. That library is not present in
|
||||
the process the console's homebrew loader creates for a payload application, and
|
||||
the loader refuses the module without printing anything the user can see. A link
|
||||
against the wrong kernel stub produces this, and so does a core built by a
|
||||
makefile that picked the `sys` variant on its own.
|
||||
|
||||
**Fix.** `readelf -dW dist/<TITLE_ID>/eboot.bin | grep NEEDED` must list
|
||||
`libkernel_web.sprx` and must not list `libkernel_sys.sprx`. `prospero-clang`
|
||||
links the `web` variant by default, so a `sys` entry means a flag, a makefile
|
||||
variable or a prebuilt archive pulled it in: find the archive with
|
||||
`prospero-nm -D` and rebuild it through `$CC`, not with a hardcoded compiler.
|
||||
|
||||
**Tell it apart from.** A missing `sce_sys/param.json` looks the same from the
|
||||
launcher's side but the console's log shows the title directory being rejected
|
||||
before any module is read.
|
||||
|
||||
## `check-ps5-object: not a PS5 object; the toolchain was bypassed`
|
||||
|
||||
**Cause.** The file was compiled by the host compiler. Prospero output and a host
|
||||
Linux object are both ELF x86-64 with the System V ABI, so nothing else tells
|
||||
them apart — the import table is the only reliable signal. This happens when a
|
||||
makefile hardcodes `CC = gcc` or `CC = clang` in a branch that does not read the
|
||||
environment.
|
||||
|
||||
**Fix.** Pass the toolchain as make *variables* (`CC="$CC" CXX="$CXX" AR="$AR"
|
||||
RANLIB="$RANLIB"` on the command line), because a command-line variable overrides
|
||||
even an unconditional assignment in the makefile. Then rebuild from clean: a
|
||||
stale object from the host build survives in the same tree and will be linked
|
||||
again if the timestamps say it is current.
|
||||
|
||||
## `error: <name>_libretro.so does not export retro_api_version`
|
||||
|
||||
**Cause.** The core was built with its symbols hidden or stripped from the
|
||||
dynamic table. RetroArch loads cores with `dlopen` and resolves them with
|
||||
`dlsym`, so a core whose entry points exist only in the static symbol table links
|
||||
fine, stages fine, and fails to load on the console.
|
||||
|
||||
**Fix.** Build the core with `-fPIC` and without a version script or `--strip-all`
|
||||
that removes dynamic symbols, then re-check with
|
||||
`prospero-nm -D --defined-only <core>`. Note that `nm --defined-only` without
|
||||
`-D` reports "no symbols" on a perfectly good core, because the static table is
|
||||
gone; that is not a failure.
|
||||
|
||||
## A `make` target in `vendor/` cannot find a header the patch was supposed to add
|
||||
|
||||
**Cause.** `vendor/retroarch` is a patched tree, and a partial re-fetch, an
|
||||
interrupted patch or a checkout that skipped `tools/fetch-retroarch.sh` leaves it
|
||||
half-applied. Nothing in `vendor/` is committed, so the tree on disk is the only
|
||||
copy.
|
||||
|
||||
**Fix.** `rm -rf vendor && tools/fetch-retroarch.sh`. It verifies the pinned
|
||||
digest before extracting, so a re-fetch is cheap and cannot silently pick up a
|
||||
different upstream. Never edit a file under `vendor/` to get unblocked: put the
|
||||
change in `patches/` and re-run the fetcher, or the next fetch discards it.
|
||||
|
||||
## The title closes immediately and the log says `PRX_SCE_MODULE_LOAD_ERROR`
|
||||
|
||||
**Cause.** The console's loader could not load a module the application needs, and
|
||||
it names the reason itself:
|
||||
|
||||
```text
|
||||
# exception: 0xa0020102 (PRX_SCE_MODULE_LOAD_ERROR)
|
||||
# === Lack of a .prx file in /app0/sce_module is detected!!! ===
|
||||
# Copy the file (e.g. libc.prx) from target/sce_module.
|
||||
```
|
||||
|
||||
`/app0` is the title's own directory as the application sees it, so this means the
|
||||
file at `sce_module/libc.prx` inside the title folder is missing, misnamed, or
|
||||
not a module the loader accepts. The size settles which: the signed module is
|
||||
`1,284,674` bytes, and a copy that is `1,335,962` bytes is the module's raw ELF
|
||||
rather than its signed container, which the loader refuses — and then reports as
|
||||
absent.
|
||||
|
||||
**Fix.** Put the signed module in place, with that exact lower-case name:
|
||||
|
||||
```text
|
||||
sce_module/libc.prx 1,284,674 bytes sha256 e6ff45d16adf687855cc3b33b0c8a4132b6504360b221e0a34c7e99fb3ba0036
|
||||
```
|
||||
|
||||
`dist/<TITLE_ID>/sce_module/libc.prx` in this repository is that file, and it is
|
||||
byte-identical to the sibling project's `../PS5_Vulkan/runtime/libc.prx`.
|
||||
|
||||
**Tell it apart from.** A null-pointer crash inside a *raw* `eboot.bin` looks
|
||||
similar from the sofa — the title closes at once — but its log shows `signal: 11
|
||||
(SIGSEGV)` with `fault address: 0000000000000001` and no `PRX_SCE_MODULE_LOAD_ERROR`
|
||||
line. That failure is the image, not the module; see `docs/FINDINGS.md`.
|
||||
|
||||
## Known benign
|
||||
|
||||
Messages that are expected and safe, with the exact text to match. Anything not
|
||||
on this list is a real failure until it is understood.
|
||||
|
||||
| Message | Why it is safe | Seen in |
|
||||
| --- | --- | --- |
|
||||
| `Fontconfig error: Cannot load default config file: No such file: (null)` | The payload starts with no font configuration file, so fontconfig falls back to its built-in defaults. Text still renders through the font this build loads directly, which is why the menu is readable. The message is printed once, at startup, before any driver line. | The Option 1 baseline run, `evidence/m1-baseline-loads/` |
|
||||
| An empty list of captures under `evidence/` | The evidence gate has nothing to replay before the first console run lands. The gate prints `0 captures replayed` and passes. | `tools/verify.sh evidence` during M0 |
|
||||
@@ -1,126 +0,0 @@
|
||||
# XMB horizontal-navigation allocation investigation
|
||||
|
||||
## Scope
|
||||
|
||||
The owner reproduced the crash by holding left/right between XMB tabs. This
|
||||
investigation uses the existing console capture and the exact configured source;
|
||||
it does not launch a title, change allocation policy, or modify PS5_Vulkan.
|
||||
There is no accepted crash fix yet.
|
||||
|
||||
## What the capture establishes
|
||||
|
||||
See `evidence/xmb-memory-crash/` for the symbolized console failure. Between
|
||||
5,004 ms and the first failure at 6,429 ms, tracked live native requests grow
|
||||
from 4,625,679 to 11,616,951 bytes: a 6,991,272-byte increase. The failing
|
||||
15,488-byte allocation belongs to `xmb_list_insert`, through `xmb_alloc_node`.
|
||||
The following three logged failures are the same size/caller, with successful
|
||||
648-byte callback allocations between them. XMB returns from the failed node
|
||||
allocation without stopping the surrounding entry-building loop.
|
||||
|
||||
The last caller breakdown predates the rise. A failed allocation's caller is
|
||||
not necessarily the owner of the memory already occupying the heap. Therefore
|
||||
the capture does **not** establish that XMB nodes account for that entire rise,
|
||||
nor identify the destination tab or its list length. Native accounting excludes
|
||||
allocations internal to system libraries. Neither total PS5 memory exhaustion
|
||||
nor the native heap's exact limit, fragmentation or corruption is established.
|
||||
|
||||
The later fatal fault is separate: texture unloading enters `vkQueueWaitIdle`,
|
||||
sync allocation fails, and Mesa's allocation-error logger dereferences NULL.
|
||||
Image creations minus destructions stay at 131; that rules out growth in the
|
||||
observed live-image count over these snapshots, not driver-internal leaks or
|
||||
growth in bytes per image. The allocation-log flood was already fixed before
|
||||
this capture.
|
||||
|
||||
## Why tab switching allocates
|
||||
|
||||
1. `xmb_list_cache` optionally copies the visible outgoing entries into one
|
||||
animation list. `xmb_list_deep_copy` frees that list's previous nodes and
|
||||
callbacks before replacing it. It does not keep a queue of old lists.
|
||||
2. `menu_driver_deferred_push_content_list` dispatches the destination tab to
|
||||
its display-list builder. The inspected main/settings/history/favourites,
|
||||
contentless-core, playlist-directory and horizontal-playlist cases clear the
|
||||
previous selection list before repopulating it.
|
||||
3. `menu_entries_append` allocates an XMB node and a callback object per entry.
|
||||
Ordinary nodes contain an embedded thumbnail-path structure even when the
|
||||
entry has no thumbnail. The x86-64 layout matches the console request:
|
||||
|
||||
| Component | Bytes |
|
||||
| --- | ---: |
|
||||
| Seven path arrays and four name arrays | 15,360 |
|
||||
| Complete thumbnail-path structure | 15,400 |
|
||||
| Complete XMB node | 15,488 |
|
||||
| Menu callback object | 648 |
|
||||
| Node + callback per entry, excluding strings/list storage | 16,136 |
|
||||
|
||||
4. These requests are below the native wrapper's 1 MiB mapping threshold.
|
||||
Repeated medium-sized list allocations therefore remain on the native heap,
|
||||
unlike large core buffers. Five hundred entries need 8,068,000 requested
|
||||
bytes for nodes and callbacks alone. **500 is an illustrative size, not the
|
||||
measured length of the owner's tab.**
|
||||
5. Held-button tab repeats have a 99,000-microsecond delay when scroll
|
||||
acceleration is active. Each accepted switch can repeat the work above.
|
||||
Speed increases allocation turnover; this alone does not prove retained
|
||||
memory growth.
|
||||
|
||||
## Wallpaper and error-path work
|
||||
|
||||
`xmb_update_dynamic_wallpaper` calls `xmb_load_image(..., MENU_IMAGE_NONE)`
|
||||
when a changed candidate wallpaper is invalid. It then sets `bg_file_path` to
|
||||
NULL, so a nonempty missing candidate is not remembered as handled. The next
|
||||
update can take the same branch. `xmb_load_image` destroys the background and
|
||||
white fallback texture, then creates the white texture again. This happens
|
||||
even when there was already no background image to replace.
|
||||
|
||||
The captured configuration enables dynamic wallpapers. The crash stack reaches
|
||||
this wallpaper-update/white-texture-unload path; the capture does not include
|
||||
the resolved wallpaper candidate. Missing-wallpaper churn is thus a confirmed
|
||||
source behavior and a plausible repeat trigger, not a measured explanation for
|
||||
the 6,991,272-byte rise. It explains why changing tabs can enter a GPU queue
|
||||
wait while the native allocator is already failing.
|
||||
|
||||
## Local experiment and its limits
|
||||
|
||||
`tools/probe-xmb-allocations.py` extracts the actual node allocation/copy/free,
|
||||
list copying/clearing and file-list function bodies from `build/ra-conf`, and
|
||||
uses the real structure headers. It compiles a host harness with AddressSanitizer
|
||||
and UndefinedBehaviorSanitizer. GPU/animation boundaries are inert and nodes
|
||||
have no textures; list construction and its chosen lengths are synthetic.
|
||||
This is not a complete frontend, timing, task-queue, native-heap or GPU test.
|
||||
|
||||
The experiment alternates list lengths 12, 20, 1, 500 and 6, copying up to eight
|
||||
visible entries each time. Across 10,000 replacements / 4,566,034 allocation
|
||||
calls, end-of-cycle live requested bytes stay at 259,288, peak at 8,236,400, and
|
||||
return to zero after cleanup. The exercised ordinary-node lifecycle therefore
|
||||
does not accumulate old lists. Native fragmentation remains outside this test.
|
||||
|
||||
A separate ownership check finds that `xmb_free_node` does not release its
|
||||
`console_name` string. This is a real small leak for named nodes; the ordinary
|
||||
selection nodes in this experiment have NULL names. It does not establish the
|
||||
cause of the console spike. LeakSanitizer cannot run under the desktop sandbox's
|
||||
ptrace; explicit allocation accounting verifies cleanup instead. The first
|
||||
attempt failed on that tooling limitation; rerunning with only leak scanning
|
||||
disabled passed both other sanitizers and all accounting assertions.
|
||||
|
||||
Replay the committed result after preparing the configured source:
|
||||
|
||||
```sh
|
||||
python3 tools/probe-xmb-allocations.py --expect evidence/xmb-allocation-investigation/host-probe.json
|
||||
bash tools/verify.sh format evidence
|
||||
```
|
||||
|
||||
## Next discriminating measurement
|
||||
|
||||
Before changing allocator policy, record the top live allocation callers once
|
||||
on the **first** failure, plus bounded numeric tab/list counters around clear
|
||||
and populate: destination kind, list size, live node count, copied node count,
|
||||
and native bytes before/after. Avoid entry names, content paths and per-entry
|
||||
logging. This distinguishes a large destination list, retained nodes, other
|
||||
allocation owners, and failure despite stable live requests. It needs another
|
||||
owner-authorized console run; no new diagnostic binary was built in this step.
|
||||
|
||||
Candidate fixes to evaluate after attribution are lazy/shared thumbnail-path
|
||||
storage for XMB entries, eliminating redundant white-texture recreation, and
|
||||
proper allocation-failure handling in the frontend and driver logger. Merely
|
||||
slowing navigation or increasing the heap would not establish the root cause.
|
||||
|
||||
Evidence recorded 2026-09-19 on `codex/xmb-allocation-diagnostics`.
|
||||
@@ -1,110 +0,0 @@
|
||||
# XMB list allocation safety
|
||||
|
||||
The owner confirmed crash-free navigation with the diagnostic build and then
|
||||
confirmed the normal build eliminated the five-second hitches. Current logging
|
||||
is preserved: RetroArch.log, trace and passive klog remain available, while the
|
||||
expensive memory observer is opt-in. Evidence: `evidence/xmb-safe-list-run/`.
|
||||
|
||||
## Changes and rationale
|
||||
|
||||
- Patch 0080 changes XMB's inline 15,400-byte thumbnail-path structure into an
|
||||
optional pointer. Ordinary nodes shrink from 15,488 to 96 bytes. Paths are
|
||||
initialized on demand for visible icon thumbnails, released outside the visible
|
||||
range, and independently owned by animation copies. Node destruction also frees
|
||||
console_name. Image tasks copy their path; they do not retain the path structure.
|
||||
- `ps5_menu_malloc` allocates XMB nodes and menu callback objects in 64 KiB anonymous
|
||||
mapped slabs, with 128- and 1,024-byte payload classes. A locked bitmap allocates
|
||||
slots; the last free unlinks and unmaps the slab. Native pointers are identified
|
||||
without reading their allocation headers and still return to libc. Ordinary
|
||||
`free` and `realloc` dispatch by ownership; failure preserves the old allocation.
|
||||
This avoids one mmap per object and leaves native heap room for strings, list
|
||||
arrays, playlist parsing, system libraries and the GPU driver. Allocations above
|
||||
1,024 bytes are rejected by this narrow API. The existing general 1 MiB threshold
|
||||
is unchanged. Diagnostic mapped counts now include requested slab-object bytes,
|
||||
excluding slab rounding, headers and size-class padding.
|
||||
- File-list append/insert check growth and string allocations before publishing an
|
||||
entry. Menu append/prepend allocate callbacks first and reject missing XMB nodes,
|
||||
rolling back the newly inserted item. A per-list failure flag suppresses repeated
|
||||
attempts until clear. The playlist loop stops at the first rejected allocation.
|
||||
Existing complete entries remain valid; no incomplete callback/node is published.
|
||||
- Animation copies check capacity, strings, node ownership and callback allocations.
|
||||
Failure discards the optional copy, preserves the source, and leaves an empty
|
||||
destination. Empty-list icon loops are bounded. Container allocations initialize
|
||||
the new flag and are checked. The patch set grows from 150 to 176 edits.
|
||||
- The build fingerprint now includes patched headers, because file_list_t changes
|
||||
size. Every frontend consumer must rebuild against the same layout. No new SDK,
|
||||
dependency, compiler flag or version pin is introduced. PS5_Vulkan is unchanged.
|
||||
|
||||
## Host verification
|
||||
|
||||
Reproduction commands:
|
||||
|
||||
```sh
|
||||
python3 -m unittest tests.test_menu_memory tests.test_xmb_safe_lists -v
|
||||
PS5_MEMORY_DIAGNOSTICS=1 bash tools/verify.sh
|
||||
```
|
||||
|
||||
The new tests compile and execute actual patched list/node/menu functions and the
|
||||
native memory wrapper with AddressSanitizer and UndefinedBehaviorSanitizer. They
|
||||
exercise 7,384 synthetic entries, every allocation position in append/prepend and
|
||||
animation copy, failure retry suppression, non-LIFO release, realloc failure,
|
||||
foreign allocations, concurrent use, and complete mapping/ownership cleanup.
|
||||
LeakSanitizer is disabled for the sandbox; explicit live-byte/mapping accounting
|
||||
checks leaks. GPU work, callback binding and menu geometry are stubbed in the
|
||||
source-extracted test: host success does not establish native navigation/rendering.
|
||||
|
||||
The first full gate attempt failed only on the deliberately pinned patch count
|
||||
(150); it was updated to 176 to cover the 26 new edits. The final run passed all five gates and 71 tests; 278/278 frontend sources
|
||||
compiled. `evidence/xmb-safe-list-run/diagnostic-build.json` records the exact candidate and source hashes.
|
||||
The diagnostic image was uploaded with runtime identity readback and unchanged
|
||||
live config; the owner subsequently ran it manually. No launch was sent by the agent. Exact symbols and logs are in `klog/xmb-safe-lists/`.
|
||||
|
||||
## Future console regression checks
|
||||
|
||||
Preserve the current console logs and configuration before deployment. Save the
|
||||
matching ELF, map, build identity, staged manifest and linked archive hashes in
|
||||
ignored klog/ before another build. Check the console is idle before upload; verify
|
||||
the uploaded runtime identity. Do not replace live configuration or user playlists.
|
||||
|
||||
After the owner authorizes launch, start klog before launch and keep collecting
|
||||
memory-diagnostics.log, retroarch.log and trace.txt. Ask the owner to enter the same
|
||||
7,384-entry custom playlist, navigate left/right naturally and rapidly, scroll its
|
||||
entries, leave/revisit it several times, and verify icons/text/input remain correct.
|
||||
Also check a small built-in tab, RGUI/XMB transition, and a game/menu transition.
|
||||
Record whether the owner closes the title or it exits unexpectedly.
|
||||
|
||||
Pass requires no allocation failures or dropped diagnostic records, no Vulkan
|
||||
refusals/API errors/fatal kernel signals, responsive clean navigation confirmed by
|
||||
the owner, and node/callback bytes attributed to the mapped route. Native usage
|
||||
must no longer show the old per-entry node/callback burst. Compare repeated returns
|
||||
to a small tab for retained growth; inspect differences rather than assuming every
|
||||
cache returns to an identical count. Keep the acceptance evidence with each verified build.
|
||||
|
||||
The unsafe PS5_Vulkan logger under genuine allocation failure is a separate known
|
||||
issue. This frontend fix does not fix that driver code or guarantee recovery
|
||||
from arbitrary process-wide exhaustion; it prevents this list workload from consuming
|
||||
its native heap and makes the covered list construction/copy failures safe.
|
||||
|
||||
## Five-second stutter comparison
|
||||
|
||||
The diagnostic calls ps5::memory::tick before presentation. Every five seconds
|
||||
it holds the observer mutex, scans 131,072 tracking slots, aggregates callers and
|
||||
synchronously writes summary/caller rows. This matches the reported hitch cadence.
|
||||
The owner confirmed the normal build fixed it; exact stall duration was not measured.
|
||||
|
||||
```sh
|
||||
PS5_MEMORY_DIAGNOSTICS=0 PS5_VULKAN_DIR="$PWD/build/xmb-normal-vulkan" bash tools/verify.sh
|
||||
```
|
||||
|
||||
All five gates passed, including 71 tests and compilation of 278 frontend sources.
|
||||
The ignored mirror uses the same four archived driver libraries as the successful
|
||||
diagnostic through the existing PS5_VULKAN_DIR override. Mesa utility object
|
||||
changes were confined to debug compilation-directory information; stripped object
|
||||
bytes match. No sibling driver sources were changed. Binary inspection confirms
|
||||
observer symbols/hooks are absent, while ps5_menu_malloc and native wrappers remain.
|
||||
Upload identity was read back and live configuration preserved. No launch or kill
|
||||
was issued by the agent. The owner subsequently reported “That fixed it” and
|
||||
requested committing on main with existing logging. No new normal-run logs were
|
||||
captured; the normal acceptance is the owner's observation, not a measured FPS.
|
||||
Exact identities, hashes, commands and scope are in diagnostic-build.json and
|
||||
normal-build.json under `evidence/xmb-safe-list-run/`.
|
||||
@@ -1,68 +0,0 @@
|
||||
# Which source each published release was built from
|
||||
|
||||
Each GitHub release of this project carries one ZIP. This page records, for every
|
||||
release, the commit whose tree the ZIP's `eboot.bin` was built from, the revisions
|
||||
of what was linked into it, and where the release's tag points. It was established
|
||||
on 2026-09-29 from the published assets (downloaded and hashed), their upload
|
||||
times, the repositories' public push events and this repository's history. What
|
||||
could not be established is said so.
|
||||
|
||||
**The tags do not always name the built commit.** The releases were created with
|
||||
`gh release create` against `main`, and GitHub made each tag from whatever `main`
|
||||
held on GitHub at that moment. Three times the build was pushed afterwards, so the
|
||||
tag names an older commit than the one built. Tags are not moved (published history
|
||||
is not rewritten); the table below is the correction, and docs/RELEASING.md is the
|
||||
procedure that prevents it.
|
||||
|
||||
## The releases
|
||||
|
||||
| Release | Asset (sha256) | Tag points to | Built from | Evidence for "built from" |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| v0.1.0-alpha.1 | `PS5_RetroArch-v0.1.0-alpha.1.zip` `757a6ab774b990638a71e8dbe80deb6d66e799b53c4b52ac4cb504280c3388c4` | `a18cec6` | `b1dced9` | The ZIP was uploaded at 2026-09-20 01:02 UTC, after the release was created; its `eboot.bin` build identity `e11018a7…` is recorded in `b1dced9` (docs/THUMBNAIL_INPUT_DEFAULTS.md), pushed at 01:01 UTC. |
|
||||
| v0.2.0-alpha.2 | `…v0.2.0-alpha.2.zip` `5f611cb32e8212fb8a0b63f32c2329bfecd828dfd4434949a9a1366f332b13f2` | `d0a815b` | `d0a815b` | Pushed at 13:49 UTC, release at 13:53 UTC; no later commit before the upload. The build identity `cce8e166…` is not recorded in the repository. |
|
||||
| v0.3.0-alpha.3 | `…v0.3.0-alpha.3.zip` `d313b30dba97ed82277ee74a495801306a64a24fe365e9aba464c8df25da86eb` | `18dc105` | a tree at or after `c1d9f15`, most likely `6c7f418` | The ZIP's `eboot.bin` contains `src/permissions_ps5.cpp`'s log string ("permissions: /app0 checked=…"), which `c1d9f15` added after the tag. `6c7f418` is the last commit before the upload; the build identity `ad98b960…` is not recorded. |
|
||||
| v0.4.0-alpha.4 | `…v0.4.0-alpha.4.zip` `093fd883bbff63237486d3f7b9b1cd29c9a0d3142e42bf53367861b72757b144` | `52009a9` | `52009a9` | Build identity `870bd1bb…` is recorded in `054ab98`, an ancestor of the tag; the next commit (`a9452a6`) is documentation. |
|
||||
| v0.5.0-alpha.5 | `…v0.5.0-alpha.5.zip` `aa85d74e224bab3f0dc414c1176f3aa04ddf02b5d86d522b218d6c9c30e769de` | `1ae4a1a` (2026-09-26) | `72847d6` | The tag's tree cannot link RADV (`tools/build-title.sh` there has no RADV path; it arrived in `f932c82` on 2026-09-28). Build identity `f96bdb0e…` is recorded in `72847d6`, pushed at 12:30 UTC, five minutes after the release. |
|
||||
|
||||
Every "built from" commit above is now on the public `main`.
|
||||
|
||||
## What was linked in, per release
|
||||
|
||||
| Release | RetroArch | Cores (pinned upstream or fork revision) | Graphics driver | SDK |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| alpha.1 | 1.22.2 `69a4f0e` + patches | FCEUmm `236ccdf`, mGBA `7a12d6d`, Snes9x `fae2fea`, FBNeo `6bb3167`, Genesis Plus GX `c2838c7` | ps5vk from `../PS5_Vulkan`, revision not recorded; built before PS5_Vulkan's public history begins (2026-09-20) | ps5-payload-dev v0.42 release |
|
||||
| alpha.2 | same | + PPSSPP v1.20.4 `fa50bb1` | ps5vk, revision not recorded (PS5_Vulkan `085aac6` was pushed 5 minutes before the release) | v0.42 release |
|
||||
| alpha.3 | same | + Dolphin `c6630001` (libretro/dolphin) | ps5vk, revision not recorded; the latest driver commit before the upload (`81b21d8`) was pushed 2026-09-26 01:19 UTC, after the release | v0.42 release |
|
||||
| alpha.4 | same | + LRPS2 `6d14775` (PS5_LRPS2) | ps5vk `40e9d30` (release notes); pushed 2026-09-28 04:06 UTC, after the release | PS5_PayloadSDK `a110320`; the fork was published 2026-09-28 |
|
||||
| alpha.5 | same | same eight | RADV from PS5_Mesa `cedb774`, built by PS5_Vulkan `5d8f37d` | PS5_PayloadSDK `95c08f2` |
|
||||
|
||||
The upstream cores' pins did not change between releases. The mGBA and Genesis Plus
|
||||
GX cores built on 2026-09-29 from these pins are byte-identical to the ones in all
|
||||
five ZIPs, and FCEUmm's to alpha.2–alpha.5, which confirms those pins.
|
||||
|
||||
## Gaps these releases still carry
|
||||
|
||||
- **Licence texts.** None of the five ZIPs contains the GPL, the MPL, or the
|
||||
Snes9x, FBNeo or Genesis Plus GX licences, or the OFL of PPSSPP's fonts. Builds
|
||||
from now on stage them in `licenses/` (tools/stage-notices.py). The published
|
||||
ZIPs are unchanged.
|
||||
- **Source not offered beside the binary.** The release pages link the
|
||||
repositories but carry no source archives; several cores come from upstream
|
||||
repositories this project does not control. tools/source-bundle.py makes the
|
||||
archives; attaching them to a release is the maintainer's decision.
|
||||
- **alpha.1's driver.** The ps5vk revision linked into alpha.1 predates PS5_Vulkan's
|
||||
public history, so its source is not published anywhere today.
|
||||
- **Driver revisions of alpha.2 and alpha.3** are not recorded.
|
||||
|
||||
## Proposed "Source" section for each release's notes
|
||||
|
||||
Not applied: editing a published release is the maintainer's call.
|
||||
|
||||
```markdown
|
||||
## Source
|
||||
|
||||
This ZIP was built from PS5_RetroArch <BUILT-FROM COMMIT> (the tag <TAG> points to
|
||||
<TAG COMMIT>, an earlier commit; see docs/releases/SOURCE_CORRESPONDENCE.md). Every
|
||||
component, its licence and its source revision are listed there. The licence texts
|
||||
for this release are in <link to the licence bundle attached to this release>.
|
||||
```
|
||||
@@ -1,95 +0,0 @@
|
||||
# PS5 RetroArch v0.4.0-alpha.4
|
||||
|
||||
Native RetroArch for jailbroken PS5 consoles, rendering through
|
||||
[PS5_Vulkan](https://github.com/mihawk-99/PS5_Vulkan). This release adds GPU-rendered
|
||||
GameCube/Wii and PlayStation 2 emulation, and fixes every crash testers reported
|
||||
on earlier builds.
|
||||
|
||||
> **Alpha.** The games and paths below are the ones I have tested on my console.
|
||||
> Other games may still fail. Please report them (see *Reporting problems*).
|
||||
|
||||
## Highlights
|
||||
|
||||
- **Eight cores.** **Dolphin** (GameCube and Wii), **LRPS2** (PlayStation 2) and
|
||||
**PPSSPP** (PSP) render on the PS5's GPU through PS5_Vulkan, beside FCEUmm, mGBA,
|
||||
Snes9x, FinalBurn Neo and Genesis Plus GX.
|
||||
- **PlayStation 2 at 4K.** LRPS2 uses its Vulkan hardware renderer by default at
|
||||
6x internal resolution, with multi-threaded VU1. The God of War II and
|
||||
Final Fantasy X demos and GTA San Andreas run at full speed on my console.
|
||||
**You need your own PS2 BIOS** in `system/pcsx2/bios/`.
|
||||
- **GameCube and Wii.** Dolphin 2609 with its JIT and fast memory. Wind Waker,
|
||||
Resident Evil 4, Super Smash Bros. Melee, Mario Kart Wii and Rogue Leader are tested.
|
||||
Wind Waker ran for an hour and RE4 for 30 minutes with no crash.
|
||||
- **PSP.** PPSSPP v1.20.4 on its Vulkan renderer with JIT: God of War: Ghost of
|
||||
Sparta and Yu-Gi-Oh! GX Tag Force are tested at 10× internal resolution.
|
||||
- **120 Hz output** where the display supports it, with the correct 60 Hz fallback.
|
||||
- **Faster save states.** A 50 MB PS2 save state is written without stdio, which
|
||||
was the slow part.
|
||||
|
||||
## Fixed from tester reports
|
||||
|
||||
| Report | Status in this release |
|
||||
| --- | --- |
|
||||
| Crash after closing a game (seen with an SNES game) | **Fixed.** The video driver was being re-created on a RetroArch thread, and the console gives such threads only 64 KiB of stack. It crashed with *Threaded Video* on. RetroArch's threads now get 2 MiB, and the stack-heavy code moved to the heap. I reproduced the crash, then confirmed the fix on the console. |
|
||||
| PSP game (Ratchet & Clank: Size Matters) crashing after one to two minutes | **Fixed** in the GPU driver: its internal lists were changed from several threads at once without a lock. A new driver test reproduces the corruption on the old driver and passes on the new one. PPSSPP ran a 10-minute soak with the menu opened 25 times and did not crash. |
|
||||
| Every game at **half speed with V-Sync on** | **Fixed.** On a display that only refreshes at 60 Hz, the console accepted the 120 Hz mode and kept refreshing at 60 Hz, so each frame was shown twice. The driver now measures the real refresh rate and falls back to 60 Hz. My display is 120 Hz, so the 60 Hz path still needs confirming on a 60 Hz TV. |
|
||||
| Rogue Leader crash after about three minutes | **Fixed** (a race in Dolphin's dual-core GPU FIFO). |
|
||||
|
||||
With the half-speed fix, **V-Sync on** is the recommended setting on every display.
|
||||
|
||||
## Stability work in this release
|
||||
|
||||
- Every thread the title, RetroArch and the driver start now asks for an adequate
|
||||
stack instead of the console's 64 KiB default.
|
||||
- Driver state that several threads can reach is atomic or locked. A debug-table
|
||||
counter could write past its array when two threads recorded commands at once.
|
||||
- Before release, every core was run with a game: boot, menu open and close, then a
|
||||
Quick Menu action, with Threaded Video on for half of them and V-Sync on
|
||||
throughout. There were no crashes and no fatal signals.
|
||||
|
||||
## Known issues
|
||||
|
||||
- **Shader compilation stutter.** The first time a game needs a new shader, it can
|
||||
hitch while the shader compiles; later runs load it from the cache. Dolphin's
|
||||
default is still *Synchronous* compilation, and the driver compiles one pipeline
|
||||
at a time. Asynchronous compilation is the next piece of work.
|
||||
- **Rogue Leader** runs at 72–85% for stretches of its attract sequence, even with
|
||||
every shader cached. The cost is the driver's CPU work, and it is being worked on.
|
||||
- **PAL PS2 games** (50 fps) on a 120 Hz output alternate two and three refreshes per
|
||||
frame, which can look uneven. The console offers no 50/100 Hz mode.
|
||||
- **PS2 games in `.7z` archives** are not unpacked. Use ISO, CHD, CSO or another
|
||||
supported disc format.
|
||||
- Mario Party (Wii): black flicker on the world map was reported and has not been
|
||||
investigated yet.
|
||||
|
||||
## Install or update
|
||||
|
||||
1. Copy the complete `PPSA99169` folder to your homebrew title location, for
|
||||
example `/data/homebrew/PPSA99169/`, and launch it from your homebrew launcher.
|
||||
2. Updating keeps your `config/`, `savefiles/`, `savestates/`, `system/` and
|
||||
`content/`. Settings already saved there, such as a core's internal resolution,
|
||||
are kept rather than replaced by new defaults.
|
||||
3. Put games in `content/` and BIOS files in `system/` (PS2: `system/pcsx2/bios/`).
|
||||
No games or BIOS files are included.
|
||||
|
||||
## Reporting problems
|
||||
|
||||
Include the core, game, file format and the settings you changed. After the
|
||||
problem, and **before relaunching**, copy `trace.txt` and `retroarch.log` from the
|
||||
title folder: a new launch replaces them.
|
||||
|
||||
## Build
|
||||
|
||||
Build identity `870bd1bb94d9c35e7ec0efdf32ebfe457500bac3c4387376b7b0ea2e3c42957a`
|
||||
|
||||
| Component | Version |
|
||||
| --- | --- |
|
||||
| RetroArch | 1.22.2 with this port's patches |
|
||||
| PS5_Vulkan driver | `40e9d30` |
|
||||
| PS5 payload SDK fork | `a110320` |
|
||||
| PPSSPP | v1.20.4 |
|
||||
| Dolphin | 2609 (libretro/dolphin `c6630001`) |
|
||||
| LRPS2 | PS5_LRPS2 `6d14775` with the port patch |
|
||||
|
||||
All five host gates (format, unit, build, integration, evidence) pass, and so does
|
||||
the driver's host test suite.
|
||||
@@ -1,118 +0,0 @@
|
||||
# PS5 RetroArch v0.5.0-alpha.5
|
||||
|
||||
Native RetroArch for jailbroken PS5 consoles. This release changes the graphics
|
||||
driver: the title now renders through **RADV**, Mesa's Vulkan driver for AMD
|
||||
GPUs, ported to the PS5 in [PS5_Vulkan](https://github.com/mihawk-99/PS5_Vulkan).
|
||||
It replaces ps5vk, the driver every earlier release used.
|
||||
|
||||
> **Alpha.** The games and paths below are the ones I have tested on my console.
|
||||
> Other games may still fail. Please report them (see *Reporting problems*).
|
||||
|
||||
## Highlights
|
||||
|
||||
- **A new graphics driver.** RADV is the driver Linux uses on AMD GPUs, with its
|
||||
ACO shader compiler. It is built from my Mesa fork
|
||||
([PS5_Mesa](https://github.com/mihawk-99/PS5_Mesa)) with a PS5 layer for memory,
|
||||
command submission and display, and it reports Vulkan 1.4. The menu, the five
|
||||
software cores and the three GPU cores (PPSSPP, Dolphin, LRPS2) all render
|
||||
through it.
|
||||
- **PSP anti-aliasing.** PPSSPP's MSAA works. It needs features ps5vk never had
|
||||
(render pass 2 and depth/stencil resolve), so PPSSPP's MSAA setting was
|
||||
previously ignored.
|
||||
- **A shader cache on RADV.** Compiled pipelines are kept in
|
||||
`radv-shader-cache/` in the title folder. A game's second start reads them back
|
||||
instead of compiling: with the cache filled, PPSSPP's God of War: Ghost of
|
||||
Sparta created its pipelines in 29 ms instead of 1.4 s.
|
||||
- **Faster shader compiles.** Every allocation in the title used to wait on one
|
||||
lock, and the emulators compile shaders on several threads at once. The title's
|
||||
heap now gives each thread an arena of its own. PPSSPP compiling from an empty
|
||||
cache: 2.0 s instead of 5.5 s, with no speed lost after boot.
|
||||
- **A smaller package.** `sce_module/libvulkan.so.1` is gone: RADV is linked into
|
||||
`eboot.bin`, and nothing loaded that library (a PS5 title cannot load one).
|
||||
|
||||
## Tested on RADV for this release
|
||||
|
||||
On my console, with V-Sync on and a 120 Hz display:
|
||||
|
||||
- **Every core with a game**: FCEUmm (Super Mario Bros.), Snes9x (A Link to the
|
||||
Past), mGBA (Mario Pinball Land), Genesis Plus GX (Sonic the Hedgehog), FinalBurn
|
||||
Neo (Metal Slug), PPSSPP (God of War: Ghost of Sparta), Dolphin (The Wind Waker)
|
||||
and LRPS2 (the God of War II demo). Each ran through boot, the menu opened and
|
||||
closed, Close Content from the Quick Menu, and the same game loaded again, with
|
||||
Threaded Video on for four of them. No crash and no fatal signal, three times
|
||||
over as the driver changed during the release work.
|
||||
- **PPSSPP for ten minutes** with Threaded Video and the menu opened 25 times: no
|
||||
crash, every 10-second audio window at 98–100% speed.
|
||||
- **The pictures**: God of War: Ghost of Sparta and GTA San Andreas show the same
|
||||
frames as on ps5vk (the rain and the video move with timing); Wind Waker's title
|
||||
screen renders correctly.
|
||||
- **Speed after boot**: PPSSPP and Dolphin (Wind Waker) hold full speed once the
|
||||
game has booted. LRPS2 (GTA San Andreas) does too, but for one late 10-second
|
||||
window at 98.9%, where ps5vk had one at 99.4%.
|
||||
|
||||
The longer runs listed for v0.4.0-alpha.4 (Wind Waker for an hour, Resident Evil 4
|
||||
for 30 minutes, Super Smash Bros. Melee, Mario Kart Wii, Rogue Leader, Yu-Gi-Oh!
|
||||
GX Tag Force, the Final Fantasy X demo) were on ps5vk and have not been repeated
|
||||
on RADV yet.
|
||||
|
||||
## Fixed during the release work
|
||||
|
||||
| Problem | Status |
|
||||
| --- | --- |
|
||||
| PPSSPP crashed within four seconds of starting a game with MSAA on | **Fixed** in PPSSPP. It took the GPU's Vulkan version as usable although RetroArch opens Vulkan 1.1, so it called a Vulkan 1.2 function RADV correctly withheld. ps5vk never reached that path. |
|
||||
| Slow first compiles on several threads | **Fixed**: the title heap's arenas (above). |
|
||||
| A filled shader cache still slow to read from several threads | **Fixed** in the driver: each cache part is opened once, where every read used to reopen and lock its files. |
|
||||
|
||||
## Known issues
|
||||
|
||||
- **Dolphin's first start of a game.** With an empty shader cache, Dolphin
|
||||
compiles its ubershaders while the game boots. Wind Waker's first 20 seconds ran
|
||||
at 83% and 92% speed; ps5vk managed 86% and 99% from an empty cache. Later
|
||||
starts read the cache and match ps5vk. Making the first start as fast is the
|
||||
next piece of work.
|
||||
- **Rogue Leader** ran at 72–85% for stretches of its attract sequence on ps5vk.
|
||||
It has not been measured on RADV yet.
|
||||
- **60 Hz displays.** The driver measures the refresh rate after switching to
|
||||
120 Hz and returns to 60 Hz if the display did not follow, as Alpha 4 did. My
|
||||
display is 120 Hz, so this path still needs confirming on a 60 Hz TV.
|
||||
- **PAL PS2 games** (50 fps) on a 120 Hz output alternate two and three refreshes
|
||||
per frame, which can look uneven. The console offers no 50/100 Hz mode.
|
||||
- **PS2 games in `.7z` archives** are not unpacked. Use ISO, CHD, CSO or another
|
||||
supported disc format.
|
||||
- Mario Party (Wii): black flicker on the world map was reported and has not been
|
||||
investigated yet.
|
||||
|
||||
## Install or update
|
||||
|
||||
1. Copy the complete `PPSA99169` folder to your homebrew title location, for
|
||||
example `/data/homebrew/PPSA99169/`, and launch it from your homebrew launcher.
|
||||
2. Updating keeps your `config/`, `savefiles/`, `savestates/`, `system/` and
|
||||
`content/`. Settings already saved there, such as a core's internal
|
||||
resolution, are kept rather than replaced by new defaults.
|
||||
3. Put games in `content/` and BIOS files in `system/` (PS2: `system/pcsx2/bios/`).
|
||||
No games or BIOS files are included.
|
||||
4. After updating from an earlier release, `sce_module/libvulkan.so.1` and
|
||||
`ps5vk-shader-cache/` are no longer used and can be deleted. The first start of
|
||||
each game compiles its shaders again, into `radv-shader-cache/`.
|
||||
|
||||
## Reporting problems
|
||||
|
||||
Include the core, game, file format and the settings you changed. After the
|
||||
problem, and **before relaunching**, copy `trace.txt` and `retroarch.log` from the
|
||||
title folder: a new launch replaces them.
|
||||
|
||||
## Build
|
||||
|
||||
Build identity `f96bdb0e26dc7e540b51d33e68a0e6933156b11b461058b671544a81c8436482`
|
||||
|
||||
| Component | Version |
|
||||
| --- | --- |
|
||||
| RetroArch | 1.22.2 with this port's patches |
|
||||
| Graphics driver | RADV from PS5_Mesa `cedb774` (Mesa 26.2.0), built by PS5_Vulkan `5d8f37d` |
|
||||
| PS5 payload SDK fork | `95c08f2` |
|
||||
| PPSSPP | v1.20.4 |
|
||||
| Dolphin | 2609 (libretro/dolphin `c6630001`) |
|
||||
| LRPS2 | PS5_LRPS2 `6d14775` with the port patch |
|
||||
|
||||
All five host gates (format, unit, build, integration, evidence) pass: 74 Python
|
||||
tests, and 52 recorded captures replay.
|
||||
@@ -1,55 +0,0 @@
|
||||
# Active work
|
||||
|
||||
A filled example, for a fictional SaaS project, so the density and the tone are
|
||||
obvious. It is not part of the read order: copy the shape, not the content.
|
||||
|
||||
_Updated: 2026-03-04_
|
||||
|
||||
## Now
|
||||
|
||||
**The reconciliation worker is merged and proven on staging.** It consumes the
|
||||
ledger stream, writes the expected-balance table and reports a mismatch instead
|
||||
of correcting it, which is the behaviour the finance team asked for in
|
||||
TICKET-412. Evidence: `tools/verify.sh` green at `4c1a9f2`, and the staging run
|
||||
recorded in `evidence/reconcile/` (12,400 events, 3 seeded mismatches, all three
|
||||
reported, 0 corrections). What it owes: the retry path when the stream redelivers
|
||||
an event (it double-counts today; the test that reproduces it is `failing` in
|
||||
`pnpm test`, so the gate is red until the next step lands).
|
||||
|
||||
**The vendor export is parked.** `patches/vendor-export.patch` implements it,
|
||||
but the vendor's sandbox is the only place it can be verified and the account is
|
||||
not provisioned yet. Not merged; the patch carries the verification steps.
|
||||
|
||||
## Next
|
||||
|
||||
1. Deduplicate redelivered stream events by `event_id`, with the test first.
|
||||
2. Provision the vendor sandbox, then unpark and verify `vendor-export.patch`.
|
||||
3. Backfill the reconciliation table for February and compare the totals.
|
||||
4. Trim the raw stream retention from 30 days to 7 once the backfill is done.
|
||||
|
||||
## Working notes
|
||||
|
||||
- Migrations run before the API boots in staging; `pnpm db:migrate` twice in a
|
||||
row is safe by design, and that is the check that the down path still exists.
|
||||
- `pnpm test` needs `DATABASE_URL` pointing at the disposable container
|
||||
(`compose.dev.yml`); pointing it at staging corrupts the seeded fixtures.
|
||||
- The stream's consumer group resets its offset on redeploy unless
|
||||
`STREAM_RESUME=1` is set; the runbook now says so.
|
||||
|
||||
## Last verified
|
||||
|
||||
| Check | Result |
|
||||
| --- | --- |
|
||||
| `tools/verify.sh` (all five gates) at `4c1a9f2` | PASS: lint, 412 unit, build, 31 integration, 6 evidence replays |
|
||||
| staging smoke, `logs/smoke-2026-03-04.log` | 12,400 events consumed, 3 of 3 mismatches reported, 0 corrections |
|
||||
| February backfill dry run | 41,209 rows would be inserted; not committed to staging yet |
|
||||
| vendor sandbox | not provisioned: the export patch is parked |
|
||||
|
||||
## Open findings
|
||||
|
||||
- Redelivery double-counts: the stream guarantees at-least-once and the worker
|
||||
assumes exactly-once. The fix is deduplication, not a stronger guarantee.
|
||||
- `pnpm test` takes 90 s because the integration suite boots Postgres twice;
|
||||
acceptable for now, revisit if it passes 3 minutes.
|
||||
- The vendor's sandbox rejects requests faster than 5 per second with a 429 and
|
||||
no `Retry-After`; the parked patch waits 250 ms between calls because of it.
|
||||
@@ -1,38 +0,0 @@
|
||||
# Phase log
|
||||
|
||||
A filled example, for the same fictional SaaS project. Two entries: the landed
|
||||
step and the failure that shaped it. Copy the shape, not the content.
|
||||
|
||||
---
|
||||
|
||||
## 2026-03-04: The reconciliation worker reports mismatches instead of correcting them
|
||||
|
||||
The worker now consumes the ledger stream, writes the expected-balance table and
|
||||
emits a mismatch record when the two disagree, leaving the ledger untouched. It
|
||||
exists because finance needs the disagreement to be visible before anything
|
||||
rewrites a balance (TICKET-412), and it unblocks the February backfill.
|
||||
|
||||
**The evidence.** `tools/verify.sh` at `4c1a9f2` returned PASS on all five gates
|
||||
(412 unit, 31 integration, 6 evidence replays), and the staging run
|
||||
(`logs/smoke-2026-03-04.log`, 12,400 events) reported 3 of 3 seeded mismatches
|
||||
with 0 corrections. The distilled capture is committed at
|
||||
`evidence/reconcile/2026-03-04/`, and `tools/verify.sh evidence` replays it.
|
||||
|
||||
**What was tried first.** The first version corrected the balance when the
|
||||
expected value differed. The staging run then reported 0 mismatches and 3
|
||||
corrections, which is the opposite of what finance asked for: the correction
|
||||
erased the evidence. It was reverted in `9a1c4de`, and the worker now only
|
||||
reports.
|
||||
|
||||
**Commit.** `4c1a9f2` — Report ledger mismatches instead of correcting them.
|
||||
|
||||
---
|
||||
|
||||
## 2026-02-27: The stream offset resets on redeploy
|
||||
|
||||
The consumer group was recreated on every deploy, so each deploy replayed the
|
||||
whole stream and double-counted history. Found by comparing the row count before
|
||||
and after a no-op deploy: 41,209 rows became 82,418. The fix reads the stored
|
||||
offset when `STREAM_RESUME=1` (`b7d0e11`); the runbook and
|
||||
`docs/TROUBLESHOOTING.md` were updated in the same commit, because the next
|
||||
person to see the doubling will look there first.
|
||||
@@ -1,15 +0,0 @@
|
||||
# ProsperoLight UI
|
||||
|
||||
Static RmlUi/RCSS assets for the controller-first Sunshine host, application,
|
||||
and settings views.
|
||||
|
||||
## Files
|
||||
|
||||
- `main.rml` — launcher structure and runtime-populated view containers.
|
||||
- `styles/app.rcss` — 10-foot moonlit layout and controller focus states.
|
||||
|
||||
Runtime behavior is owned by the C++ application layer.
|
||||
|
||||
The runtime palette follows the launcher icon: deep sapphire backgrounds,
|
||||
light-blue surfaces and borders, and pale moonlight highlights. Semantic
|
||||
green, amber, and magenta states remain distinct.
|
||||
@@ -1,7 +0,0 @@
|
||||
# LVGL Montserrat bitmap fonts
|
||||
|
||||
These files are deterministically generated from LVGL's built-in, uncompressed 4-bpp Montserrat Medium C fonts. Each single-page, 32-bit, top-origin TGA declares 8 alpha bits and preserves the original 16 alpha levels; the BMFont XML preserves LVGL's line metrics, offsets, integer advances, and expanded class kerning for ASCII U+0020-U+007E, degree U+00B0, and bullet U+2022.
|
||||
|
||||
Regenerate from the repository root with `python tools/generate_lvgl_bitmap_fonts.py`; verify without writing with `python tools/generate_lvgl_bitmap_fonts.py --check`. Font Awesome PUA glyphs present in the LVGL source are intentionally excluded.
|
||||
|
||||
Montserrat is Copyright 2011 The Montserrat Project Authors and is distributed under the SIL Open Font License 1.1; see `OFL.txt`. Generated `.fnt` files use the RmlUi `Samples/basic/bitmap_font` XML schema.
|
||||
@@ -1,50 +0,0 @@
|
||||
# Controller icon assets
|
||||
|
||||
The five controller symbols were generated with OpenAI ImageGen as a single
|
||||
high-contrast mask, then deterministically cropped, recolored, and exported as
|
||||
36 x 36 uncompressed, straight-alpha TGA files for exact 1:1 PS5 rendering.
|
||||
|
||||
ImageGen prompt:
|
||||
|
||||
> Use case: ui-mockup; Asset type controller-button icon mask sprite sheet.
|
||||
> Create a precise horizontal row of five isolated, crisp white controller
|
||||
> symbols on pure black: Cross, Circle, Square, Triangle, Options. Use uniform
|
||||
> optical weight, generous separation, centered geometry, no text, no labels,
|
||||
> no glow, no shadow, no gradient, no texture, and no perspective.
|
||||
|
||||
`controller-imagegen-mask.png` is the generated source. The transparent PNG
|
||||
files are reusable masters; the corresponding TGA files are the PS5 runtime
|
||||
assets.
|
||||
|
||||
Runtime palette:
|
||||
|
||||
- Cross: `#70E1DC`
|
||||
- Circle: `#FF8793`
|
||||
- Square: `#D18BE5`
|
||||
- Triangle: `#69D997`
|
||||
- Options: `#F2F7F8`
|
||||
|
||||
The generated imagery is a project asset. The PlayStation name and controller
|
||||
symbol conventions remain trademarks of their respective owner.
|
||||
|
||||
`play.svg` and `stop.svg` are deterministic project-authored playback symbols,
|
||||
with matching 18 x 18 straight-alpha TGA runtime assets. They render directly
|
||||
on the action button without an enclosing icon well and are not PlayStation
|
||||
button symbols.
|
||||
|
||||
`page-up.svg` and `page-down.svg` are matching 18 x 18 paging chevrons shown
|
||||
beside the available Previous page and Next page actions.
|
||||
|
||||
`status-online.svg` and `status-offline.svg` are project-authored 18 x 18
|
||||
ring-and-core connection indicators. The matching TGA files are deterministic
|
||||
exact-pixel runtime renders because the accepted PS5 texture loader supports
|
||||
uncompressed TGA and intentionally does not include an SVG rasterizer.
|
||||
|
||||
`brand-medallion.svg`, `host-medallion.svg`, and `moon-crescent.svg` replace
|
||||
the remaining RmlUi-generated circles and bordered shapes. Their exact-size
|
||||
TGA renders prevent the horizontal geometry seams seen on the PS5 software
|
||||
renderer while retaining editable vector masters.
|
||||
|
||||
`prosperolight-icon.tga` is a 56 x 56 top-origin runtime miniature generated
|
||||
directly from `sce_sys/icon0.png`, so the ProsperoLight header and launcher tile
|
||||
use the same artwork.
|
||||
@@ -1,10 +0,0 @@
|
||||
# Explicit opaque blend diagnostic (2026-09-19)
|
||||
|
||||
Equivalent ONE/ZERO ADD on the stock shader pass forced explicit blend registers
|
||||
and FP16 exports without changing menu source-alpha blending. All five gates and
|
||||
25 tests passed; the full 30-second console run completed and the script closed
|
||||
it. The owner reported both blue flicker and black triangles unchanged. This is
|
||||
not a verified fix. Raw capture: `klog/gpu-explicit-blend-run.log` (ignored).
|
||||
Tested build identity: 8958ff2aee490a89b788269d647223e713483781f588b5e4b3f23452da1ac481.
|
||||
To retest, apply diagnostic.patch, run tools/verify.sh and run-title.sh --watch 30,
|
||||
and collect owner feedback. The driver was unchanged for this test.
|
||||
@@ -1,22 +0,0 @@
|
||||
# Bounded GPU readback diagnostic (2026-09-19)
|
||||
|
||||
This retired diagnostic established the fragment-input compiler defect fixed in
|
||||
PS5_Vulkan. Apply `diagnostic.patch` to this repository and copy
|
||||
`vulkan_capture.cpp` into `src/`, then run `tools/verify.sh` and the normal
|
||||
`tools/run-title.sh --no-build --watch 30` after checking console availability.
|
||||
It saves menu/target storage on frames 8 and 9 and shader/buffer/table storage on
|
||||
frame 8 through the driver's existing debug API, after `vkQueueWaitIdle`.
|
||||
It does not change GPU state or draw pixels on the CPU. Remove the hook and source
|
||||
again after diagnosis; these captures are not a shipping feature.
|
||||
|
||||
Retrieve `/app0/gpu-*.bin` over the configured FTP into ignored `klog/`.
|
||||
`decode-gpu-captures.py` decodes this run's 320x240 RGBA menu and 3840x2160 BGRA
|
||||
swapchain storage using the driver's measured tile map. `inspect-gpu-resources.py`
|
||||
is specific to the captured addresses/layout in `gpu-resources-latest-trace.txt`;
|
||||
it reads copied binary files, never live GPU memory. Raw files stay ignored.
|
||||
|
||||
Before the driver fix, all six menu vertices had white colour and the menu texture
|
||||
was correct, but no opaque-white sample rendered white. Both fragment varyings
|
||||
compiled with base 0. After the fix, 909/909 samples are white and frames 8 and 9
|
||||
are byte-identical. Evidence: `evidence/vulkan-fragment-inputs/` and
|
||||
`../PS5_Vulkan/evidence/fragment-inputs/`. Owner confirms no flicker or triangles.
|
||||
@@ -1,23 +0,0 @@
|
||||
# Retired XMB framebuffer diagnostic
|
||||
|
||||
The owner authorized screenshots to diagnose XMB corruption. This hook captured
|
||||
swapchain frames 120 and 121 after queue idle and shader/buffer/table resources
|
||||
on frame 120. It is not part of the normal title: the large synchronous writes
|
||||
pause rendering and contaminate the frontend's refresh estimate.
|
||||
|
||||
To repeat: apply `diagnostic.patch`, copy `vulkan_capture.cpp` into `src/`, run
|
||||
`tools/verify.sh`, then `tools/run-title.sh --no-build --watch 45` on an idle
|
||||
console. Retrieve `gpu-target-120.bin` and `gpu-target-121.bin` from the title's
|
||||
FTP directory into ignored `klog/`. The captured resource filenames and byte
|
||||
counts are listed in that launch's trace. XMB has no RGUI menu image, so the
|
||||
zero-byte menu capture is expected. Decode the targets with
|
||||
`python3 parked/xmb-readback/decode-targets.py <capture-directory>`.
|
||||
|
||||
Remove the hook/source again after diagnosis. Refresh the generated
|
||||
`build/ra-conf/gfx/drivers/vulkan.c` from its pristine vendor counterpart, then
|
||||
reapply `tools/apply-port-patches.py build/ra-conf` before rebuilding: the
|
||||
patcher adds named edits but does not undo a retired edit automatically.
|
||||
The driver debug API only reads
|
||||
storage; the rendered image comes from Vulkan's swapchain, not a CPU renderer.
|
||||
The original captures remain in ignored `klog/xmb-capture/` and the corrected
|
||||
ones in `klog/xmb-textures-capture/`. Evidence is in `evidence/xmb-default/`.
|
||||
@@ -1,37 +0,0 @@
|
||||
# The generated runtime module (libc.prx)
|
||||
|
||||
`sce_module/libc.prx` is the module a title carries where the console's loader
|
||||
expects a C library companion. It is generated, not copied: `make libc` (or
|
||||
`tools/rebuild-libc.sh`) builds `tooling/native/libc_builder.cpp` and runs it on the
|
||||
two manifests in `tooling/native/runtime/`, then signs the result as a fake SELF.
|
||||
The code is Copyright (C) 2026 BlackBearReloaded (ps5-native-app-boilerplate),
|
||||
GPL-3.0-or-later.
|
||||
|
||||
What the module contains:
|
||||
|
||||
- a few dozen bytes of machine code written by the builder: an initializer that hands
|
||||
the title's `malloc`/`free`/`posix_memalign` to the kernel's heap hook and registers
|
||||
three thread callbacks, and functions that return zero;
|
||||
- loader metadata: the NIDs, ELF bindings, types and sizes of the symbols the
|
||||
console's libc exports (`api-surface.txt`, recorded by observing that module, as
|
||||
its header says), the imports it declares (`imports.txt`), and a
|
||||
segment layout that matches what the loader accepts.
|
||||
|
||||
It contains no instructions or data copied from a console module. That is
|
||||
established by the generator's source, and by reproduction: on 2026-09-29 the
|
||||
module rebuilt from this repository's committed files alone was byte-identical to
|
||||
the one below, which is also the `libc.prx` in every published PS5_RetroArch release
|
||||
(v0.1.0-alpha.1 to v0.5.0-alpha.5). The name "clean-room", used for this module
|
||||
elsewhere, is BlackBearReloaded's description of how it was written; this
|
||||
repository has no record of that process beyond the source itself.
|
||||
|
||||
The expected artifact has SHA-256:
|
||||
|
||||
```text
|
||||
e6ff45d16adf687855cc3b33b0c8a4132b6504360b221e0a34c7e99fb3ba0036
|
||||
```
|
||||
|
||||
Generate it from the repository root with `make libc`, then verify it from this
|
||||
directory with `sha256sum -c libc.prx.sha256`. The generated file is ignored by
|
||||
Git; a normal build creates it too. The upstream description of the generator and
|
||||
its compatibility scope is ps5-native-app-boilerplate's `docs/RUNTIME_SHIM.md`.
|
||||
@@ -1,24 +0,0 @@
|
||||
# Native host tooling
|
||||
|
||||
All repository-owned format tooling is C or C++. Make/Bash and PowerShell are
|
||||
thin platform-specific orchestrators. No C# or .NET runtime is required.
|
||||
|
||||
The root build performs four native stages:
|
||||
|
||||
1. `prospero-clang18` compiles application sources and `native/app_crt.cpp`.
|
||||
2. LLVM lld resolves objects, static archives, public SDK import stubs,
|
||||
constructors, TLS, and unwind information into an intermediate PIE.
|
||||
3. `native/ps5-native-tool` converts that PIE to the PS5 ELF layout and wraps
|
||||
it in a deterministic development FSELF.
|
||||
4. The requested folder or optional filesystem image is assembled.
|
||||
|
||||
`native/libc_builder.cpp` independently reproduces the generated clean-room
|
||||
runtime from the two manifests under `native/runtime/`. Run `make libc` on
|
||||
Linux/WSL or `../tools/rebuild-libc.ps1` on Windows to prove both hashes.
|
||||
|
||||
`make deps` fetches the pinned public payload SDK and static zlib archive into
|
||||
`.deps/native/`; neither is installed globally. The shell and PowerShell
|
||||
bootstrappers share the same cache layout.
|
||||
|
||||
See [`../docs/NATIVE_TOOLING.md`](../docs/NATIVE_TOOLING.md) for the format
|
||||
boundary and low-level commands.
|
||||
@@ -1,22 +0,0 @@
|
||||
# Dolphin acceptance profiles
|
||||
|
||||
Core-option files for the Dolphin acceptance runs: each is `key=value` lines the
|
||||
core reads from `/app0/dolphin-options.txt` for one test run in place of the
|
||||
frontend's options (the port's test aid, `patches/dolphin/ps5-port.patch`,
|
||||
`DolphinLibretro/Main.cpp`); `#` lines are ignored. The title deletes that file on
|
||||
any launch that is not a test run, so a profile never outlives its run.
|
||||
|
||||
| File | Profile |
|
||||
| --- | --- |
|
||||
| `p0-native.txt` | 0: native baseline -- 1x, no MSAA, JIT64, fastmem, 100% clock, synchronous shaders, EFB copies on the GPU |
|
||||
| `p1-accurate.txt` | 1: accurate baseline -- safe texture cache, scaled EFB copies, GPU texture decoding, exact depth |
|
||||
| `p2-ubershaders-1x.txt`, `p2-ubershaders-6x.txt` | 2: synchronous ubershaders at 1x and 6x |
|
||||
| `p3-torture.txt` | 3: 6x, ubershaders, 16x AF, forced linear filtering, every frame presented |
|
||||
| `p4-gpu-efb.txt` | 4: 6x EFB torture with copies on the GPU |
|
||||
| `p5-ram-efb.txt` | 5: profile 4 with EFB copies to RAM (read back through the CPU) |
|
||||
| `p6-scale-Nx.txt` | 6: the scaling ladder, 1x to 6x on the accurate baseline |
|
||||
| `p7-msaa-N.txt`, `p7-max-torture.txt` | 7: 2x/4x/8x MSAA on the accurate baseline, and 6x with 8x MSAA and profile 3's settings |
|
||||
| `p12-ps5-best.txt` | 12: the PS5 defaults -- 6x, asynchronous ubershaders, 16x AF, GPU texture decoding |
|
||||
|
||||
Each file names the options its profile is defined by, rather than relying on the
|
||||
core's defaults for them.
|
||||
Reference in new issue
Block a user