Commit Graph
14017 Commits
Author SHA1 Message Date
Dean 2c4574adff Merge pull request #6247 from oneandonlydean/carry/register-clear-wait
perf(agc): clear a register fast clear without waiting for a known key store
2026-10-15 22:47:15 +13:00
Dean 2848b55b92 Merge pull request #6230 from oneandonlydean/fix/relinked-name-scan
fix(libc): list the directory once for a missing path that may be a relinked module
2026-10-15 22:46:44 +13:00
Nuno Silva bc316c7341 revert(libSceAgcDriver): drop the unused StoreAtLabelRequested helper
The eager-writeback commits added StorageTexture::StoreAtLabelRequested
(APS5_STORE_AT_LABEL). Nothing reads that variable, so the helper is dead
code; the deferred-writeback revert kept it by accident. Remove it so the
PR changes only the staged-clear sampled-read flush and its test.
2026-10-15 10:43:54 +01:00
Adrià FranchandClaude 43d7c11b80 fix(agc): a pipelined draw owns its ShaderMemory through a unique_ptr now that it cannot be moved
Co-Authored-By: Claude <[email protected]>
2026-10-15 11:36:09 +02:00
Adrià FranchandClaude 1df509baff test(agc): run the failure and draw cache reuse tests with pipelined draws, pin CMASK write ranges
Co-Authored-By: Claude <[email protected]>
2026-10-15 11:34:29 +02:00
Adrià FranchandClaude 62e5bc28d0 fix(agc): a pipelined draw keeps its patched and freshly materialized stages alive
The commit lambda held raw pointers into the draw's stage-data hits and compiled stages, which
were freed when the worker returned (agc_driver_draw_cache_reuse with APS5_PIPELINED_DRAWS=1).

Co-Authored-By: Claude <[email protected]>
2026-10-15 11:34:29 +02:00
Adrià FranchandClaude 5c09de1510 fix(agc): a failed pipelined commit stops queue 0 like a failed worker
Co-Authored-By: Claude <[email protected]>
2026-10-15 11:34:29 +02:00
Adrià FranchandClaude ee0a1889b5 fix(agc): an emptied buffer pool size class is erased again
Keeping it made evictOldest read the front of an empty deque (agc_buffer_pool aborted with a
double free) and stopped Take at an empty class instead of trying the next size.

Co-Authored-By: Claude <[email protected]>
2026-10-15 11:34:29 +02:00
Adrià FranchandClaude 4051ee19c5 docs(architecture): describe the queue-0 draw pipeline
Co-Authored-By: Claude <[email protected]>
2026-10-15 11:34:29 +02:00
Adrià FranchandClaude 5d5aeab1fb fix(agc): a queued draw's writes include its color targets' CMASK
Co-Authored-By: Claude <[email protected]>
2026-10-15 11:34:29 +02:00
Adrià FranchandClaude bacd50a568 fix(agc): a failed pipelined commit is rethrown like an inline draw error
Co-Authored-By: Claude <[email protected]>
2026-10-15 11:34:28 +02:00
Adrià FranchandClaude 3826c13146 fix(agc): a pipelined memory-value or buffer-pattern fill reads its source after queued writes
Co-Authored-By: Claude <[email protected]>
2026-10-15 11:34:28 +02:00
Dean Galvin a719fdca60 fix(agc): a pipelined fill reads its inputs after queued writes and never waits for room under the GPU lock
A pattern fill reads its count and pattern from guest memory on the queue worker. With pipelined
draws it read them while earlier commit items that write them (labels, DMA stores, draws) were
still queued, so it stored the stale value (agc_driver_pattern_buffer_fill under load). It now
drains the draw pipeline first when a queued item writes one of its input ranges.

The same fill held the GPU lock from the input read through DrawPipeline::Enqueue. With the
pipeline full, Enqueue waited for the committer while the committer's next item waited for the GPU
lock: a deadlock. The input lock is released before the fill is queued, and Enqueue throws when it
would wait for room while the calling thread holds the GPU lock.

(cherry picked from commit c653e9ea1d1030ebf691015b30ea04f1cdd59ef8)
2026-10-15 11:34:28 +02:00
Eli Yarson 084d1fe0ae fix(agc): a label packet behind a deferred label is not enqueued ahead of it
enqueueLabelPacket lacked the empty-deferred-labels guard that enqueueDmaPacket and the draw path
have. A predicated WRITE_DATA is never enqueued, so it is deferred on the worker; an unpredicated
WRITE_DATA to the same address that follows it was enqueued and committed first, and the deferred
store was recorded after it, leaving the older value (an EOP interrupt could fire out of order the
same way). With the guard the later label takes the packet path, which drains and orders it behind
the earlier one.

(cherry picked from commit 1f894c9b641da3460b684a8356655fbf1aeee635)
2026-10-15 11:34:28 +02:00
Eli Yarson 6ad05318c9 fix(agc): a predicated packet drains queued writes to its predicate before reading it
A predicated draw, dispatch or register packet stays on the pipelined keep list, so it did not drain
before the worker read its predicate. A WRITE_DATA or label to the predicate address, queued behind
earlier commit items, was not stored yet: SET_PREDICATION on P, WRITE_DATA P = 1, then a predicated
dispatch skipped it as if P were still 0 (and ran it when P was cleared). The worker now drains the
draw pipeline first when a queued item writes the predicate's bytes.

(cherry picked from commit 56b94480b3de04dfb5c4f4a1bfc5731df59eae29)
2026-10-15 11:34:28 +02:00
raghav314 dc027cc004 fix(agc): note an HTILE depth clear after the pipeline drain
The note landed ahead of the draws still queued over that depth surface.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
(cherry picked from commit 41f385acbf8da99fdf3f8a8c0bd9a8802383d05f)
2026-10-15 11:34:28 +02:00
raghav314 7b40c05a60 fix(agc): entry validation drains queued draws that write its runs
A capture already drains first; validation compared the runs with guest memory while a queued draw could still write them.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
(cherry picked from commit a9c15be3e1c27e48b2e6a04e44c47dd5203a3057)
2026-10-15 11:34:28 +02:00
raghav314 a39261b75c fix(agc): a CB metadata pass drains the draw pipeline before it runs
The resolve, CMASK and DCC passes read the color target on the worker while queued draws could still be writing it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
(cherry picked from commit c50e23120c5f139d3e11391b78b9ab228acee2c0)
2026-10-15 11:34:28 +02:00
2cd5c75dc8 fix(agc): a pipelined release with INT_SEL 3 raises no interrupt
The pipelined label path treated every nonzero RELEASE_MEM INT_SEL as an end-of-pipe interrupt;
main's PacketMemory excludes 3 (send data after write confirm). agc_driver failed on it with
APS5_PIPELINED_DRAWS=1 ("release with INT_SEL send data after write confirm raised an interrupt").

Co-Authored-By: TheAggie <[email protected]>
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-15 11:34:28 +02:00
a18ab8c009 fix(agc): a null storage image in a draw's written set writes nothing
DrawWriteRanges decoded every written storage image element, and DecodeTextureResource refuses a zero
base address, so a draw with a null storage image bound failed (PPSA01317) instead of skipping it.
The element is now skipped by the binder's own null rule (base address words zero) before it is
decoded. agc_driver_draw_write_ranges binds a null image next to a written one.

Co-Authored-By: Gjon Gojcevic <[email protected]>
Co-Authored-By: SleglJan <[email protected]>
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-15 11:34:28 +02:00
3b34d1fdb6 perf(agc): pipeline queue-0 draw commits behind the packet worker (APS5_PIPELINED_DRAWS)
With APS5_PIPELINED_DRAWS=1, queue 0's worker decodes, captures and recompiles a draw and hands it to
a committer thread that records it (Graphics::Draw under the GPU lock), so the two run in parallel
instead of one after the other. Work the committer has not reached yet is tracked by the guest ranges
it will write; a later packet that reads or writes them, or one the pipeline cannot order, drains it.

Ordered as commit items instead of drains:
- labels (WRITE_DATA, RELEASE_MEM) and same-queue waits on them; APS5_PIPELINE_DRAIN_LABELS=1 drains;
- the end of a queue-0 submission and a released REWIND tail; APS5_PIPELINE_DRAIN_SUBMISSIONS=1 drains;
- the title's buffer fill kernels; APS5_PIPELINE_DRAIN_FILLS=1 drains;
- DMA_DATA immediates and copies (APS5_PIPELINE_DMA=1).
A draw's write ranges include the storage images it writes and its exact color and depth target
ranges (APS5_EXACT_DRAW_WRITES=1), not estimates.

Draw-path costs the pipeline exposed:
- ShaderMemory moves, so a commit owns its capture's pages;
- a submission made before the worker's current collect epoch began keeps the epoch, so a worker
  behind a queue of submissions walks a surface once per catch-up (APS5_SUBMISSION_EPOCH_EACH=1 bumps
  at every submission); a thread remembers the ranges it walked for the whole epoch;
- the pipeline signals only threads that wait; the pending-image and unit-shadow registries take a
  spinning slim lock instead of std::mutex (winpthreads contention was kernel events); ScanPending skips
  the registry walk when no pending image can overlap;
- fewer heap allocations per recorded draw (reused descriptor sets and scratch vectors, buffer pool
  size classes kept).

GTA V Performance mode, prologue bank interior, RTX 5080: 11.7 -> about 20 fps together with the cheaper
captures below it.
Off by default here; the follow-up turns it on.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Co-Authored-By: Gjon Gojcevic <[email protected]>
2026-10-15 11:34:28 +02:00
Yunus Shaikh 896b737700 fix(libSceNet): keep one receive timeout across the wait for a local address
When the address came during the wait, the host receive that followed
waited its whole SO_RCVTIMEO again, so a receive could take up to twice
the timeout. FreeBSD's soreceive_dgram sleeps once per timeout. Now the
receive waits for a datagram with WSAPoll only for what is left of the
timeout, and fails with EAGAIN when it runs out, as before. Nothing
changes when the socket already had an address or has no timeout.
2026-10-15 02:34:12 -07:00
Adrià 1db84d5748 Merge pull request #1193 from MichonGoddijn231849/perf/capture-plan-walk
perf(agc): cheaper shader-memory captures per draw
2026-10-15 11:34:10 +02:00
ee6aba54 d6b002887c fix(agc-driver): size the staging pool from the device's reported memory budget
The device tier of the buffer pool kept an eighth of the device-local heap (2 GiB on a 16 GiB card) whatever VK_EXT_memory_budget said the process may use. Its budget is now the smaller of an eighth of the heap and an eighth of the reported budget, at least 512 MiB; APS5_STAGING_POOL_MIB still overrides it.

(cherry picked from commit b039e1da12d7cedc3bfdf0c1d3c0c354f3cd58aa)
2026-10-15 11:32:07 +02:00
ee6aba54 b31dac8931 fix(agc-driver): release clean storage images before ones with pending results
Evicting a storage image whose results are still on the GPU stores them to guest memory first, and that store needs a staging buffer: under memory pressure the release itself ran out of device memory and the title died on the nested allocation (PPSA15304 at 125 s on a 64 MiB write-back buffer). The release now takes clean images oldest first and turns to dirty ones only when the clean ones did not free enough; a failure inside the release is reported and ends it, leaving the outer allocation to retry with what was freed.

(cherry picked from commit 013b5546da01493cdfd2ac35f237bb7e22d15947)
2026-10-15 11:31:07 +02:00
ee6aba54 f157f8a002 fix(agc-driver): allocate texture and storage image memory through the buffer pool's recovery path
Sampled textures, depth copies and storage images allocated their device memory directly and threw on the first VK_ERROR_OUT_OF_DEVICE_MEMORY. They now go through BufferPool::AllocateMemory, so a failed image allocation trims the pool's idle buffers, releases the texture caches and retries like a buffer allocation does, and reports the recovery the same way.

(cherry picked from commit d7af5573c12e680703e648accb6e67ead5052f9f)
2026-10-15 11:31:07 +02:00
ee6aba54 8cda582730 fix(agc-driver): release the texture caches when a device allocation runs out of memory
A buffer allocation that still fails after the pool trimmed its own free slots now asks the texture caches for the request plus a reserve (the request again, at least 256 MiB) and retries once more: sampled textures go first, oldest first, then cached storage images, oldest first, with their resource cache entries removed and kept by the active recorder as an eviction from the lookup does. Only bytes whose images die now count; images a recorded batch still holds do not. The caches are skipped when the calling thread holds their mutex (an allocation inside a lookup) or the device lock cannot be taken, so the retry never waits behind a lookup. The recovered line names the released bytes next to the reclaimed ones, as does the failure.

Before this, the title PPSA15304 died 460 s into its second section on a 128 MiB buffer allocation while 2.5 GiB of cached storage images were idle.

(cherry picked from commit 31b1b227657dd7b9ced8d808a224411673d6de1c)
2026-10-15 11:31:07 +02:00
Adrià 042eb7c801 Merge pull request #454 from SP4C3B4R-8/fix/aliased-depth-surface-texture
fix(libSceAgcDriver): retire a depth surface once another writer covers its memory
2026-10-15 11:25:04 +02:00
David be5dfd6237 test(relinker): keep refusing a GS access at an instruction start for macOS
Only a segment-prefixed decoding inside an earlier instruction is skipped; a GS load that begins an instruction is still refused.
2026-10-15 13:21:10 +04:00
David 2328902c91 test(relinker): convert a function that begins inside an instruction for macOS
macos_tls_overlap converts the Windows coverage group's function-begins-inside-instruction fixture with --macos: a symbol starts inside a movabs whose immediate holds a GS-prefixed decoding. It converts, and the movabs is unchanged in the output.
2026-10-15 13:21:08 +04:00
David b689f49625 fix(relinker): skip prefixed decodings inside an earlier instruction in the macOS TLS scan
A function range that begins inside an instruction makes the code analysis
collect a second decoding from the middle of it. The Windows TLS rewriter
skips a non-FS segment-prefixed decoding there (90dcd6256); the macOS scan
did not, and refused the module. OpenSSL's assembly keeps its
"CRYPTOGAMS by <[email protected]>" string in the code segment, where the
"en" of "openssl" decodes as a GS-prefixed instruction, so 24 catalog
titles could not be converted for macOS: "Unsupported macOS guest TLS
instruction (bytes: 65 6e)". The scan now skips such decodings as the
Windows rewriter does.
2026-10-15 13:21:07 +04:00
Adrià FranchandClaude eacacc6006 fix(agc): port the capture fast path to the word masks and IrNode evaluator on main
Co-Authored-By: Claude <[email protected]>
2026-10-15 11:17:29 +02:00
Yunus Shaikh 42e6737f59 perf(libSceNet): end the wait for a local address when a bind, connect or send gives one
A receive on an unbound datagram socket checked the host socket every
10 ms, so it went on waiting up to 10 ms after another thread's bind,
connect or send had named the socket. sceNetBind now wakes the waiting
receives. On Windows a datagram connect or a send marks the socket bound
and wakes them once the host socket has its local address; a raw socket
that a send leaves without one stays unmarked and keeps waiting.
2026-10-15 02:13:52 -07:00
Yunus Shaikh 7333704c72 test(libSceNet): receive on an unbound datagram socket that another thread binds or connects
The test covered a send that names the waiting socket. A bind and a
datagram connect from another thread name it too: each case now waits in
sceNetRecv on the unbound socket, binds or connects it, and checks that a
peer's datagram to the new address reaches the waiting receive.
2026-10-15 02:13:52 -07:00
Adrià FranchandClaude 7b55c6333a fix(shader): IrResourcePlan is 712 bytes with the compact plan on libstdc++
Co-Authored-By: Claude <[email protected]>
2026-10-15 11:13:38 +02:00
Santhosh Kumar 27b196bee0 fix(shader): preserve pixel helper lanes in EXEC copies
Expand the host active-lane mask to whole quads for pixel-stage EXEC value reads so saving and restoring WQM does not drop derivative inputs. Keep the existing host-lane mask in other stages. Add a GPU regression for implicit mip selection after an EXEC save/restore, across wave32/wave64 and native/emulated barycentrics.
2026-10-15 14:42:26 +05:30
8a8a08a243 perf(agc): shader-memory reads skip page and snapshot checks they don't need
A lazy page was found accessible whole when it was made, and its blocks are
copied from guest memory on demand; accessible() no longer asks
GuestMemory::Accessible for it again on every word the SRT walk checks. A read
looks up the code and header snapshots only when it can touch them, and the
read and page counters run only under APS5_PROFILE_DRAW, where the [capture]
line prints them.

Raghav314 measured the three on Hades II (PPSA36082, RTX 4090, Linux):
capture_resources 2.48 -> 2.14 ms per frame, frame p50 12.34 -> 11.60 ms.

Co-Authored-By: Raghav314 <[email protected]>
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-15 11:11:24 +02:00
4651980eba perf(agc): cheaper shader-memory captures per draw
The per-draw capture of a shader's memory (the SRT walk and the guest pages it reads) cost about
15 us per draw packet in GTA V Performance mode; it now costs about 6.6.

- The SRT evaluator memoizes per plan value in a thread's reused, stamped array instead of a hash
  table allocated and grown on every walk (evaluate 5.3 -> 3.6 us per capture).
- ExtractPlan also stores the plan's values as 32-byte records with argument ids, and the walk
  evaluates that compact copy: the IR walk spent most of its time in cache misses (3.6 -> 2.7 us).
  The evaluator is a template over the node view, so both walks share every opcode's semantics, memo,
  cycle and failure rules. A plan that cannot be expressed compactly keeps the IR walk.
  APS5_VERIFY_SRT_COMPACT=1 walks both and throws on any difference.
- ShaderMemory's valid/read/recent marks are arrays of 64-bit words scanned with countr_zero instead
  of bitsets tested bit by bit (about 1 us per draw packet).
- Captured pages come from a per-thread pool, are found through a short vector with a last-page
  shortcut, and a page no recorded GPU work writes gets the flush hook a whole-page read would make,
  then has each 64-byte block copied when a word of it is first read (about 0.3 us per packet).
  Pages with pending GPU writes and unmapped pages are read as before.
- APS5_PROFILE_DRAW splits the capture time into SRT evaluation and materialization, and prints the
  [capture] line once per 10 s instead of every 500 captures (it cost about 2 us per capture).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Co-Authored-By: Gjon Gojcevic <[email protected]>
2026-10-15 11:11:24 +02:00
eljackjoestar-cmd 6575aa217d fix(audio): retain completed AudioOut2 grains until pushed 2026-10-15 13:06:45 +04:00
SP4C3B4R-8andClaude Opus 5.5 626cff7d26 fix(libSceAgcDriver): retire a depth surface once another writer covers its memory
A depth surface lives only in its Vulkan image, so a view of its memory
with another layout could only throw, and a matching view kept reading
the image after other writes over its memory. Retire the surface when a
color target or a written storage image it does not serve overlaps its
planes, or when a view it cannot serve finds guest memory over the planes
written since the surface was last bound; lookups then skip it and the
view reads the memory like any texture. Binding the target again brings
the surface back.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-15 11:00:36 +02:00
Adrià 0afa6a607e Merge pull request #6060 from parl0v/feat/watched-private-imports
perf(agc): keep Windows host imports of write-watched private memory watched
2026-10-15 10:54:43 +02:00
Adrià c4f03522a7 Merge pull request #5496 from Amoenus/codex/native-msaa-depth
feat(agc): sample native multisampled depth attachments
2026-10-15 10:54:18 +02:00
Yunus Shaikh a9c2717fd0 fix(libSceNet): return 0 from an unbound datagram receive after shutdown or for zero bytes
On Windows a receive on a datagram socket with no local address waits
for one. A sceNetShutdown of the receive side now ends that wait, and
later receives return 0 at once; a zero-length receive returns 0 without
waiting. FreeBSD's soreceive_dgram returns 0 in both cases. Before, a
thread blocked in such a receive kept waiting after the socket was shut
down.
2026-10-15 01:54:10 -07:00
hypermistandClaude Opus 5.5 2a12dbd400 perf(libc): find unwind tables with _dl_find_object on glibc
The unwinder walked the loaded objects with dl_iterate_phdr for each frame
of both unwind phases, under the loader lock, so threads that throw at the
same time serialize on it (the AGC driver's queue threads throw on skipped
work). _dl_find_object (glibc 2.35+) maps a PC to its object and
eh_frame_hdr without the lock. The text and data bases still come from one
walk per object, cached by eh_frame_hdr address and mapping. Other
platforms and older glibc keep the walk.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Uzy15wqYTt6iWyxYXEzUWo
2026-10-15 21:53:45 +13:00
hypermistandClaude Opus 5.5 fe6d2d2e97 fix(recompiler): read lane spills back through relative-addressing window registers
A shader using v_movrel keeps its window VGPRs as array storage, so a
v_writelane spill into one reached its v_readlane as
ReadLane(LoadVectorWindow k) and the ReadLane eliminator stopped there.
Descriptors spilled that way failed resource analysis (PPSA08427: about
750 draws per 10 s skipped with "GetSamplerResource dword 0 is not a valid
runtime value", and large UE5 compute programs failing with
"GetImageResource dword 0 is not a valid runtime value").

What lane L of window slot k holds at a load is now a forward dataflow over
the blocks, memoized per slot and lane: the chain continues at the stores
to slot k that reach the load when every path agrees on the lane's value.
A store with a computed index is passed over for slots that are only
lane-written and lane-read (the guest compiler keeps spill registers out
of the indexed array); otherwise it makes the lane unknown.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Uzy15wqYTt6iWyxYXEzUWo
2026-10-15 21:52:10 +13:00
Yunus Shaikh 23d6cf6848 test(shader): check that a key a pointer store may write keeps no key domain
The key loaded from a constant buffer at an SRT offset has a key domain.
With a global_store_dword added, the column is deselected and keeps no
key domain. Before the fix it kept the domain.
2026-10-15 08:50:15 +00:00
Yunus Shaikh 6aacf0bab5 fix(shader): clear the key domains of a column deselected because its key may be written
CheckTableStores deselects a selected column whose key the dispatch may
write: a key read through a pointer or a V# while the shader stores
through a pointer, or through a V# the shader writes. The column kept the
key domains narrowed from that same key buffer, which the domain rule
clears only when the domain's own V# is written, so a shader storing
through a pointer still narrowed its whole table on a key it may
overwrite. Deselection now clears them as well.
2026-10-15 08:50:15 +00:00
Yunus Shaikh 55a09385ab test(shader): check that a selected record's table index is not NonUniform
verifySelectedRecordUniformIndex compiles the program keyed by an SRT
value and the one keyed per lane with a two-wave workgroup. The selected
record's table index carries no NonUniform decoration and the per-lane
table's still does. Before the change the selected record's was decorated.
2026-10-15 08:50:15 +00:00
Yunus Shaikh 0334062657 perf(shader): drop NonUniform from the index of a selected image table record
A selected record is one map entry for the whole dispatch, so its
descriptor index is dynamically uniform. The emitter decorated it
NonUniform like any table index whenever the workgroup spans more than
one subgroup, and on every graphics stage, which makes drivers scalarize
the access. The element index, its access chain, the loaded image and the
sampled image of a selected record are no longer decorated; table
samplers and tables keyed per lane keep the decoration.
2026-10-15 08:50:15 +00:00
Yunus Shaikh 3b914c84b4 test(shader): pin that an S# whose record the CPU evaluates is bound directly
A sampler column is planned only when DirectSampler cannot evaluate the
S#, so no sampler record is ever selected: an S# read from a heap at an
offset the SRT walker evaluates is a direct sampler, and one keyed per
lane is a sampler table.
2026-10-15 08:50:15 +00:00