diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index b50dfcdd4..8f1b4b123 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -813,6 +813,8 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u require(available.vertexPipelineStoresAndAtomics && available.fragmentStoresAndAtomics, "graphics shader buffer writes and atomics are unavailable"); VkPhysicalDeviceFeatures enabled{}; enabled.shaderInt64 = VK_TRUE; + enabled.shaderFloat64 = available.shaderFloat64 && floatControls.shaderSignedZeroInfNanPreserveFloat64; + if (enabled.shaderFloat64) state->capabilities.push_back(spv::CapabilityFloat64); enabled.vertexPipelineStoresAndAtomics = VK_TRUE; enabled.fragmentStoresAndAtomics = VK_TRUE; enabled.tessellationShader = available.tessellationShader; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp index 2b671faec..2ec9f6b79 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp @@ -213,6 +213,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi capability == spv::CapabilityInt64 || capability == spv::CapabilityInt16 || capability == spv::CapabilityFloat16 || + capability == spv::CapabilityFloat64 || capability == spv::CapabilityStorageBuffer8BitAccess || capability == spv::CapabilityPhysicalStorageBufferAddresses || capability == spv::CapabilitySampledImageArrayDynamicIndexing || @@ -280,6 +281,11 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi for (std::size_t i = 5; i < count; ++i) Require(module.interface.insert(instruction[i]).second, "duplicate SPIR-V interface ID"); break; case spv::OpExecutionMode: + if (count == 4 && instruction[2] == spv::ExecutionModeSignedZeroInfNanPreserve) { + Require(instruction[3] == 32u || instruction[3] == 64u, "SignedZeroInfNanPreserve requires Float32 or Float64"); + executionModeTargets.insert(instruction[1]); + break; + } Require(count >= 3 && module.modes.emplace(instruction[2], std::vector(instruction.begin() + 3, instruction.end())).second, "duplicate or malformed execution mode"); executionModeTargets.insert(instruction[1]); if (instruction[2] == spv::ExecutionModeOriginUpperLeft) upperLeft = true; @@ -338,10 +344,6 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi Require(entries == 1 && memoryModels == 1, "SPIR-V must contain one entry point and memory model"); Require(entryPoint != 0 && std::all_of(executionModeTargets.begin(), executionModeTargets.end(), [&](auto target) { return target == entryPoint; }), "execution mode refers to a different entry point"); Require(!fragment || upperLeft, "fragment coordinates must use an upper-left origin"); - if (const auto preserve = module.modes.find(spv::ExecutionModeSignedZeroInfNanPreserve); preserve != module.modes.end()) { - Require(preserve->second == std::vector{32u}, "SignedZeroInfNanPreserve requires Float32"); - module.modes.erase(preserve); - } const auto mode = [&](std::uint32_t name, std::vector operands) { const auto it = module.modes.find(name); Require(it != module.modes.end() && it->second == operands, "missing or incompatible shader execution mode"); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/F64Arithmetic.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/F64Arithmetic.cpp new file mode 100644 index 000000000..8a3e6560c --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/F64Arithmetic.cpp @@ -0,0 +1,167 @@ +#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 = 4; +constexpr std::uint32_t Results = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xd564000a, 0x02020d04, 0xd565000c, + 0x02020d04, 0xd54c000e, 0x041a0d04, 0xd54c0010, 0x84320d04, 0xd5660012, 0x02020d04, 0xd5670014, + 0x02020d04, 0xd5680016, 0x02020d04, 0xd5640318, 0x22020d04, 0xe0781000, 0x80010a03, 0xe0781010, + 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x00000000u, 0x3ff00000u, 0x80000000u, 0x40000000u}, + {0x00000000u, 0x3ff80000u, 0x7fffffffu, 0x40000000u}, + {0xffffffffu, 0x7fefffffu, 0xfffff7ccu, 0x3ff00000u}, + {0x00000000u, 0xbff00000u, 0xfffffbcdu, 0x3ff00000u}, + {0x00000000u, 0x3fe00000u, 0x00000401u, 0x3ff00000u}, + {0x00000000u, 0xfff00000u, 0xffc00000u, 0x41dfffffu}, + {0x8a59a474u, 0xbefa45c5u, 0x12eaf8e8u, 0xc020f641u}, + {0x00000000u, 0x3ff00000u, 0x00000001u, 0x7ff00000u}, + {0x00000001u, 0xfff80000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0xbff00000u, 0x00000000u, 0x7ff00000u}, + {0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff80000u, 0x00000000u, 0x7ff00000u}, + {0xd9787a36u, 0xfff7c837u, 0x00000000u, 0x41f00000u}, + {0x00000000u, 0x3ff80000u, 0xffffffffu, 0xffefffffu}, + {0x00000000u, 0xfff00000u, 0x00000000u, 0xfff00000u}, + {0x047b57e5u, 0x02f22376u, 0xc66ab30du, 0x8e9a2140u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0xbff00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x3fe00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0xbfe00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x3ff80000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x40040000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0xc0040000u}, + {0x00000000u, 0x00000000u, 0xffffffffu, 0x7fefffffu}, + {0x00000000u, 0x00000000u, 0xffffffffu, 0xffefffffu}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x7ff80000u}, + {0x00000000u, 0x00000000u, 0x00000001u, 0xfff80000u}, + {0x00000000u, 0x00000000u, 0x00000001u, 0x7ff00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x7ff40000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x41e00000u} +}; +constexpr std::uint32_t Expected[32][16] = { + {0x80000000u, 0x40080000u, 0x80000000u, 0x40000000u, 0x80000000u, 0x40100000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x80000000u, 0x40000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00001u}, + {0x7fffffffu, 0x400c0000u, 0xbffffffeu, 0x40080000u, 0x9fffffffu, 0x40140000u, 0x00000000u, 0x3cb00000u, 0x00000000u, 0x3ff80000u, 0x7fffffffu, 0x40000000u, 0x00000000u, 0x7ff00000u, 0xfffffffcu, 0x3fe00001u}, + {0xffffffffu, 0x7fefffffu, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0xfff00000u, 0xfffff7ccu, 0x3ff00000u, 0xffffffffu, 0x7fefffffu, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffefffffu}, + {0x79a00000u, 0x3eafffffu, 0xfffffbcdu, 0xbff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbff00000u, 0xfffffbcdu, 0x3ff00000u, 0x00000000u, 0x80000000u, 0x79a00000u, 0x3eafffffu}, + {0x00000401u, 0x3ff80000u, 0x00000401u, 0x3fe00000u, 0x00000602u, 0x3ff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3fe00000u, 0x00000401u, 0x3ff00000u, 0x00000000u, 0x7ff00000u, 0x00000802u, 0x3fe00000u}, + {0x00000000u, 0xfff00000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff00000u, 0xffc00000u, 0x41dfffffu, 0x00000000u, 0xfff00000u, 0x00000000u, 0xfff00000u}, + {0x5ba3aa33u, 0xc020f644u, 0x220b9c1cu, 0x3f2bda21u, 0x38c9d6dcu, 0xc020f625u, 0x35aa5b80u, 0xbbc1a7fdu, 0x12eaf8e8u, 0xc020f641u, 0x8a59a474u, 0xbefa45c5u, 0x00000000u, 0xfff00000u, 0xca32479du, 0x4020f63du}, + {0x00000001u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000000u, 0x40000000u, 0x00000001u, 0x7ff80000u}, + {0x00000001u, 0xfff80000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0xfff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0xfff80000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xbff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0xbff00000u, 0x00000000u, 0x7ff00000u}, + {0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x7ff80000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0xfff80000u}, + {0xd9787a36u, 0xffffc837u, 0xd9787a36u, 0xffffc837u, 0xd9787a36u, 0xffffc837u, 0xd9787a36u, 0xffffc837u, 0xd9787a36u, 0xffffc837u, 0xd9787a36u, 0xffffc837u, 0xd9787a36u, 0xffffc837u, 0xd9787a36u, 0xffffc837u}, + {0xffffffffu, 0xffefffffu, 0x00000000u, 0xfff00000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x7ff00000u, 0xffffffffu, 0xffefffffu, 0x00000000u, 0x3ff80000u, 0x00000000u, 0x3fe80000u, 0xffffffffu, 0x7fefffffu}, + {0x00000000u, 0xfff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0xfff80000u}, + {0xc66ab30du, 0x8e9a2140u, 0x00000000u, 0x80000000u, 0xc66ab30du, 0x8e9a2140u, 0x00000000u, 0x80000000u, 0xc66ab30du, 0x8e9a2140u, 0x047b57e5u, 0x02f22376u, 0x00000000u, 0x00000000u, 0xc66ab30du, 0x0e9a2140u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x3ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u}, + {0x00000000u, 0xbff00000u, 0x00000000u, 0x80000000u, 0x00000000u, 0xbff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u}, + {0x00000000u, 0x3fe00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3fe00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3fe00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3fe00000u}, + {0x00000000u, 0xbfe00000u, 0x00000000u, 0x80000000u, 0x00000000u, 0xbfe00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbfe00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3fe00000u}, + {0x00000000u, 0x3ff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff80000u}, + {0x00000000u, 0x40040000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40040000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40040000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40040000u}, + {0x00000000u, 0xc0040000u, 0x00000000u, 0x80000000u, 0x00000000u, 0xc0040000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xc0040000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40040000u}, + {0xffffffffu, 0x7fefffffu, 0x00000000u, 0x00000000u, 0xffffffffu, 0x7fefffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0x7fefffffu, 0x00000000u, 0x00000000u, 0xffffffffu, 0x7fefffffu}, + {0xffffffffu, 0xffefffffu, 0x00000000u, 0x80000000u, 0xffffffffu, 0xffefffffu, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffefffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0x7fefffffu}, + {0x00000000u, 0x7ff80000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff80000u}, + {0x00000001u, 0xfff80000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0xfff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x7ff80000u}, + {0x00000001u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x7ff80000u}, + {0x00000000u, 0x7ffc0000u, 0x00000000u, 0x7ffc0000u, 0x00000000u, 0x7ffc0000u, 0x00000000u, 0x7ffc0000u, 0x00000000u, 0x7ffc0000u, 0x00000000u, 0x7ffc0000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ffc0000u}, + {0x00000000u, 0x41e00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x41e00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x41e00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x41e00000u} +}; +constexpr const char* Names[16] = {"add lo", "add hi", "mul lo", "mul hi", "fma lo", "fma hi", "fma error lo", "fma error hi", "min lo", "min hi", "max lo", "max hi", "ldexp lo", "ldexp hi", "add abs neg lo", "add abs neg hi"}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("f64 arithmetic: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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 std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 16; ++i) Expect(tid, out[i], Expected[tid][i], Names[i]); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("f64 arithmetic tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/F64Conversions.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/F64Conversions.cpp new file mode 100644 index 000000000..bf52b9fa7 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/F64Conversions.cpp @@ -0,0 +1,167 @@ +#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 = 4; +constexpr std::uint32_t Results = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e142104, 0x7e180905, 0x7e1c2d05, + 0xd5648010, 0x02020d06, 0xd5650112, 0x3a020d06, 0xd5640014, 0x0201ff06, 0x40080000, 0xd5980016, + 0x22010106, 0xd54c0018, 0x13e1e506, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, + 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x7f800001u, 0x00000001u, 0x00000000u, 0x00000000u}, + {0x8e317041u, 0x00000000u, 0x00000000u, 0x7ff00000u}, + {0x7f7fffffu, 0xa7abe1c2u, 0x00100000u, 0xc1e00000u}, + {0xff800000u, 0x7e9508cbu, 0x00000000u, 0xfff00000u}, + {0x7f800000u, 0xffffffffu, 0xf0000000u, 0x47efffffu}, + {0xffffffffu, 0x00000001u, 0x00000000u, 0x80000000u}, + {0x80000000u, 0x7fffffffu, 0x00000000u, 0xbff00000u}, + {0x00000000u, 0x00000000u, 0x00000001u, 0x43300000u}, + {0x6513270eu, 0x00000000u, 0x00000000u, 0x80000000u}, + {0x128b2f33u, 0x7fffffffu, 0x00000000u, 0x3ff00000u}, + {0x0ed90475u, 0x7fffffffu, 0x00000000u, 0x3fe00000u}, + {0x36f675ccu, 0x00000000u, 0x00000000u, 0xbfe00000u}, + {0x3d9c1724u, 0x7fffffffu, 0x00000000u, 0x40040000u}, + {0x6cad4a26u, 0x7fffffffu, 0x00000000u, 0xc0040000u}, + {0x7f7fffffu, 0x00000000u, 0xffffffffu, 0x7fefffffu}, + {0xf9ebdaccu, 0x00000000u, 0xffffffffu, 0xffefffffu}, + {0x8e81973eu, 0x00000001u, 0x00000000u, 0x7ff00000u}, + {0x7fc00000u, 0x00000001u, 0x00000000u, 0xfff00000u}, + {0x5f557203u, 0x7fffffffu, 0x00000000u, 0x41e00000u}, + {0x0f4205b4u, 0x00000001u, 0x00100000u, 0xc1e00000u}, + {0x7f150524u, 0x7fffffffu, 0xffc00000u, 0x41dfffffu}, + {0x7731af10u, 0x80000000u, 0x00000000u, 0x41f00000u}, + {0x5c90a958u, 0x00000001u, 0xffffffffu, 0xbfefffffu}, + {0x7f800000u, 0x00000001u, 0xffffffffu, 0x3fefffffu}, + {0x14f4733fu, 0xffffffffu, 0x00000001u, 0x43300000u}, + {0x86734721u, 0xffffffffu, 0x00000000u, 0x43400000u}, + {0x9be4bcfcu, 0x00000000u, 0xe0000000u, 0x47efffffu}, + {0x1e398f10u, 0x80000000u, 0xefffffffu, 0x47efffffu}, + {0x26e87555u, 0x80000000u, 0x00000000u, 0x36a00000u}, + {0x6bf46c69u, 0x0a097c97u, 0x00000000u, 0x36900000u}, + {0x80000000u, 0x7fffffffu, 0xffffffffu, 0x369fffffu}, + {0x92b1d3f2u, 0xffffffffu, 0xffffffffu, 0x380fffffu} +}; +constexpr std::uint32_t Expected[32][16] = { + {0x20000000u, 0x7ff80000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x40080000u, 0x00000000u, 0x80000000u, 0x6dc9c882u, 0x3fc45f30u}, + {0x20000000u, 0xb9c62e08u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x7ff00000u}, + {0xe0000000u, 0x47efffffu, 0x8f800000u, 0xc1d61507u, 0x38400000u, 0x41e4f57cu, 0x00000000u, 0x00000000u, 0x00200000u, 0x43d00000u, 0xff600000u, 0xc1dfffffu, 0x00200000u, 0x41e00000u, 0x000ae834u, 0xc1e00000u}, + {0x00000000u, 0xfff00000u, 0x32c00000u, 0x41dfa542u, 0x32c00000u, 0x41dfa542u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0xfff00000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0xbff00000u, 0xffe00000u, 0x41efffffu, 0x00000000u, 0x3ff00000u, 0xe0000008u, 0xcfefffffu, 0xf0000000u, 0x47efffffu, 0xf0000000u, 0xc7efffffu, 0xf0000000u, 0x47efffffu}, + {0xe0000000u, 0xffffffffu, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40080000u, 0x00000000u, 0x00000000u, 0x6dc9c882u, 0x3fc45f30u}, + {0x00000000u, 0x80000000u, 0xffc00000u, 0x41dfffffu, 0xffc00000u, 0x41dfffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x40000000u, 0x00000000u, 0x3ff00000u, 0xe48d8de0u, 0xbfeae833u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00000002u, 0xc6700000u, 0x00000004u, 0x43300000u, 0x00000001u, 0xc3300000u, 0x00000001u, 0x43300000u}, + {0xc0000000u, 0x44a264e1u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40080000u, 0x00000000u, 0x00000000u, 0x6dc9c882u, 0x3fc45f30u}, + {0x60000000u, 0x3a5165e6u, 0xffc00000u, 0x41dfffffu, 0xffc00000u, 0x41dfffffu, 0x00000000u, 0x3ff00000u, 0x00000000u, 0xbff00000u, 0x00000000u, 0x40100000u, 0x00000000u, 0xbff00000u, 0x0db93910u, 0x3ff28be6u}, + {0xa0000000u, 0x39db208eu, 0xffc00000u, 0x41dfffffu, 0xffc00000u, 0x41dfffffu, 0x00000000u, 0x3ff00000u, 0x00000000u, 0xbfd00000u, 0x00000000u, 0x400c0000u, 0x00000000u, 0x80000000u, 0x1b727220u, 0x3fe517ccu}, + {0x80000000u, 0x3edeceb9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3fd00000u, 0x00000000u, 0x40040000u, 0x00000000u, 0x3ff00000u, 0xc91b1bbfu, 0xbfd5d067u}, + {0x80000000u, 0x3fb382e4u, 0xffc00000u, 0x41dfffffu, 0xffc00000u, 0x41dfffffu, 0x00000000u, 0x3ff00000u, 0x00000000u, 0xc0190000u, 0x00000000u, 0x40160000u, 0x00000000u, 0xc0000000u, 0x06dc9c88u, 0x400545f3u}, + {0xc0000000u, 0x4595a944u, 0xffc00000u, 0x41dfffffu, 0xffc00000u, 0x41dfffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x40190000u, 0x00000000u, 0x3fe00000u, 0x00000000u, 0x40080000u, 0xf9236378u, 0xc002ba0cu}, + {0xe0000000u, 0x47efffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0xfff00000u, 0xffffffffu, 0x7fefffffu, 0xffffffffu, 0xffefffffu, 0xffffffffu, 0x7fefffffu}, + {0x80000000u, 0xc73d7b59u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff00000u, 0xffffffffu, 0xffefffffu, 0xffffffffu, 0x7fefffffu, 0xffffffffu, 0xffefffffu}, + {0xc0000000u, 0xb9d032e7u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x7ff00000u}, + {0x00000000u, 0x7ff80000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0xfff00000u}, + {0x60000000u, 0x43eaae40u, 0xffc00000u, 0x41dfffffu, 0xffc00000u, 0x41dfffffu, 0x00000000u, 0x3ff00000u, 0x00000000u, 0xc3d00000u, 0x00600000u, 0x41e00000u, 0x00000000u, 0xc1e00000u, 0x000517ccu, 0x41e00000u}, + {0x80000000u, 0x39e840b6u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x00000000u, 0x00200000u, 0x43d00000u, 0xff600000u, 0xc1dfffffu, 0x00200000u, 0x41e00000u, 0x000ae834u, 0xc1e00000u}, + {0x80000000u, 0x47e2a0a4u, 0xffc00000u, 0x41dfffffu, 0xffc00000u, 0x41dfffffu, 0x00000000u, 0x3ff00000u, 0xff800000u, 0xc3cfffffu, 0x00400000u, 0x41e00000u, 0xffc00000u, 0xc1dfffffu, 0xffca2f98u, 0x41dfffffu}, + {0x00000000u, 0x46e635e2u, 0x00000000u, 0xc1e00000u, 0x00000000u, 0x41e00000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0xc3f00000u, 0x00300000u, 0x41f00000u, 0x00000000u, 0xc1f00000u, 0x00028be6u, 0x41f00000u}, + {0x00000000u, 0x4392152bu, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x00000000u, 0xfffffffeu, 0x3fefffffu, 0x00000000u, 0x40000000u, 0x00000000u, 0x3ff00000u, 0xe48d8ddeu, 0xbfeae833u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x3ff00000u, 0xfffffffeu, 0xbfefffffu, 0x00000000u, 0x40100000u, 0x00000000u, 0x80000000u, 0x0db93910u, 0x3ff28be6u}, + {0xe0000000u, 0x3a9e8e67u, 0x00000000u, 0xbff00000u, 0xffe00000u, 0x41efffffu, 0x00000000u, 0x3ff00000u, 0x00000002u, 0xc6700000u, 0x00000004u, 0x43300000u, 0x00000001u, 0xc3300000u, 0x00000001u, 0x43300000u}, + {0x20000000u, 0xb8ce68e4u, 0x00000000u, 0xbff00000u, 0xffe00000u, 0x41efffffu, 0x00000000u, 0x3ff00000u, 0x00000000u, 0xc6900000u, 0x00000002u, 0x43400000u, 0x00000000u, 0xc3400000u, 0x00000000u, 0x43400000u}, + {0x80000000u, 0xbb7c979fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0xc0000020u, 0xcfefffffu, 0xe0000000u, 0x47efffffu, 0xe0000000u, 0xc7efffffu, 0xe0000000u, 0x47efffffu}, + {0x00000000u, 0x3bc731e2u, 0x00000000u, 0xc1e00000u, 0x00000000u, 0x41e00000u, 0x00000000u, 0x3ff00000u, 0xe0000006u, 0xcfefffffu, 0xefffffffu, 0x47efffffu, 0xefffffffu, 0xc7efffffu, 0xefffffffu, 0x47efffffu}, + {0xa0000000u, 0x3cdd0eaau, 0x00000000u, 0xc1e00000u, 0x00000000u, 0x41e00000u, 0x00000000u, 0x36b00000u, 0x00000000u, 0xad500000u, 0x00000000u, 0x40080000u, 0x00000000u, 0x80000000u, 0x6dc9c882u, 0x3fc45f30u}, + {0x20000000u, 0x457e8d8du, 0x2e000000u, 0x41a412f9u, 0x2e000000u, 0x41a412f9u, 0x00000000u, 0x36a00000u, 0x00000000u, 0xad300000u, 0x00000000u, 0x40080000u, 0x00000000u, 0x80000000u, 0x6dc9c882u, 0x3fc45f30u}, + {0x00000000u, 0x80000000u, 0xffc00000u, 0x41dfffffu, 0xffc00000u, 0x41dfffffu, 0xffffffffu, 0x36afffffu, 0xfffffffeu, 0xad4fffffu, 0x00000000u, 0x40080000u, 0x00000000u, 0x80000000u, 0x6dc9c882u, 0x3fc45f30u}, + {0x40000000u, 0xba563a7eu, 0x00000000u, 0xbff00000u, 0xffe00000u, 0x41efffffu, 0xffffffffu, 0x381fffffu, 0xfffffffeu, 0xb02fffffu, 0x00000000u, 0x40080000u, 0x00000000u, 0x80000000u, 0x6dc9c882u, 0x3fc45f30u} +}; +constexpr const char* Names[16] = {"cvt f64 f32 lo", "cvt f64 f32 hi", "cvt f64 i32 lo", "cvt f64 i32 hi", "cvt f64 u32 lo", "cvt f64 u32 hi", "add clamp lo", "add clamp hi", "mul abs neg omod lo", "mul abs neg omod hi", "literal lo", "literal hi", "ceil neg lo", "ceil neg hi", "fma inv2pi omod lo", "fma inv2pi omod hi"}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("f64 conversions: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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 std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 16; ++i) Expect(tid, out[i], Expected[tid][i], Names[i]); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("f64 conversions tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/F64Rounding.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/F64Rounding.cpp new file mode 100644 index 000000000..4f717f846 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/F64Rounding.cpp @@ -0,0 +1,167 @@ +#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 = 4; +constexpr std::uint32_t Results = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e142f04, 0x7e183104, 0x7e1c3504, + 0x7e203304, 0x7e247d04, 0x7e287b04, 0xd58f0016, 0x0a010104, 0x7e2e7904, 0x7e300704, 0x7e322b04, + 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, + 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x00000000u, 0xbc300000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x3c300000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xbcb00000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x3ff00000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xfff00000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x7ff80000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xc0040000u, 0x00000000u, 0x00000000u}, + {0x00000001u, 0xfff80000u, 0x00000000u, 0x00000000u}, + {0xc5f99476u, 0x4153959fu, 0x00000000u, 0x36a00000u}, + {0x33e8e7aau, 0x40a1e71cu, 0x7716dc6du, 0xbfdd8066u}, + {0x2d0e58dcu, 0x402fc4a6u, 0x00000000u, 0x40040000u}, + {0x26fc54b4u, 0x41019e75u, 0x00000000u, 0x36900000u}, + {0x8e200000u, 0x41d6c5deu, 0x672322eeu, 0xbfe815b9u}, + {0x32000000u, 0x41844f58u, 0x36863aecu, 0x3f36aa62u}, + {0x6170c9b6u, 0x409029c3u, 0x00000001u, 0x43300000u}, + {0x1bc00000u, 0x41c94321u, 0xffe00000u, 0x41efffffu}, + {0x960a8110u, 0x40bd6500u, 0x959d133du, 0xf977edf4u}, + {0xbacb6dd4u, 0x4110b770u, 0xe5000000u, 0xc1c3ad7cu}, + {0xc1666540u, 0x40f9a56fu, 0xa60b9c70u, 0x3e3cd351u}, + {0xdfafda62u, 0x419599f6u, 0x00000000u, 0x43e00000u}, + {0x1a712470u, 0x4063fcf2u, 0xb4746bf2u, 0xbe2efc36u}, + {0xf2604f52u, 0x412cb34eu, 0x00000000u, 0x41f00000u}, + {0x2ae097ecu, 0x405db192u, 0x8ac80000u, 0xc1f74d24u}, + {0xa7dbdaa2u, 0x4176b608u, 0x7079bceau, 0x94b19e9fu}, + {0x9f3e5890u, 0x407b14c4u, 0xff7f6a4eu, 0x42986654u}, + {0x50795a94u, 0x4147ce51u, 0x00000001u, 0x7ff00000u}, + {0x19e8099cu, 0x40198ad8u, 0x4402dc9cu, 0x4998ff49u}, + {0x076663a8u, 0x413adf07u, 0xffffffffu, 0x3fefffffu}, + {0xe6000000u, 0x41ada4d6u, 0x87db66a6u, 0x3fa852e9u}, + {0x8ce86774u, 0x40c06401u, 0x00000000u, 0x3ff00000u}, + {0x98b83010u, 0x40d1d266u, 0x00000000u, 0xbff00000u} +}; +constexpr std::uint32_t Expected[32][16] = { + {0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0xbff00000u, 0x00000000u, 0x80000000u, 0xffffffffu, 0x3fefffffu, 0x00000000u, 0xbfe00000u, 0xa1800000u, 0xffffffc5u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3c300000u, 0x00000000u, 0x3fe00000u, 0x21800000u, 0xffffffc5u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0xbff00000u, 0x00000000u, 0x80000000u, 0xfffffffeu, 0x3fefffffu, 0x00000000u, 0xbfe00000u, 0xa5800000u, 0xffffffcdu, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x7ff00000u, 0x7f800000u, 0x00000000u, 0x7fffffffu, 0xffffffffu}, + {0x00000000u, 0x3ff00000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3fe00000u, 0x3f800000u, 0x00000001u, 0x00000001u, 0x00000001u}, + {0x00000000u, 0xfff00000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff00000u, 0xff800000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x00000000u, 0x7ff80000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x7ff80000u, 0x7fc00000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xc0000000u, 0x00000000u, 0xc0000000u, 0x00000000u, 0xc0080000u, 0x00000000u, 0xc0000000u, 0x00000000u, 0x3fe00000u, 0x00000000u, 0xbfe40000u, 0xc0200000u, 0x00000002u, 0xfffffffeu, 0x00000000u}, + {0x00000001u, 0xfff80000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0xfff80000u, 0xffc00000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xc0000000u, 0x4153959fu, 0x00000000u, 0x415395a0u, 0xc0000000u, 0x4153959fu, 0xc0000000u, 0x4153959fu, 0xd8000000u, 0x3fb7e651u, 0xc5f99476u, 0x3fe3959fu, 0x4a9cacfeu, 0x00000017u, 0x004e567fu, 0x004e567fu}, + {0x00000000u, 0x40a1e600u, 0x00000000u, 0x40a1e800u, 0x00000000u, 0x40a1e600u, 0x00000000u, 0x40a1e800u, 0x8e7aa000u, 0x3fe1c33eu, 0x33e8e7aau, 0x3fe1e71cu, 0x450f38e2u, 0x0000000cu, 0x000008f3u, 0x000008f3u}, + {0x00000000u, 0x402e0000u, 0x00000000u, 0x40300000u, 0x00000000u, 0x402e0000u, 0x00000000u, 0x40300000u, 0xd0e58dc0u, 0x3fec4a62u, 0x2d0e58dcu, 0x3fefc4a6u, 0x417e2531u, 0x00000004u, 0x0000000fu, 0x0000000fu}, + {0x00000000u, 0x41019e70u, 0x00000000u, 0x41019e78u, 0x00000000u, 0x41019e70u, 0x00000000u, 0x41019e78u, 0x52d00000u, 0x3fe49bf1u, 0x26fc54b4u, 0x3fe19e75u, 0x480cf3a9u, 0x00000012u, 0x000233ceu, 0x000233ceu}, + {0x8e000000u, 0x41d6c5deu, 0x8e400000u, 0x41d6c5deu, 0x8e000000u, 0x41d6c5deu, 0x8e000000u, 0x41d6c5deu, 0x00000000u, 0x3fe00000u, 0x8e200000u, 0x3fe6c5deu, 0x4eb62ef4u, 0x0000001fu, 0x5b177a38u, 0x5b177a38u}, + {0x30000000u, 0x41844f58u, 0x38000000u, 0x41844f58u, 0x30000000u, 0x41844f58u, 0x30000000u, 0x41844f58u, 0x00000000u, 0x3fd00000u, 0x32000000u, 0x3fe44f58u, 0x4c227ac2u, 0x0000001au, 0x0289eb06u, 0x0289eb06u}, + {0x00000000u, 0x40902800u, 0x00000000u, 0x40902c00u, 0x00000000u, 0x40902800u, 0x00000000u, 0x40902800u, 0x0c9b6000u, 0x3fdc3617u, 0x6170c9b6u, 0x3fe029c3u, 0x44814e1bu, 0x0000000bu, 0x0000040au, 0x0000040au}, + {0x1b800000u, 0x41c94321u, 0x1c000000u, 0x41c94321u, 0x1b800000u, 0x41c94321u, 0x1c000000u, 0x41c94321u, 0x00000000u, 0x3fe00000u, 0x1bc00000u, 0x3fe94321u, 0x4e4a1909u, 0x0000001eu, 0x32864237u, 0x32864237u}, + {0x00000000u, 0x40bd6500u, 0x00000000u, 0x40bd6600u, 0x00000000u, 0x40bd6500u, 0x00000000u, 0x40bd6500u, 0x22000000u, 0x3f62c150u, 0x960a8110u, 0x3fed6500u, 0x45eb2805u, 0x0000000du, 0x00001d65u, 0x00001d65u}, + {0x00000000u, 0x4110b770u, 0x00000000u, 0x4110b774u, 0x00000000u, 0x4110b770u, 0x00000000u, 0x4110b770u, 0xba800000u, 0x3fc7596du, 0xbacb6dd4u, 0x3fe0b770u, 0x4885bb86u, 0x00000013u, 0x00042ddcu, 0x00042ddcu}, + {0x00000000u, 0x40f9a560u, 0x00000000u, 0x40f9a570u, 0x00000000u, 0x40f9a560u, 0x00000000u, 0x40f9a570u, 0xca800000u, 0x3fef82ccu, 0xc1666540u, 0x3fe9a56fu, 0x47cd2b7eu, 0x00000011u, 0x00019a56u, 0x00019a56u}, + {0xdc000000u, 0x419599f6u, 0xe0000000u, 0x419599f6u, 0xdc000000u, 0x419599f6u, 0xe0000000u, 0x419599f6u, 0x10000000u, 0x3fed7ed3u, 0xdfafda62u, 0x3fe599f6u, 0x4caccfb7u, 0x0000001bu, 0x05667db7u, 0x05667db7u}, + {0x00000000u, 0x4063e000u, 0x00000000u, 0x40640000u, 0x00000000u, 0x4063e000u, 0x00000000u, 0x40640000u, 0x71247000u, 0x3fecf21au, 0x1a712470u, 0x3fe3fcf2u, 0x431fe791u, 0x00000008u, 0x0000009fu, 0x0000009fu}, + {0x00000000u, 0x412cb34eu, 0x00000000u, 0x412cb350u, 0x00000000u, 0x412cb34eu, 0x00000000u, 0x412cb34eu, 0xea400000u, 0x3fde4c09u, 0xf2604f52u, 0x3fecb34eu, 0x49659a78u, 0x00000014u, 0x000e59a7u, 0x000e59a7u}, + {0x00000000u, 0x405d8000u, 0x00000000u, 0x405dc000u, 0x00000000u, 0x405d8000u, 0x00000000u, 0x405dc000u, 0x704bf600u, 0x3fe8c915u, 0x2ae097ecu, 0x3fedb192u, 0x42ed8c91u, 0x00000007u, 0x00000076u, 0x00000076u}, + {0xa0000000u, 0x4176b608u, 0xb0000000u, 0x4176b608u, 0xa0000000u, 0x4176b608u, 0xa0000000u, 0x4176b608u, 0x88000000u, 0x3fdf6f6au, 0xa7dbdaa2u, 0x3fe6b608u, 0x4bb5b045u, 0x00000019u, 0x016b608au, 0x016b608au}, + {0x00000000u, 0x407b1000u, 0x00000000u, 0x407b2000u, 0x00000000u, 0x407b1000u, 0x00000000u, 0x407b1000u, 0xf9624000u, 0x3fd3127cu, 0x9f3e5890u, 0x3feb14c4u, 0x43d8a625u, 0x00000009u, 0x000001b1u, 0x000001b1u}, + {0x00000000u, 0x4147ce51u, 0x80000000u, 0x4147ce51u, 0x00000000u, 0x4147ce51u, 0x80000000u, 0x4147ce51u, 0xa5000000u, 0x3fe41e56u, 0x50795a94u, 0x3fe7ce51u, 0x4a3e728bu, 0x00000016u, 0x002f9ca2u, 0x002f9ca2u}, + {0x00000000u, 0x40180000u, 0x00000000u, 0x401c0000u, 0x00000000u, 0x40180000u, 0x00000000u, 0x40180000u, 0x9e8099c0u, 0x3fd8ad81u, 0x19e8099cu, 0x3fe98ad8u, 0x40cc56c1u, 0x00000003u, 0x00000006u, 0x00000006u}, + {0x00000000u, 0x413adf07u, 0x00000000u, 0x413adf08u, 0x00000000u, 0x413adf07u, 0x00000000u, 0x413adf07u, 0xa0000000u, 0x3f9d998eu, 0x076663a8u, 0x3feadf07u, 0x49d6f838u, 0x00000015u, 0x001adf07u, 0x001adf07u}, + {0xe6000000u, 0x41ada4d6u, 0xe6000000u, 0x41ada4d6u, 0xe6000000u, 0x41ada4d6u, 0xe6000000u, 0x41ada4d6u, 0x00000000u, 0x00000000u, 0xe6000000u, 0x3feda4d6u, 0x4d6d26b7u, 0x0000001cu, 0x0ed26b73u, 0x0ed26b73u}, + {0x00000000u, 0x40c06400u, 0x00000000u, 0x40c06480u, 0x00000000u, 0x40c06400u, 0x00000000u, 0x40c06400u, 0x77400000u, 0x3f88ce86u, 0x8ce86774u, 0x3fe06401u, 0x4603200cu, 0x0000000eu, 0x000020c8u, 0x000020c8u}, + {0x00000000u, 0x40d1d240u, 0x00000000u, 0x40d1d280u, 0x00000000u, 0x40d1d240u, 0x00000000u, 0x40d1d280u, 0x18080000u, 0x3fe34c5cu, 0x98b83010u, 0x3fe1d266u, 0x468e9335u, 0x0000000fu, 0x00004749u, 0x00004749u} +}; +constexpr const char* Names[16] = {"trunc lo", "trunc hi", "ceil lo", "ceil hi", "floor lo", "floor hi", "rndne lo", "rndne hi", "fract lo", "fract hi", "frexp mant lo", "frexp mant hi", "cvt f32", "frexp exp", "cvt i32", "cvt u32"}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("f64 rounding: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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 std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 16; ++i) Expect(tid, out[i], Expected[tid][i], Names[i]); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("f64 rounding tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/CMakeLists.txt b/core/shader/recompiler/CMakeLists.txt index dfb0b1e54..0082a7142 100644 --- a/core/shader/recompiler/CMakeLists.txt +++ b/core/shader/recompiler/CMakeLists.txt @@ -73,6 +73,7 @@ function(add_shader_recompiler target runtimeTarget) ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Translation/src/ScalarInstructions.cpp ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Translation/src/VectorInstructions.cpp ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Translation/src/FloatInstructions.cpp + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Translation/src/Float64Instructions.cpp ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Translation/src/IntegerInstructions.cpp ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Translation/src/CompareInstructions.cpp ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Translation/src/ControlFlowInstructions.cpp @@ -115,6 +116,7 @@ function(add_shader_recompiler target runtimeTarget) ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/SpirvBackend/src/SpirvModule.cpp ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/SpirvBackend/src/SpirvAnalysis.cpp ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/SpirvBackend/src/SpirvAlu/SpirvAluEmitterFloat64.cpp ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/SpirvBackend/src/SpirvAlu/SpirvAluEmitterRegisters.cpp ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/SpirvBackend/src/SpirvFlowEmitter.cpp ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/SpirvBackend/src/SpirvImageEmitter.cpp diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp index 2e69db9ca..353136c3f 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp @@ -245,6 +245,26 @@ enum class IrOpcode : std::uint16_t { FPCeil32, FPTrunc32, FPFract32, + FPAdd64, + FPMul64, + FPFma64, + FPMin64, + FPMax64, + FPSaturate64, + FPLdexp64, + FPRoundEven64, + FPFloor64, + FPCeil64, + FPTrunc64, + FPFract64, + FPFrexpMant64, + FPFrexpExp64, + ConvertF32F64, + ConvertF64F32, + ConvertF64S32, + ConvertF64U32, + ConvertS32F64, + ConvertU32F64, LaneId, WriteLane, Permlane16U32, @@ -409,6 +429,7 @@ struct ExportFlags { [[nodiscard]] IrType IrOpcodeType(IrOpcode opcode); [[nodiscard]] IrType IrOpcodeArgumentType(IrOpcode opcode, std::size_t index); [[nodiscard]] bool IrOpcodeHasSideEffects(IrOpcode opcode); +[[nodiscard]] bool IsFloat64Opcode(IrOpcode opcode); [[nodiscard]] BufferAccess BufferAccessOf(IrOpcode opcode); [[nodiscard]] std::uint32_t BufferComponentCount(IrOpcode opcode); [[nodiscard]] SharedAccess SharedAccessOf(IrOpcode opcode); diff --git a/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp b/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp index 56b68a966..dd0268f9c 100644 --- a/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp +++ b/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp @@ -286,6 +286,26 @@ constexpr std::array(IrOpcode::Count)> Meta makeMeta("FPCeil32", F32, F32), makeMeta("FPTrunc32", F32, F32), makeMeta("FPFract32", F32, F32), + makeMeta("FPAdd64", U64, U64, U64), + makeMeta("FPMul64", U64, U64, U64), + makeMeta("FPFma64", U64, U64, U64, U64), + makeMeta("FPMin64", U64, U64, U64), + makeMeta("FPMax64", U64, U64, U64), + makeMeta("FPSaturate64", U64, U64), + makeMeta("FPLdexp64", U64, U64, U32), + makeMeta("FPRoundEven64", U64, U64), + makeMeta("FPFloor64", U64, U64), + makeMeta("FPCeil64", U64, U64), + makeMeta("FPTrunc64", U64, U64), + makeMeta("FPFract64", U64, U64), + makeMeta("FPFrexpMant64", U64, U64), + makeMeta("FPFrexpExp64", U32, U64), + makeMeta("ConvertF32F64", F32, U64), + makeMeta("ConvertF64F32", U64, F32), + makeMeta("ConvertF64S32", U64, U32), + makeMeta("ConvertF64U32", U64, U32), + makeMeta("ConvertS32F64", U32, U64), + makeMeta("ConvertU32F64", U32, U64), makeMeta("LaneId", U32), makeMeta("WriteLane", U32, U32, U32, U32), makeMeta("Permlane16U32", U32, U32, U32, U32, U1), @@ -609,6 +629,10 @@ ImageOpcodeInfo ImageOpcodeInfoOf(IrOpcode opcode) { } } +bool IsFloat64Opcode(IrOpcode opcode) { + return opcode >= IrOpcode::FPAdd64 && opcode <= IrOpcode::ConvertU32F64; +} + bool IrOpcodeHasSideEffects(IrOpcode opcode) { const BufferAccess bufferAccess = BufferAccessOf(opcode); if (bufferAccess == BufferAccess::Write || bufferAccess == BufferAccess::Atomic) { diff --git a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp index 190b0a595..1c0729d74 100644 --- a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp +++ b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp @@ -384,6 +384,25 @@ enum class RdnaOpcode : std::uint16_t { VSatPkU8I16, VMulLegacyF32, VMacLegacyF32, + VFmaF64, + VAddF64, + VMulF64, + VMinF64, + VMaxF64, + VLdexpF64, + VCvtI32F64, + VCvtF64I32, + VCvtF32F64, + VCvtF64F32, + VCvtU32F64, + VCvtF64U32, + VTruncF64, + VCeilF64, + VRndneF64, + VFloorF64, + VFrexpExpI32F64, + VFrexpMantF64, + VFractF64, VCvtPknormI16F32, VCvtPknormU16F32, VCvtPkU16U32, diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp index 10d89cdc9..071848712 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp @@ -334,6 +334,25 @@ bool IsVectorAluOpcode(RdnaOpcode opcode) { case RdnaOpcode::VSatPkU8I16: case RdnaOpcode::VMulLegacyF32: case RdnaOpcode::VMacLegacyF32: + case RdnaOpcode::VFmaF64: + case RdnaOpcode::VAddF64: + case RdnaOpcode::VMulF64: + case RdnaOpcode::VMinF64: + case RdnaOpcode::VMaxF64: + case RdnaOpcode::VLdexpF64: + case RdnaOpcode::VCvtI32F64: + case RdnaOpcode::VCvtF64I32: + case RdnaOpcode::VCvtF32F64: + case RdnaOpcode::VCvtF64F32: + case RdnaOpcode::VCvtU32F64: + case RdnaOpcode::VCvtF64U32: + case RdnaOpcode::VTruncF64: + case RdnaOpcode::VCeilF64: + case RdnaOpcode::VRndneF64: + case RdnaOpcode::VFloorF64: + case RdnaOpcode::VFrexpExpI32F64: + case RdnaOpcode::VFrexpMantF64: + case RdnaOpcode::VFractF64: case RdnaOpcode::VCvtPknormI16F32: case RdnaOpcode::VCvtPknormU16F32: case RdnaOpcode::VCvtPkU16U32: diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp index ab452dd00..08fdf3aec 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp @@ -128,6 +128,19 @@ constexpr VectorOpcodeInfo vop1Opcodes[] = { {0x22u, RdnaOpcode::VCeilF32}, {0x23u, RdnaOpcode::VRndneF32}, {0x24u, RdnaOpcode::VFloorF32}, + {0x03u, RdnaOpcode::VCvtI32F64}, + {0x04u, RdnaOpcode::VCvtF64I32}, + {0x0fu, RdnaOpcode::VCvtF32F64}, + {0x10u, RdnaOpcode::VCvtF64F32}, + {0x15u, RdnaOpcode::VCvtU32F64}, + {0x16u, RdnaOpcode::VCvtF64U32}, + {0x17u, RdnaOpcode::VTruncF64}, + {0x18u, RdnaOpcode::VCeilF64}, + {0x19u, RdnaOpcode::VRndneF64}, + {0x1au, RdnaOpcode::VFloorF64}, + {0x3cu, RdnaOpcode::VFrexpExpI32F64}, + {0x3du, RdnaOpcode::VFrexpMantF64}, + {0x3eu, RdnaOpcode::VFractF64}, {0x25u, RdnaOpcode::VExpF32}, {0x27u, RdnaOpcode::VLogF32}, {0x2bu, RdnaOpcode::VRcpIflagF32}, @@ -192,6 +205,19 @@ constexpr VectorOpcodeInfo vop3EncodedVop1Opcodes[] = { {0x22u, RdnaOpcode::VCeilF32}, {0x23u, RdnaOpcode::VRndneF32}, {0x24u, RdnaOpcode::VFloorF32}, + {0x03u, RdnaOpcode::VCvtI32F64}, + {0x04u, RdnaOpcode::VCvtF64I32}, + {0x0fu, RdnaOpcode::VCvtF32F64}, + {0x10u, RdnaOpcode::VCvtF64F32}, + {0x15u, RdnaOpcode::VCvtU32F64}, + {0x16u, RdnaOpcode::VCvtF64U32}, + {0x17u, RdnaOpcode::VTruncF64}, + {0x18u, RdnaOpcode::VCeilF64}, + {0x19u, RdnaOpcode::VRndneF64}, + {0x1au, RdnaOpcode::VFloorF64}, + {0x3cu, RdnaOpcode::VFrexpExpI32F64}, + {0x3du, RdnaOpcode::VFrexpMantF64}, + {0x3eu, RdnaOpcode::VFractF64}, {0x25u, RdnaOpcode::VExpF32}, {0x27u, RdnaOpcode::VLogF32}, {0x2bu, RdnaOpcode::VRcpIflagF32}, @@ -510,6 +536,12 @@ constexpr VectorOpcodeInfo vop3Opcodes[] = { {0x362u, RdnaOpcode::VLdexpF32}, {0x312u, RdnaOpcode::VCvtPknormI16F16}, {0x313u, RdnaOpcode::VCvtPknormU16F16}, + {0x14cu, RdnaOpcode::VFmaF64}, + {0x164u, RdnaOpcode::VAddF64}, + {0x165u, RdnaOpcode::VMulF64}, + {0x166u, RdnaOpcode::VMinF64}, + {0x167u, RdnaOpcode::VMaxF64}, + {0x168u, RdnaOpcode::VLdexpF64}, {0x363u, RdnaOpcode::VBfmB32}, {0x364u, RdnaOpcode::VBcntU32B32}, {0x365u, RdnaOpcode::VMbcntLoU32B32}, @@ -705,6 +737,17 @@ bool isVop1FloatSourceOpcode(RdnaOpcode opcode) { case RdnaOpcode::VCeilF32: case RdnaOpcode::VRndneF32: case RdnaOpcode::VFloorF32: + case RdnaOpcode::VCvtI32F64: + case RdnaOpcode::VCvtF32F64: + case RdnaOpcode::VCvtU32F64: + case RdnaOpcode::VCvtF64F32: + case RdnaOpcode::VTruncF64: + case RdnaOpcode::VCeilF64: + case RdnaOpcode::VRndneF64: + case RdnaOpcode::VFloorF64: + case RdnaOpcode::VFrexpExpI32F64: + case RdnaOpcode::VFrexpMantF64: + case RdnaOpcode::VFractF64: case RdnaOpcode::VExpF32: case RdnaOpcode::VLogF32: case RdnaOpcode::VRsqF32: @@ -747,6 +790,16 @@ bool isVop1FloatResultOpcode(RdnaOpcode opcode) { case RdnaOpcode::VCeilF32: case RdnaOpcode::VRndneF32: case RdnaOpcode::VFloorF32: + case RdnaOpcode::VCvtF64I32: + case RdnaOpcode::VCvtF64U32: + case RdnaOpcode::VCvtF32F64: + case RdnaOpcode::VCvtF64F32: + case RdnaOpcode::VTruncF64: + case RdnaOpcode::VCeilF64: + case RdnaOpcode::VRndneF64: + case RdnaOpcode::VFloorF64: + case RdnaOpcode::VFrexpMantF64: + case RdnaOpcode::VFractF64: case RdnaOpcode::VFrexpMantF32: case RdnaOpcode::VExpF32: case RdnaOpcode::VLogF32: @@ -1527,6 +1580,11 @@ std::uint32_t nativeVop3SourceCount(RdnaOpcode opcode) { case RdnaOpcode::VWritelaneB32: case RdnaOpcode::VCvtPkrtzF16F32: case RdnaOpcode::VLdexpF32: + case RdnaOpcode::VAddF64: + case RdnaOpcode::VMulF64: + case RdnaOpcode::VMinF64: + case RdnaOpcode::VMaxF64: + case RdnaOpcode::VLdexpF64: case RdnaOpcode::VBfmB32: case RdnaOpcode::VBcntU32B32: case RdnaOpcode::VCvtPknormI16F32: @@ -1693,7 +1751,13 @@ bool supportsNativeVop3SourceModifiers(RdnaOpcode opcode) { case RdnaOpcode::VMin3F32: case RdnaOpcode::VMax3F32: case RdnaOpcode::VMed3F32: - case RdnaOpcode::VLdexpF32: return true; + case RdnaOpcode::VLdexpF32: + case RdnaOpcode::VFmaF64: + case RdnaOpcode::VAddF64: + case RdnaOpcode::VMulF64: + case RdnaOpcode::VMinF64: + case RdnaOpcode::VMaxF64: + case RdnaOpcode::VLdexpF64: return true; default: return false; } } @@ -1715,6 +1779,12 @@ bool supportsNativeVop3ResultModifiers(RdnaOpcode opcode) { case RdnaOpcode::VFmaF16: case RdnaOpcode::VCvtPkrtzF16F32: case RdnaOpcode::VLdexpF32: + case RdnaOpcode::VFmaF64: + case RdnaOpcode::VAddF64: + case RdnaOpcode::VMulF64: + case RdnaOpcode::VMinF64: + case RdnaOpcode::VMaxF64: + case RdnaOpcode::VLdexpF64: case RdnaOpcode::VMin3F32: case RdnaOpcode::VMax3F32: case RdnaOpcode::VMed3F32: return true; @@ -1764,7 +1834,7 @@ void checkNativeVop3Modifiers(RdnaOpcode opcode, bool permlane, bool carryInOut, } return; } - if (opcode == RdnaOpcode::VLdexpF32) { + if (opcode == RdnaOpcode::VLdexpF32 || opcode == RdnaOpcode::VLdexpF64) { if ((abs & ~1u) != 0u || opSel != 0u || (neg & ~1u) != 0u) { throw std::invalid_argument("VOP3 source modifiers are not implemented"); } diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvAnalysis.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvAnalysis.hpp index 589c75c62..40ba4fccb 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvAnalysis.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvAnalysis.hpp @@ -19,6 +19,7 @@ struct SpirvRequirements { bool functionScratch = false; bool pixelValidMask = false; bool bufferInt64Atomics = false; + bool float64 = false; bool coherentBuffers = false; std::vector capabilities; std::vector extensions; diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp index 5168ca80d..d8a717463 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp @@ -179,6 +179,26 @@ std::uint32_t EmitFPFloor32(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitFPCeil32(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitFPTrunc32(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitFPFract32(SpirvEmitterState& state, std::uint32_t arg0); +std::uint32_t EmitFPAdd64(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1); +std::uint32_t EmitFPMul64(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1); +std::uint32_t EmitFPFma64(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1, std::uint32_t arg2); +std::uint32_t EmitFPMin64(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1); +std::uint32_t EmitFPMax64(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1); +std::uint32_t EmitFPSaturate64(SpirvEmitterState& state, std::uint32_t arg0); +std::uint32_t EmitFPLdexp64(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1); +std::uint32_t EmitFPRoundEven64(SpirvEmitterState& state, std::uint32_t arg0); +std::uint32_t EmitFPFloor64(SpirvEmitterState& state, std::uint32_t arg0); +std::uint32_t EmitFPCeil64(SpirvEmitterState& state, std::uint32_t arg0); +std::uint32_t EmitFPTrunc64(SpirvEmitterState& state, std::uint32_t arg0); +std::uint32_t EmitFPFract64(SpirvEmitterState& state, std::uint32_t arg0); +std::uint32_t EmitFPFrexpMant64(SpirvEmitterState& state, std::uint32_t arg0); +std::uint32_t EmitFPFrexpExp64(SpirvEmitterState& state, std::uint32_t arg0); +std::uint32_t EmitConvertF32F64(SpirvEmitterState& state, std::uint32_t arg0); +std::uint32_t EmitConvertF64F32(SpirvEmitterState& state, std::uint32_t arg0); +std::uint32_t EmitConvertF64S32(SpirvEmitterState& state, std::uint32_t arg0); +std::uint32_t EmitConvertF64U32(SpirvEmitterState& state, std::uint32_t arg0); +std::uint32_t EmitConvertS32F64(SpirvEmitterState& state, std::uint32_t arg0); +std::uint32_t EmitConvertU32F64(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitBitCastF16U16(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitConvertU32U16(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitConvertU32U8(SpirvEmitterState& state, std::uint32_t arg0); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterFloat64.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterFloat64.cpp new file mode 100644 index 000000000..efe4723b0 --- /dev/null +++ b/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterFloat64.cpp @@ -0,0 +1,290 @@ +#include "SpirvBackend/SpirvEmitterInstructions.hpp" +#include +#include + +namespace ShaderRecompiler { +namespace { + +struct F64Bits { + std::uint32_t low = 0; + std::uint32_t high = 0; +}; + +std::uint32_t TypeF64(SpirvEmitterState& state) { + return state.module.Type(spv::OpTypeFloat, 64u); +} + +std::uint32_t ConstantF64(SpirvEmitterState& state, std::uint64_t bits) { + return state.module.Constant(spv::OpConstant, TypeF64(state), static_cast(bits), static_cast(bits >> 32u)); +} + +std::uint32_t ToF64(SpirvEmitterState& state, std::uint32_t bits) { + return Unary(state, spv::OpBitcast, TypeF64(state), bits); +} + +std::uint32_t FromF64(SpirvEmitterState& state, std::uint32_t value) { + return Unary(state, spv::OpBitcast, TypeU64(state), value); +} + +F64Bits Split(SpirvEmitterState& state, std::uint32_t bits) { + F64Bits result{state.module.AllocateId(), state.module.AllocateId()}; + state.module.AddFunction(spv::OpCompositeExtract, TypeU32(state), result.low, bits, 0u); + state.module.AddFunction(spv::OpCompositeExtract, TypeU32(state), result.high, bits, 1u); + return result; +} + +std::uint32_t Join(SpirvEmitterState& state, F64Bits bits) { + const auto result = state.module.AllocateId(); + state.module.AddFunction(spv::OpCompositeConstruct, TypeU64(state), result, bits.low, bits.high); + return result; +} + +F64Bits SelectBits(SpirvEmitterState& state, std::uint32_t condition, F64Bits trueValue, F64Bits falseValue) { + return {Select(state, TypeU32(state), condition, trueValue.low, falseValue.low), Select(state, TypeU32(state), condition, trueValue.high, falseValue.high)}; +} + +std::uint32_t Exact(SpirvEmitterState& state, std::uint32_t opcode, std::uint32_t lhs, std::uint32_t rhs) { + const auto result = Binary(state, opcode, TypeF64(state), lhs, rhs); + state.module.AddAnnotation(spv::OpDecorate, result, spv::DecorationNoContraction); + return result; +} + +template +std::uint32_t Glsl(SpirvEmitterState& state, std::uint32_t type, std::uint32_t opcode, TArguments... arguments) { + const auto result = state.module.AllocateId(); + state.module.AddFunction(spv::OpExtInst, type, result, GlslStd450(state), opcode, arguments...); + return result; +} + +struct F64Class { + F64Bits bits; + std::uint32_t nan = 0; + std::uint32_t signalingNan = 0; + std::uint32_t zero = 0; + std::uint32_t negative = 0; +}; + +F64Class Classify(SpirvEmitterState& state, std::uint32_t value) { + F64Class cls; + cls.bits = Split(state, value); + const auto absHigh = Binary(state, spv::OpBitwiseAnd, TypeU32(state), cls.bits.high, ConstantU32(state, 0x7fffffffu)); + const auto aboveInf = Binary(state, spv::OpUGreaterThan, TypeBool(state), absHigh, ConstantU32(state, 0x7ff00000u)); + const auto infHigh = Binary(state, spv::OpIEqual, TypeBool(state), absHigh, ConstantU32(state, 0x7ff00000u)); + const auto lowNonzero = Binary(state, spv::OpINotEqual, TypeBool(state), cls.bits.low, ConstantU32(state, 0u)); + cls.nan = Binary(state, spv::OpLogicalOr, TypeBool(state), aboveInf, Binary(state, spv::OpLogicalAnd, TypeBool(state), infHigh, lowNonzero)); + const auto quietBit = Binary(state, spv::OpBitwiseAnd, TypeU32(state), cls.bits.high, ConstantU32(state, 0x00080000u)); + const auto signaling = Binary(state, spv::OpIEqual, TypeBool(state), quietBit, ConstantU32(state, 0u)); + cls.signalingNan = Binary(state, spv::OpLogicalAnd, TypeBool(state), cls.nan, signaling); + const auto magnitude = Binary(state, spv::OpBitwiseOr, TypeU32(state), absHigh, cls.bits.low); + cls.zero = Binary(state, spv::OpIEqual, TypeBool(state), magnitude, ConstantU32(state, 0u)); + cls.negative = Binary(state, spv::OpINotEqual, TypeBool(state), absHigh, cls.bits.high); + return cls; +} + +F64Bits Quiet(SpirvEmitterState& state, F64Bits bits) { + return {bits.low, Binary(state, spv::OpBitwiseOr, TypeU32(state), bits.high, ConstantU32(state, 0x00080000u))}; +} + +std::uint32_t QuietResult(SpirvEmitterState& state, std::uint32_t value) { + const auto cls = Classify(state, FromF64(state, value)); + return Join(state, SelectBits(state, cls.nan, Quiet(state, cls.bits), cls.bits)); +} + +std::uint32_t MinMax(SpirvEmitterState& state, std::uint32_t lhs, std::uint32_t rhs, bool maxValue) { + const auto a = Classify(state, lhs); + const auto b = Classify(state, rhs); + const auto ordered = Binary(state, maxValue ? spv::OpFOrdGreaterThan : spv::OpFOrdLessThan, TypeBool(state), ToF64(state, lhs), ToF64(state, rhs)); + auto result = SelectBits(state, ordered, a.bits, b.bits); + const auto bothZero = Binary(state, spv::OpLogicalAnd, TypeBool(state), a.zero, b.zero); + const auto zero = maxValue ? SelectBits(state, a.negative, b.bits, a.bits) : SelectBits(state, a.negative, a.bits, b.bits); + result = SelectBits(state, bothZero, zero, result); + result = SelectBits(state, a.nan, b.bits, result); + result = SelectBits(state, b.nan, a.bits, result); + result = SelectBits(state, b.signalingNan, Quiet(state, b.bits), result); + result = SelectBits(state, a.signalingNan, Quiet(state, a.bits), result); + return Join(state, result); +} + +std::uint32_t Trunc(SpirvEmitterState& state, std::uint32_t value) { + const auto cls = Classify(state, value); + const auto exponent = Binary(state, spv::OpBitwiseAnd, TypeU32(state), Binary(state, spv::OpShiftRightLogical, TypeU32(state), cls.bits.high, ConstantU32(state, 20u)), ConstantU32(state, 0x7ffu)); + const auto fractionBits = Binary(state, spv::OpISub, TypeU32(state), ConstantU32(state, 1075u), exponent); + const auto highShift = Binary(state, spv::OpISub, TypeU32(state), Glsl(state, TypeU32(state), GLSLstd450UMax, fractionBits, ConstantU32(state, 32u)), ConstantU32(state, 32u)); + const auto lowShift = Glsl(state, TypeU32(state), GLSLstd450UMin, fractionBits, ConstantU32(state, 31u)); + const auto wideFraction = Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), fractionBits, ConstantU32(state, 32u)); + const auto lowMask = Select(state, TypeU32(state), wideFraction, ConstantU32(state, 0u), Binary(state, spv::OpShiftLeftLogical, TypeU32(state), ConstantU32(state, 0xffffffffu), lowShift)); + const auto highMask = Binary(state, spv::OpShiftLeftLogical, TypeU32(state), ConstantU32(state, 0xffffffffu), highShift); + const F64Bits masked{Binary(state, spv::OpBitwiseAnd, TypeU32(state), cls.bits.low, lowMask), Binary(state, spv::OpBitwiseAnd, TypeU32(state), cls.bits.high, highMask)}; + const F64Bits signedZero{ConstantU32(state, 0u), Binary(state, spv::OpBitwiseAnd, TypeU32(state), cls.bits.high, ConstantU32(state, 0x80000000u))}; + const auto belowOne = Binary(state, spv::OpULessThan, TypeBool(state), exponent, ConstantU32(state, 1023u)); + const auto integral = Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), exponent, ConstantU32(state, 1075u)); + auto result = SelectBits(state, belowOne, signedZero, masked); + result = SelectBits(state, integral, cls.bits, result); + return ToF64(state, Join(state, SelectBits(state, cls.nan, Quiet(state, cls.bits), result))); +} + +std::uint32_t Floor(SpirvEmitterState& state, std::uint32_t bits) { + const auto truncated = Trunc(state, bits); + const auto below = Binary(state, spv::OpFOrdLessThan, TypeBool(state), ToF64(state, bits), truncated); + return Select(state, TypeF64(state), below, Exact(state, spv::OpFSub, truncated, ConstantF64(state, 0x3ff0000000000000ull)), truncated); +} + +} + +std::uint32_t EmitFPAdd64(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1) { + return QuietResult(state, Exact(state, spv::OpFAdd, ToF64(state, arg0), ToF64(state, arg1))); +} + +std::uint32_t EmitFPMul64(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1) { + return QuietResult(state, Exact(state, spv::OpFMul, ToF64(state, arg0), ToF64(state, arg1))); +} + +std::uint32_t EmitFPFma64(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1, std::uint32_t arg2) { + const auto result = state.module.AllocateId(); + state.module.AddFunction(spv::OpExtInst, TypeF64(state), result, GlslStd450(state), GLSLstd450Fma, ToF64(state, arg0), ToF64(state, arg1), ToF64(state, arg2)); + state.module.AddAnnotation(spv::OpDecorate, result, spv::DecorationNoContraction); + return QuietResult(state, result); +} + +std::uint32_t EmitFPMin64(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1) { + return MinMax(state, arg0, arg1, false); +} + +std::uint32_t EmitFPMax64(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1) { + return MinMax(state, arg0, arg1, true); +} + +std::uint32_t EmitFPSaturate64(SpirvEmitterState& state, std::uint32_t arg0) { + const auto value = ToF64(state, arg0); + const auto positive = Binary(state, spv::OpFOrdGreaterThan, TypeBool(state), value, ConstantF64(state, 0u)); + const auto belowOne = Binary(state, spv::OpFOrdLessThan, TypeBool(state), value, ConstantF64(state, 0x3ff0000000000000ull)); + const auto upper = Select(state, TypeF64(state), belowOne, value, ConstantF64(state, 0x3ff0000000000000ull)); + return FromF64(state, Select(state, TypeF64(state), positive, upper, ConstantF64(state, 0u))); +} + +std::uint32_t EmitFPLdexp64(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1) { + const auto cls = Classify(state, arg0); + const auto exponentOf = [&](std::uint32_t high) { + return Binary(state, spv::OpBitwiseAnd, TypeU32(state), Binary(state, spv::OpShiftRightLogical, TypeU32(state), high, ConstantU32(state, 20u)), ConstantU32(state, 0x7ffu)); + }; + const auto subnormal = Binary(state, spv::OpIEqual, TypeBool(state), exponentOf(cls.bits.high), ConstantU32(state, 0u)); + const auto scaled = Exact(state, spv::OpFMul, ToF64(state, arg0), ConstantF64(state, 0x43f0000000000000ull)); + const auto normal = Split(state, FromF64(state, Select(state, TypeF64(state), subnormal, scaled, ToF64(state, arg0)))); + const auto bias = Select(state, TypeU32(state), subnormal, ConstantU32(state, 1022u + 64u), ConstantU32(state, 1022u)); + const auto exponent = Glsl(state, TypeI32(state), GLSLstd450SClamp, Unary(state, spv::OpBitcast, TypeI32(state), arg1), ConstantI32(state, -2200), ConstantI32(state, 2200)); + const auto shift = Binary(state, spv::OpISub, TypeU32(state), exponentOf(normal.high), bias); + const auto total = Glsl(state, TypeI32(state), GLSLstd450SClamp, Binary(state, spv::OpIAdd, TypeI32(state), Unary(state, spv::OpBitcast, TypeI32(state), shift), exponent), ConstantI32(state, -1100), ConstantI32(state, 1100)); + const auto mantissa = Join(state, {normal.low, Binary(state, spv::OpBitwiseOr, TypeU32(state), Binary(state, spv::OpBitwiseAnd, TypeU32(state), normal.high, ConstantU32(state, 0x800fffffu)), ConstantU32(state, 1022u << 20u))}); + const auto power = [&](std::uint32_t value) { + const auto biased = Binary(state, spv::OpIAdd, TypeI32(state), value, ConstantI32(state, 1023)); + const auto high = Binary(state, spv::OpShiftLeftLogical, TypeU32(state), Unary(state, spv::OpBitcast, TypeU32(state), biased), ConstantU32(state, 20u)); + return ToF64(state, Join(state, {ConstantU32(state, 0u), high})); + }; + const auto first = Glsl(state, TypeI32(state), GLSLstd450SClamp, total, ConstantI32(state, -1021), ConstantI32(state, 1023)); + const auto second = Binary(state, spv::OpISub, TypeI32(state), total, first); + const auto exact = Exact(state, spv::OpFMul, ToF64(state, mantissa), power(first)); + const auto result = Split(state, FromF64(state, Exact(state, spv::OpFMul, exact, power(Glsl(state, TypeI32(state), GLSLstd450SMax, second, ConstantI32(state, -60)))))); + const auto special = Binary(state, spv::OpIEqual, TypeBool(state), exponentOf(cls.bits.high), ConstantU32(state, 0x7ffu)); + const auto passthrough = Binary(state, spv::OpLogicalOr, TypeBool(state), cls.zero, special); + return Join(state, SelectBits(state, passthrough, SelectBits(state, cls.nan, Quiet(state, cls.bits), cls.bits), result)); +} + +std::uint32_t EmitFPRoundEven64(SpirvEmitterState& state, std::uint32_t arg0) { + const auto cls = Classify(state, arg0); + const auto value = ToF64(state, arg0); + const auto magnitude = Glsl(state, TypeF64(state), GLSLstd450FAbs, value); + const auto shift = ConstantF64(state, 0x4330000000000000ull); + const auto rounded = Split(state, FromF64(state, Exact(state, spv::OpFSub, Exact(state, spv::OpFAdd, magnitude, shift), shift))); + const auto sign = Binary(state, spv::OpBitwiseAnd, TypeU32(state), cls.bits.high, ConstantU32(state, 0x80000000u)); + const F64Bits signedRounded{rounded.low, Binary(state, spv::OpBitwiseOr, TypeU32(state), rounded.high, sign)}; + const auto integral = Binary(state, spv::OpFOrdGreaterThanEqual, TypeBool(state), magnitude, shift); + const auto result = SelectBits(state, integral, cls.bits, signedRounded); + return Join(state, SelectBits(state, cls.nan, Quiet(state, cls.bits), result)); +} + +std::uint32_t EmitFPFloor64(SpirvEmitterState& state, std::uint32_t arg0) { + return FromF64(state, Floor(state, arg0)); +} + +std::uint32_t EmitFPCeil64(SpirvEmitterState& state, std::uint32_t arg0) { + const auto truncated = Trunc(state, arg0); + const auto above = Binary(state, spv::OpFOrdGreaterThan, TypeBool(state), ToF64(state, arg0), truncated); + return FromF64(state, Select(state, TypeF64(state), above, Exact(state, spv::OpFAdd, truncated, ConstantF64(state, 0x3ff0000000000000ull)), truncated)); +} + +std::uint32_t EmitFPTrunc64(SpirvEmitterState& state, std::uint32_t arg0) { + return FromF64(state, Trunc(state, arg0)); +} + +std::uint32_t EmitFPFract64(SpirvEmitterState& state, std::uint32_t arg0) { + const auto value = ToF64(state, arg0); + const auto fraction = Exact(state, spv::OpFSub, value, Floor(state, arg0)); + const auto belowOne = ConstantF64(state, 0x3fefffffffffffffull); + const auto overflow = Binary(state, spv::OpFOrdGreaterThan, TypeBool(state), fraction, belowOne); + return QuietResult(state, Select(state, TypeF64(state), overflow, belowOne, fraction)); +} + +std::uint32_t EmitFPFrexpMant64(SpirvEmitterState& state, std::uint32_t arg0) { + const auto cls = Classify(state, arg0); + const auto split = Glsl(state, state.module.Type(spv::OpTypeStruct, TypeF64(state), TypeI32(state)), GLSLstd450FrexpStruct, ToF64(state, arg0)); + const auto mantissa = state.module.AllocateId(); + state.module.AddFunction(spv::OpCompositeExtract, TypeF64(state), mantissa, split, 0u); + const auto exponentBits = Binary(state, spv::OpBitwiseAnd, TypeU32(state), cls.bits.high, ConstantU32(state, 0x7ff00000u)); + const auto special = Binary(state, spv::OpIEqual, TypeBool(state), exponentBits, ConstantU32(state, 0x7ff00000u)); + const auto passthrough = SelectBits(state, cls.nan, Quiet(state, cls.bits), cls.bits); + return Join(state, SelectBits(state, special, passthrough, Split(state, FromF64(state, mantissa)))); +} + +std::uint32_t EmitFPFrexpExp64(SpirvEmitterState& state, std::uint32_t arg0) { + const auto bits = Split(state, arg0); + const auto split = Glsl(state, state.module.Type(spv::OpTypeStruct, TypeF64(state), TypeI32(state)), GLSLstd450FrexpStruct, ToF64(state, arg0)); + const auto exponent = state.module.AllocateId(); + state.module.AddFunction(spv::OpCompositeExtract, TypeI32(state), exponent, split, 1u); + const auto exponentBits = Binary(state, spv::OpBitwiseAnd, TypeU32(state), bits.high, ConstantU32(state, 0x7ff00000u)); + const auto special = Binary(state, spv::OpIEqual, TypeBool(state), exponentBits, ConstantU32(state, 0x7ff00000u)); + return Select(state, TypeU32(state), special, ConstantU32(state, 0u), Unary(state, spv::OpBitcast, TypeU32(state), exponent)); +} + +std::uint32_t EmitConvertF32F64(SpirvEmitterState& state, std::uint32_t arg0) { + const auto converted = Unary(state, spv::OpBitcast, TypeU32(state), Unary(state, spv::OpFConvert, TypeF32(state), ToF64(state, arg0))); + const auto quiet = Binary(state, spv::OpBitwiseOr, TypeU32(state), converted, ConstantU32(state, 0x00400000u)); + return Unary(state, spv::OpBitcast, TypeF32(state), Select(state, TypeU32(state), Classify(state, arg0).nan, quiet, converted)); +} + +std::uint32_t EmitConvertF64F32(SpirvEmitterState& state, std::uint32_t arg0) { + const auto converted = Split(state, FromF64(state, Unary(state, spv::OpFConvert, TypeF64(state), arg0))); + const auto nan = Binary(state, spv::OpFUnordNotEqual, TypeBool(state), arg0, arg0); + return Join(state, SelectBits(state, nan, Quiet(state, converted), converted)); +} + +std::uint32_t EmitConvertF64S32(SpirvEmitterState& state, std::uint32_t arg0) { + return FromF64(state, Unary(state, spv::OpConvertSToF, TypeF64(state), Unary(state, spv::OpBitcast, TypeI32(state), arg0))); +} + +std::uint32_t EmitConvertF64U32(SpirvEmitterState& state, std::uint32_t arg0) { + return FromF64(state, Unary(state, spv::OpConvertUToF, TypeF64(state), arg0)); +} + +std::uint32_t EmitConvertS32F64(SpirvEmitterState& state, std::uint32_t arg0) { + const auto value = ToF64(state, arg0); + const auto low = Binary(state, spv::OpFOrdLessThanEqual, TypeBool(state), value, ConstantF64(state, 0xc1e0000000000000ull)); + const auto high = Binary(state, spv::OpFOrdGreaterThanEqual, TypeBool(state), value, ConstantF64(state, 0x41e0000000000000ull)); + const auto ordered = Binary(state, spv::OpFOrdEqual, TypeBool(state), value, value); + const auto safe = Select(state, TypeF64(state), ordered, value, ConstantF64(state, 0u)); + const auto clamped = Trunc(state, FromF64(state, Select(state, TypeF64(state), Binary(state, spv::OpLogicalOr, TypeBool(state), low, high), ConstantF64(state, 0u), safe))); + const auto converted = Unary(state, spv::OpBitcast, TypeU32(state), Unary(state, spv::OpConvertFToS, TypeI32(state), clamped)); + const auto saturated = Select(state, TypeU32(state), high, ConstantU32(state, 0x7fffffffu), converted); + return Select(state, TypeU32(state), low, ConstantU32(state, 0x80000000u), saturated); +} + +std::uint32_t EmitConvertU32F64(SpirvEmitterState& state, std::uint32_t arg0) { + const auto value = ToF64(state, arg0); + const auto low = Binary(state, spv::OpFUnordLessThanEqual, TypeBool(state), value, ConstantF64(state, 0u)); + const auto high = Binary(state, spv::OpFOrdGreaterThanEqual, TypeBool(state), value, ConstantF64(state, 0x41f0000000000000ull)); + const auto clamped = Trunc(state, FromF64(state, Select(state, TypeF64(state), Binary(state, spv::OpLogicalOr, TypeBool(state), low, high), ConstantF64(state, 0u), value))); + const auto converted = Unary(state, spv::OpConvertFToU, TypeU32(state), clamped); + const auto saturated = Select(state, TypeU32(state), high, ConstantU32(state, 0xffffffffu), converted); + return Select(state, TypeU32(state), low, ConstantU32(state, 0u), saturated); +} + +} diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp index c8e4b025e..edcc2d49b 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp @@ -70,6 +70,9 @@ SpirvRequirements AnalyzeProgramRequirements(const IrProgram& program) { if (BufferAccessOf(inst->Opcode()) == BufferAccess::Atomic && inst->Type() == IrType::U64) { requirements.bufferInt64Atomics = true; } + if (IsFloat64Opcode(inst->Opcode())) { + requirements.float64 = true; + } const auto addressAccess = AddressOpcodeInfoOf(inst->Opcode()).access; if (addressAccess != AddressAccess::None) { const auto memoryIndex = inst->Flags().index; diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp index f92f674d0..5f63a5ade 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp @@ -419,6 +419,26 @@ void EmitDirectInstruction(SpirvValueEmitContext& ctx, const IrValue& inst) { case IrOpcode::FPCeil32: return Invoke(EmitFPCeil32, ctx, inst); case IrOpcode::FPTrunc32: return Invoke(EmitFPTrunc32, ctx, inst); case IrOpcode::FPFract32: return Invoke(EmitFPFract32, ctx, inst); + case IrOpcode::FPAdd64: return Invoke(EmitFPAdd64, ctx, inst); + case IrOpcode::FPMul64: return Invoke(EmitFPMul64, ctx, inst); + case IrOpcode::FPFma64: return Invoke(EmitFPFma64, ctx, inst); + case IrOpcode::FPMin64: return Invoke(EmitFPMin64, ctx, inst); + case IrOpcode::FPMax64: return Invoke(EmitFPMax64, ctx, inst); + case IrOpcode::FPSaturate64: return Invoke(EmitFPSaturate64, ctx, inst); + case IrOpcode::FPLdexp64: return Invoke(EmitFPLdexp64, ctx, inst); + case IrOpcode::FPRoundEven64: return Invoke(EmitFPRoundEven64, ctx, inst); + case IrOpcode::FPFloor64: return Invoke(EmitFPFloor64, ctx, inst); + case IrOpcode::FPCeil64: return Invoke(EmitFPCeil64, ctx, inst); + case IrOpcode::FPTrunc64: return Invoke(EmitFPTrunc64, ctx, inst); + case IrOpcode::FPFract64: return Invoke(EmitFPFract64, ctx, inst); + case IrOpcode::FPFrexpMant64: return Invoke(EmitFPFrexpMant64, ctx, inst); + case IrOpcode::FPFrexpExp64: return Invoke(EmitFPFrexpExp64, ctx, inst); + case IrOpcode::ConvertF32F64: return Invoke(EmitConvertF32F64, ctx, inst); + case IrOpcode::ConvertF64F32: return Invoke(EmitConvertF64F32, ctx, inst); + case IrOpcode::ConvertF64S32: return Invoke(EmitConvertF64S32, ctx, inst); + case IrOpcode::ConvertF64U32: return Invoke(EmitConvertF64U32, ctx, inst); + case IrOpcode::ConvertS32F64: return Invoke(EmitConvertS32F64, ctx, inst); + case IrOpcode::ConvertU32F64: return Invoke(EmitConvertU32F64, ctx, inst); case IrOpcode::LaneId: return Invoke(EmitLaneId, ctx, inst); case IrOpcode::WriteLane: return Invoke(EmitWriteLane, ctx, inst); case IrOpcode::Permlane16U32: return Invoke(EmitPermlane16U32, ctx, inst); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvModuleSetup.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvModuleSetup.cpp index bf6dfb48b..3a3b9539d 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvModuleSetup.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvModuleSetup.cpp @@ -166,6 +166,13 @@ void DefineModule(SpirvEmitterState& state) { state.module.EmitCapability(spv::CapabilitySignedZeroInfNanPreserve); state.module.EmitExtension("SPV_KHR_float_controls"); state.module.AddExecutionMode(state.mainFunc, spv::ExecutionModeSignedZeroInfNanPreserve, 32u); + if (state.requirements.float64) { + if (std::find(state.supportedCapabilities.begin(), state.supportedCapabilities.end(), static_cast(spv::CapabilityFloat64)) == state.supportedCapabilities.end()) { + throw std::runtime_error("64-bit float instructions need the Float64 capability, which the device lacks"); + } + state.module.EmitCapability(spv::CapabilityFloat64); + state.module.AddExecutionMode(state.mainFunc, spv::ExecutionModeSignedZeroInfNanPreserve, 64u); + } if (const auto* workgroup = ShaderWorkgroupInput(state)) { const std::uint32_t derivativeDefault = state.requirements.computeDerivatives ? 2u : 1u; std::uint32_t localX = workgroup->threadsNum[0] != 0u ? workgroup->threadsNum[0] : derivativeDefault; diff --git a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp index d06a2a573..ed8a2f0a2 100644 --- a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp +++ b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp @@ -52,6 +52,7 @@ private: std::array readU32Pair(const RdnaOperand& operand); IrU64 readU64(const RdnaOperand& operand); std::array readF64Bits(const RdnaOperand& operand); + IrU64 readF64(const RdnaOperand& operand); IrF32 readF16LaneAsF32(const RdnaOperand& operand, bool highLane, bool packed = false); IrF32 readF16AsF32(const RdnaOperand& operand); IrU32 readF16SourceBits(const RdnaOperand& operand); @@ -162,6 +163,7 @@ private: bool vSatPkU8I16(const RdnaInstruction& inst); bool vMulLegacyF32(const RdnaInstruction& inst, bool accumulate); void emitFloat16ClassCompare(const RdnaInstruction& inst, bool cmpx); + bool float64Operation(const RdnaInstruction& inst, IrOpcode opcode); bool vDot2cF32F16(const RdnaInstruction& inst); bool vCubeidF32(const RdnaInstruction& inst); bool vCubescF32(const RdnaInstruction& inst); diff --git a/core/shader/recompiler/Translation/src/Float64Instructions.cpp b/core/shader/recompiler/Translation/src/Float64Instructions.cpp new file mode 100644 index 000000000..a2a338252 --- /dev/null +++ b/core/shader/recompiler/Translation/src/Float64Instructions.cpp @@ -0,0 +1,45 @@ +#include "Translation/TranslationContext.hpp" +#include +#include + +namespace ShaderRecompiler { + +IrU64 TranslationContext::readF64(const RdnaOperand& operand) { + std::array pair{IrU32(ir.Constant(0u)), readRawU32(operand)}; + if (operand.kind == RdnaOperandKind::FloatInlineConstant && operand.value == 0x3e22f983u) { + pair = {IrU32(ir.Constant(0x6dc9c882u)), IrU32(ir.Constant(0x3fc45f30u))}; + } else if (operand.kind != RdnaOperandKind::LiteralConstant) { + pair = readU32Pair(plainOperand(operand)); + } + if (operand.absolute) { + pair[1] = IrU32(ir.BitwiseAnd(pair[1].Value(), ir.Constant(0x7fffffffu))); + } + if (operand.negate) { + pair[1] = IrU32(ir.BitwiseXor(pair[1].Value(), ir.Constant(0x80000000u))); + } + return IrU64(ir.ConstructU64(pair[0].Value(), pair[1].Value())); +} + +bool TranslationContext::float64Operation(const RdnaInstruction& inst, IrOpcode opcode) { + const std::size_t count = IrOpcodeOperandCount(opcode); + std::array args{}; + for (std::uint32_t index = 0u; index < count; ++index) { + const RdnaOperand& operand = sourceAt(inst, index); + switch (IrOpcodeArgumentType(opcode, index)) { + case IrType::U64: args[index] = &readF64(operand).Value(); break; + case IrType::F32: args[index] = readOperand(operand, IrType::F32); break; + default: args[index] = &readU32(operand).Value(); break; + } + } + const IrType type = IrOpcodeType(opcode); + IrValue* result = count == 1u ? &ir.Emit(opcode, type, {args[0]}) : count == 2u ? &ir.Emit(opcode, type, {args[0], args[1]}) : &ir.Emit(opcode, type, {args[0], args[1], args[2]}); + RdnaOperand destination = inst.destination; + destination.omod = 0u; + if (type == IrType::U64 && destination.clamp) { + result = &ir.Emit(IrOpcode::FPSaturate64, IrType::U64, {result}); + } + writeOperand(destination, result); + return true; +} + +} diff --git a/core/shader/recompiler/Translation/src/VectorInstructions.cpp b/core/shader/recompiler/Translation/src/VectorInstructions.cpp index d0bd343aa..111f40b12 100644 --- a/core/shader/recompiler/Translation/src/VectorInstructions.cpp +++ b/core/shader/recompiler/Translation/src/VectorInstructions.cpp @@ -835,6 +835,44 @@ bool TranslationContext::emitVector(const RdnaInstruction& inst) { return vDivFixupF16(inst); case RdnaOpcode::VFrexpMantF32: return vFrexpMantF32(inst); + case RdnaOpcode::VAddF64: + return float64Operation(inst, IrOpcode::FPAdd64); + case RdnaOpcode::VMulF64: + return float64Operation(inst, IrOpcode::FPMul64); + case RdnaOpcode::VFmaF64: + return float64Operation(inst, IrOpcode::FPFma64); + case RdnaOpcode::VMinF64: + return float64Operation(inst, IrOpcode::FPMin64); + case RdnaOpcode::VMaxF64: + return float64Operation(inst, IrOpcode::FPMax64); + case RdnaOpcode::VLdexpF64: + return float64Operation(inst, IrOpcode::FPLdexp64); + case RdnaOpcode::VTruncF64: + return float64Operation(inst, IrOpcode::FPTrunc64); + case RdnaOpcode::VCeilF64: + return float64Operation(inst, IrOpcode::FPCeil64); + case RdnaOpcode::VRndneF64: + return float64Operation(inst, IrOpcode::FPRoundEven64); + case RdnaOpcode::VFloorF64: + return float64Operation(inst, IrOpcode::FPFloor64); + case RdnaOpcode::VFractF64: + return float64Operation(inst, IrOpcode::FPFract64); + case RdnaOpcode::VFrexpMantF64: + return float64Operation(inst, IrOpcode::FPFrexpMant64); + case RdnaOpcode::VFrexpExpI32F64: + return float64Operation(inst, IrOpcode::FPFrexpExp64); + case RdnaOpcode::VCvtF32F64: + return float64Operation(inst, IrOpcode::ConvertF32F64); + case RdnaOpcode::VCvtF64F32: + return float64Operation(inst, IrOpcode::ConvertF64F32); + case RdnaOpcode::VCvtF64I32: + return float64Operation(inst, IrOpcode::ConvertF64S32); + case RdnaOpcode::VCvtF64U32: + return float64Operation(inst, IrOpcode::ConvertF64U32); + case RdnaOpcode::VCvtI32F64: + return float64Operation(inst, IrOpcode::ConvertS32F64); + case RdnaOpcode::VCvtU32F64: + return float64Operation(inst, IrOpcode::ConvertU32F64); case RdnaOpcode::VRcpF32: return floatUnary(inst, IrOpcode::FPRecip32); case RdnaOpcode::VRcpIflagF32: