INPUT_MAPPING.md names the actions and says that omitted actions keep their built-in bindings, but not what those bindings are. The table is read from InputMapping in core/libs/prx/libScePad/include/InputMapping.hpp.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The host heap's VK_EXT_memory_budget report lowered a set
APS5_HOST_IMPORT_MIB of 6400 MiB to 4480 MiB on Resident Evil Village
(PPSA01556), where 6400 MiB runs without a vkAllocateMemory failure, and
85,047 imports were refused. A set APS5_HOST_IMPORT_MIB is now never
lowered by the report, and the default is held to the report only once
an import's vkAllocateMemory has failed. The failure ceiling is
unchanged. The first time the report would lower the budget, a [gpu]
host import budget line prints the heap's reported budget and usage and
the live imports, and the refusal line names where its budget came from.
The Shader cache section says only that subdirectories of other versions are removed after 14 days. housekeeping() in ShaderDiskCache.cpp removes nothing from the current version, and no size or entry limit applies there.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A heap mirror is refreshed from the write watch. acquireMirror checked
the watch only when it made a mirror and reused an existing one as it
was. Once part of the range left the watch, the next address-based build
threw "a heap mirror's range is no longer write-watched" from the
refresh, and a build served from the address-space cache threw the same
from its heap runs. A range leaves the watch for good when a host import
that does not keep the watch covers it: on Windows a direct import of
private memory that is not settled (below the 1 MiB settle floor, or any
when imports cannot be settled), elsewhere any import while imports are
compared.
A heap mirror now records the unwatch serial at which its range was last
seen fully watched. Its reuse, and a cache hit over it, compare that
serial with GuestMemory::UnwatchSerial and look at the watch again only
when a range has left it since. A mirror whose range is no longer
watched is not reused, the cache is not taken (before its imports are
stamped), and the pass plans the range again: an import if the budget
allows, else a copy. A refused heap mirror also drops the registry's
stale one, whose buffer would stay allocated otherwise.
sceSysmoduleIsLoaded, sceSysmoduleLoadModule and sceSysmoduleUnloadModule
return success for an id missing from the module table, because titles that
load or unload such an id aborted otherwise (b4db9fdd6, a2520aa07). Nothing
tested or recorded it, and two commented-out throws were left in the code.
Remove the commented-out throws, list the three functions under Silent stubs
in the library's technical debt file, and test that unknown ids succeed
without changing state while the internal variants still throw.
Written with AI assistance (Claude).
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Since ab3e92605 the entry stub gives each missing standard handle
anyps5.log beside the executable, and the crash report writes to the
standard error handle, so it reaches the log too. The entry now records
only what is still lost: the stub's errors about the executable path or
about waiting for the previous LoadExec process, written before the log
is opened, and output to the missing handles when the log cannot be
created.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_0154rPujpieMaCnZ6WMobg7M
A geometry program with VGT_GS_OUT_PRIM_TYPE 1 was refused as an
unsupported output assembly. Each primitive export of an NGG line-strip
program is one line whose indices are packed like a triangle's, so the
mesh module now declares OutputLinesEXT with PrimitiveLineIndicesEXT for
it. A subgroup gets VGT_GS_MAX_VERT_OUT - 1 lines per input primitive and
at least GE_MAX_OUTPUT_PER_SUBGROUP line slots; fast launch keeps using
PRIM_AMP_FACTOR. The output size is part of the cache, draw, validation
and pipeline keys, and the request serialization version becomes 31.
Triangle-strip geometry is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The logger thread, the bounded queue that blocks its caller, the LogSink
hook, APS5_LOG_SYNC and APS5_LOG_STATS, where the queue is flushed and
what stays outside it: direct fprintf(stderr) and printf calls are written
at once, so they can appear before older queued lines.
APS5_LAZY_SHADER_PREPARE=1 keeps registration validation and decoding but skips queueing the preparation. Draws and dispatches prepare a deferred snapshot on first use and cache it; explicit ABI resolution still prepares its stages. Titles that register thousands of shaders at boot no longer hold every prepared shader in memory before the first frame.
Cover lazy dispatch, metadata and failure cases in both asynchronous and synchronous modes.
AnyPS5 exports some libkernel functions, such as __error, from
libc.prx, which libkernel.prx links. On Linux they resolve through that
dependency. On Windows, the import stubs search only the listed
modules, and libc.prx was listed only when libSceLibcInternal.prx was
needed. A PS4 homebrew that needs libkernel.prx but no C library (the
OpenOrbis graphics sample) failed with "unresolved ELF import
9BcDykPmo1I". libc.prx is now also listed when libkernel.prx is needed.
PS5 executables need libSceLibcInternal.prx and convert unchanged.
Village's 2x checkerboard puts its two samples at x = +4/16 and -4/16
and swaps their indices on odd rows. On a device without a 2x2 sample
location grid every pixel takes pixel (0,0)'s locations, so the odd
rows hold the console's sample 1 in sample 0 and the reverse: a pass
that reads the samples by index, as checkerboard reconstruction does,
exchanges the two half-pixels of every odd row, and SampleID, sample
masks and exported coverage name the wrong samples there.
Such a pattern holds one set of positions in every pixel, so drawing
with pixel (0,0)'s locations is exact up to the order of each pixel's
samples. The draw now keeps the guest's order: the pixel shader reads
SampleID, applies the sample mask and exports coverage through each
pixel's order, and every render pass drawing with the stand-in puts the
samples of its multisampled attachments in the stand-in's order before
it and back in the guest's after it. Color targets do that through a
storage view of their texels as unsigned integers, depth through a copy
and a per-sample pass writing FragDepth, and stencil through
VK_EXT_shader_stencil_export or one pass per bit.
It needs shaderStorageImageMultisample with a storage view of the color
format, sampleRateShading, and a depth format FragDepth writes back
exactly (16-bit or 32-bit float); otherwise, and for patterns whose
pixels hold different positions, the stand-in alone is used, and the
log line says why. Devices with the grid are unchanged.
Recompile requests carry the order as serialization version 32 (#6470
takes 31), and the shader cache format becomes 40 (open pull requests
take 37 and 39).
sceImeDialogInit wrote "Slayer" into an empty text buffer and the
dialog finished on the second status poll, so a title never got text
from the user: ProsperoRadio's search typed "Slayer" by itself and
found no station.
The dialog now runs until the user answers on the host keyboard, the
way #4756 made sceImeOpen's panel-less IME take it. While no IME panel
is open, libSceIme hands the game window's text and editing keys to
a sink that the dialog registers. Typing edits the text up to
maxTextLength without splitting a surrogate pair, Enter writes it to
the title's buffer and finishes with end status 0, and Escape finishes
with 1. Abort still finishes with 2. A second Init reports busy, and a
null parameter or buffer or a maximum length outside 1 to 2048 is
refused, as in shadPS4.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
A resource-cache entry keeps every sampled texture of its descriptor sets alive. The sampled texture cache passes over textures something else holds and, past its ceiling, drops them anyway; textures it makes without caching are never in it. Entries holding either kept them alive with no budget counting them, bounded only by the 1,024-entry and 65,536-element counts, until device memory ran out.
The texture cache now marks such textures released. ResourceCache::Insert refuses descriptor sets holding one and hands the entries that hold one back to the caller, like RemoveImage does for evicted storage images.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Imports were held to a configured budget only. With APS5_HOST_IMPORT_MIB
at 6864 on a 15.4 GB PC, Village's imports reached the NVIDIA driver's
pinning ceiling of about 6.56 GiB, and from there every import was tried
and failed at vkAllocateMemory, 992 times.
Where the device enables VK_EXT_memory_budget, the budget is now also at
most the host heap's reported budget less its other usage and an eighth,
in 64 MiB steps, read at most once a second. A vkAllocateMemory failure
within an eighth of the budget, or the third in a row, lowers the budget
to the live imports, so later imports are refused or evict instead of
failing; an isolated failure further below only marks its range failed,
as before. A remembered refusal stays remembered while the budget does
not grow.
FC 27 stops on Windows with Failed to load module / error 126 because this PRX is absent. Export Initialize and Terminate as no-ops and keep RequestDevices throwing until the buffer layout is known.
Co-authored-by: Cursor <[email protected]>
sceAudioOut2ContextGetQueueLevel left out the grains that
sceAudioOut2ContextAdvance completed and no push had consumed yet. While
pushes filled the 40 ms cushion the level stayed at 0, so a title that
renders as many grains as the level leaves room for and pushes them on
another thread rendered a full queue again after each push. PPSA23226's
Wwise sink does that and stopped once more than 16 completed grains
awaited a push.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An address-based build asks for every registered GPU range whenever it
rebuilds its address space, and at a full budget it was refused each
one again: a title held at its default budget counted 304,394 refusals
over about 12,000 ranges at its title screen.
A request that may not evict (the build's walk, an import when a range
is mapped) now remembers a refused run with the live bytes and the
budget at the refusal. Until live imports drop below those bytes, the
budget changes or one of the run's ranges changes, a request for that
range is answered from memory, still counted as refused and also as
remembered, and a remembered block is skipped, so a block stepped down
for the budget keeps its import across registry changes. A request
that may evict always tries again, and a new import forgets the
refusals of the runs it covers. The shutdown report and the budget
line print how many refusals were remembered.
An import covered one registered range unless adjacent ranges filled a
whole aligned 8 MiB window. A title that maps its GPU heap in 64 KiB
pieces got one import per piece from every requester: Village held
about 27,000 of them at its first scene, enough to fill the budget.
The run of an address is now the largest aligned power-of-two block,
from twice the import alignment up to the window, that holds the
address and that continuing readable ranges fill exactly.
A block over the budget steps down to the largest block that fits what
is left of it, counting what the requester may evict, and nothing is
tried when not even the address's own range fits, so a full budget
costs one refusal per request as before. A block that would replace an
import a build holds, or that fails (a refused Windows alias,
vkAllocateMemory), steps down to the next smaller block. A failed block
is remembered until one of its ranges changes, instead of pinning the
span to single ranges for the session. An address-based build no
longer asks for the ranges inside the block it just imported.
A storage image over 64 adjacent pieces now finds its block import at
write-back instead of importing every piece as a segment. The split
write-back test keeps its two ranges apart with a 64 KiB window.
The ancillary input's sample number (bits 11:8) was the SampleId
builtin in every pixel shader. Vulkan enables sample shading for a
shader that uses SampleId, so a shader that reads the sample field or
the whole ancillary word ran once per sample on a multisampled draw
even when the guest runs it once per pixel (PS_ITER_SAMPLE clear), and
FragCoord, and with it POS_X_FLOAT and POS_Y_FLOAT, moved to the
sample. The sample number is now SampleId only when the request says
the guest iterates the shader, and otherwise 0, the iterated sample
number of a pixel the hardware does not iterate.
The other inputs that depend on the sample follow where the console
evaluates them:
- PERSP_SAMPLE and LINEAR_SAMPLE are interpolated with
InterpolateAtOffset at sample 0's offset in a shader the guest does
not iterate, and in an iterated one at the offset of the host's
SampleId, read from a table of the guest's locations, because Vulkan
leaves plain barycentrics under sample shading at an
implementation-defined position (lavapipe uses the pixel centre).
- PERSP_CENTER and LINEAR_CENTER are interpolated at offset 0 in an
iterated shader.
- POS_X_FLOAT and POS_Y_FLOAT follow SPI_BARYC_CNTL: the centre is
floor(FragCoord) + 0.5 under sample shading, a sample position in a
shader the guest does not iterate is the centre plus sample 0's
offset, and POS_FLOAT_ULC gives the upper-left corner.
The offsets need InterpolationFunction (sampleRateShading) and the
fragment-shader barycentric path; devices without them keep the host's
default positions. The shader disk cache format goes to 39.