A pixel shader can interpolate one parameter through a perspective pair for
some components and a linear pair for others. Genesis Plus GX (PPSA99921)
reads attribute 0's x and y through one pair and its z through the other in
the shader behind its menu. Host interpolation gives each input a single
Vulkan interpolation, so it cannot express that, but HostInterpolationChecker
asked InputIsLinear, which throws for such an input, and the title stopped at
"pixel input 0 is interpolated through both a perspective and a linear I/J
pair" instead of taking the barycentric path.
HostInterpolationChecker::Lower now declines when an input is in both the
linear and perspective masks, so Recompile translates the shader again with
barycentrics, which interpolate every v_interp through the pair it reads.
agc_shader_memory now requires that fallback, with both barycentrics, for the
two-pair case it used to expect to fail.
An invalid-descriptor fault check of a GPU-descriptor buffer access, and the
swizzle-selector check of a formatted load, is not emitted when an identical
check on the same descriptor under the same execution guard sits earlier in
the same IR block or in a dominating block. The fault record, the invocation
stop and the host error stay the same; only the pc stored for a fault that two
invocations race to record can differ. APS5_NO_FAULT_CHECK_DEDUP restores every
check.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
NoteFailureOnce kept the last counted exception in a thread_local
std::exception_ptr. In a prx on Windows its destructor runs after emulated
TLS freed the object, which prx_thread_local_destructors refuses. Keep it in
HostThreadLocal like the recompiler's other per-thread state.
host-probe checked the raw CPU name with SafeText, which rejects '@', so it
failed on CPUs whose name ends in a clock ("... CPU @ 2.10GHz") although the
report keeps the name with the clock cut. Check it through the report.
NotImplemented_nid_no_patch now expands to NotImplementedAt_nid_no_patch, so
emscripten_module_unwind_tests, which does not link libc, also stubs the
located entry point.
The UINT storage view for float16 image stores was chosen only when the
global binding number fell in the uint storage image slots of the main
stage. Binding numbers are stage * Binding::Count + slot, so a pixel,
hull or domain shader's slots never matched: its uint writes went to a
float16 view and the store was lost on AMD's Windows driver. The slot is
now taken from the binding number within its stage.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01VAPyfZR5GPZaWxRz67e9FT
An image table column's snapshot could not be reused unwritten when the
column is addressed through a pointer, or when the capture had already
read pages of the table before taking its write stamp: the column took
no stamp, or stampWrites refused it, so every capture compared the whole
table again. A pointer-addressed column now stamps its remembered read
range before the compare. With an explicit span query, stampWrites keeps
a stamp over pages the capture already read when they still match
memory after the collect; without one it refuses as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
When the device has native f16 math and VK_KHR_shader_fma with shaderFmaFloat16, a packed f16
fma is one f16vec2 OpFmaKHR (SPV_KHR_fma). Vulkan requires OpFmaKHR to be correctly rounded and
to respect RoundingModeRTE and DenormPreserve, so it gives the fused, once-rounded result the f32
round-to-odd sum computes. The NaN, FP16_OVFL and clamp rules still run on the result bits.
Rebased onto current main by tibzejoker.
Co-Authored-By: thibaut <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01PHJtpJ4hw3KWJhobipK8ia
The snapshot check that let Materialize accept an attempt during which the
guest allocation generation moved looked only at the registered image
views. A remap of the memory the capture read descriptors from left those
views unchanged, so the attempt was accepted with descriptor words read
before the remap. The generation check on main did not cover this either:
ShaderMemory keeps the pages it read across attempts, so a retry read the
same cached words.
ShaderMemory now records the guest allocations behind each page when it
first reads it. The runtime hook, now snapshotCurrent, drops every page
whose allocations changed, so the next attempt reads it again, and accepts
the attempt only when no page changed and the image views are current.
agc_shader_memory gains a directly bound sampled array image case through
the real CaptureRegisteredViewsCurrent (an unrelated mapping during the
attempt is accepted and keeps the view, a resize of the image's own range
is retried) and a capture whose descriptor page is remapped mid-capture,
which must return the descriptor written after the remap.
AI-assisted.
Materialize cleared registeredImages before adding the table views, which
dropped the views materializeSnapshot registers for directly bound images
on every attempt. materializeSnapshot already starts each attempt from an
empty snapshot, so the clear is removed.
AI-assisted.
Conflicts resolved:
- libSceAgcDriver CMakeLists.txt: main moved test registration into tests/cmake/<exe>.cmake (tools/agc_test_split.py resolve); this branch's nested image table tests now sit in agc_driver_image_table_tests.cmake, and its nested snapshot retry test sits next to main's descriptor phis test in agc_shader_memory_tests.cmake.
- ResourceMaterializer.cpp: table equality compares both this branch's nested/pointers and main's resourceViews; the footprint counts main's resource views and this branch's pointers.
- ShaderDiskCache.cpp: both sides grew the descriptor source record (this branch: TableColumn::buffer, 1 byte; main: requiredBlocks, an 8-byte count), so the decoder minimum is 72 + 1 + 8 = 81, and the field-count asserts are DescriptorSource 4 and TableColumn 19.
- ShaderDiskCache.hpp: FormatVersion 56, an unclaimed number (main has 52; 53, 54 and 55 are already set by other open PRs).
- ResourceMaterializer.cpp (semantic, no text conflict): main added the per-image resourceViews loop to Snapshot(source, table), and it uses the top-level table index; this branch moved that copying into CopyRecords(), which nested tables share and which has no table index. The loop now runs in Snapshot right after CopyRecords, as on main; nested tables carry no resource views, as before.
Where robust loads are not exact, a driver may round a binding's range up
to a DWORD (RADV does), so a V# of 7 bytes gets a length of 2 DWORDs and
the length check passes the DWORD at byte 4 that the V# size excludes.
Specialize the low two bits of the V# size in the free fourth word of the
buffer's specialization slots, key the binding plan and the result memo
on it, and keep the carry-only guard only when both the base and the size
are whole DWORDs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A scalar buffer read through a static V# checks the carry, that the V# is
present and the offset against the V# size in 64 bits, and then the
offset against the binding's length. When the V# base is DWORD aligned,
the binding starts at the base and spans the V# size (less where it is
cut), and a V# that is not present is bound to the empty placeholder, so
the length check alone gives the same value once that placeholder reads
as zero. Zero the placeholder when it is created and keep only the carry
check there. A base off a DWORD boundary keeps every check, since its
view is rounded up a DWORD. Devices with exact robust loads have no
length check and are unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ResourceMaterializer::Materialize retried whenever the guest allocation
generation moved during an attempt, and threw "runtime resource snapshot
did not stabilize" after three. Any allocation change moves it, so a
title that maps memory while it loads can move it during every attempt.
PPSA22408 aborted that way during loading in 2 of 2 runs with #11346 and
its COND_INDIRECT_BUFFER change, and in about 1 of 3 otherwise.
A new SrtRuntime hook, imageViewsCurrent, checks whether the image views
a snapshot registered still match their allocations. The driver backs it
with CaptureRegisteredViewsCurrent, the check its dispatch cache already
uses. Materialize accepts an attempt whose generation moved when the
check passes, and retries as before when it fails or no hook is set.
AI-assisted.
A 3D sample of the same image through another sampler no longer feeds the gather's
state, z modes 4 and 5 no longer count as border addressing, a null image no
longer divides by a zero extent, and the test covers per-axis modes, rounding
ties, denormals, null images and the vertex stage.