Merge pull request #1017 from NahuelGenchi/fix/v-fma-f64-subnormals
fix(shader): round v_fma_f64 once for subnormal operands and results
This commit is contained in:
commit
00dd2ba844
|
|
@ -0,0 +1,157 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
|
||||
#include "Recompiler.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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 = 2;
|
||||
alignas(256) std::array<std::uint32_t, Threads * Inputs> Input{};
|
||||
alignas(256) std::array<std::uint32_t, Threads * Results> Output{};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 12> Code{
|
||||
0x34020085, 0x34060083, 0xe0381000, 0x80000401, 0xe0381010, 0x80000801, 0xbf8c3f70, 0xd54c000c,
|
||||
0x04220d04, 0xe0741000, 0x80010c03, 0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 13> FmasCode{
|
||||
0x34020085, 0x34060083, 0xe0381000, 0x80000401, 0xe0381010, 0x80000801, 0xbf8c3f70, 0xbeea03c1,
|
||||
0xd570000c, 0x04220d04, 0xe0741000, 0x80010c03, 0xbf810000,
|
||||
};
|
||||
|
||||
constexpr std::uint64_t Rows[Threads][4] = {
|
||||
{0xaf909f1570e40fb2ull, 0x10697a78dd1df49bull, 0x8000000000000000ull, 0x800d3be6095b3463ull},
|
||||
{0x1e50511fd30f5d67ull, 0x24c92a1b12df5283ull, 0x8329a9b24976c10bull, 0x0006cc7690f01937ull},
|
||||
{0x98cd9ffcf3ed94edull, 0x2a51579178c2f7a9ull, 0x03300e10060c0a21ull, 0x000826b2393bc091ull},
|
||||
{0x2c67bd24a078a9e3ull, 0x169c3293f5b93146ull, 0x8314eb0558ce14d3ull, 0x8002b781181390c7ull},
|
||||
{0xbcca08e86f6c243dull, 0x0647cf3a0cd21b23ull, 0x03235effea3a0f40ull, 0x00053433dc65a9cbull},
|
||||
{0x97cb9cf8c8a2fbb6ull, 0xab2cb31ef6264b43ull, 0x8308c3ea76c1ebe5ull, 0x00014c7d2a0da8cbull},
|
||||
{0x0946591c0410303cull, 0xba059dbfbeb6d4adull, 0x80099e28c52b6c3dull, 0x835e314afa5d1a07ull},
|
||||
{0xcee37832c306ee31ull, 0x80017e95cd7d98dfull, 0x8c168438ac98afa4ull, 0x0ecd18c0555dceefull},
|
||||
{0x800e8b91a326e01dull, 0xd020fb9f53a2df2cull, 0x8cb514f5ff6d74b0ull, 0x103ee09fedab559aull},
|
||||
{0x401fac71cda29715ull, 0x8008000020000000ull, 0x002fac724c545e4full, 0x0000000000000007ull},
|
||||
{0x2e4c000000000000ull, 0x11aeb6db849700ffull, 0x800d70000a021070ull, 0x8000000000000000ull},
|
||||
{0x41d0000000000008ull, 0x0000000040000002ull, 0x80761e2b2ef2fce7ull, 0x0084f0ea6986819dull},
|
||||
{0xfc21916e447cf802ull, 0x8004eec859174483ull, 0x3be2fe907aa80074ull, 0x3c26da145be5eaacull},
|
||||
{0x35d175fd70782c6cull, 0x0c6033e7239c8fe0ull, 0x0005cb65548dabd1ull, 0x0241aea1c6a7627dull},
|
||||
{0xb4fb1dc3f1ce6211ull, 0x8b80000000000100ull, 0x800000000b9b5f36ull, 0x008b1dc3f1b72d04ull},
|
||||
{0x0008000000000000ull, 0x8004000000000000ull, 0x0000000000000000ull, 0x8000000000000000ull},
|
||||
{0x0008000000000000ull, 0x0004000000000000ull, 0x0170000000000000ull, 0x0170000000000000ull},
|
||||
{0x4004000000000000ull, 0x0000000000000001ull, 0x0000000000000000ull, 0x0000000000000002ull},
|
||||
{0x3fe0000000000000ull, 0x0000000000000001ull, 0x0000000000000000ull, 0x0000000000000000ull},
|
||||
{0x3fe0000000000001ull, 0x0000000000000001ull, 0x0000000000000000ull, 0x0000000000000001ull},
|
||||
{0x3fefffffffffffffull, 0x0010000000000000ull, 0x0000000000000000ull, 0x0010000000000000ull},
|
||||
{0x1e60000000000000ull, 0x1e60000000000000ull, 0x8000000000000001ull, 0x0000000000000000ull},
|
||||
{0x3ff0000000000001ull, 0x0018000000000001ull, 0x8010000000000000ull, 0x0008000000000003ull},
|
||||
{0x3ff8000000000000ull, 0x4000000000000000ull, 0x3fd0000000000000ull, 0x400a000000000000ull},
|
||||
{0x3ff0000000000001ull, 0x3ff0000000000001ull, 0xbff0000000000000ull, 0x3cc0000000000000ull},
|
||||
{0xffefffffffffffffull, 0x3ff0000000000000ull, 0x000fffffffffffffull, 0xffefffffffffffffull},
|
||||
{0x5fefffffffffffffull, 0x5fefffffffffffffull, 0x0000000000000000ull, 0x7feffffffffffffeull},
|
||||
{0x000fffffffffffffull, 0x4000000000000000ull, 0x8000000000000001ull, 0x001ffffffffffffdull},
|
||||
{0x8000000000000003ull, 0x7fe8000000000000ull, 0x3ff0000000000000ull, 0x3fefffffffffffeeull},
|
||||
{0x1a75555555555555ull, 0x000aaaaaaaaaaaabull, 0x0000000000000000ull, 0x0000000000000000ull},
|
||||
{0x0173000000000000ull, 0x3e17000000000000ull, 0x80001b5000000000ull, 0x0000000000000000ull},
|
||||
{0xa000000000000001ull, 0x1fffffffffffffffull, 0x0010000000000000ull, 0x8000000000000000ull},
|
||||
};
|
||||
|
||||
constexpr std::uint64_t FmasRows[][4] = {
|
||||
{0xcee37832c306ee31ull, 0x80017e95cd7d98dfull, 0x8c168438ac98afa4ull, 0x06cd18c0555dceefull},
|
||||
{0x800e8b91a326e01dull, 0xd020fb9f53a2df2cull, 0x8cb514f5ff6d74b0ull, 0x083ee09fedab559aull},
|
||||
{0xfc21916e447cf802ull, 0x8004eec859174483ull, 0x3be2fe907aa80074ull, 0x3426da145be5eaacull},
|
||||
{0x6456b4d74e778407ull, 0x0000000000000112ull, 0x20280ac49e7f906cull, 0x19b84d8e7e014d9full},
|
||||
{0x3b87a9e17b60d8beull, 0x0c5959dcff86b48aull, 0x026239792b5377a2ull, 0x0004afcb04199627ull},
|
||||
{0xaf25fa06e8714b4bull, 0x18d2b6bb683e8122ull, 0x02a53f820f7c716bull, 0x800cda229eb9a09dull},
|
||||
{0xb99c5fe31c736fbbull, 0x8e5264264dcfc027ull, 0x8022aa8efefbe52cull, 0x0008275eae4b3d41ull},
|
||||
{0xc6fdfbc0001ad5dfull, 0x00ffcab17e745bd1ull, 0x82e0e3a0b85211a8ull, 0x800ee4e6bd8a79e5ull},
|
||||
{0xc1d1a6cfa76ad3b9ull, 0x862ba38c1492703full, 0x017130645c1851d5ull, 0x000f3ef62c247005ull},
|
||||
{0xada41786a4b824bcull, 0x1a554baa4a83fe63ull, 0x0134fb8e603406d4ull, 0x800d5ef26b6f7a29ull},
|
||||
{0xb81b70578b6c14b9ull, 0x8fcdd6775e710cc1ull, 0x829c666c28b85110ull, 0x0006656d3ea3b537ull},
|
||||
{0x2c256baa0ef1c91bull, 0x9baa7695febd5f53ull, 0x82d3dfc5a191618bull, 0x800236db72e217c3ull},
|
||||
{0x355013dab21e2539ull, 0x1296e6732f928ca7ull, 0x82f4e48c5e9c126cull, 0x0005c0b776cad66bull},
|
||||
{0xc458817a2d2ea918ull, 0x03a473fd45d66f08ull, 0x03240e0c4f7cfcc4ull, 0x800fa9bfa078e4f1ull},
|
||||
{0x33291df738c3f676ull, 0x146a0194f3933c51ull, 0x0120115306c4e7b2ull, 0x000028d32d6f3f07ull},
|
||||
{0xb1d797d703ad9014ull, 0x9613cb63b23f1b8bull, 0x0244b2cb24e6fb3aull, 0x00074c0e33ad5c9dull},
|
||||
{0x4656b4266997b7f2ull, 0x819765c0b9bfcad0ull, 0x03c9a52d2b9f1094ull, 0x80084cd678e92eefull},
|
||||
{0x4513191709caec8cull, 0x02d3b5ddc4ac6b05ull, 0x826384dfaf4b9e81ull, 0x0005e1b7f963c7dbull},
|
||||
{0xba6ec9ec81f6efb7ull, 0x8d785fa355446f4eull, 0x0279c121fe09c0aaull, 0x0005dcdd7f8c7cd3ull},
|
||||
{0xbe667b0282fcc03cull, 0x897ce8a400290c2cull, 0x0378775e473d7a50ull, 0x000513c44ed72187ull},
|
||||
};
|
||||
|
||||
std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t bytes) {
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(data);
|
||||
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu), bytes, 0x31016facu};
|
||||
}
|
||||
|
||||
std::string Hex(std::uint64_t value) {
|
||||
char text[24];
|
||||
std::snprintf(text, sizeof(text), "0x%016llx", static_cast<unsigned long long>(value));
|
||||
return text;
|
||||
}
|
||||
|
||||
template <std::size_t Words, std::size_t Count>
|
||||
void Run(AgcDriver::VulkanDevice& device, const std::array<std::uint32_t, Words>& program, const std::uint64_t (&rows)[Count][4]) {
|
||||
Input.fill(0u);
|
||||
for (std::uint32_t tid = 0; tid < Count; ++tid) {
|
||||
for (std::uint32_t operand = 0; operand < 3; ++operand) {
|
||||
Input[tid * Inputs + operand * 2] = static_cast<std::uint32_t>(rows[tid][operand]);
|
||||
Input[tid * Inputs + operand * 2 + 1] = static_cast<std::uint32_t>(rows[tid][operand] >> 32u);
|
||||
}
|
||||
}
|
||||
Output.fill(0xdeadbeefu);
|
||||
std::vector<std::uint32_t> userData(8, 0u);
|
||||
const auto input = BufferDescriptor(Input.data(), static_cast<std::uint32_t>(Input.size() * 4u));
|
||||
const auto output = BufferDescriptor(Output.data(), static_cast<std::uint32_t>(Output.size() * 4u));
|
||||
std::copy(input.begin(), input.end(), userData.begin());
|
||||
std::copy(output.begin(), output.end(), userData.begin() + 4);
|
||||
const std::span<const std::uint32_t> code(program);
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(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<std::uintptr_t>(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<std::uintptr_t>(code.data()));
|
||||
device.WaitIdle();
|
||||
}
|
||||
|
||||
template <std::size_t Count>
|
||||
void Check(const char* name, const std::uint64_t (&rows)[Count][4]) {
|
||||
for (std::uint32_t tid = 0; tid < Count; ++tid) {
|
||||
const std::uint64_t actual = Output[tid * Results] | (static_cast<std::uint64_t>(Output[tid * Results + 1]) << 32u);
|
||||
Require(actual == rows[tid][3], std::string(name) + ": lane " + std::to_string(tid) + " (" + Hex(rows[tid][0]) + ", " + Hex(rows[tid][1]) + ", " + Hex(rows[tid][2]) + ") is " + Hex(actual) + ", expected " + Hex(rows[tid][3]));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
Run(*device, Code, Rows);
|
||||
Check("v_fma_f64", Rows);
|
||||
Run(*device, FmasCode, FmasRows);
|
||||
Check("v_div_fmas_f64 with VCC set", FmasRows);
|
||||
std::puts("v_fma_f64 subnormal tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -306,32 +306,89 @@ std::uint32_t FusedMultiplyAdd(SpirvEmitterState& state, std::uint32_t arg0, std
|
|||
};
|
||||
|
||||
const auto signedInt = [&](std::uint32_t value) { return Unary(state, spv::OpBitcast, TypeI32(state), value); };
|
||||
const auto product = Binary(state, spv::OpISub, TypeI32(state), signedInt(Binary(state, spv::OpIAdd, TypeU32(state), exponentA, exponentB)), ConstantI32(state, 2046));
|
||||
const auto distance = Binary(state, spv::OpISub, TypeI32(state), Binary(state, spv::OpISub, TypeI32(state), signedInt(exponentC), ConstantI32(state, 1023)), product);
|
||||
const auto far = Binary(state, spv::OpLogicalAnd, TypeBool(state), Binary(state, spv::OpSGreaterThanEqual, TypeBool(state), distance, ConstantI32(state, 60)), Unary(state, spv::OpLogicalNot, TypeBool(state), c.zero));
|
||||
const auto both = [&](std::uint32_t lhs, std::uint32_t rhs) { return Binary(state, spv::OpLogicalAnd, TypeBool(state), lhs, rhs); };
|
||||
const auto either = [&](std::uint32_t lhs, std::uint32_t rhs) { return Binary(state, spv::OpLogicalOr, TypeBool(state), lhs, rhs); };
|
||||
const auto negate = [&](std::uint32_t value) { return Unary(state, spv::OpLogicalNot, TypeBool(state), value); };
|
||||
struct Normalized {
|
||||
F64Bits bits;
|
||||
std::uint32_t exponent = 0;
|
||||
};
|
||||
const auto normalize = [&](const F64Class& value, std::uint32_t bits, std::uint32_t field) {
|
||||
const auto subnormal = isField(field, 0u);
|
||||
const auto widened = Split(state, FromF64(state, Exact(state, spv::OpFMul, ToF64(state, bits), ConstantF64(state, 0x43f0000000000000ull))));
|
||||
const auto normal = SelectBits(state, subnormal, widened, value.bits);
|
||||
const auto bias = Select(state, TypeI32(state), subnormal, ConstantI32(state, 64), ConstantI32(state, 0));
|
||||
return Normalized{normal, Binary(state, spv::OpISub, TypeI32(state), signedInt(ExponentField(state, normal.high)), bias)};
|
||||
};
|
||||
const auto normalA = normalize(a, arg0, exponentA);
|
||||
const auto normalB = normalize(b, arg1, exponentB);
|
||||
const auto normalC = normalize(c, arg2, exponentC);
|
||||
const auto product = Binary(state, spv::OpISub, TypeI32(state), Binary(state, spv::OpIAdd, TypeI32(state), normalA.exponent, normalB.exponent), ConstantI32(state, 2046));
|
||||
const auto distance = Binary(state, spv::OpISub, TypeI32(state), Binary(state, spv::OpISub, TypeI32(state), normalC.exponent, ConstantI32(state, 1023)), product);
|
||||
const auto far = both(Binary(state, spv::OpSGreaterThanEqual, TypeBool(state), distance, ConstantI32(state, 60)), negate(c.zero));
|
||||
const auto tiny = Binary(state, spv::OpSLessThan, TypeBool(state), distance, ConstantI32(state, -150));
|
||||
const auto clamped = Glsl(state, TypeI32(state), GLSLstd450SClamp, distance, ConstantI32(state, -150), ConstantI32(state, 60));
|
||||
const auto sign = Binary(state, spv::OpBitwiseAnd, TypeU32(state), c.bits.high, ConstantU32(state, 0x80000000u));
|
||||
const F64Bits tinyBits{ConstantU32(state, 0u), Binary(state, spv::OpBitwiseOr, TypeU32(state), sign, ConstantU32(state, (1023u - 150u) << 20u))};
|
||||
const auto rebiased = Unary(state, spv::OpBitcast, TypeU32(state), Binary(state, spv::OpIAdd, TypeI32(state), clamped, ConstantI32(state, 1023)));
|
||||
const auto scaledC = SelectBits(state, c.zero, c.bits, SelectBits(state, tiny, tinyBits, WithExponent(state, c.bits, rebiased)));
|
||||
const auto scaledC = SelectBits(state, c.zero, c.bits, SelectBits(state, tiny, tinyBits, WithExponent(state, normalC.bits, rebiased)));
|
||||
|
||||
const auto exact = TwoProduct(state, ToF64(state, Join(state, WithExponent(state, a.bits, ConstantU32(state, 1023u)))), ToF64(state, Join(state, WithExponent(state, b.bits, ConstantU32(state, 1023u)))));
|
||||
const auto exact = TwoProduct(state, ToF64(state, Join(state, WithExponent(state, normalA.bits, ConstantU32(state, 1023u)))), ToF64(state, Join(state, WithExponent(state, normalB.bits, ConstantU32(state, 1023u)))));
|
||||
const auto sum = TwoSum(state, ToF64(state, Join(state, scaledC)), exact.high);
|
||||
const auto scaled = Exact(state, spv::OpFAdd, sum.high, AddRoundToOdd(state, sum.low, exact.low));
|
||||
const auto power = PowerOfTwo(state, scale);
|
||||
const auto total = Glsl(state, TypeI32(state), GLSLstd450SClamp, Binary(state, spv::OpIAdd, TypeI32(state), product, scale), ConstantI32(state, -2044), ConstantI32(state, 2046));
|
||||
const auto half = Binary(state, spv::OpSDiv, TypeI32(state), total, ConstantI32(state, 2));
|
||||
const auto rescaled = Exact(state, spv::OpFMul, Exact(state, spv::OpFMul, scaled, PowerOfTwo(state, half)), PowerOfTwo(state, Binary(state, spv::OpISub, TypeI32(state), total, half)));
|
||||
const auto farValue = Split(state, FromF64(state, Exact(state, spv::OpFMul, ToF64(state, arg2), power)));
|
||||
const auto finite = SelectBits(state, far, farValue, Split(state, FromF64(state, rescaled)));
|
||||
const auto remainder = TwoSum(state, sum.low, exact.low);
|
||||
const auto odd = AddRoundToOdd(state, sum.low, exact.low);
|
||||
const auto rounded = TwoSum(state, sum.high, odd);
|
||||
const auto residual = Exact(state, spv::OpFAdd, rounded.low, Exact(state, spv::OpFAdd, Exact(state, spv::OpFSub, remainder.high, odd), remainder.low));
|
||||
const auto productSign = Binary(state, spv::OpBitwiseAnd, TypeU32(state), Binary(state, spv::OpBitwiseXor, TypeU32(state), a.bits.high, b.bits.high), ConstantU32(state, 0x80000000u));
|
||||
const auto productNegative = Binary(state, spv::OpINotEqual, TypeBool(state), productSign, ConstantU32(state, 0u));
|
||||
const auto above = either(both(far, negate(productNegative)), both(negate(far), Binary(state, spv::OpFOrdGreaterThan, TypeBool(state), residual, ConstantF64(state, 0u))));
|
||||
const auto below = either(both(far, productNegative), both(negate(far), Binary(state, spv::OpFOrdLessThan, TypeBool(state), residual, ConstantF64(state, 0u))));
|
||||
|
||||
auto value = SelectBits(state, far, normalC.bits, Split(state, FromF64(state, rounded.high)));
|
||||
const auto farTotal = Binary(state, spv::OpISub, TypeI32(state), Binary(state, spv::OpIAdd, TypeI32(state), normalC.exponent, scale), signedInt(ExponentField(state, normalC.bits.high)));
|
||||
const auto total = Select(state, TypeI32(state), far, farTotal, Binary(state, spv::OpIAdd, TypeI32(state), product, scale));
|
||||
const auto resultExponent = [&](F64Bits bits) { return Binary(state, spv::OpIAdd, TypeI32(state), signedInt(ExponentField(state, bits.high)), total); };
|
||||
const auto magnitude = Binary(state, spv::OpBitwiseOr, TypeU32(state), Binary(state, spv::OpBitwiseAnd, TypeU32(state), value.high, ConstantU32(state, 0x7fffffffu)), value.low);
|
||||
const auto zero = Binary(state, spv::OpIEqual, TypeBool(state), magnitude, ConstantU32(state, 0u));
|
||||
const auto unrounded = resultExponent(value);
|
||||
const auto shift = Binary(state, spv::OpISub, TypeI32(state), ConstantI32(state, 1), unrounded);
|
||||
const auto dropped = Unary(state, spv::OpBitcast, TypeU32(state), Glsl(state, TypeI32(state), GLSLstd450SClamp, shift, ConstantI32(state, 1), ConstantI32(state, 54)));
|
||||
const auto isHalf = [&](std::uint32_t word, std::uint32_t bits) {
|
||||
const auto mask = Binary(state, spv::OpShiftRightLogical, TypeU32(state), ConstantU32(state, 0xffffffffu), Binary(state, spv::OpISub, TypeU32(state), ConstantU32(state, 32u), bits));
|
||||
const auto half = Binary(state, spv::OpShiftLeftLogical, TypeU32(state), ConstantU32(state, 1u), Binary(state, spv::OpISub, TypeU32(state), bits, ConstantU32(state, 1u)));
|
||||
return Binary(state, spv::OpIEqual, TypeBool(state), Binary(state, spv::OpBitwiseAnd, TypeU32(state), word, mask), half);
|
||||
};
|
||||
const auto lowHalf = isHalf(value.low, Glsl(state, TypeU32(state), GLSLstd450UMin, dropped, ConstantU32(state, 32u)));
|
||||
const auto significandHigh = Binary(state, spv::OpBitwiseOr, TypeU32(state), Binary(state, spv::OpBitwiseAnd, TypeU32(state), value.high, ConstantU32(state, 0x000fffffu)), ConstantU32(state, 0x00100000u));
|
||||
const auto highBits = Binary(state, spv::OpISub, TypeU32(state), Glsl(state, TypeU32(state), GLSLstd450UMax, dropped, ConstantU32(state, 33u)), ConstantU32(state, 32u));
|
||||
const auto highHalf = both(Binary(state, spv::OpIEqual, TypeBool(state), value.low, ConstantU32(state, 0u)), isHalf(significandHigh, highBits));
|
||||
const auto wide = Binary(state, spv::OpUGreaterThan, TypeBool(state), dropped, ConstantU32(state, 32u));
|
||||
const auto onHalf = either(both(wide, highHalf), both(negate(wide), lowHalf));
|
||||
const auto inRange = both(Binary(state, spv::OpSLessThanEqual, TypeBool(state), unrounded, ConstantI32(state, 0)), Binary(state, spv::OpSLessThanEqual, TypeBool(state), shift, ConstantI32(state, 54)));
|
||||
const auto midpoint = both(both(negate(zero), inRange), both(onHalf, either(above, below)));
|
||||
const auto negative = Binary(state, spv::OpINotEqual, TypeBool(state), Binary(state, spv::OpBitwiseAnd, TypeU32(state), value.high, ConstantU32(state, 0x80000000u)), ConstantU32(state, 0u));
|
||||
const auto away = either(both(above, negate(negative)), both(below, negative));
|
||||
const auto carry = Select(state, TypeU32(state), Binary(state, spv::OpIEqual, TypeBool(state), value.low, ConstantU32(state, 0xffffffffu)), ConstantU32(state, 1u), ConstantU32(state, 0u));
|
||||
const auto borrow = Select(state, TypeU32(state), Binary(state, spv::OpIEqual, TypeBool(state), value.low, ConstantU32(state, 0u)), ConstantU32(state, 1u), ConstantU32(state, 0u));
|
||||
const F64Bits increased{Binary(state, spv::OpIAdd, TypeU32(state), value.low, ConstantU32(state, 1u)), Binary(state, spv::OpIAdd, TypeU32(state), value.high, carry)};
|
||||
const F64Bits decreased{Binary(state, spv::OpISub, TypeU32(state), value.low, ConstantU32(state, 1u)), Binary(state, spv::OpISub, TypeU32(state), value.high, borrow)};
|
||||
value = SelectBits(state, midpoint, SelectBits(state, away, increased, decreased), value);
|
||||
|
||||
const auto exponent = resultExponent(value);
|
||||
const auto normalResult = WithExponent(state, value, Unary(state, spv::OpBitcast, TypeU32(state), Glsl(state, TypeI32(state), GLSLstd450SClamp, exponent, ConstantI32(state, 1), ConstantI32(state, 2046))));
|
||||
const auto lowest = PowerOfTwo(state, Glsl(state, TypeI32(state), GLSLstd450SMax, Binary(state, spv::OpISub, TypeI32(state), exponent, ConstantI32(state, 1)), ConstantI32(state, -60)));
|
||||
const auto subnormalResult = Split(state, FromF64(state, Exact(state, spv::OpFMul, ToF64(state, Join(state, WithExponent(state, value, ConstantU32(state, 1u)))), lowest)));
|
||||
const F64Bits infinity{ConstantU32(state, 0u), Binary(state, spv::OpBitwiseOr, TypeU32(state), Binary(state, spv::OpBitwiseAnd, TypeU32(state), value.high, ConstantU32(state, 0x80000000u)), ConstantU32(state, 0x7ff00000u))};
|
||||
auto finite = SelectBits(state, Binary(state, spv::OpSGreaterThanEqual, TypeBool(state), exponent, ConstantI32(state, 1)), normalResult, subnormalResult);
|
||||
finite = SelectBits(state, Binary(state, spv::OpSGreaterThanEqual, TypeBool(state), exponent, ConstantI32(state, 2047)), infinity, finite);
|
||||
finite = SelectBits(state, zero, value, finite);
|
||||
|
||||
const auto power = PowerOfTwo(state, scale);
|
||||
auto fallback = Split(state, FromF64(state, Exact(state, spv::OpFMul, Exact(state, spv::OpFAdd, Exact(state, spv::OpFMul, ToF64(state, arg0), ToF64(state, arg1)), ToF64(state, arg2)), power)));
|
||||
const auto finiteProduct = Binary(state, spv::OpLogicalAnd, TypeBool(state), Unary(state, spv::OpLogicalNot, TypeBool(state), isField(exponentA, 0x7ffu)), Unary(state, spv::OpLogicalNot, TypeBool(state), isField(exponentB, 0x7ffu)));
|
||||
fallback = SelectBits(state, Binary(state, spv::OpLogicalAnd, TypeBool(state), c.infinite, finiteProduct), c.bits, fallback);
|
||||
fallback = SelectBits(state, Classify(state, Join(state, fallback)).nan, {ConstantU32(state, 0u), ConstantU32(state, 0xfff80000u)}, fallback);
|
||||
const auto subnormalC = Binary(state, spv::OpLogicalAnd, TypeBool(state), isField(exponentC, 0u), Unary(state, spv::OpLogicalNot, TypeBool(state), c.zero));
|
||||
const auto special = any({isField(exponentA, 0u), isField(exponentB, 0u), isField(exponentA, 0x7ffu), isField(exponentB, 0x7ffu), isField(exponentC, 0x7ffu), subnormalC});
|
||||
const auto special = any({a.zero, b.zero, isField(exponentA, 0x7ffu), isField(exponentB, 0x7ffu), isField(exponentC, 0x7ffu)});
|
||||
|
||||
auto result = SelectBits(state, special, fallback, finite);
|
||||
result = SelectBits(state, c.nan, Quiet(state, c.bits), result);
|
||||
|
|
|
|||
|
|
@ -302,7 +302,7 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- On a host subgroup narrower than the wave, [`v_readlane_b32`](../../core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp) of a constant lane outside it builds only as lane 63 of a half-wave scan (DPP row_shr 1/2/4/8, `v_permlanex16_b32`, EXEC -1); other such reads throw.
|
||||
- [Shader recompilation](../../core/shader/recompiler/Recompiler.cpp) currently occurs right before it was transferred to Vulkan with caching, but should be moved to the [relinker](../../core/relinker/main.cpp) stage. For this purpose, [shader/recompiler](../../core/shader/recompiler) was written completely independently from [libs/prx](../../core/libs/prx).
|
||||
- [v_interp_p1ll_f16, v_interp_p1lv_f16 and v_interp_p2_f16](../../core/shader/recompiler/Translation/src/AttributeInstructions.cpp) read the 16-bit halves of each vertex's parameter through `VK_KHR_fragment_shader_barycentric` and throw without it, as fixed-function interpolation cannot split a 32-bit attribute. P10 and P20 are the vertex differences rounded to f16 to nearest even, which is not confirmed on hardware. v_interp_p1ll_f16 and v_interp_p1lv_f16 round the exact `P10 * I + P0` once to f32 and v_interp_p2_f16 rounds the exact `P20 * J + S2` once to f16, as measured on RDNA2 (#697); the exact sums are computed in f64, so these instructions need the Float64 capability. They follow the shader's float mode, measured on RDNA2 (gfx1035) in all eight combinations of `IEEE_MODE` and kept/flushed f32 and f16 denormals over 768 special and 1,024 random rows. With IEEE=1 a NaN operand is returned quieted; with IEEE=0 it is returned unquieted, except that v_interp_p2_f16's f16 result is always quiet. Flushed f32 denormals flush I/J, v_interp_p2_f16's f32 S2 and the f32 result; flushed f16 denormals flush P0, P10/P20, v_interp_p1lv_f16's f16 S2 and the f16 result. Denormal modes 1 and 2 (flush only inputs or only outputs) and rounding other than to nearest even are not measured and throw. An unknown mode follows IEEE=0 with denormals kept. Lavapipe has no barycentrics, so the draw test is skipped in CI; `agc_driver_interpolation_modes` checks the arithmetic in every mode against the hardware rows on any device with Float64.
|
||||
- [v_fma_f64](../../core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterFloat64.cpp) is fused exactly only for normal inputs: with a subnormal input it is a separate multiply and add, which rounds twice.
|
||||
- [v_fma_f64 and v_div_fmas_f64](../../core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterFloat64.cpp) with a subnormal operand or result round once, as IEEE 754 fusedMultiplyAdd does with f64 denormals kept, measured on RDNA2 (gfx1035) at IEEE=0 and IEEE=1, `v_div_fmas_f64` with VCC clear and set. The f16/f64 denormal field of `FLOAT_MODE` (bits 7:6) is not read: when it flushes, the hardware flushes the operands and the result of `v_fma_f64` (tininess before rounding) and only the result of `v_div_fmas_f64`, while these keep them.
|
||||
- [SINT storage images](../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) of 8- and 16-bit components saturate each stored 32-bit value to the component range, as adriadam10 measured for image stores on RDNA2 (gfx1035) in #944 (448 rows match); the PS5's gfx1013 is not measured.
|
||||
- [`StoreTexel`](../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) writes channel 1 of a two-channel format from the data slot whose DST_SEL is Y, for SINT and UINT alike. With DST_SEL X,Y,Z,W, gfx1035 writes it from the W slot, as adriadam10 measured in #944.
|
||||
- [COND_EXEC](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp) reads its condition after the earlier packets of its queue complete. Without PFP_SYNC_ME the console's graphics PFP reads it ahead of the ME, so a title that races the read sees a later value here.
|
||||
|
|
|
|||
Loading…
Reference in New Issue