diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/SdwaCvtF32Ubyte.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/SdwaCvtF32Ubyte.cpp new file mode 100644 index 000000000..4468d5d22 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/SdwaCvtF32Ubyte.cpp @@ -0,0 +1,104 @@ +#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 + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 64; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 4; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array SdwaCode{ + 0x34020082, 0x34060082, 0xe0302000, 0x80000401, + 0x7e1402ff, 0xabcd1234, 0x7e1602ff, 0xabcd1234, 0x7e1802ff, 0xabcd1234, 0x7e1a02ff, 0xabcd1234, + 0xbf8c3f70, 0x7e1424f9, 0x00040604, 0x7e1624f9, 0x00050604, 0x7e1826f9, 0x000d0604, 0x7e1a28f9, 0x000b0604, + 0xe0702000, 0x80010a03, 0xe0702004, 0x80010b03, 0xe0702008, 0x80010c03, 0xe070200c, 0x80010d03, + 0xbf810000, +}; + +void FillInput() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Input[tid * Inputs] = tid | ((tid + 64u) << 8u) | ((255u - tid) << 16u) | (((tid * 3u + 128u) & 0xffu) << 24u); + } +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +std::uint32_t FloatBits(std::uint32_t value) { + return std::bit_cast(static_cast(value)); +} + +std::array BufferDescriptor(const void* data, std::uint32_t count) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu) | (4u << 16u), count, 0x11016facu}; +} + +void Run(AgcDriver::VulkanDevice& device) { + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size())); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size())); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(SdwaCode); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 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(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto source = Input[tid * Inputs]; + const auto* out = &Output[tid * Results]; + const auto where = [&](std::uint32_t result) { return "sdwa cvt f32 ubyte: thread " + std::to_string(tid) + " result " + std::to_string(result) + " is " + Hex(out[result]); }; + const auto byte1 = FloatBits((source >> 8u) & 0xffu); + const auto byte3 = FloatBits(source >> 24u); + const auto signFill = FloatBits((source & 0x80000000u) != 0u ? 0xffu : 0u); + Require(out[0] == byte1, where(0) + ", expected " + Hex(byte1) + ": v_cvt_f32_ubyte1 src0_sel:WORD_0 must convert bits 15:8"); + Require(out[1] == byte3, where(1) + ", expected " + Hex(byte3) + ": v_cvt_f32_ubyte1 src0_sel:WORD_1 must convert bits 31:24"); + Require(out[2] == signFill, where(2) + ", expected " + Hex(signFill) + ": v_cvt_f32_ubyte2 src0_sel:WORD_1 src0_sext must convert the sign extension of the high word"); + Require(out[3] == signFill, where(3) + ", expected " + Hex(signFill) + ": v_cvt_f32_ubyte3 src0_sel:BYTE_3 src0_sext must convert the sign extension of the top byte"); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + FillInput(); + Run(*device); + Check(); + std::puts("sdwa cvt f32 ubyte tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp index 1d9c25efe..c0c29d52f 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp @@ -1126,6 +1126,9 @@ constexpr Vop1SdwaRule vop1SdwaRules[] = { {RdnaOpcode::VCvtF32U32, sdwaSelAll(), 0, 0, false}, {RdnaOpcode::VCvtF32I32, sdwaSelAll(), 0, 0, false}, {RdnaOpcode::VCvtF32Ubyte0, sdwaSelAll(), 0, 0, false}, + {RdnaOpcode::VCvtF32Ubyte1, sdwaSelAll(), 0, 0, false}, + {RdnaOpcode::VCvtF32Ubyte2, sdwaSelAll(), 0, 0, false}, + {RdnaOpcode::VCvtF32Ubyte3, sdwaSelAll(), 0, 0, false}, {RdnaOpcode::VNotB32, sdwaSelAll(), sdwaSelBytes() | sdwaSelWords(), sdwaSelAll(), false}, {RdnaOpcode::VFfblB32, sdwaSelAll(), 0, 0, false}, {RdnaOpcode::VFfbhU32, sdwaSelAll(), 0, 0, false}, diff --git a/docs/dev/debt/shader.md b/docs/dev/debt/shader.md index de34303b9..7163d5530 100644 --- a/docs/dev/debt/shader.md +++ b/docs/dev/debt/shader.md @@ -113,3 +113,4 @@ - [Relative vector addressing](../../../core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp) (`v_movrels_b32`, `v_movreld_b32`, `v_movrelsd_b32`, `v_movrelsd_2_b32`, `v_swaprel_b32`) keeps every vector register from the lowest base register the program addresses relatively up to the last register the program names in a zero-initialised function-scope array per invocation (`LoadVectorWindow`/`StoreVectorWindow`, one element per register), so an indexed access is one load or store instead of a compare-and-select chain over the window; the host compiler decides whether that array stays in registers or spills to scratch, which is not measured. A register in the window the program never wrote reads 0, as an unwritten SSA register does; the console's value is undefined. M0 is unsigned: an index at or past the window reads `v0` and drops its write, and `v_swaprel_b32` writes neither side when either is out of range, as measured on RDNA2 (gfx1035, `agc_driver_relative_indexing`). The window ends at the last register the program names rather than at the shader's VGPR allocation, which the recompiler does not receive, so a relative write past the named registers but inside the allocation is dropped where the hardware keeps it for a later relative read. Killzone: Liberation's PSP vertex program (PPSA11142, 3176 ISA words, 104 relative accesses over 154 registers) went from 649,515 to 392,333 SPIR-V words with this. Pixel programs whose relative span contains the packed ancillary input instead keep that span in SSA and implement indexed operations with compare/select chains. This keeps ancillary definitions visible to field lowering and masked-write proofs; storing the unmodelled whole word in a private array would require composing unknown bits. A fold after masked-select elimination lowers fields whose consumers become visible only after redundant copies are removed. A whole-word use that the masked-select pass keeps gets the composed word described under the packed pixel ancillary word. Both representations include implicit entry inputs in the inferred register limit and retain unsigned M0, inactive-write and DPP update rules; wave32/wave64 native Vulkan readbacks cover relative reads, writes, split-offset swaps, inactive writes, DPP masks and out-of-range accesses, but console comparison and the performance cost of selecting over this span are unverified. - [s_call_b64](../../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp) saves `PC + 4` over `GetShaderBase` and branches to `PC + 4 + signext(SIMM16) * 4`, following [AMD RDNA2 ISA](https://gpuopen.com/download/rdna2-shader-instruction-set-architecture.pdf) sections 3.6.3, 12.2 and 12.3. Forward callees after `s_endpgm` are decoded. Measured on RDNA2 (gfx1036) in wave32 and wave64 with SIMM16 = 3: the link pair receives `PC + 4`, the branch goes to `PC + 4 + signext(SIMM16) * 4`, and `s_setpc_b64` of the link returns to the instruction after the call. Execution on the PS5 (gfx1013) and title compatibility for these bounded scalar calls remain unverified; `s_rfe_b64` still throws in translation. - [BDA span reads](../../../core/shader/recompiler/SpirvBackend/src/SpirvBdaRead.cpp) (shader recompiler) - `read_bda_span` is one selection: one arm calls `load_bda_span`, a `DontInline` function that holds the in-place dword loads and calls nothing, and the other arm is the per-dword byte fallback. Two other shapes of the same reads each hang a group of NVIDIA drivers, where a dispatch never finishes and the device is lost. With the loads emitted in the arm itself, a dispatch whose lanes took both arms hung on an RTX 3060 and an RTX 4060 Laptop GPU (Windows, driver 591.86) and an RTX 5060 (Linux, driver 590.48.01) ([#3291](https://github.com/boykopovar/AnyPS5/issues/3291)). With the loads and the fallback as two selections, the first under the span's condition and the second under its negation, it hung on an RTX 4070 Laptop GPU (Windows, drivers 595.97 and 596.36), an RTX 5080 (Windows, driver 610.62) and an RTX 4090 (Linux, driver 610.57.04) ([#4975](https://github.com/boykopovar/AnyPS5/issues/4975)). The current shape is measured on the RTX 4090 with driver 610.57.04 only, where it does not hang with or without SPIRV-Tools; a shape with the loads in their own `DontInline` function was reported passing on driver 591.86 in #3291. The cause inside the driver is not known. +- [SDWA source selects on `v_cvt_f32_ubyte1`, `v_cvt_f32_ubyte2` and `v_cvt_f32_ubyte3`](../../../core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp) (shader recompiler) - byte N is taken from source 0 after `SRC0_SEL` and `SRC0_SEXT` select and extend it, since SDWA supplies source 0 (RDNA 2 ISA guide 6.8 and 13.3.7) and the opcode reads `S0.u[8N+7:8N]` (12.8). Not measured on hardware.