4.5 KiB
4.5 KiB
Architecture
How a PS5 executable becomes a native Linux or Windows program. Nothing is emulated: the converted executable runs as a normal process and calls native implementations of the system libraries.
Overview
flowchart LR
subgraph input["PS5 game"]
elf["input.elf"]
mods["sce_module/*"]
end
subgraph relinker["relinker (core/relinker)"]
direction TB
intel["--to-intel:<br/>lower AMD-only instructions<br/>(codegen)"]
pipeline["RelinkerPipeline:<br/>read imports by NID,<br/>check syscalls, filter unused NIDs,<br/>build SysV dynamic section"]
guest["GuestModuleBuilder:<br/>convert bundled modules"]
patcher["LinuxElfPatcher / WindowsPePatcher"]
intel -.-> pipeline --> guest --> patcher
end
subgraph build["build (core/libs)"]
direction TB
prx["core/libs/prx/*<br/>shared libraries"]
nid["nid_patcher:<br/>rename exports to their NIDs"]
prx --> nid
end
subgraph out["Native program"]
app["app.elf / app.exe"]
app0["app0/sce_module/*"]
libs["libs/*.prx"]
end
elf --> intel
mods --> guest
patcher --> app
guest --> app0
nid --> libs
app -- "OS loader binds imports by NID" --> libs
app0 -- "OS loader binds imports by NID" --> libs
core/relinker/main.cppruns the steps in this order.--to-intelis optional, see USAGE.md.- Each library in
core/libs/prxbuilds as a shared library. After the build,nid_patcher(core/libs/nid) renames every export to its NID, computed from the function name.APS5_EXPORT("<nid>", func)sets the NID directly when the name is unknown.
Graphics
flowchart TB
subgraph init["Game initialization"]
direction LR
create["libSceAgc:<br/>create shader,<br/>link stages,<br/>primitive state"] --> reg["libSceAgcDriver:<br/>RegisterShader,<br/>Resolve*Abi"]
end
subgraph recompiler["core/shader/recompiler"]
direction LR
dec["RdnaDecoder"] --> cf["ControlFlow:<br/>graph + structurize"]
cf --> tr["Translation:<br/>RDNA to IR"]
tr --> opt["Optimization:<br/>SSA, resources,<br/>bindings"]
opt --> spv["SpirvBackend:<br/>emit SPIR-V"]
end
subgraph frame["Each frame"]
direction LR
game["Game:<br/>command buffers"] --> submit["libSceAgcDriver/Submit<br/>DCB / ACB"]
submit --> pm4["Execution/Pm4:<br/>state, draws,<br/>dispatches"]
pm4 -- "user data +<br/>descriptors" --> inv["Invocation:<br/>specialization constants,<br/>descriptor heaps, BDA"]
inv --> vk["libSceAgcDriver/Graphics:<br/>Vulkan pipeline"]
end
init -- "code + static ABI" --> disk{"Compiled artifact<br/>on disk?"}
disk -- no --> recompiler
disk -- yes --> art["Prepared artifact:<br/>SPIR-V with no<br/>resource values"]
recompiler --> art
art -- "looked up at<br/>draw / dispatch" --> frame
- Shaders are compiled when the game registers them, not when they are first drawn.
ShaderRegistry.cpprunsDriver::RegisterShaderfor every shader the game creates, andResolve*Abiwhen libSceAgc links stages or sets the primitive state. Each one compiles the artifact for that static ABI (stage, wave size, push constant layout, linked stages). - An artifact does not depend on the resources bound at draw time. The shader reads them at runtime, as on the PS5: buffers, images and samplers from descriptor arrays and typed heaps indexed at runtime, and user data from the shader data block defined in
RuntimeAbi.hpp. - At a draw or dispatch, the driver looks up the prepared artifact and fails if there is none. Only compute shaders the game never registered are compiled at that point. The draw supplies the invocation: the specialization constants listed in
PipelineSpecialization.hpp, the descriptors and the push constants. Each distinct set of constants produces one specialized SPIR-V module, kept in memory. Recompiler.cppruns the stages in this order. WithANYPS5_ENABLE_SPIRV_TOOLS, the SPIR-V is also validated and optimized with SPIRV-Tools.ShaderDiskCachestores the artifacts on disk so later runs skip the recompilation.