Skip to content

fix(shader): split a BDA span read's in-place loads from its fallback - #4759

Merged
oneandonlydean merged 1 commit into
boykopovar:mainfrom
fdfawef324:fix/bda-span-read-split
Oct 13, 2026
Merged

oneandonlydean merged 1 commit into
boykopovar:mainfrom
fdfawef324:fix/bda-span-read-split

Conversation

@fdfawef324

Copy link
Copy Markdown
Contributor

What

Fixes #3291. Resubmits #3289 (closed by the intake bot) by its author, rebased onto current main.

On an RTX 3060 (Windows, driver 591.86) a dispatch that reads guest memory through read_bda_span never finishes when its lanes split between the span's in-place dword loads and its per-dword byte fallback (which calls read_bda_dword_bytes). Windows resets the GPU and the device is lost. How the tests then fail depends on where the loss is observed. Most tests fail in teardown: the lost batch keeps its lease, so the teardown waits 60 s for the pinned guest allocation and aborts with guest allocation ... is still pinned by GPU work or an import. agc_driver_pixel_interlock instead fails after a few seconds with vkDeviceWaitIdle: Vulkan result -4 (VK_ERROR_DEVICE_LOST) and never reaches the pin wait.

The hang is not general to NVIDIA. The reports in #3291 show:

  • karanvora2599, RTX 4060 Laptop GPU, Windows, driver 591.86: the same tests fail on main (device lost after about 20 s, then the 60 s lease wait), and all 15 BDA tests pass with this change (tested as b54fe62, the same code patch).
  • MojoisDev, RTX 5060, Linux, driver 590.48.01: the flat-load test reproduces the pinned-allocation abort on main, and the nine originally failing tests pass with this change (tested as 3389172, the same code patch). 21 of the 22 affected tests pass three serial repeats; the remaining failure, agc_driver_wave32_subgroup, is reported there as a separate problem. A RADV cross-check passes all 22.
  • AchrafReyani, RTX 4070 Laptop GPU, Windows, driver 616.92: the hang does not reproduce on main, and the change causes no regression there (356 of 356 tests pass).
  • Amoenus, RTX 5080, Windows, driver 610.62: agc_driver_flat_apertures_* parts time out on main, but this change was never tested there, and the APS5_BDA_BYTE_READS=1 control also times out. That failure is unresolved and may be a different problem; this PR does not claim to fix it.

The two paths were the arms of one OpSelectionMerge selection inside the DontInline function. They are now two selections, the loads under the span's condition and the fallback under its negation, each producing a zero vector on the lane that does not take it, and the result is chosen after both. Every lane still runs exactly the loads, page-table lookups, fault records and invocation stops it ran before, so no RDNA instruction semantics change; only the SPIR-V control-flow shape does (SPIR-V 2.11 structured control flow). The recompiler emits SPIR-V 1.3 for every stage except mesh shaders (1.4), and OpSelect can only choose between composite values with a scalar condition from 1.4 on, so the final join stays a selection.

The driver-side cause is not known, so the shape is recorded in docs/dev/debt/shader.md (code comments are not allowed), so that it is not folded back into one selection.

#3655 (open, RADV page-table walk inlining) changes the same lines of SpirvBdaRead.cpp for another reason; whichever merges second rebases. #4224 carried this commit and was closed because it was not its author's.

Tested

  • OS Windows Server 2025, GPU RTX 3060 (driver 591.86 = 32.0.15.9186), GCC 15.2 (winlibs), Release -O3. All runs below are on this machine unless stated otherwise.
  • Red on main 3e0e01f with the fix reverted (ANYPS5_NO_SHADER_CACHE=1, ctest -j 4): 9 of the 23 affected tests fail. agc_driver_flat_loads, agc_driver_flat_loads_x2_x3, agc_driver_flat_loads_x4, agc_driver_global_loads, agc_driver_global_loads_x2_x3, agc_driver_global_loads_x4, agc_driver_flat_apertures_2 and agc_driver_flat_apertures_5 abort after 64.2 to 70.6 s on the 60 s pin wait. agc_driver_pixel_interlock fails after 6.3 s with vkDeviceWaitIdle: Vulkan result -4.
  • Green with the fix, same base and settings: all 23 (agc_driver_(flat|global)_loads*, agc_driver_pixel_interlock, agc_driver_flat_apertures_0 to _13, agc_driver_bda_device, agc_driver_bda_span_reads) pass in 52.3 s at -j 4, the slowest in 18.6 s.
  • Full library, ctest -R "^(agc_|shader_)" -j 4 with ANYPS5_SHADER_CACHE_DIR set to a new empty directory (most of these tests also set ANYPS5_NO_SHADER_CACHE=1 themselves):
    • on 3e0e01f: 444 of 444 pass (agc_driver_stencil_ref_export skipped), 199.6 s, slowest test 16.0 s (agc_driver_prepared_shaders_debug_failure). An earlier run of the same set failed agc_driver_scalar_swappc_fetch once (302.7 s, then a vertex value mismatch); it passed alone, in 8 of 8 repeats and in this rerun.
    • after rebasing onto 44ab0c3: 443 of 444 pass, 270.4 s, slowest test 29.6 s (agc_driver_swap_alt_blend) while other jobs shared the GPU. agc_driver_depth_storage_reuse failed once with vkWaitForFences graphics: Vulkan result 2 (VK_TIMEOUT) after 10.7 s; it passed 8 of 8 repeats alone. All 23 affected tests passed in this run.
  • Rebased onto main 42496c5 (one more AGC texture change, no shader change) and rechecked there: the 23 affected tests pass in 33.0 s at -j 4 with ANYPS5_NO_SHADER_CACHE=1, the slowest in 21.4 s (agc_driver_flat_apertures_8).
  • Linux CI on main (lavapipe, run 38301611844 on 3e0e01f) passes these tests without the change, so the hang does not reproduce on lavapipe and CI passes either way. This comes from CI, not from a run on this machine.
  • python -I tools/check_conventions.py --base upstream/main: passed.
  • Not tested with a title.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Unblocks: none
  • AI-assisted: yes

🤖 Generated with Claude Code

On an RTX 3060 (Windows, driver 591.86) a dispatch never finishes when
the lanes of read_bda_span split between its in-place dword loads and
its per-dword byte fallback, which calls read_bda_dword_bytes: Windows
resets the GPU and the device is lost. The two were the arms of one
selection in the DontInline function. They are now two selections, the
loads under the span's condition and the fallback under its negation,
joined after both. Loads, lookups, faults and stops are unchanged for
every lane; only the SPIR-V control-flow shape changes (SPIR-V
specification 2.11, structured control flow). Every stage except mesh
shaders is emitted as SPIR-V 1.3, and OpSelect chooses between
composites with a scalar condition only from 1.4 on, so the join stays
a selection.

The same hang and fix were reported in boykopovar#3291 on an RTX 4060 Laptop GPU
(Windows, driver 591.86) and an RTX 5060 (Linux, driver 590.48.01). An
RTX 4070 Laptop GPU on driver 616.92 does not hang with either shape.
The driver-side cause is unknown, so the shape is recorded in
docs/dev/debt/shader.md.

On main the hang fails nine tests on the RTX 3060, in two ways.
agc_driver_{flat,global}_loads{,_x2_x3,_x4} and
agc_driver_flat_apertures_{2,5} abort after 64 to 71 s: the lost batch
keeps its lease, so teardown waits 60 s for the pinned guest allocation.
agc_driver_pixel_interlock fails after about 4 to 7 s with
vkDeviceWaitIdle returning VK_ERROR_DEVICE_LOST (-4) and never reaches
the pin wait. All nine pass with the change.

Fixes boykopovar#3291

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.

Pull request Conflicting files (lines on main) Same exports Same new files
#3655 core/shader/recompiler/SpirvBackend/src/SpirvBdaRead.cpp L182-199, L201-203
#3891 docs/dev/debt/shader.md L102
#4156 docs/dev/debt/shader.md L102
#4396 docs/dev/debt/shader.md L102
#4397 docs/dev/debt/shader.md L102
#4422 docs/dev/debt/shader.md L102
#4423 docs/dev/debt/shader.md L102
#4424 docs/dev/debt/shader.md L102
#4520 docs/dev/debt/shader.md L102
#4521 docs/dev/debt/shader.md L102
#4522 docs/dev/debt/shader.md L102
#4523 docs/dev/debt/shader.md L102
#4566 docs/dev/debt/shader.md L102
#4599 docs/dev/debt/shader.md L102
#4718 docs/dev/debt/shader.md L102
#4740 docs/dev/debt/shader.md L102
#4749 docs/dev/debt/shader.md L102
#4760 docs/dev/debt/shader.md L102
#4806 docs/dev/debt/shader.md L102
#4813 docs/dev/debt/shader.md L102

@johnohhh1

Copy link
Copy Markdown

Tested this on an RTX 5070, Linux, NVIDIA 595.99.02. All 23 affected tests pass on both the baseline and with this PR applied, with zero skips or timeouts. The original hang does not reproduce on this GPU/driver combination, and I observed no regression from the patch in these tests.

  • Ubuntu 26.04.1 LTS, kernel 7.0.0-38-generic, GCC 15.2.0, Release, ANYPS5_ENABLE_SPIRV_TOOLS=ON.
  • Baseline: 43e3a70f8143f014f621c48abbdb137fffd7346e.
  • Patched: the same base plus f1b2e605e7597603f716abb3cc2924db5d5db8b3, applied without conflicts. The local patch ID matches the PR.
  • Tests: agc_driver_(flat|global)_loads*, agc_driver_pixel_interlock, agc_driver_flat_apertures_0–_13, agc_driver_bda_device, and agc_driver_bda_span_reads.
  • Serial execution, 30-second timeout, ANYPS5_NO_SHADER_CACHE=1, ANYPS5_REQUIRE_VULKAN=1, explicit RTX 5070 selection and NVIDIA Vulkan ICD. Logs confirm the selected GPU.

Each version completed one 23-test run. The slowest test was agc_driver_bda_device at about 8.1 seconds in both. NVIDIA's driver cache remained at its default; no performance claim. No game title or full graphics-suite testing.

This adds RTX 5070 / 595.99.02 coverage to the no-reproduction side of #3291. It does not independently confirm the fix for the original hang. Build settings, hashes, JUnit results and verbose logs were retained for both runs.

AI-assisted validation and report: Codex. Tested the existing PR patch.

@kr4y4

kr4y4 commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

On an RTX 4070 Laptop GPU with NVIDIA 595.97, this change causes the hang it fixes on 591.86. agc_driver_bda_span_reads and agc_driver_pixel_interlock lose the device on main ea2bbef, and both pass with SpirvBdaRead.cpp as it was before this PR.

Setup: Windows 11 (26200), WinLibs GCC 15.2.0 r7, Release, CI configure flags, an RTX 4070 Laptop GPU (NVIDIA 595.97) and an Intel Raptor Lake-S iGPU (32.0.101.6790). I ran the 23 affected tests from the description with ANYPS5_NO_SHADER_CACHE=1, --repeat until-fail:3 and --parallel 4:

NVIDIA 595.97 Intel
main ea2bbef agc_driver_bda_span_reads and agc_driver_pixel_interlock fail 23 of 23
main ea2bbef with SpirvBdaRead.cpp from f1b2e60^ 23 of 23, slowest 2.8 s 23 of 23
  • The failing runs print [gpu] recorded batch still running on the GPU after 5 s, then the same at 10, 15 and 20 s. After that they fail with vkWaitForFences recorder: Vulkan result -4 (span reads) and vkDeviceWaitIdle: Vulkan result -4 (pixel interlock). Windows logged an nvlddmkm event 153 at each failure, and the display reset. agc_driver_bda_device also failed in some of these runs, while the device was lost.
  • The same happened before the merge, with this branch merged into main 495bf91, in a full-suite run and in a rerun of the three tests. main 495bf91 itself passed all 1023 on both GPUs.
  • Each full-suite run that lost the device also had one guest_video_out_* test time out. On the Intel iGPU, the full suite on ea2bbef passes 1038 of 1038.

So on 595.97 the split selections hang and the single selection doesn't, the reverse of 591.86. The description reports no regression with 616.92 on the same GPU model, so this depends on the driver version. I can run a candidate shape on 595.97 if that helps.

Written with AI assistance.

@ps54all

ps54all commented Oct 13, 2026

Copy link
Copy Markdown

Same result on an RTX 3080 (Ampere desktop, 0x2206), NVIDIA 610.74, Windows 11, WinLibs GCC 15.2.0 r7, Release. I bisected it in a clean main worktree, rebuilding only the two test targets between runs:

Tree agc_driver_bda_span_reads agc_driver_pixel_interlock
e51428bf2^1 (before this PR) pass pass
e51428bf2 (this PR merged) fail fail (vkDeviceWaitIdle: Vulkan result -4)
ea2bbefb2 (current main) fail fail

The failures are deterministic, both with and without APS5_NO_PIPELINE_LTO=1. agc_driver_depth_fast_clear and agc_driver_pixel_valid_mask pass in the same session. With this PR reverted on my integration branch (current main plus a few open PRs), all 26 bda/flat_/global_loads/pixel_interlock tests pass.

Amoenus's comment in #3291 shows the same two tests failing at e51428bf2 on an RTX 5080 with 610.62. Results by driver so far:

  • needs the split: 591.86 (Windows and Linux reports)
  • hangs with the split: 595.97 Windows (kr4y4), 610.62 and 610.74 Windows
  • passes either way: 595.99.02 Linux (johnohhh1), 616.92 (description)

I can run a candidate shape on 610.74.

AI-assisted (Claude Code); I ran the above on my machine.

oneandonlydean added a commit that referenced this pull request Oct 13, 2026
read_bda_span chooses between the span's in-place dword loads and its
per-dword byte fallback, which calls read_bda_dword_bytes. Each shape of
that choice so far hangs a group of NVIDIA drivers: a dispatch never
finishes and the device is lost.

As the two arms of one selection, with the loads emitted in the arm,
it hung drivers 591.86 and 590.48.01 when lanes took both arms (#3291).
As two selections, the loads under the span's condition and the fallback
under its negation (#4759), it hangs 595.97, 596.36, 610.62, 610.74 and
610.57.04 (#4975).

The loads now live in load_bda_span, a DontInline function that calls
nothing, and read_bda_span is one selection again: one arm calls
load_bda_span, the other is the fallback as before. Probe, loads,
lookups, faults and stops are unchanged for every lane.

On an RTX 4090 (Linux, driver 610.57.04) the two-selection shape hangs
agc_driver_scalar_wide_loads and agc_driver_flat_apertures_0..13 in a
build with SPIRV-Tools, and agc_driver_bda_span_reads and
agc_driver_pixel_interlock in a build without. All pass with this
change in both builds. Drivers 591.86, 590.48.01, 595.97, 596.36,
610.62 and 610.74 are not tested; a shape with the loads in their own
DontInline function was reported passing on 591.86 in #3291.

agc_shader_memory now requires that a span read function holds no load
and calls its load function once, and that the load function holds the
four loads and no call.

Refs #4975
RINNECODER pushed a commit to RINNECODER/AnyPS5 that referenced this pull request Oct 13, 2026
read_bda_span chooses between the span's in-place dword loads and its
per-dword byte fallback, which calls read_bda_dword_bytes. Each shape of
that choice so far hangs a group of NVIDIA drivers: a dispatch never
finishes and the device is lost.

As the two arms of one selection, with the loads emitted in the arm,
it hung drivers 591.86 and 590.48.01 when lanes took both arms (boykopovar#3291).
As two selections, the loads under the span's condition and the fallback
under its negation (boykopovar#4759), it hangs 595.97, 596.36, 610.62, 610.74 and
610.57.04 (boykopovar#4975).

The loads now live in load_bda_span, a DontInline function that calls
nothing, and read_bda_span is one selection again: one arm calls
load_bda_span, the other is the fallback as before. Probe, loads,
lookups, faults and stops are unchanged for every lane.

On an RTX 4090 (Linux, driver 610.57.04) the two-selection shape hangs
agc_driver_scalar_wide_loads and agc_driver_flat_apertures_0..13 in a
build with SPIRV-Tools, and agc_driver_bda_span_reads and
agc_driver_pixel_interlock in a build without. All pass with this
change in both builds. Drivers 591.86, 590.48.01, 595.97, 596.36,
610.62 and 610.74 are not tested; a shape with the loads in their own
DontInline function was reported passing on 591.86 in boykopovar#3291.

agc_shader_memory now requires that a span read function holds no load
and calls its load function once, and that the load function holds the
four loads and no call.

Refs boykopovar#4975

(cherry picked from commit 8c04efc)

Fork: libSceAgc* and docs/dev/debt hunks dropped (AGC slice / file absent).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RINNECODER added a commit to RINNECODER/AnyPS5 that referenced this pull request Oct 13, 2026
* perf(shader): search a bindless image table once per access

EmitImage selects the table slot of a bindless image before it branches over
the image's runtime modes, and EmitImageMode then searched the table again in
every mode arm. Each search is a binary search of dependent flattened_srt
loads, so every bindless sample ran two. The mode arms now take the selection
EmitImage already made; the modes copy the base image, so the mapping offset,
root and iteration count the search reads are the same.

(cherry picked from commit d759a43)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(agc,shader): prime the two host shared mutexes added after 48dcaac

Commit 48dcaac introduced PrimedSharedMutex and converted every host std::shared_mutex that existed at the time, because winpthreads initializes a static rwlock on first use and a concurrent first lock returns EINVAL, which std::shared_mutex ignores (docs/dev/TechnicalDebt.md:10).

Two host mutexes were added afterward and missed the conversion:
- Recompiler.cpp registrationModule() static mutex (added by baf3437)
- MultisampleTarget.cpp guardedMutex() static mutex (added by d109f39)

Apply the same priming. Recompiler.cpp reuses the PrimedSharedMutex already defined in that translation unit; MultisampleTarget.cpp gets the identical local helper to match the existing inline precedent.

Tested: Windows 11 x64, WinLibs GCC 15.2.0 (matches CI). Compiled Recompiler.cpp.obj and all three MultisampleTarget.cpp.obj targets with the project build flags; check_conventions.py passes.

(cherry picked from commit 4d37c93)

Fork: MultisampleTarget.cpp hunk left to the AGC slice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(shader): drop the input flush that rcp, rsq, sqrt, exp and log results never use

The SPIR-V emitters of FPRecip32, FPRecipSqrt32, FPSqrt, FPExp2 and FPLog2
flushed a denormal input to its signed zero before the host operation.
Since #537, unaryFloatSpecials replaces the host result for every input
below 2^-126 with the hardware's result for a zero of the same sign: rcp
and rsq give a signed infinity, sqrt the signed zero, log -inf and exp 1.0.
floatUnary and float16Unary both go through it, so the flushed input could
only change a value that is then discarded. v_rcp_iflag_f32 gets the same
special values and already went without the flush.

The pixel prologue computes PositionW as FPRecip32 of FragCoord.w without
those special values, so it now flushes FragCoord.w in the IR first and
gives the same result as before.

(cherry picked from commit 1dc99ba)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(shader): split a BDA span read's in-place loads from its fallback

On an RTX 3060 (Windows, driver 591.86) a dispatch never finishes when
the lanes of read_bda_span split between its in-place dword loads and
its per-dword byte fallback, which calls read_bda_dword_bytes: Windows
resets the GPU and the device is lost. The two were the arms of one
selection in the DontInline function. They are now two selections, the
loads under the span's condition and the fallback under its negation,
joined after both. Loads, lookups, faults and stops are unchanged for
every lane; only the SPIR-V control-flow shape changes (SPIR-V
specification 2.11, structured control flow). Every stage except mesh
shaders is emitted as SPIR-V 1.3, and OpSelect chooses between
composites with a scalar condition only from 1.4 on, so the join stays
a selection.

The same hang and fix were reported in boykopovar#3291 on an RTX 4060 Laptop GPU
(Windows, driver 591.86) and an RTX 5060 (Linux, driver 590.48.01). An
RTX 4070 Laptop GPU on driver 616.92 does not hang with either shape.
The driver-side cause is unknown, so the shape is recorded in
docs/dev/debt/shader.md.

On main the hang fails nine tests on the RTX 3060, in two ways.
agc_driver_{flat,global}_loads{,_x2_x3,_x4} and
agc_driver_flat_apertures_{2,5} abort after 64 to 71 s: the lost batch
keeps its lease, so teardown waits 60 s for the pinned guest allocation.
agc_driver_pixel_interlock fails after about 4 to 7 s with
vkDeviceWaitIdle returning VK_ERROR_DEVICE_LOST (-4) and never reaches
the pin wait. All nine pass with the change.

Fixes boykopovar#3291

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

(cherry picked from commit f1b2e60)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(shader): load a BDA span in place from its own function

read_bda_span chooses between the span's in-place dword loads and its
per-dword byte fallback, which calls read_bda_dword_bytes. Each shape of
that choice so far hangs a group of NVIDIA drivers: a dispatch never
finishes and the device is lost.

As the two arms of one selection, with the loads emitted in the arm,
it hung drivers 591.86 and 590.48.01 when lanes took both arms (boykopovar#3291).
As two selections, the loads under the span's condition and the fallback
under its negation (boykopovar#4759), it hangs 595.97, 596.36, 610.62, 610.74 and
610.57.04 (boykopovar#4975).

The loads now live in load_bda_span, a DontInline function that calls
nothing, and read_bda_span is one selection again: one arm calls
load_bda_span, the other is the fallback as before. Probe, loads,
lookups, faults and stops are unchanged for every lane.

On an RTX 4090 (Linux, driver 610.57.04) the two-selection shape hangs
agc_driver_scalar_wide_loads and agc_driver_flat_apertures_0..13 in a
build with SPIRV-Tools, and agc_driver_bda_span_reads and
agc_driver_pixel_interlock in a build without. All pass with this
change in both builds. Drivers 591.86, 590.48.01, 595.97, 596.36,
610.62 and 610.74 are not tested; a shape with the loads in their own
DontInline function was reported passing on 591.86 in boykopovar#3291.

agc_shader_memory now requires that a span read function holds no load
and calls its load function once, and that the load function holds the
four loads and no call.

Refs boykopovar#4975

(cherry picked from commit 8c04efc)

Fork: libSceAgc* and docs/dev/debt hunks dropped (AGC slice / file absent).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(shader): convert f16 operands with UnpackHalf2x16 where f16 denormals are kept

Since boykopovar#1463, every f16 operand read decodes sign, exponent and mantissa
with integer operations, 8 to 12 instructions more per read than
UnpackHalf2x16 on NVIDIA. The decode works around an AMD Windows driver
that folds the mask of a byte-selected half into UnpackHalf2x16.

A new target flag, float16DenormPreserve, is set when the device reports
shaderDenormPreserveFloat16 and denormBehaviorIndependence ALL, the same
rule as float64DenormPreserve. With it, EmitF16BitsToF32 uses
UnpackHalf2x16 and declares DenormPreserve for 16-bit floats, under
which SPV_KHR_float_controls keeps unpacked subnormal halves. NVIDIA
returns the default NaN for every NaN half, so the NaN bits are rebuilt
from the half: its sign at bit 31, its payload at bits 22:13. The
DenormPreserve 16 mode is declared once, shared with the native f32 to
f16 conversion. Without the flag (lavapipe; RADV reports 32_BIT_ONLY)
the integer decode stays. Halves of fields narrower than 16 bits, such
as SDWA byte selects, keep the integer decode on every device.

The flag is serialized (request version 17) and part of the cache key.

New test agc_driver_f16_to_f32_unpack converts all 65,536 halves, with
nonzero high bits, through v_cvt_f32_f16, and byte-selected halves
through v_add_f16 SDWA, with each decode the device supports, and
compares the bits with an exact decode. agc_driver_f16_to_f32_unpack_spirv
checks which decode the SPIR-V uses with and without the flag.

(cherry picked from commit e4f212b)

Fork: request version 21 kept from upstream, on top of version 20 (paClVsOutCntl) from the AGC slice. The Vulkan device half (VulkanDevice.cpp) and the libSceAgcDriver tests are dropped; on Metal float16DenormPreserve stays false, so the integer f16 decode is kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(shader): skip the f16 NaN rebuild for operands that are only compared

With float16DenormPreserve, EmitF16BitsToF32 rebuilds the bits of a NaN
half after UnpackHalf2x16, because NVIDIA returns the default NaN and
Intel quiets it. When every use of the converted operand is an f32
comparison or a NaN test, directly or through abs and neg, only whether
it is NaN matters, so the conversion returns UnpackHalf2x16's result as
is. v_cmp_*_f16 goes from 11.5 to 4 SASS on an RTX 4060.

agc_driver_f16_to_f32_unpack adds v_cmp_u_f16 and v_cmp_lt_f16 over all
65,536 halves with each decode the device supports. Its SPIR-V check
requires the v_cvt_f32_f16 conversion to rebuild the NaN bits and the
compared conversions not to.

(cherry picked from commit 352bf30)

Fork: libSceAgc* and docs/dev/debt hunks dropped (AGC slice / file absent).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(shader): keep cube image gradients out of the face coordinate bias

image_sample_d and image_sample_cd on a cube pass their gradients through the
same conversion as the face coordinates, which subtracts 1.0 to move AMD's
[1, 2] face range to [0, 1]. A gradient is a difference of coordinates, so the
bias turned a one-texel gradient into one of about -1 and the sample read the
smallest mip. Gradients now skip the bias.

agc_driver_image_sample_cube_derivatives samples every face of a 16x16 cube at
five levels through image_sample_d and checks the level and face it read.

TechnicalDebt records that the gradient handling follows Mesa and was not
measured on hardware.

(cherry picked from commit 73eaa1f)

Fork: libSceAgc* and docs/dev/debt hunks dropped (AGC slice / file absent).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(shader): declare the GDS buffer coherent

GDS is one memory for the whole GPU on AMD hardware: every work-group's
ds_read and ds_write with gds see the others' writes, with no cache in
between. The recompiler keeps it in a storage buffer that was not decorated
Coherent, so on a GPU whose L1 caches storage buffer loads, NVIDIA's from
Turing on among them, a plain GDS read could return a value cached before
another work-group's GDS write. Atomics were not affected. The gds buffer is
now declared Coherent.

verifyCoherentGds in agc_shader_memory compiles a ds_read_b32 and a
ds_write_b32 on GDS and checks that the gds buffer is Coherent.

(cherry picked from commit 0998eb4)

Fork: libSceAgc* and docs/dev/debt hunks dropped (AGC slice / file absent).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(shader): reclaim unreachable cached source IR values

(cherry picked from commit 57b2dc8)

Fork: libSceAgc* and docs/dev/debt hunks dropped (AGC slice / file absent).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(shader): read the ancillary render target index from Layer when the draw exports it

(cherry picked from commit 7015c04)

Fork: the DrawDecode.cpp line that sets layerExported is in libSceAgcDriver and left to the AGC slice; until it lands layerExported stays false and the ancillary render target index reads as 0, as before. Fork ShaderDiskCache format kept (bumped once at the end of the slice); libSceAgcDriver tests and docs/dev/debt dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(shader): serialize the pixel layer export flag from request version 23

main allocates request version 22 to the tessellation patches per group, so a version 22 request has no layer export byte.

(cherry picked from commit de5279b)

Fork: request version 23 kept to match upstream; version 22 (patchesPerGroup, from AGC commit 25a8372) is not in this tree yet, so the fork writes no version 22 field. libSceAgcDriver test hunk dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(shader): bump the shader disk cache format for the sync-8 shader slice

The ported recompiler changes add layerExported to the cached pixel info
and change generated shader output, so cached entries from format 31 are
not reused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Karan Vora <52519664+karanvora2599@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Mohamed Aziz Hadjkacem <mohamedaziz.hadjkacem21@gmail.com>
Co-authored-by: fdfawef324 <287205748+fdfawef324@users.noreply.github.com>
Co-authored-by: Dean Galvin <dean.galvin@live.com>
Co-authored-by: eljackjoestar-cmd <eljackjoestar@gmail.com>
Co-authored-by: TideOPL <obinewman101@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs shader core/shader

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Recompiler: NVIDIA hangs (device lost) when read_bda_span lanes split between in-place loads and the fallback; branch and commit ready

5 participants