Commit Graph
221 Commits
Author SHA1 Message Date
Dean Galvinandmazen dc38f31983 Merge main into fix/skip-inaccessible-branched-scalar-load
Shader disk cache FormatVersion 43: main took 42 with #7966, and this
change also changes the cached plan.

Co-authored-by: mazen <[email protected]>
2026-10-21 14:23:05 +13:00
Dean 1faa800f08 Merge pull request #7966 from KarthikeyaAnna/fix/agc-1d-depth-compare-2d
fix(agc): back 1D depth comparison textures with 2D images
2026-10-21 14:21:13 +13:00
Adrià 636aa09cb1 Merge pull request #7520 from milescoviello/perf/ir-single-use-list
perf(shader): keep one use list per IR value
2026-10-20 17:26:34 +02:00
Jan ŠléglandClaude Opus 5.5 8b8219dcef feat(agc): run registered merged hull programs (binary type 3)
A registered type-3 hull program holds the local and the hull part of a
tessellation draw in one binary, addressed through SPI_SHADER_PGM_LO_LS
(SH 0x148) with no SPI_SHADER_PGM_LO_HS: the local part runs on the lanes
below s3[7:0] and writes LDS, an s_barrier follows, and the hull part runs
on the lanes below s3[15:8] and may store the registers the local part
filled. The draw decode asked 0x148 for a type-5 local half, and
PPSA11386 (007 First Light) stopped in its level load with "registered
program refers to an incompatible shader binary type".

The draw decode now gives a registered type-3 program both the local and
the hull role and marks the tessellation configuration mergedHull. The
local stage only passes gl_VertexIndex and gl_InstanceIndex in a 16-byte
record. The hull stage runs the whole program, one invocation per output
control point: v2 and v5 come from the record of control point
min(gl_InvocationID, inputs - 1), v3 is the relative vertex id, and the
LDS becomes a per-control-point output array that a control point writes
in its own record and reads in any record of its patch after the barrier.
Registers live across the barrier stay in the invocation.

The hull stage of a merged program runs at one lane per invocation on
every device (HostSubgroupSize 1). Its lanes are gl_InvocationID, not host
subgroup invocations, so the s_cbranch_execz around the local part tests
the invocation's own lane. With the device's subgroup it took a subgroup
ballot, and lavapipe, whose subgroup stages lack the tessellation control
stage, rejected the draw ("subgroup capability 61 is unsupported for
shader stage 2").

The lowering refuses more input than output control points, LDS writes
outside the own record, reads outside the patch's records, barriers that
some invocations do not reach or that sit in a loop, reads or writes of
another lane's register, outputs beyond the device limits, and fetch
subroutines. Type 3 is prepared with its stage group like type 5 and
takes its float mode from RSRC1_HS. The hull user-data address (SH
0x102/0x103) is optional for a merged program: s[0:1] take it, unchecked,
when the draw sets it and are zero otherwise.

Both stages refer to one binary, so a draw lists each code and header
region once (AppendProgramMemory); a request's memory regions must not
overlap. An indirect draw's record words go into every program whose user
data holds the register, so the hull stage of a merged program reads
them too.

mergedHull is part of the recompile cache key, the draw decode comparison
and the request serialization (version 39).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-20 15:55:50 +02:00
Maz 53ed35b362 fix(shader): skip unreachable flat SRT loads under a known branch
Capture evaluated every flattened scalar slot even when a clean scalar
branch skipped it, so an inaccessible direct user-data root refused the
draw. Record flat-slot uses on resource control-flow blocks and evaluate
only the reachable ones; keep refusing taken and unresolved paths.

Fixes #8272.
2026-10-20 13:11:24 +04:00
karthikeyaAnna c97e5e0338 fix(agc): back 1D depth comparison textures with 2D images
Back 1D and 1D-array depth comparison textures with 2D Vulkan images and views of height 1. Drivers such as AMD RADV reject 1D depth images (VK_ERROR_FORMAT_NOT_SUPPORTED). The shader recompiler maps 1D depth comparison samplers to 2D image descriptors, and pins the normalized vertical coordinate to 0.5 to sample the center of row 0 without hitting borders.
2026-10-20 14:21:58 +05:30
Adrià e5b65a911a Merge pull request #7518 from milescoviello/perf/ir-reserve-arguments
perf(shader): reserve an IR value's arguments for its opcode at creation
2026-10-20 00:36:26 +02:00
Adrià efd81c3690 Merge pull request #7503 from milescoviello/perf/reclaim-owned-marks
perf(shader): mark live values of a reclaimed program without a hash set
2026-10-20 00:17:55 +02:00
Adrià 602d8e66b1 Merge pull request #7429 from ps54all/feat/shader-ds-ordered-count-swap
feat(shader): model ds_ordered_count swap
2026-10-19 22:17:42 +02:00
ps54allandClaude Opus 5.5 b0558ce95f feat(shader): model ds_ordered_count swap
A ds_ordered_count with offset1 bit 4 set swaps the first active lane's value into the GDS counter instead of adding it. Emit it as one device-scope atomic exchange through the same path as the ordered add, and return the counter's previous value to every active lane.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-19 16:41:03 +02:00
thuanquangandClaude Opus 5.5 1ed8eb6d8b fix(shader): sample a 3D T# through DIM 2D_ARRAY as a volume
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-19 16:19:13 +02:00
Adrià e0b8be9dc5 Merge pull request #6530 from milescoviello/feat/smoothing-mixed-samples
feat(agc): draw radeonsi line and polygon smoothing (SAMPLE_COVERAGE, merged mixed samples)
2026-10-19 06:06:26 +02:00
Adrià 23629c2847 Merge pull request #6454 from EagleBearerYS/perf/shader-image-table-reuse-v3-pr
perf(agc): share one image table per heap and write epoch through capture, compare, resolution and descriptors
2026-10-19 03:39:03 +02:00
milescovielloandClaude Opus 5.5 03b0db0520 feat(shader): load the SAMPLE_COVERAGE pixel input from gl_SampleMaskIn
SPI_PS_INPUT_ENA/ADDR bit 14 (SAMPLE_COVERAGE_ENA) gives the pixel shader one
VGPR, after ANCILLARY, holding the samples the primitive covers. The decoder
refused it and the recompiler never loaded the VGPR. It is now loaded from
gl_SampleMaskIn[0] (sharing the input the partial quad sample mask already
declares). On a multisampled draw the hardware word is the whole pixel's
coverage while gl_SampleMaskIn holds only the invocation's samples when the
pixel shader runs per sample, so that case, and sample-rate or ancillary inputs
beside it, are refused.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-19 01:25:21 +00:00
Adrià 00666eeb36 Merge pull request #6470 from EagleBearerYS/fix/recompiler-once-per-pixel-sample-inputs
fix(recompiler): run a pixel shader the guest does not iterate once per pixel
2026-10-19 03:21:34 +02:00
Yunus Shaikh 106b79e007 Merge main into perf/shader-image-table-reuse-v3-pr
The data-hit verification keeps this branch's pending-span query on its ShaderMemory and takes #5958's CaptureInvocation, which retries a capture with converted samples.
2026-10-18 17:04:23 -07:00
Yunus Shaikh 10b798fb99 Merge main into perf/shader-image-table-reuse-v3-pr
Keeps this branch's tagVariantRuns declaration next to main's dumpRequest suffix parameter in Driver.hpp, and reads #6232's pointer-chain test words from its table's snapshot, where this branch keeps them.
2026-10-18 17:03:39 -07:00
Adrià 0fd3555ad6 Merge pull request #5958 from ps54all/feat/shader-converted-image-sample
feat(shader): sample converted unorm images in the shader
2026-10-19 00:24:10 +02:00
Yunus Shaikh d13a90d015 Merge main into fix/recompiler-once-per-pixel-sample-inputs
Resolves the shader debt file bullet by bullet: main's EmitSetAttribute entry, and this branch's PERSP_SAMPLE/LINEAR_SAMPLE and pixel-position entries.
2026-10-18 15:14:25 -07:00
ANONYMOUSZED-beep 486f3069ae feat(shader): follow pointer chains from image-table records to the T# 2026-10-18 20:49:04 +02:00
Yunus Shaikh d0325058f7 Merge main into perf/shader-image-table-reuse-v3-pr
#7527's selected-record limit test reads a record's descriptor words through the
table snapshot's shared words list; this branch keeps a snapshot's words per column,
so the test reads them from its column instead.
2026-10-18 11:33:00 -07:00
Yunus Shaikh dc47a81b02 Merge main into fix/recompiler-once-per-pixel-sample-inputs 2026-10-18 11:30:31 -07:00
ps54allandClaude Opus 5.5 8545c23013 feat(shader): sample converted unorm images in the shader
10_11_11_UNORM (format 30) images are read and written through an
R32_UINT view, so the hardware sampler cannot filter them. image_sample
of a 2D or 2D array view now fetches the one or four texels at LOD 0,
decodes them and blends them in the shader, with bilinear weights on
the 8-bit grid, for point or bilinear filtering with wrap or
clamp-to-edge addressing.

A shader gets the converted sample mode only after a converted
descriptor reaches one of its sampled images: that capture throws
ConvertedSampleRequired and the driver prepares the program again
with GuestContext::convertedSamples.

The R10_G11_B11 float format is sampled natively since #7271 and keeps
its refusal on the converted path.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-18 17:43:35 +02:00
Dean 385ed71f2c Merge pull request #7368 from CrimsonMac/fix/device-lds-mixed-width-atomics
fix(shader): use 64-bit buffer atomics for device-memory LDS
2026-10-19 01:51:59 +13:00
milescovielloandClaude Opus 5.5 d484281b61 perf(shader): keep one use list per IR value
Every IR value kept two lists of its users: uses (the users) and
operandUses (the users with their operand index). AddArgument,
ReplaceArgument and ReplaceAllUsesWith updated both, and each value
allocated and freed both. Only operandUses is kept now. Uses() is a view
of the users in operandUses, and HasUses, UseCount and Invalidate read
it. sizeof(IrValue) drops from 152 to 128 bytes; the plan value encoder
writes no use lists, so only its size check changes.

The two lists held the same users in the same order, except after
ReplaceArgument on a user that took the value in more than one
operand: uses dropped the user's first entry and operandUses the
replaced operand's entry. Uses() now follows operandUses there too.

shader_ir_use_rewrite counted the allocations of both lists: its
allocation failure check now fails the one reserve ReplaceAllUsesWith
makes, and its growth check allows the allocations of one list, which
main exceeds.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-18 11:23:47 +00:00
milescovielloandClaude Opus 5.5 41b25e4f57 perf(shader): reserve an IR value's arguments for its opcode at creation
Every IR value grew its argument vector one AddArgument at a time from
empty, so a value with three arguments allocated three times (capacity
1, 2 and then 4) and freed the first two. The constructor now reserves
IrOpcodeOperandCount(opcode) arguments, the count IrBuilder::Emit
already requires, so a value built with its opcode's arguments
allocates once. Opcodes without fixed arguments (constants, phis)
reserve nothing, as before. The IR and the SPIR-V are unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-18 11:11:11 +00:00
Yunus Shaikh dad24e3a4f Merge main into perf/shader-image-table-reuse-v3-pr 2026-10-18 02:56:48 -07:00
milescovielloandClaude Opus 5.5 3fdef48e54 perf(shader): mark live values of a reclaimed program without a hash set
IrProgram::ReclaimUnusedValues put every live value into an
unordered_set, one allocation each, and looked every value of the
program up in it again. Values enter the program only through
CreateValue, which numbers them with nextValueId, so an id names at
most one of them. The marks of those values now live in a vector
indexed by id, after a check that the id's owner is the same pointer.
A value the program does not own, such as one in the resource plan's
value storage, which may reuse an id, still goes into a set. The values
kept and removed are unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-18 09:47:56 +00:00
Dean ccb0c09268 Merge pull request #7240 from gjonn/fix/shader-unnormalized-offset-base-level
fix(shader): fold texel offsets into base-level unnormalized samples
2026-10-18 18:58:51 +13:00
Yunus Shaikh a508aa03c4 Merge main into perf/shader-image-table-reuse-v3-pr 2026-10-17 22:15:10 -07:00
Dean 0e03263013 Merge pull request #6095 from meh7an/perf/shader-srt-tape
Merged while @adriadam10 is away; his requested change is in place.
2026-10-18 17:41:16 +13:00
Crimson af0af833c1 fix(shader): use 64-bit buffer atomics for device-memory LDS
Compute LDS beyond the device's shared-memory limit lives in a storage
buffer, and its 64-bit atomics took a lock in that buffer. AMD's Windows
driver compiles the lock's acquire and release to s_barrier, which inside
the divergent lock loop pairs with the program's own barriers, so the
device-memory case of agc_driver_lds_lock_budget read half its sum.

With shaderBufferInt64Atomics the device-memory LDS now uses a 64-bit view
of the buffer instead: 64-bit atomics are compare-exchange loops on it and
32-bit atomics stay native, so no lock is taken. Without the feature the
lock is kept. The per-workgroup slice is rounded up to an even dword count
so that the 64-bit view stays aligned.
2026-10-18 05:47:30 +03:00
Yunus Shaikh 2848253fe4 Merge main into fix/recompiler-once-per-pixel-sample-inputs 2026-10-17 19:06:51 -07:00
Yunus Shaikh 49788713ac Merge #6243's head into perf/shader-image-table-reuse-v3-pr
#6243's new head merges main f1af6c5915, whose #7325 adds
verifyChainedTableOffset next to the selected-record tests this
branch carries from #6243. That test reads the record words from the
snapshot's shared word list; this branch keeps the words with each
table's snapshot, so the merge reads them from the table it checks.
2026-10-17 17:57:47 -07:00
Yunus Shaikh 8c6765be71 Merge main into perf/shader-direct-cpu-evaluable-images
Main's #7325 adds verifyChainedTableOffset to the image table tests
where this branch adds its selected-record tests. Keep both, with
main's call after this branch's.
2026-10-17 17:32:11 -07:00
Dean bf3b5d679d Merge pull request #4020 from MrMha/perf/linear-shader-use-rewrites
perf(shader): rewrite high-fan-out IR uses in linear time
2026-10-18 13:22:31 +13:00
Gjon Gojcevic 7c19c66def fix(shader): fold texel offsets into base-level unnormalized samples
An implicit-LOD or gradient sample with a texel offset through an
unnormalized S# was refused at bind time with "unnormalized guest sampler
is used by an implicit-LOD sample". PPSA01317 aborts on boot on such a
pixel image_sample_o. The base-level path already samples these through
OpImageSampleExplicitLod with LOD 0, which Vulkan allows for unnormalized
samplers, but it dropped the offset. It now adds the offset to the
coordinates in that branch, as the explicit-LOD offset fold does, so a
sampler whose uses also include offsets takes the base-level path. A
normalized S# keeps the offset operand.

AI-assisted: yes
2026-10-17 14:43:23 -04:00
Mehran acee01a4e6 perf(shader): compile resource plan walks instead of interpreting them 2026-10-17 03:04:48 +03:00
Yunus Shaikh 7f7d4064c8 refactor(agc): place the image table reuse's includes and shared resolution declaration apart from neighbouring edits
Moves a few include lines and the ResolveImageTablesShared declaration by a line or two, so they no longer share an insertion point with other open pull requests. No behaviour change.
2026-10-15 09:45:11 -07: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 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 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 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
Yunus Shaikh 4344c1dc29 Merge branch 'main' into perf/shader-direct-cpu-evaluable-images 2026-10-15 06:38:09 -07:00
Adrià 489466d3f9 Merge pull request #5734 from YossiYad/fix/strip-last-provoking
fix(agc): refuse last-vertex strips only for reads of a vertex slot
2026-10-15 15:03:44 +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
Yunus Shaikh 0b099964f0 Merge branch 'main' into perf/shader-direct-cpu-evaluable-images 2026-10-15 03:19:52 -07: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
Yunus Shaikh d4b33b0ae8 perf(shader): select the record of a T# whose offset the CPU evaluates
Since #757, a T# loaded from a buffer heap at a runtime offset is an
image table column, and every capture snapshots the whole heap, up to
65,536 records, even when user data or an SRT read fixes the offset.
When the T#'s eight dwords and its offset are runtime values the SRT
walker evaluates, the column is now selected: the offset is kept as a
descriptor source, the shader indexes entry 0, and the snapshot reads
only the record at the evaluated offset, with the heap base moved to
that record.

The column keeps a table's semantics. An invalid T#, an unmapped record
or an offset that carries past 32 bits is poison that faults only when a
lane selects it; an offset the walker cannot read faults the table
instead of throwing; the record's mode is picked per entry, so no
pipeline variant is added; and a dispatch that writes the record faults
it as written by its dispatch. Dwords past the heap read zeros, as the
scalar loads would, and a record past the key cap is read rather than
poisoned.

A column stays a whole table when its offset is chosen per lane, or when
the shader may write the memory the offset is read from at run time: the
buffer it is read from (by V#), or any memory when the read or a write
goes through a pointer or a GPU-only descriptor. Samplers are unchanged:
an S# the walker evaluates is already a direct sampler. The plan stores
the selection, so the disk cache format is 36.

Five agc_shader_memory and agc_shader_bindless checks asserted the old
planning and now assert the selected record:
- "image table SRT key": a key read through the SRT narrowed a 4-key
  table to records 0 and 2. It now selects record 2 alone, captures no
  other record, and a changed key moves the capture without changing the
  module or its disk key.
- "image table key union" and "image table late key": two keys of one
  heap shared one column with two value key domains. Each key now
  selects its own record, as two one-record tables.
- "image table unkeyed access": a read keyed through the SRT shared a
  whole column with a read keyed per lane. The first now selects its
  record; the second keeps the whole, unnarrowed table.
- "uniform table image": the T# at a user data offset was one key of a
  full table. It is now the one selected record, and the record the user
  data does not select is not read.
A value key domain only arises for a key the walker evaluates, so it no
longer narrows an image column; key buffer domains (keys loaded per
lane) still do.
2026-10-15 02:48:55 +00:00