Merge pull request #3306 from CouchRiv/fix-shader-ldexp-f32-denorm-keep
fix(shader): keep f32 subnormals in v_ldexp_f32 when denormals are kept
This commit is contained in:
commit
92bc441279
|
|
@ -0,0 +1,163 @@
|
|||
#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 <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using AgcDriver::Graphics::Require;
|
||||
using ShaderRecompiler::ShaderStage;
|
||||
|
||||
constexpr std::uint32_t Threads = 64;
|
||||
constexpr std::uint32_t Inputs = 4;
|
||||
constexpr std::uint32_t Results = 16;
|
||||
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, 10> Code{
|
||||
0x34020084u, 0x34060086u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, 0xd762000au, 0x02020b04u, 0xe0701000u,
|
||||
0x80010a03u, 0xbf810000u,
|
||||
};
|
||||
|
||||
struct LdexpVector {
|
||||
std::uint32_t value;
|
||||
std::uint32_t exponent;
|
||||
std::uint32_t kept;
|
||||
std::uint32_t keptIeee;
|
||||
std::uint32_t flushed;
|
||||
std::uint32_t keptTowardPositive;
|
||||
std::uint32_t keptTowardNegative;
|
||||
std::uint32_t keptTowardZero;
|
||||
std::uint32_t flushedTowardPositive;
|
||||
std::uint32_t flushedTowardNegative;
|
||||
std::uint32_t flushedTowardZero;
|
||||
};
|
||||
|
||||
constexpr std::array<LdexpVector, 36> Vectors{{
|
||||
{0x00000001u, 0x00000003u, 0x00000008u, 0x00000008u, 0x00000000u, 0x00000008u, 0x00000008u, 0x00000008u, 0x00000000u, 0x00000000u, 0x00000000u},
|
||||
{0x00000001u, 0x00000016u, 0x00400000u, 0x00400000u, 0x00000000u, 0x00400000u, 0x00400000u, 0x00400000u, 0x00000000u, 0x00000000u, 0x00000000u},
|
||||
{0x007fffffu, 0xffffffe9u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u},
|
||||
{0x007fffffu, 0xfffffffdu, 0x00100000u, 0x00100000u, 0x00000000u, 0x00100000u, 0x000fffffu, 0x000fffffu, 0x00000000u, 0x00000000u, 0x00000000u},
|
||||
{0x00000001u, 0x00000019u, 0x01800000u, 0x01800000u, 0x00000000u, 0x01800000u, 0x01800000u, 0x01800000u, 0x00000000u, 0x00000000u, 0x00000000u},
|
||||
{0x00000001u, 0x0000007du, 0x33800000u, 0x33800000u, 0x00000000u, 0x33800000u, 0x33800000u, 0x33800000u, 0x00000000u, 0x00000000u, 0x00000000u},
|
||||
{0x00000001u, 0x000003e8u, 0x7f800000u, 0x7f800000u, 0x00000000u, 0x7f800000u, 0x7f7fffffu, 0x7f7fffffu, 0x00000000u, 0x00000000u, 0x00000000u},
|
||||
{0x80000001u, 0x000003e8u, 0xff800000u, 0xff800000u, 0x80000000u, 0xff7fffffu, 0xff800000u, 0xff7fffffu, 0x80000000u, 0x80000000u, 0x80000000u},
|
||||
{0x80000001u, 0x00000003u, 0x80000008u, 0x80000008u, 0x80000000u, 0x80000008u, 0x80000008u, 0x80000008u, 0x80000000u, 0x80000000u, 0x80000000u},
|
||||
{0x3f800000u, 0xffffff6cu, 0x00000002u, 0x00000002u, 0x00000000u, 0x00000002u, 0x00000002u, 0x00000002u, 0x00000000u, 0x00000000u, 0x00000000u},
|
||||
{0x3f000000u, 0xffffff6cu, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u},
|
||||
{0x3f000000u, 0xffffff82u, 0x00400000u, 0x00400000u, 0x00000000u, 0x00400000u, 0x00400000u, 0x00400000u, 0x00000000u, 0x00000000u, 0x00000000u},
|
||||
{0xbf000000u, 0xffffff6cu, 0x80000001u, 0x80000001u, 0x80000000u, 0x80000001u, 0x80000001u, 0x80000001u, 0x80000000u, 0x80000000u, 0x80000000u},
|
||||
{0x00800000u, 0xffffffe8u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u},
|
||||
{0x3eaaaaabu, 0xffffff83u, 0x00555556u, 0x00555556u, 0x00000000u, 0x00555556u, 0x00555555u, 0x00555555u, 0x00000000u, 0x00000000u, 0x00000000u},
|
||||
{0x80800000u, 0xffffffe8u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000001u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u},
|
||||
{0x3f7fffffu, 0xffffff82u, 0x00800000u, 0x00800000u, 0x00000000u, 0x00800000u, 0x007fffffu, 0x007fffffu, 0x00000000u, 0x00000000u, 0x00000000u},
|
||||
{0xbfc00000u, 0xffffff6au, 0x80000001u, 0x80000001u, 0x80000000u, 0x80000000u, 0x80000001u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u},
|
||||
{0x3f7fffffu, 0xffffff6cu, 0x00000002u, 0x00000002u, 0x00000000u, 0x00000002u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u},
|
||||
{0x3fc00000u, 0xffffff6cu, 0x00000003u, 0x00000003u, 0x00000000u, 0x00000003u, 0x00000003u, 0x00000003u, 0x00000000u, 0x00000000u, 0x00000000u},
|
||||
{0x00000000u, 0x00000003u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u},
|
||||
{0x80000000u, 0x00000003u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u},
|
||||
{0x7f800000u, 0x00000003u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u},
|
||||
{0xff800000u, 0xfffffffdu, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u},
|
||||
{0x7fc00000u, 0x00000003u, 0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0x7fc00000u},
|
||||
{0x7fa00000u, 0x00000003u, 0x7fa00000u, 0x7fe00000u, 0x7fa00000u, 0x7fa00000u, 0x7fa00000u, 0x7fa00000u, 0x7fa00000u, 0x7fa00000u, 0x7fa00000u},
|
||||
{0xffbfffffu, 0xfffffffdu, 0xffbfffffu, 0xffffffffu, 0xffbfffffu, 0xffbfffffu, 0xffbfffffu, 0xffbfffffu, 0xffbfffffu, 0xffbfffffu, 0xffbfffffu},
|
||||
{0x7f800001u, 0x00000019u, 0x7f800001u, 0x7fc00001u, 0x7f800001u, 0x7f800001u, 0x7f800001u, 0x7f800001u, 0x7f800001u, 0x7f800001u, 0x7f800001u},
|
||||
{0x3f800000u, 0x000003e8u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f7fffffu, 0x7f7fffffu, 0x7f800000u, 0x7f7fffffu, 0x7f7fffffu},
|
||||
{0x3f800000u, 0xfffffc17u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u},
|
||||
{0x3f800000u, 0x00000081u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f7fffffu, 0x7f7fffffu, 0x7f800000u, 0x7f7fffffu, 0x7f7fffffu},
|
||||
{0xbf800000u, 0x00000081u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff7fffffu, 0xff800000u, 0xff7fffffu, 0xff7fffffu, 0xff800000u, 0xff7fffffu},
|
||||
{0x40200000u, 0xffffff6au, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000002u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u},
|
||||
{0xc0e80000u, 0xffffff6cu, 0x8000000eu, 0x8000000eu, 0x80000000u, 0x8000000eu, 0x8000000fu, 0x8000000eu, 0x80000000u, 0x80000000u, 0x80000000u},
|
||||
{0x3f800000u, 0xfffe7960u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u},
|
||||
{0xbf800000u, 0xfffe7960u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000001u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u},
|
||||
}};
|
||||
|
||||
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::uint32_t value) {
|
||||
char text[16];
|
||||
std::snprintf(text, sizeof(text), "0x%08x", value);
|
||||
return text;
|
||||
}
|
||||
|
||||
void Run(AgcDriver::VulkanDevice& device, const std::optional<ShaderRecompiler::ShaderFloatMode>& floatMode) {
|
||||
Input.fill(0u);
|
||||
for (std::uint32_t lane = 0; lane < Vectors.size(); ++lane) {
|
||||
Input[lane * Inputs] = Vectors[lane].value;
|
||||
Input[lane * Inputs + 1] = Vectors[lane].exponent;
|
||||
}
|
||||
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(Code);
|
||||
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}, 0, {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;
|
||||
request.context.floatMode = floatMode;
|
||||
const auto result = ShaderRecompiler::Recompile(request);
|
||||
device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
|
||||
device.WaitIdle();
|
||||
}
|
||||
|
||||
void Check(std::uint32_t LdexpVector::*expected, const char* mode) {
|
||||
for (std::uint32_t lane = 0; lane < Vectors.size(); ++lane) {
|
||||
const auto& vector = Vectors[lane];
|
||||
const auto actual = Output[lane * Results];
|
||||
Require(actual == vector.*expected, std::string("ldexp f32 denormals: ") + mode + " v_ldexp_f32 (" + Hex(vector.value) + ", " + Hex(vector.exponent) + ") is " + Hex(actual) + ", expected " + Hex(vector.*expected));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
Run(*device, ShaderRecompiler::ShaderFloatMode{0xf0u, true, false, false});
|
||||
Check(&LdexpVector::kept, "denormals kept, IEEE=0");
|
||||
Run(*device, ShaderRecompiler::ShaderFloatMode{0xf0u, true, true, false});
|
||||
Check(&LdexpVector::keptIeee, "denormals kept, IEEE=1");
|
||||
Run(*device, ShaderRecompiler::ShaderFloatMode{0xf1u, true, false, false});
|
||||
Check(&LdexpVector::keptTowardPositive, "denormals kept, round toward +inf");
|
||||
Run(*device, ShaderRecompiler::ShaderFloatMode{0xf2u, true, false, false});
|
||||
Check(&LdexpVector::keptTowardNegative, "denormals kept, round toward -inf");
|
||||
Run(*device, ShaderRecompiler::ShaderFloatMode{0xf3u, true, false, false});
|
||||
Check(&LdexpVector::keptTowardZero, "denormals kept, round toward zero");
|
||||
Run(*device, ShaderRecompiler::ShaderFloatMode{0xc0u, true, false, false});
|
||||
Check(&LdexpVector::flushed, "denormals flushed");
|
||||
Run(*device, ShaderRecompiler::ShaderFloatMode{0xc1u, true, false, false});
|
||||
Check(&LdexpVector::flushedTowardPositive, "denormals flushed, round toward +inf");
|
||||
Run(*device, ShaderRecompiler::ShaderFloatMode{0xc2u, true, false, false});
|
||||
Check(&LdexpVector::flushedTowardNegative, "denormals flushed, round toward -inf");
|
||||
Run(*device, ShaderRecompiler::ShaderFloatMode{0xc3u, true, false, false});
|
||||
Check(&LdexpVector::flushedTowardZero, "denormals flushed, round toward zero");
|
||||
Run(*device, std::nullopt);
|
||||
Check(&LdexpVector::flushed, "no float mode");
|
||||
std::puts("ldexp f32 denormal tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -698,10 +698,53 @@ bool TranslationContext::vLdexpF32(const RdnaInstruction& inst) {
|
|||
const IrU32 sign(ir.BitwiseAnd(bits.Value(), ir.Constant(0x80000000u)));
|
||||
const IrU32 exponent(ir.Emit(IrOpcode::BitFieldUExtract, IrType::U32, {&bits.Value(), &ir.Constant(23u), &ir.Constant(8u)}));
|
||||
const IrU32 clamped(ir.Emit(IrOpcode::SMax32, IrType::U32, {&ir.Emit(IrOpcode::SMin32, IrType::U32, {&offset.Value(), &ir.Constant(512u)}), &ir.Constant(static_cast<std::uint32_t>(-512))}));
|
||||
const IrU32 overflow(ir.BitwiseOr(sign.Value(), ir.Constant(0x7f800000u)));
|
||||
const std::uint32_t denorm32 = floatMode.has_value() ? (floatMode->floatMode >> 4u) & 3u : 0u;
|
||||
const std::uint32_t roundMode = floatMode.has_value() ? floatMode->floatMode & 3u : 0u;
|
||||
IrValue* positive = roundMode == 1u || roundMode == 2u ? &ir.IEqual(sign.Value(), ir.Constant(0u)) : nullptr;
|
||||
IrValue* overflowBits = &overflow.Value();
|
||||
if (roundMode == 1u) overflowBits = &ir.Select(*positive, ir.Constant(0x7f800000u), ir.Constant(0xff7fffffu));
|
||||
else if (roundMode == 2u) overflowBits = &ir.Select(*positive, ir.Constant(0x7f7fffffu), ir.Constant(0xff800000u));
|
||||
else if (roundMode == 3u) overflowBits = &ir.BitwiseOr(sign.Value(), ir.Constant(0x7f7fffffu));
|
||||
if (denorm32 == 1u || denorm32 == 2u) {
|
||||
throw std::runtime_error("f32 denormal mode " + std::to_string(denorm32) + " at pc " + std::to_string(inst.programCounter) + " is not implemented");
|
||||
}
|
||||
if (denorm32 == 3u) {
|
||||
const IrU32 mantissa(ir.BitwiseAnd(bits.Value(), ir.Constant(0x007fffffu)));
|
||||
const IrU1 isSubnormal(ir.IEqual(exponent.Value(), ir.Constant(0u)));
|
||||
const IrU32 sig(ir.Select(isSubnormal.Value(), mantissa.Value(), ir.BitwiseOr(mantissa.Value(), ir.Constant(0x00800000u))));
|
||||
const IrU32 msb(ir.Emit(IrOpcode::FindUMsb32, IrType::U32, {&sig.Value()}));
|
||||
const IrU32 normShift(ir.ISub(ir.Constant(23u), msb.Value()));
|
||||
const IrU32 sigNorm(ir.ShiftLeftLogical(sig.Value(), normShift.Value()));
|
||||
const IrU32 effExp(ir.Select(isSubnormal.Value(), ir.Constant(1u), exponent.Value()));
|
||||
const IrU32 expNorm(ir.ISub(ir.IAdd(effExp.Value(), clamped.Value()), normShift.Value()));
|
||||
const IrU32 normResult(ir.BitwiseOr(sign.Value(), ir.BitwiseOr(ir.ShiftLeftLogical(expNorm.Value(), ir.Constant(23u)), ir.BitwiseAnd(sigNorm.Value(), ir.Constant(0x007fffffu)))));
|
||||
const IrU32 underShiftRaw(ir.ISub(ir.Constant(1u), expNorm.Value()));
|
||||
const IrU32 underShift(ir.Emit(IrOpcode::SMin32, IrType::U32, {&ir.Emit(IrOpcode::SMax32, IrType::U32, {&underShiftRaw.Value(), &ir.Constant(1u)}), &ir.Constant(25u)}));
|
||||
const IrU32 unrounded(ir.ShiftRightLogical(sigNorm.Value(), underShift.Value()));
|
||||
const IrU32 half(ir.ShiftLeftLogical(ir.Constant(1u), ir.ISub(underShift.Value(), ir.Constant(1u))));
|
||||
const IrU32 remMask(ir.BitwiseOr(ir.ISub(half.Value(), ir.Constant(1u)), half.Value()));
|
||||
const IrU32 rem(ir.BitwiseAnd(sigNorm.Value(), remMask.Value()));
|
||||
const IrU1 remGreater(ir.UGreaterThan(rem.Value(), half.Value()));
|
||||
const IrU1 remEqual(ir.IEqual(rem.Value(), half.Value()));
|
||||
const IrU1 lsbOdd(ir.INotEqual(ir.BitwiseAnd(unrounded.Value(), ir.Constant(1u)), ir.Constant(0u)));
|
||||
IrValue* roundUp = &ir.LogicalOr(remGreater.Value(), ir.LogicalAnd(remEqual.Value(), lsbOdd.Value()));
|
||||
if (roundMode == 1u) roundUp = &ir.LogicalAnd(ir.INotEqual(rem.Value(), ir.Constant(0u)), *positive);
|
||||
else if (roundMode == 2u) roundUp = &ir.LogicalAnd(ir.INotEqual(rem.Value(), ir.Constant(0u)), ir.LogicalNot(*positive));
|
||||
const IrU32 magnitude(roundMode == 3u ? unrounded : IrU32(ir.Select(*roundUp, ir.IAdd(unrounded.Value(), ir.Constant(1u)), unrounded.Value())));
|
||||
const IrU32 subResult(ir.BitwiseOr(sign.Value(), magnitude.Value()));
|
||||
IrU32 result(ir.Select(ir.Emit(IrOpcode::SGreaterThanEqual32, IrType::U1, {&expNorm.Value(), &ir.Constant(255u)}), *overflowBits, normResult.Value()));
|
||||
result = IrU32(ir.Select(ir.Emit(IrOpcode::SLessThanEqual32, IrType::U1, {&expNorm.Value(), &ir.Constant(0u)}), subResult.Value(), result.Value()));
|
||||
const IrU1 zero(ir.IEqual(ir.BitwiseAnd(bits.Value(), ir.Constant(0x7fffffffu)), ir.Constant(0u)));
|
||||
result = IrU32(ir.Select(zero.Value(), bits.Value(), result.Value()));
|
||||
const IrU1 nan(ir.UGreaterThan(ir.BitwiseAnd(bits.Value(), ir.Constant(0x7fffffffu)), ir.Constant(0x7f800000u)));
|
||||
result = IrU32(ir.Select(ir.IEqual(exponent.Value(), ir.Constant(0xffu)), ir.Select(nan.Value(), quietNan32(bits).Value(), bits.Value()), result.Value()));
|
||||
writeOperand(inst.destination, &ir.BitCastF32(result.Value()));
|
||||
return true;
|
||||
}
|
||||
const IrU32 scaled(ir.IAdd(exponent.Value(), clamped.Value()));
|
||||
const IrU32 normal(ir.BitwiseOr(ir.BitwiseAnd(bits.Value(), ir.Constant(0x807fffffu)), ir.ShiftLeftLogical(scaled.Value(), ir.Constant(23u))));
|
||||
const IrU32 overflow(ir.BitwiseOr(sign.Value(), ir.Constant(0x7f800000u)));
|
||||
IrU32 result(ir.Select(ir.Emit(IrOpcode::SGreaterThanEqual32, IrType::U1, {&scaled.Value(), &ir.Constant(255u)}), overflow.Value(), normal.Value()));
|
||||
IrU32 result(ir.Select(ir.Emit(IrOpcode::SGreaterThanEqual32, IrType::U1, {&scaled.Value(), &ir.Constant(255u)}), *overflowBits, normal.Value()));
|
||||
result = IrU32(ir.Select(ir.Emit(IrOpcode::SLessThanEqual32, IrType::U1, {&scaled.Value(), &ir.Constant(0u)}), sign.Value(), result.Value()));
|
||||
const IrU1 nan(ir.UGreaterThan(ir.BitwiseAnd(bits.Value(), ir.Constant(0x7fffffffu)), ir.Constant(0x7f800000u)));
|
||||
result = IrU32(ir.Select(ir.IEqual(exponent.Value(), ir.Constant(0xffu)), ir.Select(nan.Value(), quietNan32(bits).Value(), bits.Value()), result.Value()));
|
||||
|
|
|
|||
|
|
@ -425,7 +425,7 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [Signaling NaN inputs](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) of the f32 and f16 unary instructions (`v_sqrt/rcp/rsq/log/exp/sin/cos/fract/floor/ceil/trunc/rndne/rcp_iflag/frexp_mant`), `v_ldexp_f32/f16`, `v_frexp_mant_f16`, `v_div_fixup_f32/f16` and `v_mullit_f32` are returned quieted only with IEEE=1 and unchanged with IEEE=0, measured on RDNA2 (gfx1035) in both modes, and so are those of the f64 forms (`v_sqrt/rcp/rsq/fract/floor/ceil/trunc/rndne/frexp_mant/ldexp/div_fixup_f64`) and `v_cvt_f64_f32`, measured on RDNA2 (gfx1036) in both modes; shaders without a known float mode follow IEEE=0. `v_pack_b32_f16` quiets each signaling NaN half by the same rule, measured on RDNA2 (gfx1036) in both modes. A signaling NaN result of `v_cubesc/cubetc/cubema_f32` is quieted by the same rule and keeps the sign the instruction gives it, measured on RDNA2 (gfx1036) in both modes. `v_add/sub/subrev/mul/fma/fmac/fmamk/fmaak/mul_legacy_f32` and `v_fma_mix_f32` with f32 sources choose their NaN result on the bits, measured over 1000 operand triples in both modes: the first NaN source in operand order after its modifiers (the subtrahend negated, as the hardware adds its negation), quieted only with IEEE=1, or the default NaN 0xffc00000 for an invalid operation; in the fma forms an invalid product (inf * 0) gives the default NaN even when src2 is a NaN, and in the legacy forms a zero factor makes the product 0 even when the other factor is a NaN (a NaN src2 still wins). `v_mad_legacy_f32` and `v_mac_legacy_f32` follow the same rule with their separately rounded product, as measured on gfx1010 (#1281, #1290): a NaN source quieted only with IEEE=1, inf - inf giving 0xffc00000, the zero rule hiding a NaN factor and a NaN src2 winning. The f16 and f64 arithmetic, `v_fma_mix_f32` with f16 sources and the separately rounded mad/mac forms (not measurable on RDNA2) follow the same rule on hardware but their NaN bits are left to the driver. `v_cvt_f16_f32`, `v_cvt_f32_f64`, `v_dot2_f32_f16`, `v_fma_mixlo_f16` and `v_fma_mixhi_f16` always quiet and `v_div_scale_f32` never does: the f32 signaling NaN 0x7f812345 as src0 or src2 gives 0x7e09 from mixlo and mixhi at IEEE=0 and IEEE=1 on RDNA2 (gfx1036), where `v_fma_mix_f32` gives 0x7f812345 at IEEE=0 and 0x7fc12345 at IEEE=1. The payload of an f16 NaN source of mixlo and mixhi is left to the driver's f16 to f32 conversion, which NVIDIA replaces.
|
||||
- [Scalar f16 add, sub, subrev and mul denormals](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) follow the `FLOAT_MODE` f16 denormal field at nearest-even: bit 0 preserves inputs and bit 1 preserves the result; otherwise a denormal is flushed to its signed zero. Measured on RDNA2 (gfx1036) with IEEE=0/1 and FP16_OVFL=0/1, over 32 operand pairs in all four denormal modes, including signed zeros, denormals, minimum normals, infinities and NaNs. Multiply detects tininess after rounding to 11 significant bits with an unbounded exponent, before gradual half packing: `0x3bff * 0x0400` flushes, while `0x3c01 * 0x03ff` rounds to `0x0400` and is kept. Directed rounding and ternary arithmetic keep their previous denormal lowering. Shaders without a known float mode keep the previous lowering. Scalar `v_sub_f16`/`v_subrev_f16` share `float16Binary` with add/mul and were measured on gfx1036 over the same operand set as add (1156 non-NaN-input rows per mode); NaN result bits still depend on the driver as recorded above.
|
||||
- [Packed f16 NaN results](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) (`v_pk_add/mul/fma/min/max_f16`) follow the hardware measured on RDNA2 (gfx1035) at IEEE=0 and IEEE=1 with f16 round-to-nearest-even and f16 denormals kept: with IEEE=1 a signaling NaN input is quieted and min/max return the other operand only for a quiet NaN; with IEEE=0 NaN inputs pass through unquieted and min/max ignore signaling. In `v_pk_fma_f16` an invalid product (inf * 0) gives the default NaN even when src2 is a NaN. A shader without a known float mode is translated as IEEE=0, the mode titles use; a mode with another f16 rounding or that flushes f16 denormals throws.
|
||||
- [f32 denormals](../../core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp) follow the shader's `FLOAT_MODE` f32 denormal field (bits 5:4) when it flushes them (0, and shaders without a known float mode, as in the 0xc0 most titles use), measured on RDNA2 (gfx1035) over 1444 rows per instruction at IEEE=0 and IEEE=1. `v_add/sub/subrev/mul/mul_legacy/mullit/fma/fmac/fmamk/fmaak/min/max/min3/max3/med3/fract/floor/ceil/frexp_mant/sin/cos_f32` flush their f32 inputs and their result to a zero of the same sign (`v_mullit_f32` measured on RDNA2 (gfx1036) over 6907 rows per mode at IEEE=0 and IEEE=1, so a positive denormal src2 counts as zero); the f32 compares, `v_cvt_i32/u32/rpi_i32/flr_i32_f32`, `v_frexp_exp_i32_f32`, `v_cvt_f64_f32`, `v_cvt_f16_f32`, `v_cvt_pkrtz_f16_f32`, `v_cvt_pknorm_i16/u16_f32` and `v_cvt_pk_u8_f32` flush their f32 inputs. `v_div_scale_f32` flushes all three inputs to signed zeros, measured on RDNA2 (gfx1036) over 2744 operand triples per mode at IEEE=0 and IEEE=1 with f32 denormals flushed and kept; its scale-down behaviour and host denormal preservation remain incomplete. `v_div_fixup_f32` flushes its src1 and src2 and keeps a denormal src0 and result, measured on RDNA2 (gfx1036) over 6859 rows per mode at IEEE=0 and IEEE=1. Tininess is detected after rounding, measured on RDNA2 (gfx1036) for `v_mul_f32` and `v_mullit_f32` at IEEE=0 and IEEE=1: a product is flushed when its value rounded to 24 bits with an unbounded exponent is below 2^-126, so `0x3f7ffffe * 0x00800001` (2^-126 * (1 - 2^-46)) gives `0x00800000` and `0x3f7fffff * 0x00800000` (2^-126 * (1 - 2^-24)) gives zero. The check rounds in the f32 round mode of `FLOAT_MODE` (bits 1:0), measured in all four modes, while the products themselves are still rounded to nearest even. This applies to `v_mul_f32`, `v_mul_legacy_f32`, `v_mullit_f32`, and to `v_fma_f32`, `v_fmac_f32`, `v_fmamk_f32`, `v_fmaak_f32` and `v_fma_mix_f32` when the addend is zero; with a nonzero addend the fused result is kept, so an exact result just below 2^-126 that rounds to it is not flushed. In the three directed modes the check is computed exactly from the significands. To nearest even (0, the mode of shaders without a known float mode) one factor is multiplied by 2^64, which is exact below 2^64, and the product with the other factor is compared with 2^-62. That multiply is a plain `OpFMul` like the product, and no f32 rounding mode is requested in the SPIR-V, so the check relies on the driver rounding f32 to nearest even by default, as the products do, and a product that rounds up to 2^-126 is the emitted product. `v_fma_mix_f32` follows this rule with mixed f16/f32 sources too, measured on RDNA2 (gfx1036) with f16 in either multiplicand slot, low and high half selection, and an f32 or f16 zero addend: f32 `0x00801002` times f16 `0x3bff` gives `0x00800000` to nearest at IEEE=0 and IEEE=1, while the neighbours `0x00801001` and `0x00801003` give zero and `0x00800001`. The tiny-product boundary was also measured for both signs in all four f32 round modes at IEEE=0; f16 inputs retain their previous conversion and denormal handling. `v_fma_mixlo_f16` and `v_fma_mixhi_f16` flush their f32 sources too and keep a product below 2^-126, measured on RDNA2 (gfx1036). `v_mad_legacy_f32` and `v_mac_legacy_f32` flush their inputs, their product and their result in every mode, see its entry; their product is still checked for tininess on its exact value, so one below 2^-126 gives zero even when it rounds to 2^-126, as the console's separately rounded encoding is not measured at this boundary. Denormal modes 1 and 2 throw for the other instructions. Not covered, and not measurable on RDNA2 (no such opcodes) or not measured: `v_mac_f32`, `v_mad_f32`, `v_madak_f32` and `v_madmk_f32` (rounded separately), `v_dot2_f32_f16` and `v_div_fmas_f32` (on hardware `v_div_fmas_f32` flushes only its result), f32 interpolation and `ds_add_f32` keep the previous lowering; `ds_min/max/cmpst_f32` flush both operands by the field, see the LDS float atomics entry. With f32 denormals kept (3) the results depend on the driver's default denormal behaviour, as no `DenormPreserve` execution mode is requested, and `v_ldexp_f32` flushes in every mode.
|
||||
- [f32 denormals](../../core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp) follow the shader's `FLOAT_MODE` f32 denormal field (bits 5:4) when it flushes them (0, and shaders without a known float mode, as in the 0xc0 most titles use), measured on RDNA2 (gfx1035) over 1444 rows per instruction at IEEE=0 and IEEE=1. `v_add/sub/subrev/mul/mul_legacy/mullit/fma/fmac/fmamk/fmaak/min/max/min3/max3/med3/fract/floor/ceil/frexp_mant/sin/cos_f32` flush their f32 inputs and their result to a zero of the same sign (`v_mullit_f32` measured on RDNA2 (gfx1036) over 6907 rows per mode at IEEE=0 and IEEE=1, so a positive denormal src2 counts as zero); the f32 compares, `v_cvt_i32/u32/rpi_i32/flr_i32_f32`, `v_frexp_exp_i32_f32`, `v_cvt_f64_f32`, `v_cvt_f16_f32`, `v_cvt_pkrtz_f16_f32`, `v_cvt_pknorm_i16/u16_f32` and `v_cvt_pk_u8_f32` flush their f32 inputs. `v_div_scale_f32` flushes all three inputs to signed zeros, measured on RDNA2 (gfx1036) over 2744 operand triples per mode at IEEE=0 and IEEE=1 with f32 denormals flushed and kept; its scale-down behaviour and host denormal preservation remain incomplete. `v_div_fixup_f32` flushes its src1 and src2 and keeps a denormal src0 and result, measured on RDNA2 (gfx1036) over 6859 rows per mode at IEEE=0 and IEEE=1. Tininess is detected after rounding, measured on RDNA2 (gfx1036) for `v_mul_f32` and `v_mullit_f32` at IEEE=0 and IEEE=1: a product is flushed when its value rounded to 24 bits with an unbounded exponent is below 2^-126, so `0x3f7ffffe * 0x00800001` (2^-126 * (1 - 2^-46)) gives `0x00800000` and `0x3f7fffff * 0x00800000` (2^-126 * (1 - 2^-24)) gives zero. The check rounds in the f32 round mode of `FLOAT_MODE` (bits 1:0), measured in all four modes, while the products themselves are still rounded to nearest even. This applies to `v_mul_f32`, `v_mul_legacy_f32`, `v_mullit_f32`, and to `v_fma_f32`, `v_fmac_f32`, `v_fmamk_f32`, `v_fmaak_f32` and `v_fma_mix_f32` when the addend is zero; with a nonzero addend the fused result is kept, so an exact result just below 2^-126 that rounds to it is not flushed. In the three directed modes the check is computed exactly from the significands. To nearest even (0, the mode of shaders without a known float mode) one factor is multiplied by 2^64, which is exact below 2^64, and the product with the other factor is compared with 2^-62. That multiply is a plain `OpFMul` like the product, and no f32 rounding mode is requested in the SPIR-V, so the check relies on the driver rounding f32 to nearest even by default, as the products do, and a product that rounds up to 2^-126 is the emitted product. `v_fma_mix_f32` follows this rule with mixed f16/f32 sources too, measured on RDNA2 (gfx1036) with f16 in either multiplicand slot, low and high half selection, and an f32 or f16 zero addend: f32 `0x00801002` times f16 `0x3bff` gives `0x00800000` to nearest at IEEE=0 and IEEE=1, while the neighbours `0x00801001` and `0x00801003` give zero and `0x00800001`. The tiny-product boundary was also measured for both signs in all four f32 round modes at IEEE=0; f16 inputs retain their previous conversion and denormal handling. `v_fma_mixlo_f16` and `v_fma_mixhi_f16` flush their f32 sources too and keep a product below 2^-126, measured on RDNA2 (gfx1036). `v_mad_legacy_f32` and `v_mac_legacy_f32` flush their inputs, their product and their result in every mode, see its entry; their product is still checked for tininess on its exact value, so one below 2^-126 gives zero even when it rounds to 2^-126, as the console's separately rounded encoding is not measured at this boundary. Denormal modes 1 and 2 throw for the other instructions. Not covered, and not measurable on RDNA2 (no such opcodes) or not measured: `v_mac_f32`, `v_mad_f32`, `v_madak_f32` and `v_madmk_f32` (rounded separately), `v_dot2_f32_f16` and `v_div_fmas_f32` (on hardware `v_div_fmas_f32` flushes only its result), f32 interpolation and `ds_add_f32` keep the previous lowering; `ds_min/max/cmpst_f32` flush both operands by the field, see the LDS float atomics entry. With f32 denormals kept (3) the results depend on the driver's default denormal behaviour, as no `DenormPreserve` execution mode is requested; `v_ldexp_f32` is the exception: it preserves subnormal inputs and rounds a subnormal result in the f32 round mode of `FLOAT_MODE`, measured on RDNA2 (gfx1036) over 2163 rows in each of the four round modes at IEEE=0 and IEEE=1. An overflow rounds like any other result (infinity, or the largest finite value when the mode rounds that sign toward zero). With f32 denormals flushed it flushes a subnormal input and result, measured over the same rows in each round mode at IEEE=0 only.
|
||||
- The executable file that [relinker](../../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) generates opens the console when launched, which is inconvenient for playability.
|
||||
- [Overlay output bus](../../core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp) (libSceVideoOut) - flips on a port opened with `SCE_VIDEO_OUT_BUS_TYPE_OVERLAY` complete and signal their events like main-bus flips, but their buffers are not shown: the overlay is not composited over the main output. Presenting them as whole frames made the window alternate between the two buses
|
||||
- [PA_CL_CLIP_CNTL](../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - `DX_LINEAR_ATTR_CLIP_ENA` (bit 24) clear is drawn like bit 24 set, with Vulkan's clipping of `NoPerspective` outputs; what a clear bit changes on the console is undocumented.
|
||||
|
|
|
|||
Loading…
Reference in New Issue