diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/VopcCompareF64.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/VopcCompareF64.cpp new file mode 100644 index 000000000..8a9f1b893 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/VopcCompareF64.cpp @@ -0,0 +1,217 @@ +#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 + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 8; +constexpr std::uint32_t Results = 4; +constexpr std::uint32_t Compares = 41; +constexpr std::uint32_t RandomRounds = 4; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array CompareCode{ + 0x34020083, 0x34060082, 0xe0302000, 0x80000401, 0xe0302004, 0x80000501, 0xe0302008, 0x80000601, + 0xe030200c, 0x80000701, 0xe0302010, 0x80000801, 0xe0302014, 0x80000901, 0xe0302018, 0x80000e01, + 0xe030201c, 0x80000f01, 0x7e140280, 0x7e160280, 0xbf8c3f70, 0x7c400d04, 0xd5010014, 0x01a90280, + 0xd76f000a, 0x04290114, 0x7c420d04, 0xd5010014, 0x01a90280, 0xd76f000a, 0x04290314, 0x7c440d04, + 0xd5010014, 0x01a90280, 0xd76f000a, 0x04290514, 0x7c460d04, 0xd5010014, 0x01a90280, 0xd76f000a, + 0x04290714, 0x7c480d04, 0xd5010014, 0x01a90280, 0xd76f000a, 0x04290914, 0x7c4a0d04, 0xd5010014, + 0x01a90280, 0xd76f000a, 0x04290b14, 0x7c4c0d04, 0xd5010014, 0x01a90280, 0xd76f000a, 0x04290d14, + 0x7c4e0d04, 0xd5010014, 0x01a90280, 0xd76f000a, 0x04290f14, 0x7c500d04, 0xd5010014, 0x01a90280, + 0xd76f000a, 0x04291114, 0x7c520d04, 0xd5010014, 0x01a90280, 0xd76f000a, 0x04291314, 0x7c540d04, + 0xd5010014, 0x01a90280, 0xd76f000a, 0x04291514, 0x7c560d04, 0xd5010014, 0x01a90280, 0xd76f000a, + 0x04291714, 0x7c580d04, 0xd5010014, 0x01a90280, 0xd76f000a, 0x04291914, 0x7c5a0d04, 0xd5010014, + 0x01a90280, 0xd76f000a, 0x04291b14, 0x7c5c0d04, 0xd5010014, 0x01a90280, 0xd76f000a, 0x04291d14, + 0x7c5e0d04, 0xd5010014, 0x01a90280, 0xd76f000a, 0x04291f14, 0x7d501104, 0xd5010014, 0x01a90280, + 0xd76f000a, 0x04292114, 0xbe9e037e, 0x7e280280, 0x7c600d04, 0x7e280281, 0xbefe031e, 0xd76f000a, + 0x04292314, 0xbe9e037e, 0x7e280280, 0x7c620d04, 0x7e280281, 0xbefe031e, 0xd76f000a, 0x04292514, + 0xbe9e037e, 0x7e280280, 0x7c640d04, 0x7e280281, 0xbefe031e, 0xd76f000a, 0x04292714, 0xbe9e037e, + 0x7e280280, 0x7c660d04, 0x7e280281, 0xbefe031e, 0xd76f000a, 0x04292914, 0xbe9e037e, 0x7e280280, + 0x7c680d04, 0x7e280281, 0xbefe031e, 0xd76f000a, 0x04292b14, 0xbe9e037e, 0x7e280280, 0x7c6a0d04, + 0x7e280281, 0xbefe031e, 0xd76f000a, 0x04292d14, 0xbe9e037e, 0x7e280280, 0x7c6c0d04, 0x7e280281, + 0xbefe031e, 0xd76f000a, 0x04292f14, 0xbe9e037e, 0x7e280280, 0x7c6e0d04, 0x7e280281, 0xbefe031e, + 0xd76f000a, 0x04293114, 0xbe9e037e, 0x7e280280, 0x7c700d04, 0x7e280281, 0xbefe031e, 0xd76f000a, + 0x04293314, 0xbe9e037e, 0x7e280280, 0x7c720d04, 0x7e280281, 0xbefe031e, 0xd76f000a, 0x04293514, + 0xbe9e037e, 0x7e280280, 0x7c740d04, 0x7e280281, 0xbefe031e, 0xd76f000a, 0x04293714, 0xbe9e037e, + 0x7e280280, 0x7c760d04, 0x7e280281, 0xbefe031e, 0xd76f000a, 0x04293914, 0xbe9e037e, 0x7e280280, + 0x7c780d04, 0x7e280281, 0xbefe031e, 0xd76f000a, 0x04293b14, 0xbe9e037e, 0x7e280280, 0x7c7a0d04, + 0x7e280281, 0xbefe031e, 0xd76f000a, 0x04293d14, 0xbe9e037e, 0x7e280280, 0x7c7c0d04, 0x7e280281, + 0xbefe031e, 0xd76f000a, 0x04293f14, 0xbe9e037e, 0x7e280280, 0x7c7e0d04, 0x7e280281, 0xbefe031e, + 0xd76f000b, 0x042d0114, 0xbe9e037e, 0x7e280280, 0x7d701104, 0x7e280281, 0xbefe031e, 0xd76f000b, + 0x042d0314, 0x7c4208ff, 0x40080000, 0xd5010014, 0x01a90280, 0xd76f000b, 0x042d0514, 0x7c4c08f2, + 0xd5010014, 0x01a90280, 0xd76f000b, 0x042d0714, 0x7c4408f8, 0xd5010014, 0x01a90280, 0xd76f000b, + 0x042d0914, 0x7c420881, 0xd5010014, 0x01a90280, 0xd76f000b, 0x042d0b14, 0xd421016a, 0x00020d04, + 0xd5010014, 0x01a90280, 0xd76f000b, 0x042d0d14, 0xd4a8016a, 0x00021104, 0xd5010014, 0x01a90280, + 0xd76f000b, 0x042d0f14, 0xbe9e037e, 0x7e280280, 0xd43b037e, 0x00020d04, 0x7e280281, 0xbefe031e, + 0xd76f000b, 0x042d1114, 0xe0702000, 0x80010a03, 0xe0702004, 0x80010b03, 0xbf810000, +}; + +constexpr std::array Edges{ + 0x7ff8000000000000ull, 0x7ff0000000000001ull, 0xfff8000000000001ull, 0x7ff0000100000000ull, 0x7ff0000000000000ull, + 0xfff0000000000000ull, 0x0000000000000000ull, 0x8000000000000000ull, 0x0000000000000001ull, 0x0000000000000002ull, + 0x800fffffffffffffull, 0x0010000000000000ull, 0x3ff0000000000000ull, 0x3ff0000000000001ull, 0xbff0000000000000ull, + 0x4008000000000000ull, 0xc008000000000000ull, 0x3fc45f306dc9c882ull, 0x3fc45f3060000000ull, 0x7fefffffffffffffull, +}; + +constexpr std::array Masks{ + 0x001u, 0x002u, 0x004u, 0x008u, 0x010u, 0x020u, 0x040u, 0x080u, 0x100u, 0x200u, 0x3ffu, 0x000u, 0x155u, 0x2aau, 0xfffffc00u, 0x207u, +}; + +constexpr std::uint32_t EdgeRounds = (Edges.size() * Edges.size() + Threads - 1u) / Threads; + +void FillInput(std::uint32_t round) { + std::uint64_t state = 0x853c49e6748fea9bull + round; + const auto next = [&] { + state = state * 6364136223846793005ull + 1442695040888963407ull; + const auto high = static_cast(state >> 32u); + state = state * 6364136223846793005ull + 1442695040888963407ull; + return (static_cast(high) << 32u) | static_cast(state >> 32u); + }; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + std::uint64_t a = 0u; + std::uint64_t b = 0u; + std::uint32_t mask = 0u; + if (round < EdgeRounds) { + const std::uint32_t pair = round * Threads + tid; + const std::uint32_t first = pair % Edges.size(); + const std::uint32_t second = (pair / Edges.size()) % Edges.size(); + a = Edges[first]; + b = Edges[second]; + mask = Masks[(first + second) % Masks.size()]; + } else { + a = next(); + b = tid % 5u == 0u ? a : (tid % 7u == 0u ? a ^ 0x8000000000000000ull : next()); + mask = static_cast(next()); + } + auto* words = &Input[tid * Inputs]; + words[0] = static_cast(a); + words[1] = static_cast(a >> 32u); + words[2] = static_cast(b); + words[3] = static_cast(b >> 32u); + words[4] = mask; + } +} + +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, 0x01016facu}; +} + +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(CompareCode); + 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(); +} + +std::uint32_t ClassBit(std::uint64_t bits) { + const bool negative = (bits >> 63u) != 0u; + switch (std::fpclassify(std::bit_cast(bits))) { + case FP_NAN: return (bits & 0x0008000000000000ull) != 0u ? 1u : 0u; + case FP_INFINITE: return negative ? 2u : 9u; + case FP_NORMAL: return negative ? 3u : 8u; + case FP_SUBNORMAL: return negative ? 4u : 7u; + default: return negative ? 5u : 6u; + } +} + +bool ClassMatch(std::uint64_t bits, std::uint32_t mask) { + return ((mask >> ClassBit(bits)) & 1u) != 0u; +} + +void Check(std::uint32_t round) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto* in = &Input[tid * Inputs]; + const std::uint64_t abits = in[0] | (static_cast(in[1]) << 32u); + const std::uint64_t bbits = in[2] | (static_cast(in[3]) << 32u); + const std::uint32_t mask = in[4]; + const double a = std::bit_cast(abits); + const double b = std::bit_cast(bbits); + const bool unordered = std::isnan(a) || std::isnan(b); + const std::array base{ + false, + a < b, + a == b, + a <= b, + a > b, + a < b || a > b, + a >= b, + !unordered, + unordered, + !(a >= b), + !(a < b || a > b), + !(a > b), + !(a <= b), + !(a == b), + !(a < b), + true, + ClassMatch(abits, mask), + }; + std::array expected{}; + for (std::uint32_t k = 0; k < base.size(); ++k) { + expected[k] = base[k]; + expected[base.size() + k] = base[k]; + } + expected[34] = 3.0 < a; + expected[35] = 1.0 >= a; + expected[36] = std::bit_cast(0x3fc45f306dc9c882ull) == a; + expected[37] = std::numeric_limits::denorm_min() < a; + expected[38] = std::fabs(a) < b; + expected[39] = ClassMatch(abits & 0x7fffffffffffffffull, mask); + expected[40] = !(std::fabs(a) > std::fabs(b)); + for (std::uint32_t k = 0; k < expected.size(); ++k) { + const bool actual = ((Output[tid * Results + k / 32u] >> (k % 32u)) & 1u) != 0u; + Require(actual == expected[k], "vopc f64 compares: round " + std::to_string(round) + " thread " + std::to_string(tid) + " compare " + std::to_string(k) + " is " + std::to_string(actual) + ", expected " + std::to_string(expected[k])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + for (std::uint32_t round = 0; round < EdgeRounds + RandomRounds; ++round) { + FillInput(round); + Run(*device); + Check(round); + } + std::puts("vopc f64 compare 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/RdnaOpcode.hpp b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp index 78b707566..0f76c74ca 100644 --- a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp +++ b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp @@ -580,6 +580,40 @@ enum class RdnaOpcode : std::uint16_t { VCmpxNlgF16, VCmpxNleF16, VCmpxTruF16, + VCmpFF64, + VCmpLtF64, + VCmpEqF64, + VCmpLeF64, + VCmpGtF64, + VCmpLgF64, + VCmpGeF64, + VCmpOF64, + VCmpUF64, + VCmpNgeF64, + VCmpNlgF64, + VCmpNgtF64, + VCmpNleF64, + VCmpNeqF64, + VCmpNltF64, + VCmpTruF64, + VCmpClassF64, + VCmpxFF64, + VCmpxLtF64, + VCmpxEqF64, + VCmpxLeF64, + VCmpxGtF64, + VCmpxLgF64, + VCmpxGeF64, + VCmpxOF64, + VCmpxUF64, + VCmpxNgeF64, + VCmpxNlgF64, + VCmpxNgtF64, + VCmpxNleF64, + VCmpxNeqF64, + VCmpxNltF64, + VCmpxTruF64, + VCmpxClassF64, VCmpxLtU32, VCmpxEqU32, VCmpxLeU32, diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp index 4d84433bb..5696a630b 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp @@ -529,6 +529,40 @@ bool IsVectorAluOpcode(RdnaOpcode opcode) { case RdnaOpcode::VCmpxNlgF16: case RdnaOpcode::VCmpxNleF16: case RdnaOpcode::VCmpxTruF16: + case RdnaOpcode::VCmpFF64: + case RdnaOpcode::VCmpLtF64: + case RdnaOpcode::VCmpEqF64: + case RdnaOpcode::VCmpLeF64: + case RdnaOpcode::VCmpGtF64: + case RdnaOpcode::VCmpLgF64: + case RdnaOpcode::VCmpGeF64: + case RdnaOpcode::VCmpOF64: + case RdnaOpcode::VCmpUF64: + case RdnaOpcode::VCmpNgeF64: + case RdnaOpcode::VCmpNlgF64: + case RdnaOpcode::VCmpNgtF64: + case RdnaOpcode::VCmpNleF64: + case RdnaOpcode::VCmpNeqF64: + case RdnaOpcode::VCmpNltF64: + case RdnaOpcode::VCmpTruF64: + case RdnaOpcode::VCmpClassF64: + case RdnaOpcode::VCmpxFF64: + case RdnaOpcode::VCmpxLtF64: + case RdnaOpcode::VCmpxEqF64: + case RdnaOpcode::VCmpxLeF64: + case RdnaOpcode::VCmpxGtF64: + case RdnaOpcode::VCmpxLgF64: + case RdnaOpcode::VCmpxGeF64: + case RdnaOpcode::VCmpxOF64: + case RdnaOpcode::VCmpxUF64: + case RdnaOpcode::VCmpxNgeF64: + case RdnaOpcode::VCmpxNlgF64: + case RdnaOpcode::VCmpxNgtF64: + case RdnaOpcode::VCmpxNleF64: + case RdnaOpcode::VCmpxNeqF64: + case RdnaOpcode::VCmpxNltF64: + case RdnaOpcode::VCmpxTruF64: + case RdnaOpcode::VCmpxClassF64: case RdnaOpcode::VCmpxNeU64: case RdnaOpcode::VCmpxLtU32: case RdnaOpcode::VCmpxEqU32: diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp index 8b6dc65bb..4587c519d 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp @@ -384,6 +384,40 @@ constexpr VopcOpcodeInfo vopcOpcodes[] = { {0xfau, RdnaOpcode::VCmpxNlgF16}, {0xfcu, RdnaOpcode::VCmpxNleF16}, {0xffu, RdnaOpcode::VCmpxTruF16}, + {0x20u, RdnaOpcode::VCmpFF64, false}, + {0x21u, RdnaOpcode::VCmpLtF64, false}, + {0x22u, RdnaOpcode::VCmpEqF64, false}, + {0x23u, RdnaOpcode::VCmpLeF64, false}, + {0x24u, RdnaOpcode::VCmpGtF64, false}, + {0x25u, RdnaOpcode::VCmpLgF64, false}, + {0x26u, RdnaOpcode::VCmpGeF64, false}, + {0x27u, RdnaOpcode::VCmpOF64, false}, + {0x28u, RdnaOpcode::VCmpUF64, false}, + {0x29u, RdnaOpcode::VCmpNgeF64, false}, + {0x2au, RdnaOpcode::VCmpNlgF64, false}, + {0x2bu, RdnaOpcode::VCmpNgtF64, false}, + {0x2cu, RdnaOpcode::VCmpNleF64, false}, + {0x2du, RdnaOpcode::VCmpNeqF64, false}, + {0x2eu, RdnaOpcode::VCmpNltF64, false}, + {0x2fu, RdnaOpcode::VCmpTruF64, false}, + {0x30u, RdnaOpcode::VCmpxFF64, false}, + {0x31u, RdnaOpcode::VCmpxLtF64, false}, + {0x32u, RdnaOpcode::VCmpxEqF64, false}, + {0x33u, RdnaOpcode::VCmpxLeF64, false}, + {0x34u, RdnaOpcode::VCmpxGtF64, false}, + {0x35u, RdnaOpcode::VCmpxLgF64, false}, + {0x36u, RdnaOpcode::VCmpxGeF64, false}, + {0x37u, RdnaOpcode::VCmpxOF64, false}, + {0x38u, RdnaOpcode::VCmpxUF64, false}, + {0x39u, RdnaOpcode::VCmpxNgeF64, false}, + {0x3au, RdnaOpcode::VCmpxNlgF64, false}, + {0x3bu, RdnaOpcode::VCmpxNgtF64, false}, + {0x3cu, RdnaOpcode::VCmpxNleF64, false}, + {0x3du, RdnaOpcode::VCmpxNeqF64, false}, + {0x3eu, RdnaOpcode::VCmpxNltF64, false}, + {0x3fu, RdnaOpcode::VCmpxTruF64, false}, + {0xa8u, RdnaOpcode::VCmpClassF64, false}, + {0xb8u, RdnaOpcode::VCmpxClassF64, false}, }; constexpr VectorOpcodeInfo vop3Opcodes[] = { @@ -716,7 +750,50 @@ bool isVop1FloatResultOpcode(RdnaOpcode opcode) { } } +bool isVopcFloat64CompareOpcode(RdnaOpcode opcode) { + switch (opcode) { + case RdnaOpcode::VCmpFF64: + case RdnaOpcode::VCmpLtF64: + case RdnaOpcode::VCmpEqF64: + case RdnaOpcode::VCmpLeF64: + case RdnaOpcode::VCmpGtF64: + case RdnaOpcode::VCmpLgF64: + case RdnaOpcode::VCmpGeF64: + case RdnaOpcode::VCmpOF64: + case RdnaOpcode::VCmpUF64: + case RdnaOpcode::VCmpNgeF64: + case RdnaOpcode::VCmpNlgF64: + case RdnaOpcode::VCmpNgtF64: + case RdnaOpcode::VCmpNleF64: + case RdnaOpcode::VCmpNeqF64: + case RdnaOpcode::VCmpNltF64: + case RdnaOpcode::VCmpTruF64: + case RdnaOpcode::VCmpClassF64: + case RdnaOpcode::VCmpxFF64: + case RdnaOpcode::VCmpxLtF64: + case RdnaOpcode::VCmpxEqF64: + case RdnaOpcode::VCmpxLeF64: + case RdnaOpcode::VCmpxGtF64: + case RdnaOpcode::VCmpxLgF64: + case RdnaOpcode::VCmpxGeF64: + case RdnaOpcode::VCmpxOF64: + case RdnaOpcode::VCmpxUF64: + case RdnaOpcode::VCmpxNgeF64: + case RdnaOpcode::VCmpxNlgF64: + case RdnaOpcode::VCmpxNgtF64: + case RdnaOpcode::VCmpxNleF64: + case RdnaOpcode::VCmpxNeqF64: + case RdnaOpcode::VCmpxNltF64: + case RdnaOpcode::VCmpxTruF64: + case RdnaOpcode::VCmpxClassF64: return true; + default: return false; + } +} + bool isVopcFloatCompareOpcode(RdnaOpcode opcode) { + if (isVopcFloat64CompareOpcode(opcode)) { + return true; + } switch (opcode) { case RdnaOpcode::VCmpFF32: case RdnaOpcode::VCmpLtF32: @@ -1323,7 +1400,24 @@ bool isVopcCompareExec(RdnaOpcode opcode) { case RdnaOpcode::VCmpxGeF16: case RdnaOpcode::VCmpxNgtF16: case RdnaOpcode::VCmpxNeqF16: - case RdnaOpcode::VCmpxNltF16: return true; + case RdnaOpcode::VCmpxNltF16: + case RdnaOpcode::VCmpxFF64: + case RdnaOpcode::VCmpxLtF64: + case RdnaOpcode::VCmpxEqF64: + case RdnaOpcode::VCmpxLeF64: + case RdnaOpcode::VCmpxGtF64: + case RdnaOpcode::VCmpxLgF64: + case RdnaOpcode::VCmpxGeF64: + case RdnaOpcode::VCmpxOF64: + case RdnaOpcode::VCmpxUF64: + case RdnaOpcode::VCmpxNgeF64: + case RdnaOpcode::VCmpxNlgF64: + case RdnaOpcode::VCmpxNgtF64: + case RdnaOpcode::VCmpxNleF64: + case RdnaOpcode::VCmpxNeqF64: + case RdnaOpcode::VCmpxNltF64: + case RdnaOpcode::VCmpxTruF64: + case RdnaOpcode::VCmpxClassF64: return true; default: return false; } } @@ -1774,6 +1868,9 @@ RdnaInstruction DecodeRdnaVopc(std::uint32_t programCounter, std::span readU32Pair(const RdnaOperand& operand); IrU64 readU64(const RdnaOperand& operand); + std::array readF64Bits(const RdnaOperand& operand); IrF32 readF16LaneAsF32(const RdnaOperand& operand, bool highLane, bool packed = false); IrF32 readF16AsF32(const RdnaOperand& operand); IrU32 readF16SourceBits(const RdnaOperand& operand); @@ -120,6 +121,10 @@ private: void emitInteger64Order(const RdnaInstruction& inst, bool signedValue, bool swap, bool negate, bool cmpx); void emitFloatOrderedCompare(const RdnaInstruction& inst, bool ordered, bool half, bool cmpx); void emitFloatClassCompare(const RdnaInstruction& inst, bool cmpx); + IrU1 float64IsNan(const std::array& bits); + IrU64 float64OrderKey(const std::array& bits); + void emitFloat64Compare(const RdnaInstruction& inst, bool less, bool equal, bool greater, bool unordered, bool cmpx); + void emitFloat64ClassCompare(const RdnaInstruction& inst, bool cmpx); void vCvtF32Ubyte(const RdnaInstruction& inst, std::uint32_t byteIndex); void vCvtF32U32(const RdnaInstruction& inst); void vCvtF32I32(const RdnaInstruction& inst); diff --git a/core/shader/recompiler/Translation/src/CompareInstructions.cpp b/core/shader/recompiler/Translation/src/CompareInstructions.cpp index 1c3e837b3..9a8766729 100644 --- a/core/shader/recompiler/Translation/src/CompareInstructions.cpp +++ b/core/shader/recompiler/Translation/src/CompareInstructions.cpp @@ -79,6 +79,74 @@ void TranslationContext::emitFloatClassCompare(const RdnaInstruction& inst, bool emitCompareResult(inst, IrU1(ir.Emit(IrOpcode::FPCmpClass32, IrType::U1, {value, mask})), false, cmpx); } +IrU1 TranslationContext::float64IsNan(const std::array& bits) { + IrValue& magnitude = ir.ConstructU64(bits[0].Value(), ir.BitwiseAnd(bits[1].Value(), ir.Constant(0x7fffffffu))); + return IrU1(ir.Emit(IrOpcode::UGreaterThan64, IrType::U1, {&magnitude, &ir.ConstantU64(0x7ff0000000000000ull)})); +} + +IrU64 TranslationContext::float64OrderKey(const std::array& bits) { + IrValue& magnitude = ir.ConstructU64(bits[0].Value(), ir.BitwiseAnd(bits[1].Value(), ir.Constant(0x7fffffffu))); + IrValue& signWord = ir.ShiftRightArithmetic(bits[1].Value(), ir.Constant(31u)); + IrValue& signMask = ir.ConstructU64(signWord, signWord); + IrValue& negativeMagnitude = ir.Emit(IrOpcode::BitwiseAnd64, IrType::U64, {&magnitude, &signMask}); + IrValue& doubled = ir.Emit(IrOpcode::ShiftLeftLogical64, IrType::U64, {&negativeMagnitude, &ir.Constant(1u)}); + return IrU64(ir.Emit(IrOpcode::ISub64, IrType::U64, {&magnitude, &doubled})); +} + +void TranslationContext::emitFloat64Compare(const RdnaInstruction& inst, bool less, bool equal, bool greater, bool unordered, bool cmpx) { + const std::array lhs = readF64Bits(sourceAt(inst, 0u)); + const std::array rhs = readF64Bits(sourceAt(inst, 1u)); + const IrU1 lhsNan = float64IsNan(lhs); + const IrU1 rhsNan = float64IsNan(rhs); + IrValue& isUnordered = ir.LogicalOr(lhsNan.Value(), rhsNan.Value()); + const IrU64 lhsKey = float64OrderKey(lhs); + const IrU64 rhsKey = float64OrderKey(rhs); + IrValue* relation = nullptr; + const auto include = [&](IrValue& term) { + relation = relation == nullptr ? &term : &ir.LogicalOr(*relation, term); + }; + if (less) { + include(ir.Emit(IrOpcode::SLessThan64, IrType::U1, {&lhsKey.Value(), &rhsKey.Value()})); + } + if (equal) { + include(ir.Emit(IrOpcode::IEqual64, IrType::U1, {&lhsKey.Value(), &rhsKey.Value()})); + } + if (greater) { + include(ir.Emit(IrOpcode::SLessThan64, IrType::U1, {&rhsKey.Value(), &lhsKey.Value()})); + } + if (relation == nullptr) { + relation = &ir.ConstantBool(false); + } + IrValue& result = unordered ? ir.LogicalOr(isUnordered, *relation) : ir.LogicalAnd(ir.LogicalNot(isUnordered), *relation); + emitCompareResult(inst, IrU1(result), false, cmpx); +} + +void TranslationContext::emitFloat64ClassCompare(const RdnaInstruction& inst, bool cmpx) { + const RdnaOperand& maskOperand = sourceAt(inst, 1u); + if (maskOperand.absolute || maskOperand.negate) { + throw std::runtime_error("TranslationContext::emitFloat64ClassCompare class mask does not take source modifiers"); + } + const std::array bits = readF64Bits(sourceAt(inst, 0u)); + const IrU32 mask = readU32(maskOperand); + IrValue& zero = ir.Constant(0u); + IrValue& negative = ir.INotEqual(ir.BitwiseAnd(bits[1].Value(), ir.Constant(0x80000000u)), zero); + IrValue& exponent = ir.BitwiseAnd(bits[1].Value(), ir.Constant(0x7ff00000u)); + IrValue& fraction = ir.BitwiseOr(ir.BitwiseAnd(bits[1].Value(), ir.Constant(0x000fffffu)), bits[0].Value()); + IrValue& fractionNonZero = ir.INotEqual(fraction, zero); + IrValue& quiet = ir.INotEqual(ir.BitwiseAnd(bits[1].Value(), ir.Constant(0x00080000u)), zero); + IrValue& nan = ir.Select(quiet, ir.Constant(1u), zero); + IrValue& infinity = ir.Select(negative, ir.Constant(2u), ir.Constant(9u)); + IrValue& normal = ir.Select(negative, ir.Constant(3u), ir.Constant(8u)); + IrValue& denormal = ir.Select(negative, ir.Constant(4u), ir.Constant(7u)); + IrValue& signedZero = ir.Select(negative, ir.Constant(5u), ir.Constant(6u)); + IrValue& exponentMax = ir.Select(fractionNonZero, nan, infinity); + IrValue& exponentZero = ir.Select(fractionNonZero, denormal, signedZero); + IrValue& finite = ir.Select(ir.IEqual(exponent, zero), exponentZero, normal); + IrValue& index = ir.Select(ir.IEqual(exponent, ir.Constant(0x7ff00000u)), exponentMax, finite); + IrValue& selected = ir.BitwiseAnd(ir.ShiftRightLogical(mask.Value(), index), ir.Constant(1u)); + emitCompareResult(inst, IrU1(ir.INotEqual(selected, zero)), false, cmpx); +} + void TranslateCompareInstruction(TranslationContext& context, const RdnaInstruction& instruction) { throw std::runtime_error("TranslateCompareInstruction not implemented"); } diff --git a/core/shader/recompiler/Translation/src/TranslationContext/SubwordPacking.cpp b/core/shader/recompiler/Translation/src/TranslationContext/SubwordPacking.cpp index b70730250..903d821ee 100644 --- a/core/shader/recompiler/Translation/src/TranslationContext/SubwordPacking.cpp +++ b/core/shader/recompiler/Translation/src/TranslationContext/SubwordPacking.cpp @@ -28,6 +28,25 @@ IrU64 TranslationContext::readU64(const RdnaOperand& operand) { return IrU64(ir.ConstructU64(pair[0].Value(), pair[1].Value())); } +std::array TranslationContext::readF64Bits(const RdnaOperand& operand) { + std::array bits{IrU32(ir.Constant(0u)), IrU32(ir.Constant(operand.value))}; + if (operand.kind == RdnaOperandKind::FloatInlineConstant && operand.value == 0x3e22f983u) { + bits = {IrU32(ir.Constant(0x6dc9c882u)), IrU32(ir.Constant(0x3fc45f30u))}; + } else if (operand.kind != RdnaOperandKind::LiteralConstant) { + RdnaOperand source = operand; + source.absolute = false; + source.negate = false; + bits = readU32Pair(source); + } + if (operand.absolute) { + bits[1] = IrU32(ir.BitwiseAnd(bits[1].Value(), ir.Constant(0x7fffffffu))); + } + if (operand.negate) { + bits[1] = IrU32(ir.BitwiseXor(bits[1].Value(), ir.Constant(0x80000000u))); + } + return bits; +} + std::array TranslationContext::extractU64(IrU64 value) { return {IrU32(ir.CompositeExtract(value.Value(), 0u)), IrU32(ir.CompositeExtract(value.Value(), 1u))}; } diff --git a/core/shader/recompiler/Translation/src/VectorInstructions.cpp b/core/shader/recompiler/Translation/src/VectorInstructions.cpp index 01ccbdcc4..91ece4f3d 100644 --- a/core/shader/recompiler/Translation/src/VectorInstructions.cpp +++ b/core/shader/recompiler/Translation/src/VectorInstructions.cpp @@ -534,6 +534,108 @@ bool TranslationContext::emitVector(const RdnaInstruction& inst) { case RdnaOpcode::VCmpxClassF32: emitFloatClassCompare(inst, true); return true; + case RdnaOpcode::VCmpFF64: + emitCompareConstant(inst, false, false, false); + return true; + case RdnaOpcode::VCmpLtF64: + emitFloat64Compare(inst, true, false, false, false, false); + return true; + case RdnaOpcode::VCmpEqF64: + emitFloat64Compare(inst, false, true, false, false, false); + return true; + case RdnaOpcode::VCmpLeF64: + emitFloat64Compare(inst, true, true, false, false, false); + return true; + case RdnaOpcode::VCmpGtF64: + emitFloat64Compare(inst, false, false, true, false, false); + return true; + case RdnaOpcode::VCmpLgF64: + emitFloat64Compare(inst, true, false, true, false, false); + return true; + case RdnaOpcode::VCmpGeF64: + emitFloat64Compare(inst, false, true, true, false, false); + return true; + case RdnaOpcode::VCmpOF64: + emitFloat64Compare(inst, true, true, true, false, false); + return true; + case RdnaOpcode::VCmpUF64: + emitFloat64Compare(inst, false, false, false, true, false); + return true; + case RdnaOpcode::VCmpNgeF64: + emitFloat64Compare(inst, true, false, false, true, false); + return true; + case RdnaOpcode::VCmpNlgF64: + emitFloat64Compare(inst, false, true, false, true, false); + return true; + case RdnaOpcode::VCmpNgtF64: + emitFloat64Compare(inst, true, true, false, true, false); + return true; + case RdnaOpcode::VCmpNleF64: + emitFloat64Compare(inst, false, false, true, true, false); + return true; + case RdnaOpcode::VCmpNeqF64: + emitFloat64Compare(inst, true, false, true, true, false); + return true; + case RdnaOpcode::VCmpNltF64: + emitFloat64Compare(inst, false, true, true, true, false); + return true; + case RdnaOpcode::VCmpTruF64: + emitCompareConstant(inst, true, false, false); + return true; + case RdnaOpcode::VCmpClassF64: + emitFloat64ClassCompare(inst, false); + return true; + case RdnaOpcode::VCmpxFF64: + emitCompareConstant(inst, false, false, true); + return true; + case RdnaOpcode::VCmpxLtF64: + emitFloat64Compare(inst, true, false, false, false, true); + return true; + case RdnaOpcode::VCmpxEqF64: + emitFloat64Compare(inst, false, true, false, false, true); + return true; + case RdnaOpcode::VCmpxLeF64: + emitFloat64Compare(inst, true, true, false, false, true); + return true; + case RdnaOpcode::VCmpxGtF64: + emitFloat64Compare(inst, false, false, true, false, true); + return true; + case RdnaOpcode::VCmpxLgF64: + emitFloat64Compare(inst, true, false, true, false, true); + return true; + case RdnaOpcode::VCmpxGeF64: + emitFloat64Compare(inst, false, true, true, false, true); + return true; + case RdnaOpcode::VCmpxOF64: + emitFloat64Compare(inst, true, true, true, false, true); + return true; + case RdnaOpcode::VCmpxUF64: + emitFloat64Compare(inst, false, false, false, true, true); + return true; + case RdnaOpcode::VCmpxNgeF64: + emitFloat64Compare(inst, true, false, false, true, true); + return true; + case RdnaOpcode::VCmpxNlgF64: + emitFloat64Compare(inst, false, true, false, true, true); + return true; + case RdnaOpcode::VCmpxNgtF64: + emitFloat64Compare(inst, true, true, false, true, true); + return true; + case RdnaOpcode::VCmpxNleF64: + emitFloat64Compare(inst, false, false, true, true, true); + return true; + case RdnaOpcode::VCmpxNeqF64: + emitFloat64Compare(inst, true, false, true, true, true); + return true; + case RdnaOpcode::VCmpxNltF64: + emitFloat64Compare(inst, false, true, true, true, true); + return true; + case RdnaOpcode::VCmpxTruF64: + emitCompareConstant(inst, true, false, true); + return true; + case RdnaOpcode::VCmpxClassF64: + emitFloat64ClassCompare(inst, true); + return true; case RdnaOpcode::VCvtF32Ubyte0: vCvtF32Ubyte(inst, 0u); return true;