The recompiler refused centre-located barycentrics in a multisampled
merge on a device without interpolation functions and fragment shader
barycentrics. It did so even when every sample sits at the pixel
centre, as in a draw raised from a one-sample rasterizer, where the
barycentrics are the centre's anyway. The refusal now applies only
when the merge's sample locations are off the centre. The pixel
request carries those locations for such a merge.
The recompile test runs on two states. On a 2-sample rasterizer, the
missing barycentrics throw, and the shader compiles once they are
present. On a raised draw, the shader compiles without them.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A merge into a multisampled 4_4_4_4 target runs the pixel shader once
per sample. A shader that stores to guest memory, runs atomics or uses
GDS would then repeat those writes for every covered sample. An
8-sample pixel would add to a counter eight times. Such a merge now
throws.
The recompile request carries the merge's sample count, not whether
the guest asked for per-sample shading, so this also refuses shaders
the guest iterates itself, where the console repeats the writes too.
A recompile test checks that a 2-sample merging shader adding to a
buffer counter throws, and that the same shader without the merge
compiles.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A partial write of a multisampled 4_4_4_4 target makes the draw shade
per sample. For a shader the guest runs per pixel, every invocation of
a pixel must then see the same inputs. Otherwise its colours and kill
decisions follow each sample, while the console runs one invocation.
The decode now throws in two cases when the guest did not ask for
per-sample shading. The first is a shader that reads the ancillary
word, whose sample number would become the invocation's. The second
is a shader that reads centroid, sample-located or pull-model
barycentrics, Z, 1/W, or a position at the centroid or a sample. That
case is allowed when every sample sits at the pixel centre, as in a
draw raised from a one-sample rasterizer. The recompiler throws for
centre-located barycentrics on a device without interpolation
functions and fragment shader barycentrics, where they would not be
rebuilt at the centre.
Decode tests run each moving input on a 2-sample rasterizer, where it
throws, and with PS_ITER_SAMPLE, where it decodes. The raised draw
keeps the located inputs and refuses the ancillary word. Centre and
upper-left positions, centre barycentrics, the front face and the
fixed-point position decode, and a recompile test covers the missing
barycentrics. The debt entry now lists what is kept and what throws.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Per-sample shading of a merging draw makes centroid and sample-located
inputs and the ancillary sample number follow the invocation's sample
(centre-located inputs too on devices without interpolation functions
and fragment shader barycentrics), and the 2-sample merge was measured
on one GPU only.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The refusal of a merging shader that writes guest memory under late
tests fired in two cases where the early tests change nothing.
The first was a draw with no depth, stencil or depth-bounds test. Early
tests reject no pixel there, so every write the console makes still
happens. The target merge now carries whether the draw has such a
test, and the refusal applies only then. This adds one byte to the
version 40 merge fields of the recompile request.
The second was any GDS binding, including a shader that only reads GDS.
The refusal now looks for GDS writes, atomics, appends and consumes.
Tests: the depth and stencil decode checks the new flag, and the
static shader key depends on it. A counter-adding shader under late
tests compiles without a test and throws with one. A late-test merge
that reads GDS compiles, and one that adds to GDS throws.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A merged 4_4_4_4 write runs its pixel shader after early depth and
stencil tests. Vulkan counts a sample for an occlusion query at those
tests, so pixels the guest shader kills under late tests would be
counted, and on the console they are not. Once the game has read an
occlusion counter the recorder counts samples for every later draw,
so from then on such a merge throws. The check runs where the recorded
and recipe draw paths prepare their inputs. The state records
DB_SHADER_CONTROL.KILL_ENABLE for it, which fits in the existing
padding.
The execution test starts sample counting and then checks two cases: a
merge that kills a checkerboard under late tests is refused, and a
merge that kills nothing still draws. A decode test reads KILL_ENABLE.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A partial 4_4_4_4 write merges inside a pixel shader that declares
early fragment tests. When the guest runs the shader under late tests,
pixels that the depth or stencil test rejects still execute it on the
console, so their buffer and image stores, atomics and GDS operations
happen there. Under forced early tests they would be dropped. Such a
merge now throws, unless the guest asked for early tests itself. The
merged target's own image write does not count.
A recompile test checks two things for a merging shader that adds to a
buffer counter: under late tests it throws, and under the guest's early
tests it compiles with its one atomic add beside the merge. The same
shader without a merge still compiles under late tests.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A one-sample target under a multisampled rasterizer whose
OVERRASTERIZATION_AMOUNT matches (radeonsi line and polygon smoothing)
decodes with mergedCoverage and is drawn through mixed-samples coverage
reduction onto its one-sample attachments. A merging draw leaves the
4_4_4_4 target out of the attachments, a combination that is not tested,
so a partial write of such a draw now throws; the limit is listed in the
technical debt file.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Occlusion counts are taken at the forced early tests, the refused merge
draws and why a late depth write was not used, and the one GPU the merge
was measured on.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A CB resolve from a packed color target throws, so a 2-sample 4_4_4_4
target cannot be resolved by the color block.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The raw-byte check that runs before a syscall pattern between PS4 unwind
functions is skipped now also accepts mov rax, imm64, merges a following
mov al, imm8 or mov ax, imm16 into the earlier constant, and walks back
over any number of register moves or zeroings that leave rax alone
instead of at most seven. Visited states keep the walk linear in the
bytes it covers.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
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]>
A relocation or lea target outside the unwind functions of a PS4 image
is now decoded up to its first jump, return or trap, or to the start of
the next unwind function, which also bounds the transfer check of the
tail rule. A target whose run holds an fs:[disp32] access, the form the
linear TLS sweep roots, is followed as code. The other targets taken as
data are counted in one message and kept as entry points that the
Windows TLS rewrite may not overwrite. The analysis merges nested
unwind ranges like the scans.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The skip of syscall patterns outside the unwind functions let some real
syscalls through. The scan now merges nested and overlapping unwind
ranges with the instruction scanner's MergeCodeRanges, so a pattern
inside an outer function is refused. A constant syscall number is also
looked for in the bytes in front of the pattern, whatever alignment the
decoding took: a mov eax, mov rax or xor eax, eax that ends at the
pattern or before register moves and zeroings that leave rax alone. The
decoded lookback requires each instruction to end where the next one
starts. Guest modules without PT_SCE_DYNLIBDATA get no unwind ranges,
so their scan stays strict even when their unwind table is read.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The code analysis of the Windows and macOS outputs followed every
relocation and lea target inside the code range as code. PS4 images keep
read-only data between unwind functions, and strings and tables there
decoded into garbage, failing the conversion. Outside the unwind functions
such a target is now code only when it is 16-byte aligned, starts less
than 256 bytes after an unwind function and decodes to a jump, return or
trap; lea targets go through the same rule instead of the graph's own.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
In a payload process on a PS5 (firmware 12.70), sysctlbyname returns -1
with ENOENT for thirteen names that threw here, and leaves the buffer and
the length alone, for a read, a size query, a null length and a null
buffer with a null length. sysctl does the same for the fixed MIBs of
seven of them. sysctlnametomib gives those seven MIBs and returns ENOENT
for hw.cacheline_size and hw.usermem, which threw too.
machdep.cacheline_size already returned ENOENT and is now tested with
the others. A write to the new names and MIBs throws, as the console's
answer to a write was not tried. The hw.realmem and vm.overcommit debt
entries now say that a payload process gets ENOENT for them as well.
AI-assisted: yes
Co-Authored-By: Claude Opus 5.5 <[email protected]>