A pixel or vertex shader with a 64-bit image atomic declares Int64Atomics,
Int64ImageEXT and SPV_EXT_shader_image_int64, which graphics shader
validation rejected ("SPIR-V requires unsupported device capability"),
so the draw was dropped. Compute dispatches are not validated this way.
The device records whether it enabled shaderImageInt64Atomics
(imageInt64Atomics in the device state and the graphics Context), and
ValidateShaders accepts the two capabilities and the extension when it
did.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DrawRejection read 0x1c4 with read(), which throws when the register is
absent, against the function's rule that a missing register gives no
verdict there; it now reuses the value read with value() for the
DB_SHADER_CONTROL check. Tests pin the Z-export rules: formats 1, 2, 3
and 9 with Z_EXPORT_ENABLE accepted, format 0 rejected, the sample-mask
export accepted with 32_ABGR and rejected with 32_R, formats 4 to 8
rejected, and a missing 0x1c4 accepted.
Bit 17 of PA_SC_MODE_CNTL_1 (MULTI_SHADER_ENGINE_PRIM_DISCARD_ENABLE)
lets each shader engine drop primitives outside its screen region, a
work distribution setting with no visible effect. Draws with it set were
skipped as unsupported. PPSA12544 sets it (0x06020000, with the already
accepted EOV bits 25 and 26) on the draws that produce its presented
image, so every frame it presented was black.
AI-assisted: yes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DecodeState, the draw precheck and DecodePixelStageInfo rejected every
nonzero CONSERVATIVE_Z_EXPORT (DB_SHADER_CONTROL bits 13-14).
EXPORT_LESS_THAN_Z (1) and EXPORT_GREATER_THAN_Z (2) promise that the
exported Z stays on one side of the interpolated Z. The value now
reaches the recompiler as a pixel stage field, and a pixel shader that
writes FragDepth declares DepthLess or DepthGreater next to
DepthReplacing, the SPIR-V modes RADV maps to these values. Vulkan lets
those modes only add a depth test of the interpolated Z before shading,
so a shader that keeps the promise renders the same. The reserved value
3 still throws.
The field is part of the recompile cache key, the draw key and the
serialized request (version 7), and ValidateShaders accepts both modes.
LayoutFor had no case for VK_FORMAT_A2R10G10B10_UNORM_PACK32, the format
of a COLOR_2_10_10_10 color target with the alternate component swap, so
FillDccClear could not encode its 0001, 1110 and 1111 clear codes. The
format now takes the A2B10G10R10 layout: the codes set the three color
channels alike, so both packings give the same words.
A draw into a copied target under these keys now starts from the clear
value instead of the stored texels, and clearTexel (the CB metadata pass
and storeClearTexels) returns the texel instead of throwing. Textures
and resident targets, stored as A2B10G10R10, and DccAlphaOnMsb are
unchanged. Split out of #648.
DecodeState and the draw precheck rejected PA_CL_CLIP_CNTL bit 24
(DX_LINEAR_ATTR_CLIP_ENA), so every draw that sets it was skipped.
Vulkan requires clipped NoPerspective outputs to vary linearly in
framebuffer space, and RADV and radeonsi set this bit for every draw
they emit. With the bit set the console therefore clips the way the
host driver already does, and the pipeline needs no extra state.
Clip space, depth clamping and the viewport depth range decode as
with the bit clear. Every other unsupported bit still rejects the
draw, with or without bit 24.
TechnicalDebt records that a clear bit, which was already accepted,
gets the same clipping.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pixel shader color exports are compacted: export MRTi goes to the i-th
MRT slot with a non-zero CB_SHADER_MASK nibble and takes the i-th
SPI_SHADER_COL_FORMAT field. Draws still mapped export i to slot i and
read slot i's format field, so a shader with CB_SHADER_MASK 0xf000f and
SPI_SHADER_COL_FORMAT 0x44 (export 1 to slot 4) was refused with
"color export format 0".
Each color target now carries its export index next to its slot.
Attachment references, blend states, export mappings and the pipeline
and pass keys follow the export index; the color buffer, blend control
and target mask come from the slot. An unwritten export below the
highest written one stays an unused attachment.
Color targets with the SW_4KB_S swizzle (CB_COLOR_ATTRIB3 tile mode 5) are laid out in 4 KiB
blocks with the standard element order the texture detiler uses, and are resident through the
same storage images as 64 KiB targets. A CB_COLOR_VIEW selecting one slice (SLICE_START equal to
SLICE_MAX) renders into that slice: the target starts one slice size per slice after the surface,
the slice size being the surface's mip chain as a texture of the same tile mode lays it out. Views
of several slices are still rejected.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A mesh program took its draw's index count from push constants, so an
indirect mesh-path (NGG) draw needed the CPU to read its record first,
waiting for the GPU to finish everything recorded before. In PPSA21564
that was about 170 waits per 10 s at the title, menu and level (about
1 s), and one per frame in the intro (2.2 s per 10 s).
The two free mesh push dwords now carry the device address of an argument
record (a VkDrawMeshTasksIndirectCommandEXT followed by the index count
and first index). When the address is set, the prologue takes the index
count and the first index from the record (the new MeshArgument IR value,
a load through a physical storage buffer pointer) and offsets its index
reads by the first index; a direct draw leaves the address 0 and draws as
before. Mesh modules declare the physical storage buffer capability for
it, and the driver's SPIR-V validation accepts the extension in mesh
modules.
An indexed mesh draw with one record that patches no user SGPR now stays
on the GPU. A one-thread compute pass (Graphics/shaders/MeshArguments.comp)
turns the record, read in place from its host import, into the draw's
argument record: the groups and instances of vkCmdDrawMeshTasksIndirectEXT
and the index count and first index the mesh program reads, with the CPU
path's rules (count clamped to the index buffer, nothing for an empty
record or past the device's group limits). The index buffer descriptor
covers the whole buffer and the program offsets by the first index. When
the records cannot be read in place, the CPU reads them and fills the
same argument record (ResolveMeshArguments).
Driver::Submit checks the title's submission packet, which the title
passes on its own thread stack. Host thread stacks are neither registry
mappings nor write-watched, so no page-state cache covers them and on
Linux every check scans /proc/self/maps line by line: about 1.5 ms per
submission on the render thread of PPSA21564 (66% of that thread's
samples in perf at the title).
A range between the calling thread's current frame and its stack's top
holds the frames of every caller, so it is mapped readable and writable
for as long as the call lasts; verify() now accepts it without a query.
A thread on another stack (a fiber's, a signal stack) has its frame
outside the thread's bounds and still gets the page query, as does any
range that runs past the stack's top or starts below the frame.
GetCurrentThreadStackLimits needs _WIN32_WINNT 0x0602 on MinGW, as
libkernel already sets; GuestMemory.cpp is compiled by several targets,
so the definition is a property of the source file.
PA_SC_WINDOW_OFFSET is already required to be zero, so a generic,
window or viewport scissor whose WINDOW_OFFSET_DISABLE bit is clear
selects the same rectangle as one that sets it. Only the reserved
bits are still rejected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018m6Ad8zkQKPKZmyUQK9vfE
DB_DEPTH_CONTROL's DEPTH_BOUNDS_ENABLE now runs the Vulkan depth bounds
test with DB_DEPTH_BOUNDS_MIN/MAX, and PA_SU_SC_MODE_CNTL's
POLY_OFFSET_FRONT/BACK_ENABLE enable depth bias from
PA_SU_POLY_OFFSET_FRONT/BACK_SCALE/OFFSET and _CLAMP. Both are dynamic
state, so draws that differ only in their values share a pipeline.
The hardware slope scale is in 1/16 units. Depth bias with units other
than the depth format's (PA_SU_POLY_OFFSET_DB_FMT_CNTL), or that differs
between front and back faces that are both drawn, is refused. The
registers join the draw key. Depth-conditional color writes are still
refused.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Without VirtualQuery, a range no cached page table covers is looked up
line by line in /proc/self/maps. A cursor in a gap between mappings never
matched a line, so the scan parsed the whole file and then reported the
rest of the range unmapped, including mapped pages after the gap. The
scan now stops at the first mapping above the cursor, reports the gap up
to it as inaccessible and continues from there, as the VirtualQuery path
does with a free region.
PPSA21564 queries near-null addresses (0x18, 0x30, 0x58) about 1500 times
a second during resource capture; each of those parsed the whole maps
file (about 5700 lines here) instead of stopping at the first mapping.
Tests: agc_driver_graphics_tests describes three host pages with the
middle one unmapped as two ranges, a query that starts in the gap as the
page after it, and a near-null address as inaccessible.
PPSA21564 draws small indexed triangle fans (VGT_PRIMITIVE_TYPE 5, six
indices, restart enabled) into a real ES + GS (VGT_SHADER_STAGES_EN
0x2030). DecodeShaderStages took only point, line, triangle list and strip
input, so these were skipped as "unsupported geometry input or output
assembly".
- A fan is accepted as geometry input with an input step of 1, like a
strip: a subgroup of P triangles takes P + 2 vertices, in the decode,
ShaderMeshInputInfo and the draw's group count.
- The mesh entry shades the draw's vertex 0 (the hub) in ES thread 0 of
every subgroup and the subgroup's vertices from firstPrimitive + 1 in
the others; GS thread p gets ES threads (p + 1, p + 2, 0). That is the
order the hardware hands a GS with the first vertex provoking (as
Vulkan orders fans), so a triangle keeps the winding and flat color of
its rim vertex.
- The mesh path does not restart primitives: an indexed fan with restart
enabled is refused when its indices hold the restart index (all ones).
Tests: agc_driver_graphics decodes PPSA21564's fan registers (30
triangles in a 32-vertex subgroup) and rejects short subgroups;
agc_shader_memory checks the fan subgroup sizes; agc_driver_mesh_tests
(GPU, run by hand) draws non-indexed and indexed fans over three
subgroups, checks each triangle takes its first rim vertex's flat color
and the fan is not closed, and refuses a restart index.
Every vertex attribute copied its own range of guest memory, so a draw
over an interleaved buffer copied it once per attribute (seven or eleven
times for PPSA21564's level meshes). Attributes with the same stride and
input rate whose first elements lie within one record now share one copy
of their union, each bound at its offset into it.
Explicit v_interp_p1/p2 interpolation (fragment-shader barycentrics) reads every interpolated
parameter per vertex, and the rect-list shaders refused every per-vertex parameter. Their
tessellation stage emits each corner's parameter value, which is what a per-vertex read of the
generated triangles needs. Only custom parameters (custom interpolation or passthrough), whose
fourth vertex has no value of its own, stay refused; FragmentParameter carries that flag.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Draws with CB_COLOR_CONTROL MODE ELIMINATE_FAST_CLEAR (2) or DCC_DECOMPRESS
(6) were rejected as "only normal color rendering with copy ROP". On
hardware these full-target blits write the fast-clear value into the
fast-cleared pixels (or store the pixels uncompressed); the shader's output
never lands. In this driver's DCC model (surfaces stored uncompressed,
uniform fast-clear keys make every read return the clear value) that is:
- keys that are a clear code: the target's texels become that value and
its keys read uncompressed. Reads see the same values before and after.
- uncompressed keys, or a target without DCC: nothing changes.
- mixed keys (per-block metadata), a mipmapped DCC target (its keys are not
decoded) or a register clear of a texel over 64 bits: throw.
The texels go into the target's resident image (its refresh already
cleared it to a code's value; the register code is cleared on the GPU with
FillClear) and reach memory through its write-back, or into guest memory
on the CPU when there is no resident image. A resident image made under
other DCC keys is remade under the target's keys by the storage cache
before the pass reads it. DecodeColorMetadataPass
requires the draw to be only that pass: copy ROP, no depth or stencil
work, no multisampling, a viewport and scissor covering each target.
Indirect passes throw. The CB_COLOR decode moves into DecodeColorBuffer,
which also reads CB_COLOR_CLEAR_WORD0/1.
The register clear code (DCC key 0x20) had no value before: its keys were
read as the stored texels. A target under register keys now reads as its
CB_COLOR_CLEAR_WORD texel: a resident target is cleared to it before a draw
renders into it (then its keys read uncompressed), and a non-resident
target's copy starts from it.
Every decision that materializes a clear reads the keys through
CurrentDccKeys, which first waits for the batches up to the newest recorded
writer of the key range. The title's fast-clear fill of the metadata can
still be pending on the GPU when the next draw is decoded; reading the
stale bytes there made a later draw clear the target over the results of
the earlier ones.
The remaining CB modes (resolve, decompress, FMASK decompress) keep the
rejection, which now names the mode.
A guest buffer view off minStorageBufferOffsetAlignment is bound from the
aligned offset below it, and the difference goes into the shader's memory
byte offsets (3dec6ced), but only when those live in the push constants:
a shader whose shader data is a buffer failed with "a guest buffer off the
storage buffer offset alignment in a shader whose shader data is a buffer
is not implemented".
The adjustment now goes into the shader data buffer's byte at
memoryOffsetDword * 4 + element, and a template's data refresh writes the
dispatch's words with the same patches applied.
SPI_PS_INPUT_CNTL_n.OFFSET (bits 0-5) names the parameter slot pixel
input n reads, so several inputs may read one slot and an input may read
a slot the vertex stage never exports. With OFFSET bit 5 set and
FLAT_SHADE clear the input is the DEFAULT_VAL constant instead (bits 8-9:
0000, 0001, 1110, 1111). With OFFSET bit 5 and FLAT_SHADE both set the
slot in the low bits is passed through with its three vertices unchanged:
v_interp_mov P0, P10 and P20 read vertices 0, 1 and 2 rather than P0,
P1-P0 and P2-P0. The PS5 compiler writes such inputs as 0x422 and 0x423.
The old mapping masked OFFSET to five bits, so a defaulted input landed
on a real slot, and gave the second of two inputs reading one slot either
the same Location (two variables, "duplicate shader interface location")
or, when one was flat, a free Location the vertex stage never wrote
("graphics interfaces disagree at location N").
Now one input variable is declared per slot actually read, at Location =
slot, and inputs reading that slot share it. A slot read both flat and
interpolated (or through the perspective and linear pairs) is read per
vertex: flat readers take the provoking vertex, the others interpolate
with the barycentrics of their own pair. Pass-through inputs are read per
vertex at their slot without subtracting vertex 0, and reading one with
v_interp_p1/p2 is refused. Defaulted inputs read their DEFAULT_VAL
constant and declare nothing; inputs the program never reads are no
longer declared.
SPI_PS_INPUT_ENA/ADDR with PERSP_CENTROID (bit 2) or LINEAR_CENTROID
(bit 6) was rejected; PPSA21564 binds 0x326 (perspective center and
centroid, linear center, POS_X/Y).
The PS input VGPR layout now follows the hardware rule: SPI_PS_INPUT_ADDR
places every input it names in SPI order (two VGPRs per I/J pair, three
for the pull model, one otherwise), loaded or not, and ENA & ADDR selects
the loaded ones. ShaderPixelStageInfo carries inputAddr (replacing
perspectiveCenterVgpr) plus perspectiveCentroid and linearCentroid; the
recompiler places each loaded input with PixelInputVgpr. This also fixes
the system inputs, which were always written from v0 (psSystemInputBase
was never set), so POS_X overwrote the center I in any draw with
barycentrics enabled.
Draws are rasterized single-sampled, where the centroid is the pixel
center: with fragment barycentrics the centroid pairs carry the center
barycentrics (BaryCoordKHR for perspective; the linear center and
centroid pairs now get BaryCoordNoPerspKHR).
NoPerspective was decided for every parameter by one flag (LINEAR_CENTER
loaded). v_interp_p2 now records whether it reads the J of a linear or a
perspective pair, and a parameter is NoPerspective when it is read
through a linear pair; parameters read through neither keep the old
rule. A parameter read through both kinds of pair throws. Only pixel
stages consult this.
The request serializer moves to version 5 (older requests read back with
their loaded inputs as the layout); the cache key and the driver's draw
key compare and hash the new fields. The unsupported-input error names
ENA and ADDR.
The geometry path's merged ES/GS programs (VGT_SHADER_STAGES_EN.GS_EN)
were compiled as mesh-stage programs whose input metadata was never
filled in, so every such draw failed with "mesh shader translation
configuration is not supported".
Recompiler:
- BuildShaderStageInputInfo takes the draw's MeshConfiguration; the mesh
input helpers (primitive size, step and counts) are implemented.
- The prologue emulates one NGG subgroup per workgroup: s2 GS_TG_INFO,
s3 the merged wave info per wave, the GS vertex offsets (ES thread
index times VGT_ESGS_RING_ITEMSIZE, odd strip triangles swapped), the
primitive id, and the ES vertex and instance ids. An indexed draw's
vertex id is read from the index buffer through a raw V# the driver
puts in hidden user words 4..7.
- s_sendmsg GS_ALLOC_REQ becomes MeshAllocate. The epilogue zeroes the
allocation first, clamps it to the output limits, culls primitives no
thread exported and decodes the 9-bit vertex indices.
- The draw parameters live in the last six push dwords. A BDA fault in
a mesh program no longer stops the invocation (as for other programs
with barriers, the fault is recorded and the read returns zero), so
every invocation reaches the subgroup barriers and the epilogue.
- The cache keys cover the mesh configuration; requests serialize its
new esgsItemSize field (version 4).
Driver:
- DecodeShaderStages reads VGT_ESGS_RING_ITEMSIZE (now in the draw key)
and sizes the subgroup for its ES vertices, GS primitives and outputs.
- Stages compile below the mesh draw parameters, which every mesh draw
pushes; the mesh stage always takes part in the push range.
- The index-buffer V# is set per draw (per record on the CPU-resolved
indirect path); the index-buffer alias check spares read-only
elements.
- The workgroup and LocalSize checks count two guest lanes per
invocation on 32-wide subgroups; mesh draws may carry a first vertex
or instance.
agc_driver_mesh_tests (EXCLUDE_FROM_ALL, needs VK_EXT_mesh_shader)
recompiles a hand-assembled merged ES/GS program in the PS5 compiler's
layout and draws indexed and non-indexed triangle lists in one- and
two-wave subgroups and a triangle strip across odd-starting subgroups,
reading the pixels back. agc_shader_replay prints a request's draw and
mesh configuration.