fix(shaders): noperspective varyings interpolated right on AMD (Royal Family Tomb water); sirit names an undefined operand #1830
No reviewers
Labels
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
question
testing wanted
wontfix
No milestone
No assignees
2 participants
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Hookshot!1830
Loading…
Reference in a new issue
No description provided.
Delete branch "super-build/shaders"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
On AMD, the water in the Royal Family Tomb shows a red dotted line. Separately, when sirit meets an undefined operand it aborts the whole game without logging why. This PR fixes both. It's part of the "super build" series, but it builds and passes the tests on its own, on current main.
super-build/shaders(#1830)super-build/upscaling-framegen(#1831)super-build/neural(#1833)super-build/core(#1834)What changed since the first review
The branch is rebuilt on main at
d7a73062. Main now has its own versions of most of what this PR used to carry, so those parts are dropped:MEMBAR.GLfences (#1778).lower_to_maxwell.hand its tests are now exactly main's. Also dropped:on_big_stackand theRX_VK_TRACE_KEYStracing, with its process-wideset_terminate.vk_texture.cppchanges (memory_guest_contained, the unswizzle threshold,texture_touch_misses). They moved to #1834, which describes them.test_shader_stamp_cache.pychange, which also moved to #1834.What it does
vk_shader.cpp; Eden's SPIR-V back end:runtime_info.h,spirv_emit_context.*,emit_spirv_special.cpp,emit_spirv_context_get_set.cpp).noperspectivevarying. When a vertex with w close to zero is clipped, AMD interpolates the wrong depth, and itsexp2overflows into a red dotted line across the water.EmitVertex.noperspectiveinput, on every vendor. The result is the same value, interpolated a different way.Id) now throws instead ofstd::abort().EmitSPIRV's catch names the IR op and its arguments, and the program is reported as not translated instead of ending the game with nothing in the log.RX_VK_LOWER_SKIP=<key prefix,...>: leaves those programs unlowered, for bisecting. It's a diagnostic and does nothing unless set.Testing
nx2/build.py's renderer flags.tests/run_all.pyon this branch, locally (clang targeting MSVC, the CPM Vulkan headers, glslang 11.12), gives 324 passed. The 18 failures are the same 18 main atd7a73062has on this machine, all down to this PC's toolchain, so there are no new failures.🤖 Generated with Claude Code
Thanks for splitting the super build up. Several of these fixes are real, but this branch doesn't stand on its own yet, and about half of the lowering changes overlap with open or merged PRs in the same file.
Blocking
upload_unswizzle_gpucallsmemory_guest_contained(), but nothing on this branch declares or defines it. It's declared invk_internal.honsuper-build/all, which isn't part of this PR (see inline).run_all.pypicks up everytests/test_*.py. These are probably the "tests that need a compiler" that didn't run on your machine:test_lower_suld_1d.pyexpects a 1D coordinate in R254 to lower, and 1D_BUFFER / 1D_ARRAY to be refused. Both checks now go the other way.test_lower_lea_hi_constant.pyexpects a high source (Rc not RZ) and a zero shift to be refused. The new LEA.HI block accepts both.test_shader_stamp_cache.pynow asserts thatnx2/build.pyhasstreamline_sdk_pathandopennr_jobs. Neither is on this branch; they come from #1833/#1834.Overlap with other PRs (please rebase and drop the duplicates):
9edb3a5) with the sameFieldSpanline, so drop this copy. Main has it after theextended_renamingcheck, and yours comes before it, so git may keep both.!is_register[f]skip here is the broad version. It drops the bit-32 registers of TLD4 0xb63/0xf63, TMML 0xb69/0xf69, TEX 0xf60, SHFL.IDX 0x589 and STL 0x987, none of which is in theregister_fieldlist. Their Rb then stops getting its REGMAP prefix whenever another field is high. That's a silent miscompile of programs that lower fine today. Please keep #1800's version and drop these hunks.MEMBAR.GLwhen bit 78 is set. Bits 77-79 all set is.STRONG.GPU, as on the RED/ATOMG cases in this file. Here the qualifier is just dropped. The fences need to stay, so please take #1778's (approved) version. The LDG mask here also lets bit 78 through without bit 77.finishre-checks the relocations. Only one of them should land, though. Two small differences from the EXIT path: it doesn't checkbarrier_tokens, and it allows a call target ≤ index beforemain_exited, which the EXIT path refuses. It also has no test.Lowering details
IsbeReadOutputignores.32(bit 44), so a U8 read would return the whole word.PayloadWordalready clamps, so the extraClampedin the task branch is redundant.Not in the description (worth their own PR, or at least a mention):
RuntimeInfo::noperspective_outputs, the vertex outputs scaled by Position.w, fragment inputs multiplied by FragCoord.w). The math checks out, but it changes every VS+FS pipeline with a noperspective input on every vendor, not only AMD.upload_unswizzle_gpuno longer imports a pool by default, and the multithreaded CPU unswizzle threshold drops from 16 MB to 4 MB. Both are behaviour and performance changes.texture_touch_misses()has no caller on this branch.On "70 programs": per the description, that number comes from the in-game
programs (0 failed)log on the combinedsuper-build/allbranch, from one Kokiri Forest run on NVIDIA. It wasn't measured on this branch against current main. Main already lowers the 40 LEA.HI and 17 SULD.1D programs, and the R2UR one is merged too. It would help to say which programs still fail on main and which hunk fixes each one.Nits:
on_big_stackis described as a precaution that turned out not to be needed, and it's Windows-only. Its trace path callsstd::set_terminatefrom a worker thread, which replaces the handler for the whole process. TheLOWER_DEBUGblock and theRX_VK_LOWER_SKIP/RX_VK_TRACE_KEYShooks are fine as diagnostics.Duplicate of #1806, which is now merged. Main has this line after the
extended_renamingcheck, so please drop this one when you rebase.This skips every bit-32 field that isn't in the
register_fieldlist, and that list misses opcodes whose lowering reads a register at 32: TLD4 0xb63/0xf63, TMML 0xb69/0xf69, TEX 0xf60, SHFL.IDX 0x589 and STL 0x987. Before, those still got marked whenever another field setany_high. Now their Rb loses the REGMAP prefix. #1800 (approved) skips only bit 32 of form 4 of the ALU families, the actual immediate. Please keep that version and drop this one.1D and 1D_BUFFER read one coordinate register, so R254 should stay allowed. Main allows it and
test_lower_suld_1d.pychecks for it. 1D_ARRAY reads two (x, layer), so its limit is 253. Accepting 1D_BUFFER and 1D_ARRAY also flips that test's refusal checks, and OoT doesn't seem to use either. Maybe leave them refused until a program needs them.Bits 77-79 all set is
.STRONG.GPU(as on RED/ATOMG here), so dropping it loses the ordering. #1778 covers the same mask and fences the access withMEMBAR.GLwhen bit 78 is set, so I'd keep that version. This mask also lets 78 through without 77.Main's mask also required bits 84-86 and 91-95 to be zero. This check doesn't test them, so an unknown qualifier there now passes silently. Could you keep the old mask, widened only by bit 74 (.X) and Rc? Also,
test_lower_lea_hi_constant.pyexpects Rc≠RZ and a zero shift to be refused, so it fails now and needs updating.#1810 changes this same spot with a narrower rule (the previous instruction must be an unconditional BRA/EXIT). I think yours is sound, but only one should land. Small gaps compared with the EXIT path: no
barrier_tokenscheck, and call targets ≤ index are allowed beforemain_exited. A test would help.#1800 reads this field too, and cross-checks it against +0x50 (== 32 × +0x68) with a 64 KB cap and a log line. A 1 MB cap with no check is much looser, so please take #1800's version.
This noperspective rewrite isn't mentioned in the description. It affects every VS+FS pipeline with a noperspective input on every vendor. Worth its own PR, or at least a line in the description.
memory_guest_containedisn't declared or defined anywhere on this branch (it's invk_internal.h/vk_memory.cpponsuper-build/all), sovk_texture.cppwon't compile on its own.nx2/build.pyon this branch has neitherstreamline_sdk_pathnoropennr_jobs, so this assert fails. This change belongs with #1833/#1834.Thanks for the thorough review; you were right on all counts. I rebuilt the branch on main at
d7a73062, and it now builds and tests on its own. The description is rewritten.Blocking:
memory_guest_contained: thevk_texture.cppchanges moved to #1834, together with the function and its description. Nothing on this branch references them now.test_lower_suld_1d.pyandtest_lower_lea_hi_constant.py:lower_to_maxwell.his now exactly main's, so both pass unchanged.test_shader_stamp_cache.py: the change moved to #1834, wherestreamline_sdk_pathandopennr_jobslive.tests/run_all.pyon this branch locally with clang targeting MSVC, the CPM Vulkan headers and glslang 11.12. It gives 324 passed; the 18 failures are the same 18 main has on this machine, all down to the toolchain, so there are no new ones.Overlap: since your review, #1806, #1800, #1778 and #1810 all merged, and main also has SULD.1D, LEA.HI with a constant base and ISBERD.O. I dropped all of ours in their favour, including the broad
!is_register[f]skip, the 1 MB local-memory cap, the unfenced LDG/STG and our closing self-branch.Not in the description before:
texture_touch_missesmoved with its caller."70 programs": no longer claimed. Main now lowers everything those hunks did.
Nits:
on_big_stackandRX_VK_TRACE_KEYSare gone, and with them the worker thread'sstd::set_terminate.RX_VK_LOWER_SKIPstays as a diagnostic.IsbeReadOutput's.32and the redundantClampedwent with our ISBERD.O, since main's is used now.The rebase onto
d7a73062handled the earlier review, but main has moved again and vk_shader.cpp conflicts in translate_into. When you resolve it, note that #2042 removed oot_visual(), so the line you moved up with the fragment translation won't compile any more. Main's block now just translates and fills frt. The math is right: perspective interpolation of v*w, times FragCoord.w (1/w), gives back the screen-linear value. But it rewrites every VS+FS pipeline with a noperspective input on every vendor, and it hasn't run on AMD, the GPU it's for. Could you get one AMD run of the Royal Family Tomb with before/after screenshots, plus a quick NVIDIA check that nothing else with noperspective varyings moved? RX_VK_LOWER_SKIP isn't in translator_stamp, so programs translated while it was set stay unlowered in the cache after it's unset. That's fine for a diagnostic, but say so in the comment. The "(super build)" tags in comments can go.This now conflicts with main in
gpu/vk/vk_shader.cpp. Could you merge main into the branch (or rebase) when you get a chance? I'll take another look once it's up to date.Steam Deck (RADV, VanGogh): the red particles and the red water both clear
Independent hardware confirmation that this fix addresses the red artifacts reported in #1634 / #1000, not just the Royal Family Tomb water.
Setup. Hookshot
v2.1.0-rc.3-52-gebbba2e(upstreamebba2e), Steam Deck LCD, RADV on VanGogh, FSR 3.1 from the Handheld picture (1536x864) at 50% render resolution.Patch. This PR's noperspective work only: the six hunks in
runtime_info.h,spirv_emit_context.{h,cpp},emit_spirv_context_get_set.cpp,emit_spirv_special.cppandvk_shader.cpp. TheRX_VK_LOWER_SKIPbisection and the sirit /emit_spirverror-reporting changes were left out, so this is the interpolation change alone. It applies toebba2ewith offsets only and builds with the release toolchain (clang 23.1.5, renderer-only rebuild, 50 s, 0 failed).Before. Blinking red particle boxes over most of the screen, everywhere in Kokiri Village, and red-tinted sun highlights on the water — the same red as the particles. Reproduced consistently, and more obvious at 50% render resolution because upscaling magnifies the wrong pixels (a 1440p frame downscaled to the 800p panel averaged them away).
After. Same build, same save, same settings, only the renderer swapped (original
7b32684f…, patched1f37986a…): the red particles are gone and the water's sun highlights render in their normal colour. Reported by the person playing, confirmed twice.Mechanism, for the record. Main already emits
NoPerspective; what it lacks is this PR's clipped-vertex workaround, so a varying whose vertex clips nearw = 0still interpolates wrongly on AMD and the consumingexp2saturates to red. On an RTX 3090 the same build is clean, which is why this looked Deck-specific.Not tested here: geometry-shader and mesh-program paths, and any effect that reads indexed attributes (the PR leaves those alone by design). No game files, saves or logs attached.
Heads-up first: I merged main into the branch (
d85bd1be) to clear thevk_shader.cppconflict, so please pull before you push again. The conflict came from #2042 removingoot_visual()(Joey-Magnus pointed it out). The merge keeps your fragment translation moved ahead of the vertex stage and drops theoot_visualcall. On my side the shader tests pass with it and the Vulkan back end builds.Everything from my last review is handled. The rebuilt branch no longer touches
lower_to_maxwell.h,vk_texture.cppor the tests, so the build break, the three failing tests and the overlaps with #1806/#1800/#1778/#1810 are all gone, and so areon_big_stackand theset_terminate. The noperspective rewrite is now the PR's subject, and the description says what it changes on every vendor.I went through the noperspective path again:
ConvertDepthMode, which only touches z.frt) are left alone. The GSEmitVertexhooks are a no-op for now, because only the VS+FS branch setsnoperspective_outputs.HOOKSHOT_SHADER_STAMPhasheseden/,sirit/andvk_shader.cpp, so the new SPIR-V gets a new stamp by itself.Left, none of it blocking:
RX_VK_LOWER_SKIPstill stands: it isn't intranslator_stamp, so a bisection only works on programs that aren't cached yet. The easiest fix is to add"RX_VK_LOWER_SKIP"to the prefix list.bake_ds.cppneeds the same entry, becausetest_program_stamp_knobs.pychecks that the two lists match. Otherwise, say it in the comment.nx2/bake_ds/bake_ds.cppcopies this VS+FS path but doesn't setnoperspective_outputs/scaled_noperspective_inputs, so an offline-baked bank would still have the old interpolation. It's self-consistent and not broken, just without the fix. It already leaves out other live-only bits, so a follow-up is fine.(super build)tags are still inemit_spirv.cpp,stream.handvk_shader.cpp. A before/after screenshot pair in the description would be good, since this shows on screen (CONTRIBUTING.md). omgitsgela's would do if they're OK with that.CI: only the title check has run on this branch, and the build and test workflows haven't. That's why GitHub shows it as unstable.
Nit:
loadsis alreadyinfo.loads.mask | info.passthrough.mask(top ofDefineInputs), so theinfo.passthrough.AnyComponent(PositionX)term repeatsloads.AnyComponent(PositionX).|| noperspective_as_smoothis the only part that does something.github-sync referenced this pull request2026-11-12 06:22:54 +00:00
Thanks, both of you, for merging main in and for the review.
@omgitsgela: thank you for the Steam Deck run. Good to know it clears the red particles from #1634/#1000 too, not just the tomb water.
The non-blocking notes are in #2782:
bake_ds.cppnow bakes the noperspective workaround as live translation does.RX_VK_LOWER_SKIPis intranslator_stampandbake_ds.cpp's list.(super build)tags are gone.github-sync referenced this pull request2026-11-12 13:38:29 +00:00
github-sync referenced this pull request2026-11-12 13:39:06 +00:00