Commit Graph
14017 Commits
Author SHA1 Message Date
notaxeltvandCursor 7e29ba6fba feat(libSceLoginService): let PPSA34015 load without a missing module
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]>
2026-10-15 18:43:15 +02:00
JJenkx 4187f0b72c fix(build): keep bundled SPIRV validator tables valid at exit 2026-10-15 12:38:32 -04:00
Werner 5aa9af5665 fix(relinker): keep unmapped gaps out of the Windows load image
WindowsLoadImage held one buffer spanning every PT_LOAD and BuildSections
copied gap pages into a section, so an image whose segments sit far apart
allocated the whole gap twice: a 32 KiB input with a 2 GiB gap needed 4 GiB
of memory and 30-40 s for --windows and --macos. Segment pages are now kept
in per-range blocks and an unmapped gap becomes a section with only a
virtual size. Output is byte-identical for the existing test inputs.
2026-10-15 16:36:28 +00:00
Adrià ec1193dc39 Merge pull request #5775 from wachi35676/fix/libc-lgamma-tiny-arguments
fix(libc): keep lgamma finite for subnormal arguments on Windows
2026-10-15 18:36:22 +02:00
Adrià da85abc480 Merge pull request #5774 from wachi35676/fix/libc-printf-unsigned-sign-flags
fix(libc): ignore the space and + flags for unsigned printf conversions
2026-10-15 18:36:17 +02:00
fdfawef324andClaude Opus 5.5 2321f1ac7f fix(libSceAudioOut): count completed AudioOut2 grains in the queue level
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]>
2026-10-15 12:36:14 -04:00
Ondra97 819eb9bb4c fix(libkernel): preserve RTLD_GLOBAL visibility on Windows 2026-10-15 18:35:19 +02:00
Jan ŠléglandClaude Opus 5.5 726ef5f9ae perf(agc): run reads copy the missing blocks of a page mapped whole at once
ShaderMemory::readWords copied a run straight from its page only once every word of the page was
valid. A page mapped whole fills its 16-word blocks on first use, so a run over a fresh page went
through read() one word at a time: the initial-region lookup, the page lookup and a block test
per word. Image-table columns are read this way on every capture.

readWords now copies the run's missing blocks of such a page with one memcpy per run of blocks
and serves the words from the page, marking them read and recent as before. The bytes, the blocks
copied and the regions reported are those of the word-by-word path. Pages read word by word
(pending GPU writes, partly mapped) and the initial regions keep that path.

In 007 First Light's menu the capture walk takes 801 instead of 1,179 us per capture, and 132
instead of 495 us with the image-table column memo.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-15 18:23:28 +02:00
ShinMalphur 8de06375dc test(tools): cover reordered interval phases 2026-10-15 22:48:17 +06:30
ShinMalphur 9f7a8597b7 fix(tools): select largest interval phase by duration 2026-10-15 22:47:51 +06:30
Yunus Shaikh ab82f50a1b Merge branch 'fix/agc-stamp-partly-watched-stores' into perf/shader-image-table-reuse-v3-pr 2026-10-15 09:17:03 -07:00
akkadbakkad89 1a059f7af1 test(libc): cover the whole fmin and fmax family in guest_fminf 2026-10-15 16:10:36 +00:00
Yunus Shaikh 4a84bbf917 perf(agc): remember budget refusals until the budget frees
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.
2026-10-15 09:10:35 -07:00
Yunus Shaikh a24aafb75c perf(agc): import adjacent registered ranges as aligned power-of-two blocks
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.
2026-10-15 09:10:34 -07:00
Yunus Shaikh d6a374a7ea test(agc): read the overhang surfaces after FlushGpuWrites
The storage write-back overhang test read both surfaces straight from
guest memory after FlushAllPending and WaitIdle. A write-back through a
host import is only recorded there; a reader of guest memory calls
FlushGpuWrites, which waits for the batch that holds it. The test passed
because the split surface took the CPU write-back, whose SubmitAndWait
also drained the first surface's recorded store. With both surfaces
imported, neither store had landed when the test read them.
2026-10-15 09:06:54 -07:00
Yunus Shaikh ae9a520f51 feat(agc): print the host import counts at shutdown
A run without APS5_TRACE_HOST_IMPORT had no count of the imports it made
or refused. When the driver stops, one line now gives the live imports
and the imports made, refused and failed, in total and per requester:

[gpu] host imports at shutdown: N live (M MiB); made ... (... MiB),
refused ... (... MiB), failed ...; by requester (made/refused/failed):
map a/b/c, build ..., access ..., join ..., segment ..., expand ...

It prints only when an import was made, refused or failed.
agc_driver_graphics checks the line's text.
2026-10-15 09:06:54 -07:00
Yunus Shaikh 5f532117d3 feat(agc): tag each host import with its requester
Every host import now names the path that asked for it:
- map: the map-time import of a GPU mapping;
- build: an address-based build's walk and its region imports;
- access: a lookup through HostImportFor;
- join: a join at bind;
- segment: a storage image write-back segment;
- expand: a DB expand's store.

The APS5_TRACE_HOST_IMPORT ok line, the import failure line and the
budget refusal report end with the requester, so a traced run splits its
imports by path. HostImportCounters returns the imports made, refused
and failed per requester, with the live count and bytes.

agc_driver_graphics checks that each requester's imports, refusals and
failures are counted under it.
2026-10-15 09:06:54 -07:00
Yunus Shaikh a61c30bff6 test(agc): cover pixel shaders run once per pixel and once per sample
Draws a perspective triangle into a 4x target without attachments and
records, for every pixel and sample slot, how often the pixel shader
ran and what it read: the ancillary's sample number, POS_X_FLOAT and
POS_Y_FLOAT, and the PERSP_SAMPLE, LINEAR_SAMPLE or PERSP_CENTER
barycentrics, which are compared with the values at the expected
position. A shader the guest does not iterate must run once per pixel
and read sample 0's values and the centre, also with
POS_FLOAT_LOCATION at the sample, POS_FLOAT_ULC and a partial quad
sample mask; an iterated shader must run once per covered sample and
read that sample's values; a single-sample draw is unchanged. 2x draws
run where the device supports them. Module checks cover 2x and 4x
without a device: no SampleId in a non-iterated shader, sample 0's
offset, the guest's location table in an iterated shader (standard and
custom per-pixel locations), and no offsets without sampleRateShading.
2026-10-15 16:06:38 +00:00
Yunus Shaikh 2cd0c9107a fix(recompiler): run a pixel shader the guest does not iterate once per pixel
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.
2026-10-15 16:06:38 +00:00
Yunus Shaikh d5cbfd1e12 feat(agc): carry pixel iteration and sample locations in recompile requests
A pixel shader's request now says whether the guest iterates it per
sample (PA_SC_MODE_CNTL_1.PS_ITER_SAMPLE on a multisampled draw), where
SPI_BARYC_CNTL puts POS_X_FLOAT and POS_Y_FLOAT (POS_FLOAT_LOCATION,
bits 17:16, and POS_FLOAT_ULC, bit 20), and the guest's sample
locations for each pixel of the 2x2 quad when the shader reads
PERSP_SAMPLE, LINEAR_SAMPLE or a position at the sample: sample 0's
once per pixel, every sample's when iterated. The fields join the
shader cache key, the draw cache key and the request serialization
(version 31). A missing SPI_BARYC_CNTL reads as the pixel centre and
the reserved location 3 throws.

Devices with sampleRateShading now advertise the InterpolationFunction
capability, which the pixel shader validator accepts.
2026-10-15 16:06:38 +00:00
Yunus Shaikh 07a4ac7e15 fix(agc): run the read guard on a shader capture's raw word reads
On a page that recorded GPU work writes, a capture reads each dword on
its own. Where the driver's evidence says that work leaves the dword
unchanged (PendingWrite::Raw), the dword is copied straight from guest
memory, without the read guard the other word reads get through
GuestMemory::Read. The raw path (29b4601564) predates the guard
(d109f3966f), so a capture whose raw word lies in a live multisampled
target's memory has read stale bytes instead of throwing since then.

The raw read now runs GuardRead over the dword first. With no target
registered that is a few atomic loads, next to the pending-write query
each raw word already makes.

The test answers Raw for every page and word, so the capture's page is
read word by word and raw, and Sync for the word reads that were already
guarded.
2026-10-15 09:03:29 -07:00
Yunus Shaikh e01b1c46ec fix(agc): run the read guard over a shader capture's lazily read pages
Since #1193 (4651980eba) a capture copies a page in 64-byte blocks as it
reads them, instead of reading the whole page with GuestMemory::Read the
first time it touches it. The page now only gets FlushGpuWrites, which
runs the flush hook but not the read guard, so a capture over a
multisampled color target's memory, whose samples are only on the host,
read the stale guest bytes silently where it threw before.

ShaderMemory::page runs GuardRead over the page where the whole-page
read stood, before the flush, as Read does. The check stays once per
page a capture touches, as before #1193. The block copies need none: a
pooled page is reset and goes through page() again, and later reads of
a page were not checked again before #1193 either.

The test captures a shader whose SRT lies in a live 4x target's memory,
then one whose SRT lies just past it, then the first again on the
pooled page.
2026-10-15 09:03:29 -07:00
Yunus Shaikh 5a31a9c674 Merge branch 'perf/shader-direct-cpu-evaluable-images' into perf/shader-image-table-reuse-v3-pr 2026-10-15 08:54:47 -07:00
Yunus Shaikh 75a337f4bc test(agc): end a table's epoch with a store across its heap's edge into unwatched memory
A driver store and a GPU result stored by the driver, each across the last record of a write-watched heap into unwatched memory after it, must refuse the table's vouch and make the next capture read the table again. Without the stamp of a partly watched store's watched blocks, the vouch still passes.
2026-10-15 08:54:46 -07:00
Yunus Shaikh 783ff73dcd test(agc): keep the span query test's user data alive while it captures
TableRequest keeps a span of the user data it is given, so the request must not be built from a temporary.
2026-10-15 08:54:45 -07:00
Yunus Shaikh 4afa20d351 fix(agc): keep table vouches from waiting for queued draws
A table vouch and a capture's stamp ask the pending-write query about a
table's whole span, gaps between records included. With pipelined draws
that query drains queue 0 when a queued draw writes anywhere in the
range, so a vouch could wait for draws that main's per-record checks
never wait for.

Whole-span checks now go through Driver::QueryPendingSpan, which treats
a queued draw writing into the span as a pending write and refuses
instead of draining. The cached variant or the capture then falls back
to the per-record checks, which drain exactly where they did before.
2026-10-15 08:54:44 -07:00
Yunus Shaikh d21b0801a4 Merge upstream main into perf/shader-image-table-reuse-v3
Brings the series onto main 4e9a2a871 (#1193, #1201, #1202, #6060,
#6333 and the descriptor word tracking of 682b07eb0).

Resolutions:
- ShaderMemory: the column memo's supplied and spilled blocks now sit
  on #1193's page vector, page pool and word masks. Pages are found by
  binary search, since a spilled block can add many pages, and a block
  placed into a lazily read page fills the rest of the 16-word blocks it
  touches, so a later lazy read never overwrites placed words.
- ResourceMaterializer and SharedValues: the selected-record path and
  the words index carry availableDwordMask, as the snapshot hash does.
  A selected record's dwords past the end of its buffer count as read
  zeros, which is what a scalar buffer load returns there, so a key past
  the heap still selects a null image without reading the heap.
- DescriptorCache: kept table sets live beside #1201's recycled sets.
- Tests and CMake: both sides' tests are kept.
2026-10-15 08:54:44 -07:00
Yunus Shaikh 19ab974b03 fix(shader): keep the disk cache keys when the image table reuse is switched off
The shader disk cache keys every plan and variant with the recompiler's APS5_* switches that are
set (ShaderDiskCache's switch key), except the ones listed as neutral. APS5_NO_TABLE_SNAPSHOT_REUSE
and APS5_NO_TABLE_RESOLUTION_REUSE were not listed, so turning the table reuse off for a comparison
run gave every shader a new key: a cache filled with the reuse on missed throughout the boot, and
the other way round, and the comparison measured recompiles instead of the reuse.

Both switches only choose between building a column or a resolution and taking the same contents
by reference, so the output does not depend on them: they are now neutral switches. The cache
format is unchanged and FormatVersion stays as it is; only a process that sets one of them now
finds the entries stored without it.

The plan loading test gains a process with both switches set, which must load every stored plan
and variant without running the front end and produce the same output as the first process.
Before this change it failed with every plan and variant missed (17 plan misses, 17 variant
misses); now it loads all 17 with the same output.
2026-10-15 08:54:43 -07:00
Yunus Shaikh 6fb584f018 perf(agc): record a kept table set's descriptors when it is freed, not at every build
Every build of an image table binding made a record of the table descriptors it wrote, so that
a set kept after release could later skip rewriting them: a reference to each element's texture
and a copy of each element's image info. For 4,096 elements that was 4,096 shared_ptr copies and
a 96 KiB copy per build, about 70 us in the Village-like bench (table image infos 112 us per
build against main's 40 us), paid by every build, including those whose set came from the pool
chain and is never kept.

A build now notes only where its table's image infos sit among the ones it wrote, and the runs
it wrote, and only for a set from a dedicated pool. When it took a kept set, it compares that
set's record with its own infos in place. The record is made when the resources are released and
the set is kept: the runs, a copy of the infos, and the build's texture references moved over
once and shared by both sets' records, so the textures stay alive while the set refers to them.

A record is made only once the build's descriptor writes were issued; a build that threw before
vkUpdateDescriptorSets leaves its set without a record, so a later build writes the whole table
instead of trusting writes that never happened. Before, the record was set before the call.

The kept and made table writes are counted as before for sets from a dedicated pool; a set from
the pool chain is no longer counted. The image table reuse test's kept and made writes and the
descriptor cache unit test's kept-set contract are unchanged and pass.
2026-10-15 08:54:42 -07:00
Yunus Shaikh 1d66d852d1 perf(agc): keep a freed image table's descriptor set and skip rewriting a table it still holds
An image table binding holds one descriptor per distinct T# of the table (4,096 for a heap of
32,768 strided records), more than the shared pool chain takes, so every resource build created
a dedicated descriptor pool, allocated the set from it, wrote every table descriptor, and
destroyed the pool when the resources were released: 8-11 ms per build on lavapipe for the pool
and set alone.

A released dedicated set is now kept (up to eight, APS5_KEPT_DESCRIPTOR_SETS, 0 turns it off)
with a record of the table descriptors it holds: the written runs, each element's view and
layout, and the textures behind them, which the record keeps alive so a view handle cannot be
reused while the set refers to it. A build whose layout, pool sizes and variable count match a
kept set takes it instead of creating a pool, and when the table descriptors it would write are
exactly the ones the set holds (same runs, same views, same layouts) the table's writes are
left out of vkUpdateDescriptorSets; every other binding is written as before, and the full write
list is kept for the draw snapshot sets that replay it. A set is released only after the batches
that used it completed (the recorder keeps the resources), so a kept set is idle when it is
rewritten.

What invalidates a kept table: any element whose texture was replaced (a new view), whose image
layout changed, or a different set of elements; each makes the build write the whole table.

ImageTableReuses reports kept and made table writes. The image table reuse test checks three
dispatches of one table keep their writes, a texture cache drop makes one write and keeps the
next, and two tables alternating make every write; it samples every case. The descriptor cache
unit test now expects a freed oversized set to be kept and taken back by a set of the same layout
and sizes, a set of other sizes to get its own pool, at most eight kept pools, and none left
when the cache goes.
2026-10-15 08:54:41 -07:00
Yunus Shaikh acd74bc6bc perf(agc): replay a reused image table's bound elements without checking each one again
A reused table (FindReusedTable, TableCurrent and CachedTableTextures all hold) still bound every
element through the same path as a fresh one: the filter-minmax, border-swizzle and seamless-cube
requirements, and for each sampler the element is used with, a decode of the sampler's words for
the border and anisotropy checks. For a table of 4,096 elements that was most of a hit, about
1.2 ms per table on lavapipe.

Those checks depend only on the element (its words, resource and texture, which a reused table
holds unchanged), the sampler mask the binding gives the element, and the stage's sampler
sources. A table now records, for each element, whether its bind succeeded or the message that
rejected it, with the binding's sampler masks and the stage's sampler words and flags. A reuse
whose masks and sampler sources are the same replays that: a bound element takes its texture,
layer flag and described range directly, a rejected one is rejected with the recorded message
in the same order, so the fault codes are the ones a full bind assigns. Any other reuse binds
every element with the checks and records the outcome for the next. APS5_NO_TABLE_REPLAY=1 turns
the replay off.

ImageTableReuses reports the replayed tables (also counted as reused). The image table reuse test
checks that the three dispatches of one table replay twice; every case it samples runs both with
and without the replay.
2026-10-15 08:54:40 -07:00
Yunus Shaikh dc6c346de7 perf(agc): supply an unwritten column's words without walking its runs
Every unwritten reuse handed the column's block to ShaderMemory::supplyWords, which checked the
block's runs (each in order, aligned, not overlapping, adding up to its words) and asked
GuestMemory::Accessible once per group of pages: one pass over 32,768 runs per capture for
Village's heap, about 200 us in the Village-like bench.

The column memo checks the runs once when it keeps them and marks the block ordered, so a supply
takes the block's span and word count as they are. Accessibility is asked once for the span's
pages; only when that fails (a page between records unmapped, which a stride wider than a page
can leave) are the runs' pages asked group by group as before.

The supply drops to about 3 us. The table vouch test checks that a strided table is kept as one
ordered block over its records' span.
2026-10-15 08:54:40 -07:00
Yunus Shaikh 83c51e9bfc perf(agc): vouch for a cached variant's tables before the checks that walk its runs
validateVariant asked about every run of the variant before it compared anything: the pending
view for each run (syncPendingRuns), the storage-texture registry and the unit shadows for all
runs at once, and every queued label against every run. With a strided table that is 32,768
runs per hit, about 3.2 ms per validation in the Village-like bench, all of it spent finding
nothing.

The shared blocks are now vouched for first, and a vouched range whose block span the pending
view overlaps is dropped from the vouch. The other checks then run over the variant's other runs
plus one range per vouched block, its span: the same questions over a superset of the bytes, so
anything they would have found over the table's runs they still find. When the registry or the
shadows report an image over those ranges, the vouch is dropped and the checks run per run as
before.

In the bench a hit's validation drops from about 3.2 ms to 25 us.
2026-10-15 08:54:39 -07:00
Yunus Shaikh 70129f226b perf(agc): skip a vouched table's regions by range instead of one region at a time
A table of strided records is one captured region per record: 32,768 regions for Village's
heap. Validation by vouch still walked them all: VouchSharedRegions marked each one, and
validateCaptured asked the pending view about each one and tested its mark, about 100 us and
150 us per hit in the Village-like bench.

Each shared run now records the regions it was built from (AppendSharedWords takes the region's
index and joins only consecutive regions), so VouchSharedRegions returns ranges of regions with
their word count and the span of their block, one per shared run. validateCaptured asks the
pending view once per range, over the block's span, and skips the range in one step when nothing
is pending there; a range the view overlaps is checked region by region as before.

The identity test checks that runs of regions that are not consecutive stay apart.
2026-10-15 08:54:38 -07:00
Yunus Shaikh 6643e2870c perf(agc): tell the flush hook that no image is pending over a range without the registry mutex
Every guest read through GuestMemory (and every table vouch) runs the recorder's flush hook,
which asks the storage-texture registry for pending images over the range: under the registry's
mutex, even when no pending image is anywhere near it. With queue workers recording and storing
images, that mutex is contended: a Village-like bench (three workers, a pending storage image
per submission) spent 72 us per hook call waiting for it, 200 us per table vouch.

The registry now publishes the merged ranges of its pending images with the version they were
built from, rebuilt under the mutex as before. overlappingPending answers a range that no
published range overlaps without the mutex, when the published version is the registry's
current one; any other case takes the mutex as before. A change bumps the current version with
release order before the mutex is released, so a reader that could see the change through any
other path cannot read the old ranges as current.

In the bench the hook drops from 65-81 us to 4.5 us per call and a table vouch from about 200 us
to 23 us. The existing storage-texture tests cover the pending images the hook must still find.
2026-10-15 08:54:37 -07:00
Yunus Shaikh 23c201722c perf(agc): validate a cached draw or dispatch by its shared table block's vouch
A cache hit compared every captured region against guest memory, the table's words included:
about 1 MiB and 32,768 regions for Village's heap, 77-81 us per compare. When a capture shared
a column's words by reference (the memo's block), the variant records that block for those
words, and an unwritten block now vouches for them instead: one ShaderMemory::Vouched call per
block (the layout, no pending write, the flush hook, collect and UnchangedSince over the block's
span), then validateCaptured skips the regions the block holds. Regions noted pending are always
compared, and so is everything when the block is gone or refused.

The block keeps its stamp in a shared cell instead of the memo, so a compare that finds the
column unchanged after a store elsewhere in its span renews the stamp for every variant sharing
the block, not just for the thread that compared it. The cell is a sequence lock: a reader that
races a renewal gets no stamp and compares.

APS5_NO_VOUCHED_VALIDATION=1 turns the vouch off. ShaderMemory::Vouches counts vouched and
refused blocks and the words they covered.

Tests: VouchSharedRegions over a strided table (only the table's regions, one block, one call),
refused for a pending GPU write into a record or between records, a changed mapping, CPU, driver
and GPU-result stores, a store between records until a compare renews the stamp, and a released
block; and agc_driver_image_table_vouched_lookups, which dispatches over a write-watched heap
(APS5_WRITE_WATCH_IMPORTS=watch, as on Windows) and checks that hits are vouched for and that a
CPU store, a CP WRITE_DATA pending ahead of the dispatch and a driver store each refuse the vouch
and capture the table again.
2026-10-15 08:54:37 -07:00
Yunus Shaikh 97590db998 perf(shader): vouch for an unwritten table column once over its span, before stamping it
An unwritten column was vouched for run by run: ShaderMemory::unchangedSince, with its pending
write query, flush hook, collect and tracker lock, once per contiguous run of the words the
column read. A heap whose records are wider than the descriptor they hold, like Resident Evil
Village's T#s of 8 dwords at a 48-byte stride, makes every record its own run: 32,768 calls per
reuse there. Each capture also took a new stamp before it looked at the memo, so a hit paid the
pending query, flush and collect twice.

The memo's words now carry the range their runs span, and a reuse vouches for that range in one
call. The range covers more than the runs (the bytes between records), so a store between two
records refuses the vouch and the column is compared instead: still exact, never stale. A hit
is tried before the capture stamps the column; the stamp is taken only when the memo cannot be
reused unwritten. ShaderMemory::Vouches counts the vouch calls.

The test reads a strided table of 4,096 records and checks that its reuse vouches in one call,
that a store between records compares and keeps the column, and that a store into a record
reads it again.
2026-10-15 08:54:36 -07:00
Yunus Shaikh a35de33db5 fix(agc): stamp a table before its pending-write query and note GPU writes before stamping
A table column's stamp vouches that nothing wrote its memory after the stamp's generation and
nothing was pending at the time. stampWrites asked for pending writes before it took the
generation, and two GPU write paths stamped their blocks before they noted the write pending:
a label or store kept behind completions, and a DMA copy. A capture on another queue worker that
ran between those two steps saw no pending write and took a generation newer than the write's
stamp, so once the batch was reaped the column vouched for the bytes from before the write.

stampWrites now flushes, takes the layout and generation, and asks for pending writes last. The
label path notes the completion before it stamps the store. The DMA copy still stamps first, so a
newer copy's ring entry never meets an older one's generation, then notes the write pending and
stamps again, and its ring entry carries the second generation. A store noted after the pending
query is now always stamped after the generation the column keeps.

The new two-thread test stamps a GPU write from another thread while the capture asks for
pending writes; the next capture must compare the table instead of reusing it.
2026-10-15 08:54:35 -07:00
Yunus Shaikh a7fb1b6c09 perf(agc): size a cached variant's words once before copying a capture's regions
cacheDrawStages and insertDispatch grew a new variant's words one region at a time. A table column
read from pages, or shared and reported per page, is one region per 4 KiB page, so the words of a
32,768-record column grew through 256 resizes and about eight reallocations, each copying what was
already there. Both now reserve the runs and the words once before copying. Built the same way in
the image table microbenchmark, a 262,156-word capture's variant took a median of 191 us growing
per region and 79 us reserved.
2026-10-15 08:54:34 -07:00
Yunus Shaikh 25cfce3bda perf(agc): compare two captures' shared table words by identity in the draw cache
The draw cache sets each fresh stage capture against the variants it keeps (insertDrawEntry), and
the dispatch cache sets a fresh capture against its front variant, by comparing every captured word.
A 32,768-record table column is 262,144 of them, so each compare went through 1 MiB on each side
even when both captures had copied the column from the same shared block.

A variant now records the words it copied from a block the shader memory shares (SharedWords: where
they sit in the capture and in the block, and a weak reference to the block; consecutive pages of
one block join into one entry). SameCapturedWords takes the words both captures copied from the
same place of the same block as equal, since a block never changes, and compares the words around
them as before. Two blocks with equal words, words read into pages and a released block fall back
to the word compare. The reference is weak, so a kept variant holds no block alive, and a released
block's identity cannot pass to a new one: its control block stays until the last weak reference
goes.
2026-10-15 08:54:33 -07:00
Yunus Shaikh 077bc20a24 perf(shader): reuse an unchanged image table's resolution by reference on a result memo miss
A result memo miss resolves every image table of the snapshot again: ResolveImageTables decodes each
distinct T#, builds the element and poison indices and fills one map record per key, which is most
of a miss for a large table. A miss whose table did not change is common: another buffer, flat SRT
word or user data word changes the result key while the column is the one the column memo shares.

ResolveImageTablesShared keeps the last resolutions and hands one out by reference when the variant
(its id), the reducing state of the direct samplers paired with table images and every column match:
base, size, records, keys, fault, outside, and the column's codes and distinct T#s by identity. The
memo holds those shared values, so a block it compares cannot be freed and reused for other words;
a column read again is a new block and resolves again even when its words are equal.

It keeps at most 4 resolutions per variant and 256 in all within 8 Mi words, counting the columns
their keys keep alive, least recently used first out, under one mutex. A resolution is immutable
once made and is shared between threads read-only. APS5_NO_TABLE_RESOLUTION_REUSE=1 turns it off.
ImageTableReuseCounts gains the resolutions made and the ones shared.
2026-10-15 08:54:33 -07:00
Yunus Shaikh 992222d309 perf(agc): keep an unwritten table column's words by reference in the shader memory
When the write tracker vouched for an unwritten image table column, the capture still copied each of
its words into ShaderMemory's pages (supplyWords) so they would be in the captured regions, and
Regions() then found them again bit by bit. On a 32,768-record column that is a 1 MiB copy per
capture (about 120 us here) and a scan of 262,144 read bits per Regions() call (about 230 us), and
the draw path calls TakeRecentRegions() and Regions() once each per stage.

The memo now keeps a column's words in one immutable block (SrtGuestWords), and supplyWords hands
that block over. ShaderMemory keeps a reference and reports its runs page by page, the regions a
read of the same words reports, merged in address order with the pages and the initial regions; a
second stage that supplies the same block only marks it recent again. The words reach pages only
when they must: a later read in the same ShaderMemory that touches them first moves the whole block
into its pages (spilled), so a word is never reported twice, and a block that overlaps pages already
fetched or another block's runs is copied as before. A spilled block stays referenced until the
ShaderMemory goes, so the regions already taken from it stay valid. ShaderMemory::Supplies() counts
the words shared, copied and spilled.
2026-10-15 08:54:32 -07:00
Yunus Shaikh d816062960 perf(shader): share an unchanged image table column with every snapshot that reuses it
A capture that reused a memoized image table column still copied it: each record's state and code
and the distinct T#s went from the memo into the snapshot, the snapshot rebuilt its codes from the
states, and the column's per-record vectors were allocated before the memo was even looked up. Every
Recompile then hashed the codes and the distinct T#s word by word for the result memo. On a
32,768-record column of distinct T#s that was about 155 us of copying per capture and 410 us of
hashing per Recompile here, for a column nothing wrote.

A column is now built once into immutable shared values (SharedValues: its codes, its distinct T#s
and its read ranges) and the memo keeps that column. A capture that reuses it takes a reference, so
two captures of an unchanged table share one column and copy nothing, and the per-record vectors
are allocated only when the column is walked. The codes index the column's own distinct T#s
(ImageTableColumnSnapshot::words) instead of a list shared by the shader's columns, so every column
of a shader is shared whole, not only the first. A column read again after a write is a new one:
the snapshots holding the old one keep it unchanged. The result memo hashes each shared value once
and keeps the hash in it; equal contents hash equal whether they are shared or not.

ImageTableReuseCounts gains the reuses that shared a column and the columns built.
2026-10-15 08:54:31 -07:00
Romans Pokrovskis 4fe92c9c23 test(agc): align indirect argument storage at runtime 2026-10-15 18:54:28 +03:00
Yunus Shaikh 285ac78c06 test(agc): check a store across a watched import and one that left the watch
Since #6060 a settled Windows host import of private write-watched
memory stays watched, and its bytes are vouched by the write tracker's
stamps instead of compared. A GPU write that runs from such an import
into a neighbouring import that left the watch reaches the tracker as
one MarkWritten over a partly watched range. The test imports two
blocks that stay watched next to one that leaves the watch, and checks
that a driver store and an own store across the boundary stamp the
watched block they wrote, are seen over exactly its stored bytes, and
leave the other watched block unchanged.
2026-10-15 08:53:56 -07:00
Adrià 0029e62f97 Merge pull request #5773 from fogydes/fix/relinker-eh-frame-unwind-address-overflow-guards
fix(relinker): guard against address underflow in encoded unwind pointers and reject zero FDE CIE offset
2026-10-15 17:53:08 +02:00
Adrià 02a1b772ed Merge pull request #5770 from simonbronk/fix/kernel-mprotect-undefined-bits
fix(libkernel): drop undefined protection bits in sceKernelMprotect
2026-10-15 17:53:02 +02:00
Yunus Shaikh 89778ecdac fix(agc): stamp a block whose watched pages lie inside a partly watched store
stampWatchedPart stamped a block when the store's part of it started or
ended in watched memory. That is exact on Windows, where the watch
excludes whole 64 KiB blocks and the arena's host regions are 64 KiB
holes. On Linux and macOS the watch unregisters page ranges, so a
block can be watched only between two unwatched ranges, and a store
across all three left its watched pages with their old stamps.

Off Windows the block is now stamped when the store's part of it
overlaps any watched page (GuestWriteWatchOverlaps). A store that
overlaps no watched page at all returns after that one lookup, where
it made two coverage lookups per block before.

The test watches only the middle pages of a block: a driver store and
an own store around them must stamp it, a store into an unwatched end
must not.
2026-10-15 08:52:55 -07:00
Leonardo 17f08ae814 Merge branch 'main' into feat/voiceqos-endpoints 2026-10-15 17:52:30 +02:00
Yunus Shaikh f697e1a41b docs(libkernel): move the host stack protect entry next to the other protect entries
The entry now sits with the other sceKernelMprotect entries for
DirectMemory.cpp, before the Windows guest module image protection one,
instead of after the setcontext entry, where #5918 adds its own.
2026-10-15 08:52:22 -07:00