From e18d4292a287116f3c7d70309feb42ed24682b4a Mon Sep 17 00:00:00 2001 From: Michon <231849@buas.nl> Date: Fri, 2 Oct 2026 19:52:08 +0200 Subject: [PATCH 1/3] fix(shader): decode image_sample_cd as a derivative sample image_sample_cd takes its derivatives in the image_sample_d address layout; only the hardware LOD selection differs (one LOD per quad instead of per pixel). The decoder refused it. It now samples with the given gradients per lane, which matches whenever the lanes of a quad pass equal derivatives. Co-Authored-By: Claude Opus 5.5 (1M context) --- core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp index a019dd480..54ff7e8e2 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp @@ -127,9 +127,6 @@ void validateFlags(std::uint32_t flags) { if ((flags & ~known) != 0u) { throw std::runtime_error("unknown image address flags"); } - if ((flags & RdnaImageSampleFlagCd) != 0u) { - throw std::runtime_error("unsupported image coarse derivative (_cd) address layout"); - } const auto lodModes = flags & (RdnaImageSampleFlagLod | RdnaImageSampleFlagBias | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLevelZero); if (std::popcount(lodModes) > 1) { throw std::runtime_error("conflicting image LOD modes"); From 6138494928d874c2b26993b078301b855696ca1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sat, 3 Oct 2026 20:03:55 +0200 Subject: [PATCH 2/3] feat(shader): implement image_sample_cd/_g16 variants and fix a16 gradient layout Co-Authored-By: Claude --- .../execution/ImageSampleDerivatives.cpp | 323 ++++++++++++++++++ .../include/RdnaDecoder/RdnaInstruction.hpp | 4 + .../include/RdnaDecoder/RdnaOpcode.hpp | 24 ++ .../RdnaDecoder/src/RdnaImageOpDecoder.cpp | 77 ++++- .../recompiler/RdnaDecoder/src/RdnaOpcode.cpp | 24 ++ .../MemoryInstructionDispatch.cpp | 24 ++ .../MemoryResourceHelpers.cpp | 17 + 7 files changed, 476 insertions(+), 17 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleDerivatives.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleDerivatives.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleDerivatives.cpp new file mode 100644 index 000000000..5f2d50d3a --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleDerivatives.cpp @@ -0,0 +1,323 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 12; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Width = 64; +constexpr std::uint32_t Height = 4; +constexpr std::uint32_t Depth = 2; +constexpr std::uint32_t Format32Float = 22; + +alignas(256) constexpr std::array Input{ + 0x3ce00000, 0x3e000000, 0x3e800000, 0x00000000, 0x3f800000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3d300000, 0x3ec00000, 0x3e800000, 0x00000000, + 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x00000000, + 0x3d700000, 0x3f200000, 0x3e800000, 0x00000000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, + 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x3d980000, 0x3f600000, 0x3e800000, 0x00000000, + 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x00000000, + 0x3db80000, 0x3e000000, 0x3f400000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x3a800000, 0x3a800000, 0x00000000, 0x3dd80000, 0x3ec00000, 0x3f400000, 0x00000000, + 0x3f800000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x3df80000, 0x3f200000, 0x3f400000, 0x00000000, 0x00000000, 0x3a800000, 0x3a800000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3e0c0000, 0x3f600000, 0x3f400000, 0x00000000, + 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, + 0x3e1c0000, 0x3e000000, 0x3e800000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, + 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x3e2c0000, 0x3ec00000, 0x3e800000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x3a800000, 0x00000000, + 0x3e3c0000, 0x3f200000, 0x3e800000, 0x00000000, 0x3f800000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3e4c0000, 0x3f600000, 0x3e800000, 0x00000000, + 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x3e5c0000, 0x3e000000, 0x3f400000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x3a800000, 0x3a800000, 0x00000000, 0x00000000, 0x3e6c0000, 0x3ec00000, 0x3f400000, 0x00000000, + 0x3a800000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, + 0x3e7c0000, 0x3f200000, 0x3f400000, 0x00000000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, + 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x3e860000, 0x3f600000, 0x3f400000, 0x00000000, + 0x3f800000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x3e8e0000, 0x3e000000, 0x3e800000, 0x00000000, 0x00000000, 0x3a800000, 0x3a800000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3e960000, 0x3ec00000, 0x3e800000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3f800000, 0x00000000, 0x00000000, 0x00000000, + 0x3e9e0000, 0x3f200000, 0x3e800000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x3a800000, 0x3a800000, 0x00000000, 0x3ea60000, 0x3f600000, 0x3e800000, 0x00000000, + 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, + 0x3eae0000, 0x3e000000, 0x3f400000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, + 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x3eb60000, 0x3ec00000, 0x3f400000, 0x00000000, + 0x3a800000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x3ebe0000, 0x3f200000, 0x3f400000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x3f800000, 0x00000000, 0x00000000, 0x00000000, 0x3ec60000, 0x3f600000, 0x3f400000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, + 0x3ece0000, 0x3e000000, 0x3e800000, 0x00000000, 0x3a800000, 0x3a800000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3ed60000, 0x3ec00000, 0x3e800000, 0x00000000, + 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x00000000, + 0x3ede0000, 0x3f200000, 0x3e800000, 0x00000000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, + 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x3ee60000, 0x3f600000, 0x3e800000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3f800000, 0x00000000, 0x00000000, 0x00000000, + 0x3eee0000, 0x3e000000, 0x3f400000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x3a800000, 0x3a800000, 0x00000000, 0x3ef60000, 0x3ec00000, 0x3f400000, 0x00000000, + 0x3f800000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x3efe0000, 0x3f200000, 0x3f400000, 0x00000000, 0x00000000, 0x3a800000, 0x3a800000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3f030000, 0x3f600000, 0x3f400000, 0x00000000, + 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, +}; +alignas(256) std::array Buffer{}; +alignas(256) std::array Texels{}; + +alignas(256) constexpr std::array Code2D{ + 0x160200b0, 0x34060086, 0xe0381000, 0x80000401, 0xe0381010, 0x80002801, 0xe0381020, 0x80003201, + 0xbe900404, 0xbe920406, 0xbe940408, 0xbe96040a, 0xbe98040c, 0xbe9a040e, 0x7e140280, 0x7e160280, + 0x7e180280, 0x7e1a0280, 0x7e1c0280, 0x7e1e0280, 0x7e200280, 0x7e220280, 0x7e240280, 0x7e260280, + 0x7e280280, 0x7e2a0280, 0x7e2c0280, 0x7e2e0280, 0x7e300280, 0x7e320280, 0xbf8c3f70, 0x5e585328, + 0xd52f002d, 0x0201012a, 0x5e5c6732, 0xd52f002f, 0x02010134, 0x5e600b04, 0xd52f0031, 0x02010106, + 0xd52f0036, 0x02010128, 0xd52f0037, 0x02010132, 0xd52f003b, 0x02010104, 0x7e780281, 0x7e7a02f6, + 0x7e7c02ff, 0x00000101, 0x7e800328, 0x7e820329, 0x7e840332, 0x7e860333, 0x7e880304, 0x7e8a0305, + 0xf0880108, 0x00c40a40, 0x7e800328, 0x7e820329, 0x7e840332, 0x7e860333, 0x7e880304, 0x7e8a0305, + 0xf1a00108, 0x00c40b40, 0x7e800328, 0x7e820329, 0x7e840332, 0x7e860333, 0x7e880304, 0x7e8a0305, + 0xbe9e037e, 0xbefe03ff, 0xaeeeffff, 0xf1a00108, 0x00c40c40, 0xbefe031e, 0x7e80032c, 0x7e82032e, + 0x7e840304, 0x7e860305, 0xf0880109, 0x00c40d40, 0x7e80032c, 0x7e82032e, 0x7e840304, 0x7e860305, + 0xf1a00109, 0x00c40e40, 0x7e800328, 0x7e820329, 0x7e840332, 0x7e860333, 0x7e880330, 0xf0880108, + 0x40c40f40, 0x7e80032c, 0x7e82032e, 0x7e840330, 0xf0880109, 0x40c41040, 0x7e80033c, 0x7e820328, + 0x7e840329, 0x7e860332, 0x7e880333, 0x7e8a0304, 0x7e8c0305, 0xf1b00108, 0x00c41140, 0x7e80033e, + 0x7e82032c, 0x7e84032e, 0x7e860304, 0x7e880305, 0xf0c80109, 0x00c41240, 0x7e80033c, 0x7e82032c, + 0x7e84032e, 0x7e860304, 0x7e880305, 0xf1b00109, 0x00c41340, 0x7e80033c, 0x7e82032c, 0x7e84032e, + 0x7e860330, 0xf0c80109, 0x40c41440, 0x7e80033c, 0x7e82032c, 0x7e84032e, 0x7e860304, 0x7e880305, + 0xbe9e037e, 0xbefe03ff, 0xaeeeffff, 0xf1b00109, 0x00c41540, 0xbefe031e, 0xbf8c3f70, 0xe0701600, + 0x80000a03, 0xe0701604, 0x80000b03, 0xe0701608, 0x80000c03, 0xe070160c, 0x80000d03, 0xe0701610, + 0x80000e03, 0xe0701614, 0x80000f03, 0xe0701618, 0x80001003, 0xe070161c, 0x80001103, 0xe0701620, + 0x80001203, 0xe0701624, 0x80001303, 0xe0701628, 0x80001403, 0xe070162c, 0x80001503, 0xbf810000, +}; + +alignas(256) constexpr std::array Code3D{ + 0x160200b0, 0x34060086, 0xe0381000, 0x80000401, 0xe0381010, 0x80002801, 0xe0381020, 0x80003201, + 0xbe900404, 0xbe920406, 0xbe940408, 0xbe96040a, 0xbe98040c, 0xbe9a040e, 0x7e140280, 0x7e160280, + 0x7e180280, 0x7e1a0280, 0x7e1c0280, 0x7e1e0280, 0x7e200280, 0x7e220280, 0x7e240280, 0x7e260280, + 0x7e280280, 0x7e2a0280, 0x7e2c0280, 0x7e2e0280, 0x7e300280, 0x7e320280, 0xbf8c3f70, 0x5e585328, + 0xd52f002d, 0x0201012a, 0x5e5c6732, 0xd52f002f, 0x02010134, 0x5e600b04, 0xd52f0031, 0x02010106, + 0xd52f0036, 0x02010128, 0xd52f0037, 0x02010132, 0xd52f003b, 0x02010104, 0x7e780281, 0x7e7a02f6, + 0x7e7c02ff, 0x00000101, 0x7e800328, 0x7e820329, 0x7e84032a, 0x7e860332, 0x7e880333, 0x7e8a0334, + 0x7e8c0304, 0x7e8e0305, 0x7e900306, 0xf0880110, 0x00c40a40, 0x7e800328, 0x7e820329, 0x7e84032a, + 0x7e860332, 0x7e880333, 0x7e8a0334, 0x7e8c0304, 0x7e8e0305, 0x7e900306, 0xf1a00110, 0x00c40b40, + 0x7e80032c, 0x7e82032d, 0x7e84032e, 0x7e86032f, 0x7e880304, 0x7e8a0305, 0x7e8c0306, 0xf0880111, + 0x00c40c40, 0x7e80032c, 0x7e82032d, 0x7e84032e, 0x7e86032f, 0x7e880304, 0x7e8a0305, 0x7e8c0306, + 0xf1a00111, 0x00c40d40, 0x7e800328, 0x7e820329, 0x7e84032a, 0x7e860332, 0x7e880333, 0x7e8a0334, + 0x7e8c0330, 0x7e8e0331, 0xf0880110, 0x40c40e40, 0x7e80032c, 0x7e82032d, 0x7e84032e, 0x7e86032f, + 0x7e880330, 0x7e8a0331, 0xf0880111, 0x40c40f40, 0xbf8c3f70, 0xe0701600, 0x80000a03, 0xe0701604, + 0x80000b03, 0xe0701608, 0x80000c03, 0xe070160c, 0x80000d03, 0xe0701610, 0x80000e03, 0xe0701614, + 0x80000f03, 0xbf810000, +}; + +alignas(256) constexpr std::array Code1D{ + 0x160200b0, 0x34060086, 0xe0381000, 0x80000401, 0xe0381010, 0x80002801, 0xe0381020, 0x80003201, + 0xbe900404, 0xbe920406, 0xbe940408, 0xbe96040a, 0xbe98040c, 0xbe9a040e, 0x7e140280, 0x7e160280, + 0x7e180280, 0x7e1a0280, 0x7e1c0280, 0x7e1e0280, 0x7e200280, 0x7e220280, 0x7e240280, 0x7e260280, + 0x7e280280, 0x7e2a0280, 0x7e2c0280, 0x7e2e0280, 0x7e300280, 0x7e320280, 0xbf8c3f70, 0x5e585328, + 0xd52f002d, 0x0201012a, 0x5e5c6732, 0xd52f002f, 0x02010134, 0x5e600b04, 0xd52f0031, 0x02010106, + 0xd52f0036, 0x02010128, 0xd52f0037, 0x02010132, 0xd52f003b, 0x02010104, 0x7e780281, 0x7e7a02f6, + 0x7e7c02ff, 0x00000101, 0x7e800328, 0x7e820332, 0x7e840304, 0xf0880100, 0x00c40a40, 0x7e800336, + 0x7e820337, 0x7e840304, 0xf0880101, 0x00c40b40, 0x7e800336, 0x7e820337, 0x7e840304, 0xf1a00101, + 0x00c40c40, 0x7e800328, 0x7e820332, 0x7e84033b, 0xf0880100, 0x40c40d40, 0x7e800336, 0x7e820337, + 0x7e84033b, 0xf1a00101, 0x40c40e40, 0xbf8c3f70, 0xe0701600, 0x80000a03, 0xe0701604, 0x80000b03, + 0xe0701608, 0x80000c03, 0xe070160c, 0x80000d03, 0xe0701610, 0x80000e03, 0xbf810000, +}; + +constexpr std::array Names2D{"image_sample_d", "image_sample_cd", "image_sample_cd (exec)", "image_sample_d_g16", "image_sample_cd_g16", "image_sample_d a16", "image_sample_d_g16 a16", "image_sample_cd_o", "image_sample_d_o_g16", "image_sample_cd_o_g16", "image_sample_d_o_g16 a16", "image_sample_cd_o_g16 (exec)"}; +constexpr std::array Masked2D{false, false, true, false, false, false, false, false, false, false, false, true}; +constexpr std::array Expected2D{ + 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x40100000, + 0x42cc8000, 0x40100000, 0x40100000, 0x40100000, 0x42cc0000, 0x42cc8000, 0x42cc8000, 0x42cc0000, + 0x42cc8000, 0x42cc0000, 0x42cc0000, 0x42ce8000, 0x434b0000, 0x42ce8000, 0x42ce0000, 0x42ce8000, + 0x434b0000, 0x434b4000, 0x434b4000, 0x434b0000, 0x434b4000, 0x434b0000, 0x434b0000, 0x434c4000, + 0x43980000, 0x434c4000, 0x434c0000, 0x434c4000, 0x43980000, 0x43982000, 0x43982000, 0x43980000, + 0x43982000, 0x43980000, 0x43980000, 0x4398a000, 0x43988000, 0x4398a000, 0x43988000, 0x4398a000, + 0x40a00000, 0x40a00000, 0x40a00000, 0x40a00000, 0x40a00000, 0x40a00000, 0x40a00000, 0x40c00000, + 0x42d40000, 0x40c00000, 0x40c00000, 0x40c00000, 0x42d48000, 0x42d40000, 0x42d40000, 0x42d48000, + 0x42d40000, 0x42d48000, 0x42d48000, 0x42d60000, 0x434f4000, 0x42d60000, 0x42d68000, 0x42d60000, + 0x434f0000, 0x434f0000, 0x434f0000, 0x434f0000, 0x434f0000, 0x434f0000, 0x434f0000, 0x43500000, + 0x439a0000, 0x43500000, 0x43500000, 0x43500000, 0x439a0000, 0x439a0000, 0x439a0000, 0x439a0000, + 0x439a0000, 0x439a0000, 0x439a0000, 0x439a8000, 0x439a8000, 0x439a8000, 0x439a8000, 0x439a8000, + 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41200000, + 0x42dc0000, 0x41200000, 0x41200000, 0x41200000, 0x42dc0000, 0x42dc0000, 0x42dc0000, 0x42dc0000, + 0x42dc0000, 0x42dc0000, 0x42dc0000, 0x42de0000, 0x43530000, 0x42de0000, 0x42de0000, 0x42de0000, + 0x43534000, 0x43530000, 0x43530000, 0x43534000, 0x43530000, 0x43534000, 0x43534000, 0x43540000, + 0x439c2000, 0x43540000, 0x43544000, 0x43540000, 0x439c0000, 0x439c0000, 0x439c0000, 0x439c0000, + 0x439c0000, 0x439c0000, 0x439c0000, 0x439c8000, 0x439c8000, 0x439c8000, 0x439c8000, 0x439c8000, + 0x41500000, 0x41500000, 0x41500000, 0x41500000, 0x41500000, 0x41500000, 0x41500000, 0x41600000, + 0x42e40000, 0x41600000, 0x41600000, 0x41600000, 0x42e40000, 0x42e40000, 0x42e40000, 0x42e40000, + 0x42e40000, 0x42e40000, 0x42e40000, 0x42e60000, 0x43570000, 0x42e60000, 0x42e60000, 0x42e60000, + 0x43570000, 0x43570000, 0x43570000, 0x43570000, 0x43570000, 0x43570000, 0x43570000, 0x43580000, + 0x439e0000, 0x43580000, 0x43580000, 0x43580000, 0x439e2000, 0x439e0000, 0x439e0000, 0x439e2000, + 0x439e0000, 0x439e2000, 0x439e2000, 0x439e8000, 0x439ea000, 0x439e8000, 0x439ea000, 0x439e8000, + 0x41880000, 0x41880000, 0x00000000, 0x41880000, 0x41880000, 0x41880000, 0x41880000, 0x41900000, + 0x42ec0000, 0x41900000, 0x41900000, 0x00000000, 0x42ec8000, 0x42ec0000, 0x42ec8000, 0x42ec8000, + 0x42ec0000, 0x42ec8000, 0x42ec8000, 0x42ee0000, 0x435b4000, 0x42ee0000, 0x42ee8000, 0x42ee8000, + 0x435b0000, 0x435b0000, 0x435b4000, 0x435b0000, 0x435b0000, 0x435b0000, 0x435b0000, 0x435c0000, + 0x43a00000, 0x435c0000, 0x435c0000, 0x435c4000, 0x43a00000, 0x43a00000, 0x43a02000, 0x43a00000, + 0x43a00000, 0x43a00000, 0x43a00000, 0x43a08000, 0x43a08000, 0x43a08000, 0x43a08000, 0x43a0a000, + 0x41a80000, 0x41a80000, 0x00000000, 0x41a80000, 0x41a80000, 0x41a80000, 0x41a80000, 0x41b00000, + 0x42f40000, 0x41b00000, 0x41b00000, 0x00000000, 0x42f40000, 0x42f40000, 0x42f40000, 0x42f40000, + 0x42f40000, 0x42f40000, 0x42f40000, 0x42f60000, 0x435f0000, 0x42f60000, 0x42f60000, 0x42f60000, + 0x435f4000, 0x435f0000, 0x435f0000, 0x435f4000, 0x435f0000, 0x435f4000, 0x435f4000, 0x43600000, + 0x43a22000, 0x43600000, 0x43604000, 0x43600000, 0x43a20000, 0x43a20000, 0x43a20000, 0x43a20000, + 0x43a20000, 0x43a20000, 0x43a20000, 0x43a28000, 0x43a28000, 0x43a28000, 0x43a28000, 0x43a28000, + 0x41c80000, 0x41c80000, 0x00000000, 0x41c80000, 0x41c80000, 0x41c80000, 0x41c80000, 0x41d00000, + 0x42fc0000, 0x41d00000, 0x41d00000, 0x00000000, 0x42fc0000, 0x42fc0000, 0x42fc0000, 0x42fc0000, + 0x42fc0000, 0x42fc0000, 0x42fc0000, 0x42fe0000, 0x43630000, 0x42fe0000, 0x42fe0000, 0x42fe0000, + 0x43630000, 0x43630000, 0x43630000, 0x43630000, 0x43630000, 0x43630000, 0x43630000, 0x43640000, + 0x43a40000, 0x43640000, 0x43640000, 0x43640000, 0x43a42000, 0x43a40000, 0x43a40000, 0x43a42000, + 0x43a40000, 0x43a42000, 0x43a42000, 0x43a48000, 0x43a4a000, 0x43a48000, 0x43a4a000, 0x43a48000, + 0x41e80000, 0x41e80000, 0x00000000, 0x41e80000, 0x41e80000, 0x41e80000, 0x41e80000, 0x41f00000, + 0x43020000, 0x41f00000, 0x41f00000, 0x00000000, 0x43024000, 0x43020000, 0x43024000, 0x43024000, + 0x43020000, 0x43024000, 0x43024000, 0x43030000, 0x43674000, 0x43030000, 0x43034000, 0x43034000, + 0x43670000, 0x43670000, 0x00000000, 0x43670000, 0x43670000, 0x43670000, 0x43670000, 0x43680000, + 0x43a60000, 0x43680000, 0x43680000, 0x00000000, 0x43a60000, 0x43a60000, 0x43a62000, 0x43a60000, + 0x43a60000, 0x43a60000, 0x43a60000, 0x43a68000, 0x43a68000, 0x43a68000, 0x43a68000, 0x43a6a000, +}; +constexpr std::array Names3D{"image_sample_d 3D", "image_sample_cd 3D", "image_sample_d_g16 3D", "image_sample_cd_g16 3D", "image_sample_d a16 3D", "image_sample_d_g16 a16 3D"}; +constexpr std::array Masked3D{false, false, false, false, false, false}; +constexpr std::array Expected3D{ + 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x42cc0000, 0x42cc8000, + 0x42cc0000, 0x42cc8000, 0x42cc0000, 0x42cc0000, 0x434b0000, 0x434b4000, 0x434b0000, 0x434b4000, + 0x434b0000, 0x434b0000, 0x43980000, 0x43982000, 0x43980000, 0x43982000, 0x43980000, 0x43980000, + 0x447b4000, 0x447b4000, 0x447b4000, 0x447b4000, 0x447b4000, 0x447b4000, 0x448a4800, 0x448a4000, + 0x448a4800, 0x448a4000, 0x448a4800, 0x448a4800, 0x4496e000, 0x4496e000, 0x4496e000, 0x4496e000, + 0x4496e000, 0x4496e000, 0x44a38000, 0x44a38000, 0x44a38000, 0x44a38000, 0x44a38000, 0x44a38000, + 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x42dc0000, 0x42dc0000, + 0x42dc0000, 0x42dc0000, 0x42dc0000, 0x42dc0000, 0x43534000, 0x43530000, 0x43534000, 0x43530000, + 0x43534000, 0x43534000, 0x439c0000, 0x439c0000, 0x439c0000, 0x439c0000, 0x439c0000, 0x439c0000, + 0x447d4000, 0x447d4000, 0x447d4000, 0x447d4000, 0x447d4000, 0x447d4000, 0x448b4000, 0x448b4000, + 0x448b4000, 0x448b4000, 0x448b4000, 0x448b4000, 0x4497e000, 0x4497e000, 0x4497e000, 0x4497e000, + 0x4497e000, 0x4497e000, 0x44a48800, 0x44a48000, 0x44a48800, 0x44a48000, 0x44a48800, 0x44a48800, + 0x41880000, 0x41880000, 0x41880000, 0x41880000, 0x41880000, 0x41880000, 0x42ec8000, 0x42ec0000, + 0x42ec8000, 0x42ec0000, 0x42ec8000, 0x42ec8000, 0x435b0000, 0x435b0000, 0x435b0000, 0x435b0000, + 0x435b0000, 0x435b0000, 0x43a00000, 0x43a00000, 0x43a00000, 0x43a00000, 0x43a00000, 0x43a00000, + 0x447f4000, 0x447f4000, 0x447f4000, 0x447f4000, 0x447f4000, 0x447f4000, 0x448c4000, 0x448c4000, + 0x448c4000, 0x448c4000, 0x448c4000, 0x448c4000, 0x4498e800, 0x4498e000, 0x4498e800, 0x4498e000, + 0x4498e800, 0x4498e800, 0x44a58000, 0x44a58000, 0x44a58000, 0x44a58000, 0x44a58000, 0x44a58000, + 0x41c80000, 0x41c80000, 0x41c80000, 0x41c80000, 0x41c80000, 0x41c80000, 0x42fc0000, 0x42fc0000, + 0x42fc0000, 0x42fc0000, 0x42fc0000, 0x42fc0000, 0x43630000, 0x43630000, 0x43630000, 0x43630000, + 0x43630000, 0x43630000, 0x43a42000, 0x43a40000, 0x43a42000, 0x43a40000, 0x43a42000, 0x43a42000, + 0x4480a000, 0x4480a000, 0x4480a000, 0x4480a000, 0x4480a000, 0x4480a000, 0x448d4800, 0x448d4000, + 0x448d4800, 0x448d4000, 0x448d4800, 0x448d4800, 0x4499e000, 0x4499e000, 0x4499e000, 0x4499e000, + 0x4499e000, 0x4499e000, 0x44a68000, 0x44a68000, 0x44a68000, 0x44a68000, 0x44a68000, 0x44a68000, +}; +constexpr std::array Names1D{"image_sample_d 1D", "image_sample_d_g16 1D", "image_sample_cd_g16 1D", "image_sample_d a16 1D", "image_sample_cd_g16 a16 1D"}; +constexpr std::array Masked1D{false, false, false, false, false}; +constexpr std::array Expected1D{ + 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x40000000, 0x40000000, 0x40100000, + 0x40000000, 0x40100000, 0x40400000, 0x40400000, 0x40500000, 0x40400000, 0x40500000, 0x40800000, + 0x40800000, 0x40880000, 0x40800000, 0x40880000, 0x40a00000, 0x40a00000, 0x40a00000, 0x40a00000, + 0x40a00000, 0x40c80000, 0x40c80000, 0x40c00000, 0x40c80000, 0x40c00000, 0x40e00000, 0x40e00000, + 0x40e00000, 0x40e00000, 0x40e00000, 0x41000000, 0x41000000, 0x41000000, 0x41000000, 0x41000000, + 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41200000, 0x41200000, 0x41200000, + 0x41200000, 0x41200000, 0x41340000, 0x41340000, 0x41300000, 0x41340000, 0x41300000, 0x41400000, + 0x41400000, 0x41400000, 0x41400000, 0x41400000, 0x41500000, 0x41500000, 0x41500000, 0x41500000, + 0x41500000, 0x41600000, 0x41600000, 0x41600000, 0x41600000, 0x41600000, 0x41700000, 0x41700000, + 0x41700000, 0x41700000, 0x41700000, 0x41820000, 0x41820000, 0x41800000, 0x41820000, 0x41800000, + 0x41880000, 0x41880000, 0x41880000, 0x41880000, 0x41880000, 0x41920000, 0x41920000, 0x41900000, + 0x41920000, 0x41900000, 0x41980000, 0x41980000, 0x41980000, 0x41980000, 0x41980000, 0x41a00000, + 0x41a00000, 0x41a00000, 0x41a00000, 0x41a00000, 0x41a80000, 0x41a80000, 0x41a80000, 0x41a80000, + 0x41a80000, 0x41b00000, 0x41b00000, 0x41b00000, 0x41b00000, 0x41b00000, 0x41ba0000, 0x41ba0000, + 0x41b80000, 0x41ba0000, 0x41b80000, 0x41c00000, 0x41c00000, 0x41c00000, 0x41c00000, 0x41c00000, + 0x41c80000, 0x41c80000, 0x41c80000, 0x41c80000, 0x41c80000, 0x41d00000, 0x41d00000, 0x41d00000, + 0x41d00000, 0x41d00000, 0x41d80000, 0x41d80000, 0x41d80000, 0x41d80000, 0x41d80000, 0x41e20000, + 0x41e20000, 0x41e00000, 0x41e20000, 0x41e00000, 0x41e80000, 0x41e80000, 0x41e80000, 0x41e80000, + 0x41e80000, 0x41f20000, 0x41f20000, 0x41f00000, 0x41f20000, 0x41f00000, 0x41f80000, 0x41f80000, + 0x41f80000, 0x41f80000, 0x41f80000, 0x42000000, 0x42000000, 0x42000000, 0x42000000, 0x42000000, +}; + +std::array TextureDescriptor(std::uint32_t type, std::uint32_t height, std::uint32_t depth) { + const auto address = static_cast(reinterpret_cast(Texels.data())); + return { + static_cast(address >> 8u), + static_cast((address >> 40u) & 0xffu) | (Format32Float << 20u) | (((Width - 1u) & 3u) << 30u), + ((Width - 1u) >> 2u) | ((height - 1u) << 14u) | (1u << 31u), + 0xfacu | (type << 28u), + depth - 1u, 0u, 0u, 0u, + }; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +template +void Run(AgcDriver::VulkanDevice& device, const std::array& code, std::uint32_t type, std::uint32_t height, std::uint32_t depth, + const std::array& names, const std::array& masked, const std::array& expected) { + std::copy(Input.begin(), Input.end(), Buffer.begin()); + std::fill(Buffer.begin() + Threads * Inputs, Buffer.end(), 0xdeadbeefu); + for (std::uint32_t z = 0; z < depth; ++z) { + for (std::uint32_t y = 0; y < height; ++y) { + for (std::uint32_t x = 0; x < Width; ++x) { + Texels[(z * height + y) * Width + x] = static_cast(x + 100u * y + 1000u * z); + } + } + } + const auto address = reinterpret_cast(Buffer.data()); + const std::array buffer{static_cast(address), static_cast((address >> 32u) & 0xffffu), static_cast(Buffer.size() * 4u), 0x01016facu}; + const auto texture = TextureDescriptor(type, height, depth); + const std::array sampler{0x92u, (4u * 256u) << 12u, (1u << 22u) | (1u << 26u), 0u}; + std::vector userData(16, 0u); + std::copy(buffer.begin(), buffer.end(), userData.begin()); + std::copy(texture.begin(), texture.end(), userData.begin() + 4); + std::copy(sampler.begin(), sampler.end(), userData.begin() + 12); + const std::span span(code); + const std::array memory{{{reinterpret_cast(span.data()), std::as_bytes(span)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(span.data()), span, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(span.data())); + device.WaitIdle(); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + for (std::uint32_t k = 0; k < Cases; ++k) { + if (masked[k] && device.Target().subgroupSize < Threads) continue; + const auto actual = Buffer[Threads * Inputs + tid * Results + k]; + const auto wanted = expected[tid * Cases + k]; + const bool close = std::fabs(std::bit_cast(actual) - std::bit_cast(wanted)) <= 1.0f / 64.0f; + Require(close, std::string(names[k]) + ": lane " + std::to_string(tid) + " is " + Hex(actual) + ", expected " + Hex(wanted)); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + if (device->Target().subgroupSize < Threads) { + std::printf("EXEC-masked cases skipped, subgroup size %u cannot hold a wave32\n", device->Target().subgroupSize); + } + Run(*device, Code2D, 9u, Height, 1u, Names2D, Masked2D, Expected2D); + Run(*device, Code3D, 10u, Height, Depth, Names3D, Masked3D, Expected3D); + Run(*device, Code1D, 8u, 1u, 1u, Names1D, Masked1D, Expected1D); + std::puts("image sample derivatives tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaInstruction.hpp b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaInstruction.hpp index e27d5fb18..3521bf1df 100644 --- a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaInstruction.hpp +++ b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaInstruction.hpp @@ -73,8 +73,12 @@ enum RdnaImageSampleFlag : std::uint32_t { RdnaImageSampleFlagCd = 1u << 8u, RdnaImageSampleFlagGatherHorizontal = 1u << 9u, RdnaImageSampleFlagAdjust = 1u << 10u, + RdnaImageSampleFlagG16 = 1u << 11u, }; +inline constexpr std::uint32_t RdnaImageSampleGradientCountShift = 12u; +inline constexpr std::uint32_t RdnaImageSampleGradientCountMask = 3u << RdnaImageSampleGradientCountShift; + inline constexpr std::uint32_t MaxRdnaInstructionRawWords = 5; inline constexpr std::uint32_t MaxRdnaImageNsaAddressComponents = 12; diff --git a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp index 077686add..e3d02397f 100644 --- a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp +++ b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp @@ -140,6 +140,30 @@ enum class RdnaOpcode : std::uint16_t { ImageSampleDO, ImageSampleLzO, ImageSampleO, + ImageSampleCd, + ImageSampleCdCl, + ImageSampleCCd, + ImageSampleCCdCl, + ImageSampleCdO, + ImageSampleCdClO, + ImageSampleCCdO, + ImageSampleCCdClO, + ImageSampleDG16, + ImageSampleDClG16, + ImageSampleCDG16, + ImageSampleCDClG16, + ImageSampleDOG16, + ImageSampleDClOG16, + ImageSampleCDOG16, + ImageSampleCDClOG16, + ImageSampleCdG16, + ImageSampleCdClG16, + ImageSampleCCdG16, + ImageSampleCCdClG16, + ImageSampleCdOG16, + ImageSampleCdClOG16, + ImageSampleCCdOG16, + ImageSampleCCdClOG16, ImageLoad, ImageStore, ImageGetResinfo, diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp index 54ff7e8e2..f72be342e 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp @@ -40,7 +40,30 @@ constexpr ImageOpcodeInfo imageOpcodes[] = { {0x35u, RdnaOpcode::ImageSampleBO, "image_sample_b_o", RdnaImageSampleFlagBias | RdnaImageSampleFlagOffset, true, false, false}, {0x37u, RdnaOpcode::ImageSampleLzO, "image_sample_lz_o", RdnaImageSampleFlagLevelZero | RdnaImageSampleFlagOffset, true, false, false}, {0x38u, RdnaOpcode::ImageSampleCO, "image_sample_c_o", RdnaImageSampleFlagCompare | RdnaImageSampleFlagOffset, true, false, false}, - {0x68u, RdnaOpcode::ImageSample, "image_sample_cd", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagCd, true, false, false}, + {0x68u, RdnaOpcode::ImageSampleCd, "image_sample_cd", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagCd, true, false, false}, + {0x69u, RdnaOpcode::ImageSampleCdCl, "image_sample_cd_cl", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagCd, true, false, false}, + {0x6au, RdnaOpcode::ImageSampleCCd, "image_sample_c_cd", RdnaImageSampleFlagCompare | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagCd, true, false, false}, + {0x6bu, RdnaOpcode::ImageSampleCCdCl, "image_sample_c_cd_cl", RdnaImageSampleFlagCompare | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagCd, true, false, false}, + {0x6cu, RdnaOpcode::ImageSampleCdO, "image_sample_cd_o", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagOffset | RdnaImageSampleFlagCd, true, false, false}, + {0x6du, RdnaOpcode::ImageSampleCdClO, "image_sample_cd_cl_o", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagOffset | RdnaImageSampleFlagCd, true, false, false}, + {0x6eu, RdnaOpcode::ImageSampleCCdO, "image_sample_c_cd_o", RdnaImageSampleFlagCompare | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagOffset | RdnaImageSampleFlagCd, true, false, false}, + {0x6fu, RdnaOpcode::ImageSampleCCdClO, "image_sample_c_cd_cl_o", RdnaImageSampleFlagCompare | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagOffset | RdnaImageSampleFlagCd, true, false, false}, + {0xa2u, RdnaOpcode::ImageSampleDG16, "image_sample_d_g16", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagG16, true, false, false}, + {0xa3u, RdnaOpcode::ImageSampleDClG16, "image_sample_d_cl_g16", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagG16, true, false, false}, + {0xaau, RdnaOpcode::ImageSampleCDG16, "image_sample_c_d_g16", RdnaImageSampleFlagCompare | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagG16, true, false, false}, + {0xabu, RdnaOpcode::ImageSampleCDClG16, "image_sample_c_d_cl_g16", RdnaImageSampleFlagCompare | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagG16, true, false, false}, + {0xb2u, RdnaOpcode::ImageSampleDOG16, "image_sample_d_o_g16", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagOffset | RdnaImageSampleFlagG16, true, false, false}, + {0xb3u, RdnaOpcode::ImageSampleDClOG16, "image_sample_d_cl_o_g16", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagOffset | RdnaImageSampleFlagG16, true, false, false}, + {0xbau, RdnaOpcode::ImageSampleCDOG16, "image_sample_c_d_o_g16", RdnaImageSampleFlagCompare | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagOffset | RdnaImageSampleFlagG16, true, false, false}, + {0xbbu, RdnaOpcode::ImageSampleCDClOG16, "image_sample_c_d_cl_o_g16", RdnaImageSampleFlagCompare | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagOffset | RdnaImageSampleFlagG16, true, false, false}, + {0xe8u, RdnaOpcode::ImageSampleCdG16, "image_sample_cd_g16", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagCd | RdnaImageSampleFlagG16, true, false, false}, + {0xe9u, RdnaOpcode::ImageSampleCdClG16, "image_sample_cd_cl_g16", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagCd | RdnaImageSampleFlagG16, true, false, false}, + {0xeau, RdnaOpcode::ImageSampleCCdG16, "image_sample_c_cd_g16", RdnaImageSampleFlagCompare | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagCd | RdnaImageSampleFlagG16, true, false, false}, + {0xebu, RdnaOpcode::ImageSampleCCdClG16, "image_sample_c_cd_cl_g16", RdnaImageSampleFlagCompare | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagCd | RdnaImageSampleFlagG16, true, false, false}, + {0xecu, RdnaOpcode::ImageSampleCdOG16, "image_sample_cd_o_g16", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagOffset | RdnaImageSampleFlagCd | RdnaImageSampleFlagG16, true, false, false}, + {0xedu, RdnaOpcode::ImageSampleCdClOG16, "image_sample_cd_cl_o_g16", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagOffset | RdnaImageSampleFlagCd | RdnaImageSampleFlagG16, true, false, false}, + {0xeeu, RdnaOpcode::ImageSampleCCdOG16, "image_sample_c_cd_o_g16", RdnaImageSampleFlagCompare | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagOffset | RdnaImageSampleFlagCd | RdnaImageSampleFlagG16, true, false, false}, + {0xefu, RdnaOpcode::ImageSampleCCdClOG16, "image_sample_c_cd_cl_o_g16", RdnaImageSampleFlagCompare | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagOffset | RdnaImageSampleFlagCd | RdnaImageSampleFlagG16, true, false, false}, {0x33u, RdnaOpcode::ImageSample, "image_sample_d_cl_o", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagOffset, true, false, false}, {0xa0u, RdnaOpcode::ImageSample, "image_sample_a", RdnaImageSampleFlagAdjust, true, false, false}, {0xa1u, RdnaOpcode::ImageSample, "image_sample_cl_a", RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagAdjust, true, false, false}, @@ -123,37 +146,54 @@ const ImageOpcodeInfo& lookupOpcode(std::uint32_t opcode) { } void validateFlags(std::uint32_t flags) { - constexpr std::uint32_t known = (1u << 11u) - 1u; + constexpr std::uint32_t known = ((1u << 12u) - 1u) | RdnaImageSampleGradientCountMask; if ((flags & ~known) != 0u) { throw std::runtime_error("unknown image address flags"); } + if (((flags & RdnaImageSampleGradientCountMask) != 0u) != ((flags & RdnaImageSampleFlagDerivative) != 0u)) { + throw std::runtime_error("image derivative flags without a gradient count"); + } const auto lodModes = flags & (RdnaImageSampleFlagLod | RdnaImageSampleFlagBias | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLevelZero); if (std::popcount(lodModes) > 1) { throw std::runtime_error("conflicting image LOD modes"); } } -std::uint32_t componentWidth(std::uint32_t flags, std::uint32_t component) { - if ((flags & RdnaImageSampleFlagA16) == 0u) { - return 32; - } +struct ComponentShape { + std::uint32_t width; + bool startsDword; +}; + +ComponentShape componentShape(std::uint32_t flags, std::uint32_t component) { + const bool a16 = (flags & RdnaImageSampleFlagA16) != 0u; std::uint32_t cursor = 0; if ((flags & RdnaImageSampleFlagOffset) != 0u) { if (component == cursor) { - return 32; + return {32, true}; } ++cursor; } if ((flags & RdnaImageSampleFlagBias) != 0u) { if (component == cursor) { - return 16; + return {a16 ? 16u : 32u, !a16}; } ++cursor; } - if ((flags & RdnaImageSampleFlagCompare) != 0u && component == cursor) { - return 32; + if ((flags & RdnaImageSampleFlagCompare) != 0u) { + if (component == cursor) { + return {32, true}; + } + ++cursor; } - return 16; + const auto gradients = (flags & RdnaImageSampleGradientCountMask) >> RdnaImageSampleGradientCountShift; + if (component < cursor + gradients * 2u) { + if ((flags & RdnaImageSampleFlagG16) == 0u) { + return {32, true}; + } + return {16, (component - cursor) % gradients == 0u}; + } + cursor += gradients * 2u; + return {a16 ? 16u : 32u, !a16 || (gradients != 0u && component == cursor)}; } bool isBiasComponent(std::uint32_t flags, std::uint32_t component) { @@ -232,14 +272,14 @@ RdnaImageAddressComponent GetRdnaImageAddressComponentLayout(std::uint32_t flags } std::uint32_t offset = 0; for (std::uint32_t index = 0; index <= component; ++index) { - const auto width = componentWidth(flags, index); - if (width == 32u) { + const auto shape = componentShape(flags, index); + if (shape.startsDword) { offset = (offset + 31u) & ~31u; } if (index == component) { - return {offset, width}; + return {offset, shape.width}; } - offset += isBiasComponent(flags, index) ? 32u : width; + offset += isBiasComponent(flags, index) ? 32u : shape.width; } throw std::runtime_error("invalid image address component"); } @@ -288,13 +328,16 @@ RdnaInstruction DecodeRdnaMimg(std::uint32_t programCounter, std::span> 3u) & 7u); const bool multisampled = dimension == RdnaImageDimension::Dim2DMsaa || dimension == RdnaImageDimension::Dim2DMsaaArray; if (multisampled && (info.sample || info.gather || opcode == 0x60u || (opcode >= 1u && opcode <= 5u) || opcode == 9u || opcode == 0x0bu)) { throw std::runtime_error("unsupported multisampled MIMG operation"); } + auto flags = info.flags | (a16 ? RdnaImageSampleFlagA16 : 0u); + if ((info.flags & RdnaImageSampleFlagDerivative) != 0u) { + flags |= gradientCount(dimension) << RdnaImageSampleGradientCountShift; + } + validateFlags(flags); const auto dmask = (word0 >> 8u) & 15u; const bool compareSwap = info.opcode == RdnaOpcode::ImageAtomicCmpswap || info.opcode == RdnaOpcode::ImageAtomicFcmpswap; if (dmask == 0u || (compareSwap ? dmask != 3u : (info.gather || info.atomic) && !std::has_single_bit(dmask))) { diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp index 25da22e12..e602e3bf3 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp @@ -775,6 +775,30 @@ bool IsImageOpcode(RdnaOpcode opcode) { case RdnaOpcode::ImageSampleDO: case RdnaOpcode::ImageSampleLzO: case RdnaOpcode::ImageSampleO: + case RdnaOpcode::ImageSampleCd: + case RdnaOpcode::ImageSampleCdCl: + case RdnaOpcode::ImageSampleCCd: + case RdnaOpcode::ImageSampleCCdCl: + case RdnaOpcode::ImageSampleCdO: + case RdnaOpcode::ImageSampleCdClO: + case RdnaOpcode::ImageSampleCCdO: + case RdnaOpcode::ImageSampleCCdClO: + case RdnaOpcode::ImageSampleDG16: + case RdnaOpcode::ImageSampleDClG16: + case RdnaOpcode::ImageSampleCDG16: + case RdnaOpcode::ImageSampleCDClG16: + case RdnaOpcode::ImageSampleDOG16: + case RdnaOpcode::ImageSampleDClOG16: + case RdnaOpcode::ImageSampleCDOG16: + case RdnaOpcode::ImageSampleCDClOG16: + case RdnaOpcode::ImageSampleCdG16: + case RdnaOpcode::ImageSampleCdClG16: + case RdnaOpcode::ImageSampleCCdG16: + case RdnaOpcode::ImageSampleCCdClG16: + case RdnaOpcode::ImageSampleCdOG16: + case RdnaOpcode::ImageSampleCdClOG16: + case RdnaOpcode::ImageSampleCCdOG16: + case RdnaOpcode::ImageSampleCCdClOG16: case RdnaOpcode::ImageSampleLz: case RdnaOpcode::ImageLoad: case RdnaOpcode::ImageStore: diff --git a/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp b/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp index e7f6a8c6e..e43411820 100644 --- a/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp +++ b/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp @@ -431,6 +431,30 @@ bool TranslationContext::emitMemory(const RdnaInstruction& inst) { case RdnaOpcode::ImageSampleDO: case RdnaOpcode::ImageSampleLzO: case RdnaOpcode::ImageSampleO: + case RdnaOpcode::ImageSampleCd: + case RdnaOpcode::ImageSampleCdCl: + case RdnaOpcode::ImageSampleCCd: + case RdnaOpcode::ImageSampleCCdCl: + case RdnaOpcode::ImageSampleCdO: + case RdnaOpcode::ImageSampleCdClO: + case RdnaOpcode::ImageSampleCCdO: + case RdnaOpcode::ImageSampleCCdClO: + case RdnaOpcode::ImageSampleDG16: + case RdnaOpcode::ImageSampleDClG16: + case RdnaOpcode::ImageSampleCDG16: + case RdnaOpcode::ImageSampleCDClG16: + case RdnaOpcode::ImageSampleDOG16: + case RdnaOpcode::ImageSampleDClOG16: + case RdnaOpcode::ImageSampleCDOG16: + case RdnaOpcode::ImageSampleCDClOG16: + case RdnaOpcode::ImageSampleCdG16: + case RdnaOpcode::ImageSampleCdClG16: + case RdnaOpcode::ImageSampleCCdG16: + case RdnaOpcode::ImageSampleCCdClG16: + case RdnaOpcode::ImageSampleCdOG16: + case RdnaOpcode::ImageSampleCdClOG16: + case RdnaOpcode::ImageSampleCCdOG16: + case RdnaOpcode::ImageSampleCCdClOG16: return imageSample(inst); case RdnaOpcode::ImageGather4Lz: case RdnaOpcode::ImageGather4C: diff --git a/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryResourceHelpers.cpp b/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryResourceHelpers.cpp index 88e1afe14..e3e435d59 100644 --- a/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryResourceHelpers.cpp +++ b/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryResourceHelpers.cpp @@ -3,6 +3,7 @@ #include "RdnaDecoder/RdnaImageOpDecoder.hpp" #include #include +#include #include namespace ShaderRecompiler { @@ -109,6 +110,22 @@ IrValue* TranslationContext::makeImageAddress(const RdnaInstruction& inst, const components[index] = &readRawU32(offsetOperand(plainBase, index)).Value(); } } + if ((inst.imageSampleFlags & RdnaImageSampleFlagCd) != 0u) { + const auto mask = ballotMask(IrU1(ir.GetExec())); + IrValue& lane = ir.Emit(IrOpcode::LaneId, IrType::U32, {}); + IrValue& quad = ir.BitwiseAnd(lane, ir.Constant(~3u)); + IrValue& word = program.WaveSize() == 64u ? ir.Select(ir.ULessThan(lane, ir.Constant(32u)), mask[0].Value(), mask[1].Value()) : mask[0].Value(); + IrValue& active = ir.BitwiseAnd(ir.ShiftRightLogical(word, ir.BitwiseAnd(quad, ir.Constant(31u))), ir.Constant(15u)); + IrValue& first = ir.Emit(IrOpcode::FindILsb32, IrType::U32, {&active}); + IrValue& source = ir.Select(ir.INotEqual(active, ir.Constant(0u)), ir.IAdd(quad, first), lane); + const std::uint32_t gradientStart = std::popcount(inst.imageSampleFlags & (RdnaImageSampleFlagOffset | RdnaImageSampleFlagBias | RdnaImageSampleFlagCompare)); + const std::uint32_t gradients = 2u * ((inst.imageSampleFlags & RdnaImageSampleGradientCountMask) >> RdnaImageSampleGradientCountShift); + const std::uint32_t firstDword = GetRdnaImageAddressComponentLayout(inst.imageSampleFlags, gradientStart).bitOffset / 32u; + const std::uint32_t lastDword = GetRdnaImageAddressComponentLayout(inst.imageSampleFlags, gradientStart + gradients - 1u).bitOffset / 32u; + for (std::uint32_t index = firstDword; index <= lastDword; ++index) { + components[index] = &ir.Emit(IrOpcode::ReadLane, IrType::U32, {components[index], &source}); + } + } return &ir.Emit(IrOpcode::MakeImageAddress, IrOpcodeType(IrOpcode::MakeImageAddress), {components[0], components[1], components[2], components[3], components[4], components[5], components[6], components[7], components[8], components[9], components[10], components[11], components[12]}); From 559aeac8ec5f4383799798d7740ad8ae5842f4b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sun, 4 Oct 2026 08:37:00 +0200 Subject: [PATCH 3/3] fix(shader): read _cd quad gradients through a lane-varying permute ReadLane counts as a wave-uniform source, so the two-lane wave64 path shared the first half's gradients with lanes 32 to 63. The test also runs every case as wave64 with different quads in the upper half. Co-Authored-By: Claude --- .../execution/ImageSampleDerivatives.cpp | 50 +++++++++++++------ .../MemoryResourceHelpers.cpp | 4 +- 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleDerivatives.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleDerivatives.cpp index 5f2d50d3a..5c29b7e6c 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleDerivatives.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleDerivatives.cpp @@ -19,6 +19,7 @@ using AgcDriver::Graphics::Require; using ShaderRecompiler::ShaderStage; constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Wave64Threads = 64; constexpr std::uint32_t Inputs = 12; constexpr std::uint32_t Results = 16; constexpr std::uint32_t Width = 64; @@ -76,7 +77,7 @@ alignas(256) constexpr std::array Input{ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3f030000, 0x3f600000, 0x3f400000, 0x00000000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, }; -alignas(256) std::array Buffer{}; +alignas(256) std::array Buffer{}; alignas(256) std::array Texels{}; alignas(256) constexpr std::array Code2D{ @@ -258,11 +259,28 @@ std::string Hex(std::uint32_t value) { return text; } +std::uint32_t SourceRow(std::uint32_t tid) { + return tid < Threads ? tid : (tid - Threads + Threads / 2u) % Threads; +} + +template +std::array MovedStores(const std::array& code, std::uint32_t threads) { + auto moved = code; + for (auto& word : moved) { + if ((word & 0xfffff000u) == 0xe0701000u && (word & 0xfffu) >= Threads * Inputs * 4u) word += (threads - Threads) * Inputs * 4u; + } + return moved; +} + template -void Run(AgcDriver::VulkanDevice& device, const std::array& code, std::uint32_t type, std::uint32_t height, std::uint32_t depth, +void Run(AgcDriver::VulkanDevice& device, const std::array& source, std::uint32_t threads, std::uint32_t type, std::uint32_t height, std::uint32_t depth, const std::array& names, const std::array& masked, const std::array& expected) { - std::copy(Input.begin(), Input.end(), Buffer.begin()); - std::fill(Buffer.begin() + Threads * Inputs, Buffer.end(), 0xdeadbeefu); + alignas(256) static std::array code; + code = MovedStores(source, threads); + for (std::uint32_t tid = 0; tid < threads; ++tid) { + std::copy_n(Input.begin() + SourceRow(tid) * Inputs, Inputs, Buffer.begin() + tid * Inputs); + } + std::fill(Buffer.begin() + threads * Inputs, Buffer.end(), 0xdeadbeefu); for (std::uint32_t z = 0; z < depth; ++z) { for (std::uint32_t y = 0; y < height; ++y) { for (std::uint32_t x = 0; x < Width; ++x) { @@ -280,10 +298,10 @@ void Run(AgcDriver::VulkanDevice& device, const std::array span(code); const std::array memory{{{reinterpret_cast(span.data()), std::as_bytes(span)}}}; - const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{threads, 1, 1}, 0u, {false, false, false}, false, 1}; ShaderRecompiler::RecompileRequest request{ {ShaderStage::Compute, reinterpret_cast(span.data()), span, 0, {}}, - {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + {threads, 0, userData, compute, std::nullopt, std::nullopt, memory}, device.Target(), {0, 0, 0, 128} }; @@ -291,13 +309,13 @@ void Run(AgcDriver::VulkanDevice& device, const std::array(span.data())); device.WaitIdle(); - for (std::uint32_t tid = 0; tid < Threads; ++tid) { + for (std::uint32_t tid = 0; tid < threads; ++tid) { for (std::uint32_t k = 0; k < Cases; ++k) { - if (masked[k] && device.Target().subgroupSize < Threads) continue; - const auto actual = Buffer[Threads * Inputs + tid * Results + k]; - const auto wanted = expected[tid * Cases + k]; + if (masked[k] && (device.Target().subgroupSize < Threads || tid >= Threads)) continue; + const auto actual = Buffer[threads * Inputs + tid * Results + k]; + const auto wanted = expected[SourceRow(tid) * Cases + k]; const bool close = std::fabs(std::bit_cast(actual) - std::bit_cast(wanted)) <= 1.0f / 64.0f; - Require(close, std::string(names[k]) + ": lane " + std::to_string(tid) + " is " + Hex(actual) + ", expected " + Hex(wanted)); + Require(close, std::string(names[k]) + ": wave" + std::to_string(threads) + " lane " + std::to_string(tid) + " is " + Hex(actual) + ", expected " + Hex(wanted)); } } } @@ -311,9 +329,13 @@ int main() { if (device->Target().subgroupSize < Threads) { std::printf("EXEC-masked cases skipped, subgroup size %u cannot hold a wave32\n", device->Target().subgroupSize); } - Run(*device, Code2D, 9u, Height, 1u, Names2D, Masked2D, Expected2D); - Run(*device, Code3D, 10u, Height, Depth, Names3D, Masked3D, Expected3D); - Run(*device, Code1D, 8u, 1u, 1u, Names1D, Masked1D, Expected1D); + std::vector waves{Threads}; + if (device->Target().subgroupSize >= Threads) waves.push_back(Wave64Threads); + for (const auto threads : waves) { + Run(*device, Code2D, threads, 9u, Height, 1u, Names2D, Masked2D, Expected2D); + Run(*device, Code3D, threads, 10u, Height, Depth, Names3D, Masked3D, Expected3D); + Run(*device, Code1D, threads, 8u, 1u, 1u, Names1D, Masked1D, Expected1D); + } std::puts("image sample derivatives tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryResourceHelpers.cpp b/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryResourceHelpers.cpp index e3e435d59..369b78f6c 100644 --- a/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryResourceHelpers.cpp +++ b/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryResourceHelpers.cpp @@ -118,12 +118,14 @@ IrValue* TranslationContext::makeImageAddress(const RdnaInstruction& inst, const IrValue& active = ir.BitwiseAnd(ir.ShiftRightLogical(word, ir.BitwiseAnd(quad, ir.Constant(31u))), ir.Constant(15u)); IrValue& first = ir.Emit(IrOpcode::FindILsb32, IrType::U32, {&active}); IrValue& source = ir.Select(ir.INotEqual(active, ir.Constant(0u)), ir.IAdd(quad, first), lane); + IrValue& address = ir.ShiftLeftLogical(source, ir.Constant(2u)); + IrValue& exec = ir.GetExec(); const std::uint32_t gradientStart = std::popcount(inst.imageSampleFlags & (RdnaImageSampleFlagOffset | RdnaImageSampleFlagBias | RdnaImageSampleFlagCompare)); const std::uint32_t gradients = 2u * ((inst.imageSampleFlags & RdnaImageSampleGradientCountMask) >> RdnaImageSampleGradientCountShift); const std::uint32_t firstDword = GetRdnaImageAddressComponentLayout(inst.imageSampleFlags, gradientStart).bitOffset / 32u; const std::uint32_t lastDword = GetRdnaImageAddressComponentLayout(inst.imageSampleFlags, gradientStart + gradients - 1u).bitOffset / 32u; for (std::uint32_t index = firstDword; index <= lastDword; ++index) { - components[index] = &ir.Emit(IrOpcode::ReadLane, IrType::U32, {components[index], &source}); + components[index] = &ir.Emit(IrOpcode::BpermuteU32, IrOpcodeType(IrOpcode::BpermuteU32), {components[index], &address, &exec}); } } return &ir.Emit(IrOpcode::MakeImageAddress, IrOpcodeType(IrOpcode::MakeImageAddress),