Commit Graph
1192 Commits
Author SHA1 Message Date
Adrià 37e0f1688e Merge pull request #5708 from MarcZX100/codex/test-untracked-storage-repeat-5693
test(agc): cover repeated untracked storage writes after sampling
2026-10-15 17:04:27 +02:00
Adrià 56f5a6eef7 Merge pull request #5757 from hypermoist/perf/vector-window-unindexed-slots
perf(recompiler): keep the vector window's unindexed slots out of the array
2026-10-15 15:58:23 +02:00
Adrià 6359e3466a Merge pull request #5751 from NaitikJoshiPro/pr/agc-raw-graphics-beside-registered
fix(agc): read unregistered graphics programs beside registered shaders
2026-10-15 15:08:26 +02:00
Adrià 73e924acd4 Merge pull request #5750 from NaitikJoshiPro/pr/agc-queue0-idle-wait
fix(agc): queue 0 waits instead of spinning when every queue is idle
2026-10-15 15:08:03 +02:00
Adrià 3ee5371915 Merge pull request #5627 from AlxcBTW/fix/agc-htile-written-by-any-kernel
fix(agc): take depth fast clears from HTILE written by any kernel
2026-10-15 14:52:34 +02:00
Adrià a61c8a37e0 Merge pull request #5554 from Amoenus/codex/native-depth-htile-clears
fix(agc): apply completion-proven native depth clears
2026-10-15 14:51:07 +02:00
Adrià Franch d17895ed84 test(agc): size the case table for the neighbour case added on main 2026-10-15 14:26:21 +02:00
Adrià Franch 9c765fa0fa test(agc): keep the sampled output surface alive until the queued read finishes 2026-10-15 14:26:14 +02:00
MarcZX100 da1a739368 test(agc): cover repeated untracked storage writes after sampling 2026-10-15 14:26:14 +02:00
Dean Galvin af4e074a02 fix(agc): save pending storage results in every 64 KiB block that leaves the write watch
On Windows the write watch is dropped in whole 64 KiB blocks
(WriteWatchCoverage::Exclude), but the unwatch hook was given the page
range of the import. An image with results pending in the same block as
an imported page, outside that page, lost the watch without its guest
bytes saved, and storing its results threw.

The hook now gets the span of blocks Exclude drops (ExcludedSpan, the same
block math), so every image with results pending in those blocks saves its
bytes first.
2026-10-15 14:01:01 +02:00
Romans Pokrovskis 9e27d0b623 fix(agc): bound native HTILE clears to the kernel offset span 2026-10-15 14:00:57 +02:00
Romans Pokrovskis 06b787f25d test(agc): refuse a mask with an uncleared HTILE key 2026-10-15 14:00:57 +02:00
Romans Pokrovskis 764f0ace02 test(agc): exercise native HTILE producers and clear refusals 2026-10-15 14:00:57 +02:00
Romans Pokrovskis 7d0dd69f0a feat(agc): import completion-proven native HTILE clears 2026-10-15 14:00:57 +02:00
Romans Pokrovskis 5fec3f600b test(agc): exercise completed native HTILE clears 2026-10-15 14:00:57 +02:00
AlxcBTW 139e3b86ed fix(agc): route native-owned depth binds before the HTILE read and store-back
A multisampled depth target is created as, or already is, the native depth owner, and a target whose HTILE overlaps a resident depth guard aliases one. Neither may have its HTILE read or rewritten by the generic store-back: the expanded words would be a guest write over the owner's range. DepthSurfaceView decides this before the store-back, which still runs before GpuMutex() and the surfaces mutex are taken. NativeMultisampleDepth binds the owner and a single-sample alias of its HTILE and checks the words stay untouched.
2026-10-15 13:55:39 +02:00
Adrià 23dcb82499 Merge pull request #5648 from Amoenus/codex/buffer-resource-vcc-tuple
fix(shader): accept buffer resource tuples ending in VCC
2026-10-15 13:55:14 +02:00
AlxcBTW cc022df348 fix(agc): take a slice of a depth array from its own HTILE only when its words are uniform
A fill or clear the driver recognizes over an HTILE drops the range's last-read generation, so the
next bind reads the words and stores them back even when the write tracker did not see the fill's
stores. A depth view of slice 1 or later reads the HTILE at its own address
(DB_HTILE_DATA_BASE + slice * HtileMetadataBytes); the HTILE word layout is only modelled for slice 0,
so such a slice takes the clear and the store-back only when all its words are equal and keeps its
fill-time fast clears otherwise. DepthTarget::slice carries DB_DEPTH_VIEW SLICE_START.

agc_driver_depth_fast_clear_submit: an unrecognized kernel clears the HTILE on two frames in a row and
both clears are taken; slice 1 with words that are not all equal is refused and stored back untouched;
slice 1 with equal words takes the clear. agc_driver_graphics checks the decoded slice.
2026-10-15 13:53:50 +02:00
AlxcBTW 3e61fcf1f9 fix(agc): read and store back the whole pipe-aligned HTILE outside the surfaces lock
The bind-time HTILE read covered one word per 8x8 tile from DB_HTILE_DATA_BASE, but main models the
HTILE as the pipe-aligned SW_64KB_Z_X metadata (HtileMetadataBytes, the extent HtileFillCovers takes):
at 1920x1080, 1856 of the 32400 tile words lie past the first tiles*4 bytes, so they kept ZMask 0
after a store-back. The read and the store-back now cover the whole metadata and run before the
surfaces mutex is taken, since both wait for recorded work over the range. The tests' HTILE arrays
hold the whole metadata.

PPSA25344 (Unreal Engine 5) decodes its depth through HTILE in its Nanite depth export after the
depth prepass and takes ZMask 0 as a tile holding the clear value; the new
agc_driver_depth_fast_clear_submit case checks that a draw after a fast clear leaves every word
expanded.
2026-10-15 13:53:50 +02:00
esatturan 4ddff7c6e5 fix(agc): take depth fast clears from HTILE written by any kernel 2026-10-15 13:53:49 +02:00
Adrià fdea36b836 Merge pull request #5524 from Amoenus/codex/native-depth-allocation-retirement
fix(agc): retire fully released native depth owners
2026-10-15 13:50:13 +02:00
Adrià 5050a23f38 Merge pull request #5473 from MarcZX100/type0-recompiler-followup-4779
fix(shader): accept TYPE=0 images during materialization
2026-10-15 13:37:52 +02:00
Adrià 821c69e504 Merge pull request #5509 from billythemenace2/fix/shader-cube-tsharp-2d-access
fix(shader): read cube T#s through DIM 2D image_sample and image_load as face 0
2026-10-15 13:25:34 +02:00
Adrià 39d3ae13eb Merge pull request #5304 from tv7/fix/stencil-ref-export-unused
fix(agc): drop an unused stencil test value export from draws without a stencil test
2026-10-15 13:24:56 +02:00
Adrià 0f0ca7b544 Merge pull request #5200 from nikowolf123/fix/agc-blend-reversed-component-order
feat(agc): blend into color targets with a reversed component order
2026-10-15 13:24:08 +02:00
Adrià f0c6c9e7ee Merge pull request #4794 from NFSS10/nfss10/fix_bendy_and_the_ink_main_menu
fix(libSceAgcDriver): flush pending results at staged-clear sampled reads
2026-10-15 13:23:26 +02:00
NikoandClaude Opus 5.5 0bf89cd30d test(agc): cover moving the export into a reversed-order blend factor
TranslateReversedBlend may write 1 into a channel of the first color
whose source factor is ONE and take that channel's export into the
factor through the second color, when one second color cannot carry
both the source and the destination factor. None of the blends in
agc_driver_reversed_order_blend took that route, so no GPU test ran it.

The test now also blends 0x61010504 (SRC_ALPHA + ONE_MINUS_SRC_ALPHA,
alpha ONE + ONE), whose alpha channel is routed that way, into both
formats with both reversed swaps, and compares it with the exact blend
and bit for bit with the same draw into a SWAP_STD target. It passes on
an RTX 5080 and lavapipe. With 9a8816126's shader-computed factors it
fails on the RTX 5080: 160 where SWAP_STD stores 159.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 12:51:27 +02:00
NikoandClaude Opus 5.5 c434467f6c fix(agc): form reversed-order blend factors from copies of the export
The reversed-order blend computed the factors Vulkan cannot apply per
channel in the pixel shader (1 - x in fp32, a clamp for UNORM targets
and, when both positions needed it, a premultiplied export) and passed
them to the blend as a dual-source second color. A plain blend converts
the export once and forms 1 - x and the products from that converted
value, so the two paths can round differently. On UNORM8 the difference
reaches one step per substituted factor: an Intel iGPU stored 161 where
the exact blend is 159.75 for SRC_COLOR + ONE_MINUS_SRC_COLOR with an
export of 0.5 over 192 (160 through SWAP_STD), and on an RTX 5080 and
lavapipe the split path differed from the same draw into a SWAP_STD
target by up to 2 steps on other inputs.

The pixel shader now only copies values. Each channel of the second
color is the exported alpha, the channel's own export or the constant 0
or 1, and a channel of the first color whose source factor is ONE may
become 1 with its export moved into the factor. Every factor, 1 - x and
product comes from the blend through SRC1_COLOR, SRC1_ALPHA and their
ONE_MINUS forms, applied to copies of the same export values that the
first color carries. TranslateReversedBlend searches for Vulkan factors
and copy routes that reproduce each channel's guest factors and throws
for combinations no routing reproduces, such as ONE_MINUS_SRC_ALPHA in
the alpha equation next to SRC_COLOR in the color equation: of the 1995
factor combinations the shader used to compute, 932 are routed and the
others throw again, as on main. GT7's 0x65010504 is among the routed
ones. The code is now four 2-bit routes for each color, the same for
every format, and the post-pass emits no arithmetic.

agc_driver_reversed_order_blend also draws each case into a SWAP_STD
target, which Vulkan blends alone, and requires identical stored bits;
the comparison with the exact blend keeps its 1-step tolerance.
agc_driver_graphics checks every factor combination TranslateReversedBlend
accepts against the guest equations and that the post-pass adds no
arithmetic instructions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 12:51:27 +02:00
NikoandClaude Opus 5.5 16ea32c97d feat(agc): blend into color targets with a reversed component order
8_8_8_8 and 16_16_16_16 targets with COMP_SWAP 2 or 3 keep the exported
alpha in their first channel and red or blue in their fourth, so a plain
Vulkan blend would run the alpha equation on a color channel. Such draws
threw "blending into a color target with a reversed component order is
not implemented" (Gran Turismo 7, #4782).

The AMD SI and CIK register references describe CB_TARGET_MASK in the
order of components after blending and before COMP_SWAP is applied, and
RADV programs CB_BLENDn_CONTROL without regard to the swap while it
renders R4G4B4A4, B4G4R4A4, R5G5B5A1 and B5G5R5A1 through SWAP_STD_REV
and SWAP_ALT_REV and passes the CTS blend tests on Navi10. The color
block is therefore taken to blend in export order: the alpha equation
and source-alpha factors use the exported alpha and the destination
alpha is the first channel.

TranslateReversedBlend keeps the factor pairs Vulkan applies per channel
in the pipeline. Other source factors are computed by the pixel shader
from its MRT0 export (clamped for UNORM targets, as BLEND_CLAMP says)
and handed to the blend as a dual-source second color, which the
fragment output post-pass adds at location 0 index 1; when both
positions need it, the export is premultiplied by its source factor.
The code is a pipeline specialization, serialized in request version
26, and stays out of the prepared shader key.

sRGB, SNORM and integer targets, constant and second-source factors,
destination alpha in the color equation, destination factors the two
equations do not share, different color and alpha operations, MIN or
MAX with factors other than ONE, other color exports when the shader
computes factors, and devices without dualSrcBlend still throw, now
with the blend control, mapping and format in the message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 12:51:27 +02:00
fdfawef324andCodex 21dd7b06bc test(agc): check draws with an unused stencil reference
Exercise ALWAYS with KEEP and INVERT using the existing pixel program and complete color and stencil readbacks. Keep dependent comparisons and replacement operations behind the existing native extension refusal.

Co-Authored-By: Codex <noreply@openai.com>
2026-10-15 12:51:25 +02:00
tv7andClaude Opus 5.5 ac50b9869d fix(agc): drop an unused stencil test value export from draws without a stencil test
A pixel shader with DB_SHADER_CONTROL.STENCIL_TEST_VAL_EXPORT_ENABLE needs
VK_EXT_shader_stencil_export, which NVIDIA's driver does not offer. In a draw
without a stencil test the exported value has no effect, so the draw decode
drops the export there. On a device without the extension, the pixel shader is
no longer prepared when its ABI is resolved, where the draw's stencil test is
not known yet; it is prepared at its first draw.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 12:51:17 +02:00
Romans Pokrovskis da9fe3c708 fix(agc): retire fully released native depth owners
Track successful releases cumulatively across the complete depth and HTILE ownership ranges. Preserve color retirement and share its single callback; keep retired image contents alive through sampled views and recorded work. Add registry, partial/failure/protection, remap and mixed teardown regressions. Depends on native depth sampling in PR5496 and its descriptor prerequisite PR4820.
2026-10-15 13:08:13 +03: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 <noreply@anthropic.com>
2026-10-15 11:34:29 +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 c653e9ea1d)
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
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 <7807870+gjonn@users.noreply.github.com>
Co-Authored-By: SleglJan <90033145+SleglJan@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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) <noreply@anthropic.com>
Co-Authored-By: Gjon Gojcevic <7807870+gjonn@users.noreply.github.com>
2026-10-15 11:34:28 +02: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 <noreply@anthropic.com>
2026-10-15 11:00:36 +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
hypermist a4639ad2fb Merge upstream main into perf/vector-window-unindexed-slots 2026-10-15 16:02:16 +13:00
MarcZX100 e76f79f5f4 test(shader): keep reserved image types invalid 2026-10-15 01:37:27 +00:00
NaitikJoshiProandClaude Opus 5.5 498c00deb3 fix(agc): read unregistered graphics programs beside registered shaders
Since 39fdc48b5 a graphics program outside every registered shader was
read raw only while the registry was empty, and any RegisterShader after
the first raw draw threw. A title that registers some shaders through
sceAgcCreateShader and also draws programs it never registers stops at
its first such draw with "graphics program does not belong to a
compatible registered shader": oops-sdk homebrew registers its display
shaders while radeonsi (oops-mesa) submits raw programs.

Graphics programs now resolve as compute dispatches already do: an
address inside a registered shader resolves to that registration (a
registration of another type still throws), any other address is read
raw. RegisterShader throws only when the new shader's code overlaps a
graphics program that was drawn raw, where the program would get a
second identity (the draw cache revalidates raw programs by reading them
raw), instead of after any raw draw.

The drawn range is recorded once per raw snapshot: a flag on the
snapshot keeps later decodes of the same program off the lock. Recorded
ranges are merged, so the overlap check looks only at the predecessor of
the queried end.

Tests: agc_driver_prepared_graphics (a draw whose programs are registered
as compute shaders is refused, where it used an address outside every
registration before), agc_driver (ProgramSnapshot reads raw beside registered shaders
and refuses a registered program of another type; nested, overlapping
and adjacent raw ranges), agc_driver_prepared_unregistered_graphics
(raw-drawn ranges recorded exactly, unrelated code not reported).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 06:24:14 +05:30
fdfawef324 1ffc5f672b feat(agc): emulate per-pixel multisample coverage masks
Decode the four PA_SC_AA_MASK fields at the rasterized sample count. Use the native Vulkan mask when all four pixels agree and apply varying masks in the fragment shader, preserving helpers and combining coverage with alpha-to-coverage and shader mask exports. Add the normalized masks to pipeline, draw and shader keys and the versioned replay request.

Refuse unsupported conservative-raster, centroid and early depth/stencil memory-write cases. Synthetic readbacks cover driver submission, depth/stencil suppression, fragment stores and one-sample and four-sample coverage.
2026-10-15 13:00:34 +13:00
Michon 90b20afac8 fix(agc): demote quad-mask pixels to helpers instead of killing them
With PA_SC_AA_MASK covering only some pixels of a quad, the uncovered pixels
run on the hardware as helper lanes, so derivatives of the covered pixels stay
defined. OpKill ends those invocations; OpDemoteToHelperInvocation keeps them
as helpers. The device enables VK_EXT_shader_demote_to_helper_invocation when
available and passes the DemoteToHelperInvocation capability and its SPIR-V
extension to the recompiler target and the graphics shader validation. A
demoted pixel also reads as a helper, so its initial EXEC bit is clear as on the
hardware. Without demote, OpKill stays for pixel programs that read no
neighbouring lanes; implicit-LOD sampling, LOD queries and cross-lane reads are
refused. The quad pixel mask execution test now also samples a mipmapped
texture with implicit LOD under the mask.
2026-10-15 12:59:39 +13:00
Michon 72518f64fb feat(agc): single-sample sample masks as a per-quad-pixel discard
With one sample, PA_SC_AA_MASK leaves one coverage bit per pixel of the 2x2 quad,
so a draw can cover only some pixels of every quad (GTA V renders a target one
quarter of the pixels at a time this way). The pixel stage info carries that 4-bit
mask; a fragment shader whose mask is not full starts by killing the invocation
unless its gl_FragCoord quad position is covered, and runs without early fragment
tests. The mask is part of the recompile cache key and the serialized request
(version 24). Sample masks of multisampled draws stay refused.
2026-10-15 12:59:39 +13:00
Romans Pokrovskis 008f91501a test(agc): exercise native depth feedback and clear-value refusals 2026-10-15 02:51:31 +03:00
Dean ab9a476840 Merge pull request #5583 from BlizzyInc/fix/sample-through-1d
fix(shader): read explicit-LOD 2D samples through 1D T#s as measured
2026-10-15 12:34:15 +13:00
Dean 4bdfc9c801 Merge pull request #5631 from moong15/codex/skip-unused-pixel-stage
fix(agc): skip unused pixel stages with retained export formats
2026-10-15 12:33:48 +13:00
Romans Pokrovskis 6972b5ae07 fix(agc): filter stencil aliases before native depth sampling 2026-10-15 02:15:38 +03:00