A pixel shader of PPSA16833 binds more than 16 samplers outside the
sampler tables. Each takes two heap entries, one per descriptor variant,
so the 32-entry heap refused it with "shader sampler pairs exceed runtime
heap capacity" and the title aborted when a new game started.
The heap now holds 64 entries (32 samplers). Its metadata array is part
of ShaderData, which grows from 14528 to 16064 bytes, still within the
16 KiB uniform buffer every Vulkan device supports; the runtime ABI
version moves to 13 so shaders cached with the old layout are rebuilt.
The overflow message names the sampler count.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
image_store with D16 data to a storage image of a SINT format threw
"stores 16-bit data to an image of a SINT format", both when the
descriptor is decoded and in the SPIR-V image emitter, and the runtime
image modes left out the SINT conversions for such images. God of War
Ragnarok (PPSA08330) stops on it in compute shader 0x1052a17900 right
after its MFSR setup.
Each 16-bit half is now converted like the D16 format buffer stores
(EmitD16StoreComponent): sign-extended, then saturated to the
component width as for 32-bit data. The buffer form was measured on
RDNA2 in #331; the image form is not measured (TechnicalDebt).
agc_driver_image_store_sint now stores D16 data with DMASK 0xf and 0x5
to all eight SINT formats and after an image_atomic_add on 32_SINT,
instead of expecting the refusal.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A geometry program with VGT_GS_OUT_PRIM_TYPE 1 was refused as an
unsupported output assembly. Each primitive export of an NGG line-strip
program is one line whose indices are packed like a triangle's, so the
mesh module now declares OutputLinesEXT with PrimitiveLineIndicesEXT for
it. A subgroup gets VGT_GS_MAX_VERT_OUT - 1 lines per input primitive and
at least GE_MAX_OUTPUT_PER_SUBGROUP line slots; fast launch keeps using
PRIM_AMP_FACTOR. The output size is part of the cache, draw, validation
and pipeline keys, and the request serialization version becomes 31.
Triangle-strip geometry is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The SRT evaluator records every raw read with the user-data word its address is
based on, and marks a word used as a plain value. The result also names the
user-data word behind each shader-data dword.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A static, read-only image slot whose T# has TYPE 1 is now bound as a null
image, like TYPE 0 since #5473 and #4779. On gfx1010, image_sample_lz,
image_sample_b, image_load and image_get_resinfo through a format-22
TYPE 1 T# return zeros without a fault, and gfx1035 reads it as zeros
too. Written, atomic and table slots still throw, and the reserved
types 2-7 stay refused.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
FoldVertexLaneBounds replaced every LaneId < 32 compare of a vertex
program on a fixed 32-lane host subgroup with true. The half-wave scan
matcher of the SPIR-V backend recognises a lane word by the select of its
low and high words on that compare. A scan keyed by the entry EXEC ballot
lost that shape, so its v_readlane_b32 of lane 63 was no longer read as a
half-wave reduction and the recompile failed (agc_shader_memory).
Fold the lane word select itself instead, and only when both words are
constants. Lane words built from ballots or other values keep their
lower-half test.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
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]>
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.
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.
A 2D image_sample_l through a 1D T# now takes the 1D runtime mode, as
image_sample_lz already does, instead of failing the runtime image
interface. Measured on RDNA2 (gfx1036) with tools/hw-oracle against the
same sample with DIM 1D: u inside, on and past the edges, v of 0, 0.5, 1,
-0.25 and 3, LOD -1 to 6 on a 5-level R32_FLOAT texture, point, linear and
linear-mip filtering, wrap, clamp and border addressing, and HEIGHT or
DEPTH 3. All 4725 rows matched: the 1D T# decides the addressing and v is
ignored.
The execution test samples a 5-level 1D T# with distinct mip contents at
LODs -1 to 6, fractional ones included, with u inside, on and past the
edges, under point filtering with wrap and clamp and linear filtering with
linear mips and wrap, clamp and border addressing. Every result must match
the same image_sample_l with DIM 1D bit for bit; point-filtered results
must also be the expected texel of the expected level, and linear-mip
results the blend of the two levels the LOD selects.
Move bounded vertex folding and empty-image feedback into separate topics. Keep the replay coverage proof, stage contracts, finite-reference tests and mapped cache regressions; version the stencil layout separately as format 34.
Co-Authored-By: Codex <[email protected]>
Preserve instance and triangle order with one dynamic-reference shader pipeline. Require bounded references, complete export coverage, replay-safe mask control and resources; refuse unsupported side effects and feedback.
Co-Authored-By: Codex <[email protected]>
The front-face VGPR was always +1.0 / -1.0, which is its form only while
FRONT_FACE_ALL_BITS is clear (current Mesa reads it with fgt 0 / fsign).
With the bit set, as Mesa 22.0 RADV programs it, a back face reads 0 and
a front face non-zero: ACO then tests it with v_cmp_lg_u32 0. Uncharted:
Legacy of Thieves Collection (PPSA05684) sets the bit and its two-sided
cloth shaders test v_cmp_lt_u32 0, so -1.0 (0xbf800000) made every back
face a front face: the insides of the menu skeleton's rags kept their
front normals and were lit white by the spot light behind them.
The pixel stage info now carries the bit (request version 26, cache and
draw keys), the decode reads SPI_BARYC_CNTL when FRONT_FACE is an active
input, and the translator turns the builtin into 0xffffffff / 0. The
exact front value is not measured (docs/dev/debt/shader.md).
AI-assisted: yes
A plan copied every control-flow condition with all its operands, though one
that depends on a vector register is never evaluated. Such conditions are
dropped, and the copied values live in one arena without use lists.
A 3D image access bound to a 1D T# matched no runtime image mode and threw
"image descriptor is incompatible with the static runtime image interface"
(SONIC SUPERSTARS, PPSA06888, which reads a 1x1 1D T# as 3D with a plain
image_sample and an image_load). Add a 1D mode for 3D accesses whose only
uses are image_sample_lz, plain image_sample or mip-less image_load.
Measured on RDNA1 (RX 5700 XT, gfx1010) with the hardware oracle:
image_sample_lz reads the texel u selects and ignores v and r with every
addressing mode; a plain image_sample takes its texel and LOD from u alone;
image_load reads the x texel for any y when z is 0 and returns zero for any
other z, as the 3D-on-2D load measured in #2023 does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QQNVXyqeecpyneonkYL6H1
With GE_CNTL.BREAK_WAVE_AT_EOI clear, the instances of a tessellated draw share
hull threadgroups: the relative patch index counts on through the instances,
while the host's PrimitiveID restarts with each one. The local stage now passes
its draw-relative instance (InstanceIndex - BaseInstance) to the hull, which
numbers its patches instance * patches-per-instance + PrimitiveID before taking
(mod PRIM_GRP_SIZE) mod NUM_PATCHES. Instances that start a threadgroup anyway
(BREAK_WAVE_AT_EOI, or patches per instance a multiple of the group) keep the
per-instance PrimitiveID.
This replaces #4201's refusal of a hull or domain that uses its relative index
as data while BREAK_WAVE_AT_EOI is clear, and builds on its decode of the bit
(breakAtInstance, request serialization and draw keys).
- Hull and domain cache keys hold the patches per instance instead of the bit;
an indirect draw recompiles its hull and domain for each record's instance
count.
- The device enables shaderDrawParameters when available. Without it, a hull
using its relative index as data in such a draw is refused, as is a domain
doing so (passing the instance on to the domain is not implemented).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A descriptor whose dwords come from two scalar buffer loads of the same
record, where one load's offset carries an immediate addend, was refused
because the column wanted one offset expression and one buffer node for
every dword. The column now peels an immediate addend off each read's
offset and compares effective offsets (memory offset plus addend) and
the remaining base expressions, and accepts distinct buffer nodes that
are equivalent. Dword 0 keeps its offset expression, so columns that were
already planned are planned the same way.
Folding a second load into the first changes where its offset wraps:
at a dynamic offset of 0xfffffff0, a second load at +16 wraps to the
start of the buffer, while the folded read lands past 4 GiB. A column
folds a load only when its addend is no smaller than dword 0's and its
immediate is small, and it records dword 0's addend and the largest extra
addend. A record whose offset would carry past 32 bits between the two
loads is not read and becomes OffsetCarry poison.
PPSA26344 pixel shader 0x8000296000 reads T# dwords 0-3 with an
s_buffer_load_dwordx16 at key * 0x88 and dwords 4-7 with an
s_buffer_load_dwordx4 at 48 + key * 0x88.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A table column can now read its descriptor through a 64-bit pointer
stored in each record: the shader loads the record's pointer at
key * stride + addend + offset from a scalar buffer, then the T# (or S#)
through that pointer. The materializer reads each record's pointer when
it builds the table, reads the descriptor behind it and records one
read range per record. Pointer columns take their own path ahead of the
batched record reader, since a descriptor's address is known only once
its record's pointer is read. The column is planned only when the descriptor
dwords are read at an immediate offset from a GetAddressResource whose
two dwords are consecutive reads of one buffer at the same affine offset.
PPSA26344 compute shader 0x8000333c00 picks a record per lane in a
waterfall loop, reads the record's pointer from it and samples the
image behind it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>