Merge pull request #2162 from CouchRiv/fix-shader-fma-mix-f16-fused
fix(shader): round v_fma_mixlo_f16 and v_fma_mixhi_f16 once to f16
This commit is contained in:
commit
c20184aba7
|
|
@ -2,11 +2,13 @@
|
|||
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
|
||||
#include "Recompiler.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
|
@ -15,6 +17,7 @@ namespace {
|
|||
using AgcDriver::Graphics::Require;
|
||||
using ShaderRecompiler::ShaderStage;
|
||||
|
||||
constexpr std::uint32_t Float64Capability = 10;
|
||||
constexpr std::uint32_t Threads = 64;
|
||||
constexpr std::uint32_t Inputs = 4;
|
||||
constexpr std::uint32_t Results = 16;
|
||||
|
|
@ -31,6 +34,87 @@ alignas(256) constexpr std::array<std::uint32_t, 52> MixCode{
|
|||
0x80011103, 0xe0702020, 0x80011203, 0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 52> RoundingCode{
|
||||
0x34020082, 0x34060084, 0xe0302000, 0x80000401, 0xe0302004, 0x80000501, 0xe0302008, 0x80000601,
|
||||
0x7e1402ff, 0xabcd0000, 0x7e1602ff, 0x0000abcd, 0x7e1802ff, 0xabcd0000, 0x7e1a02ff, 0xabcd0000,
|
||||
0x7e1c02ff, 0xabcd0000, 0x7e1e02ff, 0xabcd0000, 0x7e2002ff, 0xabcd0000, 0xbf8c3f70, 0xcc21000a,
|
||||
0x041a0b04, 0xcc22000b, 0x041a0b04, 0xcc21400c, 0x1c1a0b04, 0xcc21000d, 0x1c1a0b04, 0xcc21400e,
|
||||
0x041a0b04, 0xcc21400f, 0x0c1a0b04, 0xcc210010, 0x141a0b04, 0xe0702000, 0x80010a03, 0xe0702004,
|
||||
0x80010b03, 0xe0702008, 0x80010c03, 0xe070200c, 0x80010d03, 0xe0702010, 0x80010e03, 0xe0702014,
|
||||
0x80010f03, 0xe0702018, 0x80011003, 0xbf810000,
|
||||
};
|
||||
|
||||
struct RoundingRow {
|
||||
std::uint32_t a, b, c, result, expected;
|
||||
};
|
||||
|
||||
constexpr std::array<RoundingRow, Threads> RoundingRows{{
|
||||
{0x000062e9u, 0x0000d159u, 0x000023ecu, 2u, 0xf89fu},
|
||||
{0xbfc00000u, 0x33800000u, 0x00800000u, 0u, 0x8001u},
|
||||
{0x3bec0000u, 0xc73c0000u, 0xb767d928u, 0u, 0xdd6bu},
|
||||
{0xb4e70000u, 0xc5500000u, 0x8d000000u, 0u, 0x15ddu},
|
||||
{0xcc750000u, 0xb0500000u, 0x2f800000u, 0u, 0x2a39u},
|
||||
{0xc7b30000u, 0x35500000u, 0x0d000000u, 0u, 0xac8bu},
|
||||
{0x36400000u, 0x4c414000u, 0xb3800000u, 0u, 0x5887u},
|
||||
{0x4ae00000u, 0x3604c000u, 0x00800000u, 0u, 0x4b43u},
|
||||
{0xcf840000u, 0x36fc0000u, 0x31054000u, 0u, 0xf80fu},
|
||||
{0x43320000u, 0x41580000u, 0xaf800000u, 0u, 0x68b1u},
|
||||
{0x38400000u, 0x34406b26u, 0x43ab9000u, 0u, 0x5d5du},
|
||||
{0x00007780u, 0x00001478u, 0x00000002u, 2u, 0x5031u},
|
||||
{0x0000e520u, 0x0000cc10u, 0x00000002u, 2u, 0x7535u},
|
||||
{0x00004138u, 0x0000e440u, 0x00000001u, 2u, 0xe98bu},
|
||||
{0x000020ccu, 0x0000d880u, 0x00000001u, 2u, 0xbd65u},
|
||||
{0x00004880u, 0x0000d81cu, 0x00000002u, 2u, 0xe49fu},
|
||||
{0x0000cad4u, 0x00005900u, 0x00008002u, 2u, 0xe845u},
|
||||
{0x00001836u, 0x0000b5b3u, 0x00003a9au, 2u, 0x3a99u},
|
||||
{0x000063f0u, 0x00005340u, 0x00008002u, 2u, 0x7b31u},
|
||||
{0x0000e700u, 0x0000cca4u, 0x00008100u, 2u, 0x780fu},
|
||||
{0x00000003u, 0x0000f89du, 0x0d000000u, 3u, 0x9eebu},
|
||||
{0x00009d20u, 0x000036a0u, 0x8d000000u, 3u, 0x983fu},
|
||||
{0x000008a5u, 0x0000b400u, 0x80800000u, 3u, 0x8253u},
|
||||
{0x00009400u, 0x00002935u, 0x8d000000u, 3u, 0x829bu},
|
||||
{0x0000c5c0u, 0x00004630u, 0xaf800000u, 3u, 0xd073u},
|
||||
{0x0000140du, 0x00000c40u, 0x42e11000u, 3u, 0x5709u},
|
||||
{0x00008800u, 0x0000b439u, 0x00800000u, 3u, 0x021du},
|
||||
{0x00006c80u, 0x00003c04u, 0x33800000u, 3u, 0x6c85u},
|
||||
{0x00002840u, 0x0000f018u, 0x33800000u, 3u, 0xdc59u},
|
||||
{0x4f200000u, 0x34d98000u, 0x00008100u, 4u, 0x643fu},
|
||||
{0xced08000u, 0x37900000u, 0x00008100u, 4u, 0xf755u},
|
||||
{0xb4b63000u, 0xce800000u, 0x00008100u, 4u, 0x5db1u},
|
||||
{0xca094000u, 0x38a00000u, 0x00008002u, 4u, 0xd95du},
|
||||
{0x37fb0000u, 0xcdb00000u, 0x00008100u, 4u, 0xf165u},
|
||||
{0x46c00000u, 0xbe374000u, 0x00000002u, 4u, 0xec4bu},
|
||||
{0x48580000u, 0xbdf60000u, 0x00008822u, 4u, 0xf67du},
|
||||
{0x40186000u, 0x46c00000u, 0x00000001u, 4u, 0x7b25u},
|
||||
{0xcc500000u, 0x33bd0000u, 0x00008001u, 4u, 0xc4cdu},
|
||||
{0x00001889u, 0x45c00000u, 0x00008002u, 5u, 0x4acdu},
|
||||
{0x0000a007u, 0xc4c00000u, 0x00000001u, 5u, 0x4a0bu},
|
||||
{0x0000a87du, 0x42c00000u, 0x00000002u, 5u, 0xc2bbu},
|
||||
{0x00003018u, 0xc6680000u, 0x00000100u, 5u, 0xe76bu},
|
||||
{0x0000d61eu, 0x3dc00000u, 0x00008001u, 5u, 0xc897u},
|
||||
{0x00002888u, 0xc7480000u, 0x00008100u, 5u, 0xe715u},
|
||||
{0x0000c064u, 0xc0900000u, 0x00000001u, 5u, 0x48f1u},
|
||||
{0x000074deu, 0xbe200000u, 0x00000001u, 5u, 0xea15u},
|
||||
{0x0000d86du, 0xc1400000u, 0x00008100u, 5u, 0x66a3u},
|
||||
{0x48c00000u, 0x000018dfu, 0x00800000u, 6u, 0x634fu},
|
||||
{0x41e00000u, 0x0000a002u, 0x2f800000u, 6u, 0xb303u},
|
||||
{0xb2f61000u, 0x00004094u, 0xc23ed000u, 6u, 0xd1f7u},
|
||||
{0xb5200000u, 0x0000941eu, 0xc575b000u, 6u, 0xebadu},
|
||||
{0xc1600000u, 0x000003dau, 0x0d000000u, 6u, 0x92bdu},
|
||||
{0x4a500000u, 0x00001894u, 0x33800000u, 6u, 0x6f71u},
|
||||
{0x3ce00000u, 0x0000681au, 0xaf800000u, 6u, 0x532du},
|
||||
{0x43a00000u, 0x00002a02u, 0x34079c43u, 6u, 0x4b83u},
|
||||
{0x43c00000u, 0x0000c81bu, 0xb3800000u, 6u, 0xea29u},
|
||||
{0x1f000000u, 0x1f000000u, 0x3f801000u, 0u, 0x3c01u},
|
||||
{0x1f000000u, 0x9f000000u, 0x3f801000u, 0u, 0x3c00u},
|
||||
{0x00800000u, 0x3e800000u, 0x3f801000u, 0u, 0x3c01u},
|
||||
{0x33000000u, 0x3f800000u, 0x00800000u, 0u, 0x0001u},
|
||||
{0x33000000u, 0x3f800000u, 0x80800000u, 0u, 0x0000u},
|
||||
{0x477ff000u, 0x3f800000u, 0xa6800000u, 0u, 0x7bffu},
|
||||
{0x7f7fffffu, 0x40000000u, 0xff7fffffu, 0u, 0x7c00u},
|
||||
{0x00003c01u, 0x00004200u, 0x00800000u, 3u, 0x4202u},
|
||||
}};
|
||||
|
||||
float Quarter(std::uint32_t index) {
|
||||
return static_cast<float>(static_cast<int>(index % 17u) - 8) / 4.0f;
|
||||
}
|
||||
|
|
@ -84,14 +168,21 @@ std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t co
|
|||
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu) | (4u << 16u), count, 0x11016facu};
|
||||
}
|
||||
|
||||
void Run(AgcDriver::VulkanDevice& device) {
|
||||
void FillRoundingInput() {
|
||||
Input.fill(0u);
|
||||
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
|
||||
const auto& row = RoundingRows[tid];
|
||||
std::copy_n(std::array<std::uint32_t, 3>{row.a, row.b, row.c}.begin(), 3, Input.begin() + tid * Inputs);
|
||||
}
|
||||
}
|
||||
|
||||
void Run(AgcDriver::VulkanDevice& device, std::span<const std::uint32_t> code) {
|
||||
Output.fill(0xdeadbeefu);
|
||||
std::vector<std::uint32_t> userData(8, 0u);
|
||||
const auto input = BufferDescriptor(Input.data(), static_cast<std::uint32_t>(Input.size()));
|
||||
const auto output = BufferDescriptor(Output.data(), static_cast<std::uint32_t>(Output.size()));
|
||||
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(MixCode);
|
||||
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{
|
||||
|
|
@ -131,15 +222,36 @@ void Check() {
|
|||
}
|
||||
}
|
||||
|
||||
void CheckRounding() {
|
||||
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
|
||||
const auto& row = RoundingRows[tid];
|
||||
const auto* out = &Output[tid * Results];
|
||||
const std::uint32_t expected = 0xabcd0000u | row.expected;
|
||||
const auto where = "mix rounding: thread " + std::to_string(tid) + " (" + Hex(row.a) + ", " + Hex(row.b) + ", " + Hex(row.c) + ") result " + std::to_string(row.result);
|
||||
Require(out[row.result] == expected, where + " is " + Hex(out[row.result]) + ", expected " + Hex(expected));
|
||||
if (row.result == 0u) {
|
||||
Require(out[1] == ((row.expected << 16u) | 0xabcdu), where + ": v_fma_mixhi_f16 is " + Hex(out[1]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
const auto capabilities = device->Target().supportedCapabilities;
|
||||
if (std::find(capabilities.begin(), capabilities.end(), Float64Capability) == capabilities.end()) {
|
||||
std::puts("skipped, v_fma_mixlo_f16 and v_fma_mixhi_f16 are computed in f64 and the device has no shaderFloat64");
|
||||
return VulkanTestSkipped;
|
||||
}
|
||||
FillInput();
|
||||
Run(*device);
|
||||
Run(*device, MixCode);
|
||||
Check();
|
||||
FillRoundingInput();
|
||||
Run(*device, RoundingCode);
|
||||
CheckRounding();
|
||||
std::puts("mix precision tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
|
|
|
|||
|
|
@ -90,6 +90,42 @@ void CheckMix(const std::string& name, const std::array<std::uint32_t, 2>& words
|
|||
Require(fused == 1u, name + " emits " + std::to_string(fused) + " fused multiply-adds");
|
||||
}
|
||||
|
||||
void CheckMixHalf(const std::string& name, const std::array<std::uint32_t, 2>& words, RdnaOpcode opcode, std::uint32_t floatMode) {
|
||||
const RdnaInstruction instruction = DecodeRdnaVectorOp(std::span<const std::uint32_t>(words), 0u);
|
||||
Require(instruction.family == RdnaInstructionFamily::VOP3P, name + " is not decoded as VOP3P");
|
||||
Require(instruction.op == opcode, name + " decodes to another opcode");
|
||||
|
||||
IrProgram program;
|
||||
auto& block = program.CreateBlock();
|
||||
program.SetEntryBlock(block);
|
||||
TranslationContext context(program, block, 256);
|
||||
context.SetFloatMode(ShaderFloatMode{floatMode, false, false, false});
|
||||
context.TranslateInstruction(instruction);
|
||||
|
||||
std::uint32_t exact = 0u;
|
||||
for (const auto* value : block.Instructions()) {
|
||||
Require(value->Opcode() != IrOpcode::FPFma32 && value->Opcode() != IrOpcode::FPMad32, name + " rounds to f32 before rounding to f16");
|
||||
if (value->Opcode() == IrOpcode::FPInterpolateF16) ++exact;
|
||||
}
|
||||
Require(exact == 1u, name + " emits " + std::to_string(exact) + " product-sums rounded once to f16");
|
||||
}
|
||||
|
||||
void RejectMixHalf(const std::string& name, const std::array<std::uint32_t, 2>& words, std::uint32_t floatMode) {
|
||||
const RdnaInstruction instruction = DecodeRdnaVectorOp(std::span<const std::uint32_t>(words), 0u);
|
||||
IrProgram program;
|
||||
auto& block = program.CreateBlock();
|
||||
program.SetEntryBlock(block);
|
||||
TranslationContext context(program, block, 256);
|
||||
context.SetFloatMode(ShaderFloatMode{floatMode, false, false, false});
|
||||
try {
|
||||
context.TranslateInstruction(instruction);
|
||||
} catch (const std::runtime_error& error) {
|
||||
Require(std::string(error.what()).find("FLOAT_MODE " + std::to_string(floatMode)) != std::string::npos, name + " fails with another error: " + error.what());
|
||||
return;
|
||||
}
|
||||
Require(false, name + " is translated in FLOAT_MODE " + std::to_string(floatMode));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
|
|
@ -105,6 +141,12 @@ int main() {
|
|||
Check("v_fmaak_f32", 0x2du, 2u, RdnaOpcode::VMadakF32, IrOpcode::FPFma32, addLiteral);
|
||||
CheckMix("v_fma_mix_f32", {0xcc200005u, 0x04220f06u});
|
||||
CheckMix("v_fma_mix_f32 op_sel_hi:[1,0,1]", {0xcc204005u, 0x0c220f06u});
|
||||
CheckMixHalf("v_fma_mixlo_f16", {0xcc210005u, 0x04220f06u}, RdnaOpcode::VMadMixloF16, 0xc0u);
|
||||
CheckMixHalf("v_fma_mixhi_f16", {0xcc220005u, 0x04220f06u}, RdnaOpcode::VMadMixhiF16, 0xc0u);
|
||||
CheckMixHalf("v_fma_mixlo_f16 op_sel_hi:[1,1,1]", {0xcc214005u, 0x1c220f06u}, RdnaOpcode::VMadMixloF16, 0xc0u);
|
||||
CheckMixHalf("v_fma_mixlo_f16 with f32 round toward zero", {0xcc210005u, 0x04220f06u}, RdnaOpcode::VMadMixloF16, 0xf3u);
|
||||
RejectMixHalf("v_fma_mixlo_f16 with f16 denormals flushed", {0xcc210005u, 0x04220f06u}, 0x30u);
|
||||
RejectMixHalf("v_fma_mixhi_f16 with f16 round toward -inf", {0xcc220005u, 0x04220f06u}, 0xf8u);
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
|
|
|
|||
|
|
@ -211,6 +211,9 @@ bool TranslationContext::float16Binary(const RdnaInstruction& inst, IrOpcode opc
|
|||
}
|
||||
|
||||
bool TranslationContext::float16Ternary(const RdnaInstruction& inst, IrOpcode opcode, bool accumulator, bool mix) {
|
||||
if (mix && floatMode.has_value() && (floatMode->floatMode & 0xccu) != 0xc0u) {
|
||||
throw std::runtime_error("v_fma_mixlo/mixhi_f16 at pc " + std::to_string(inst.programCounter) + " in FLOAT_MODE " + std::to_string(floatMode->floatMode) + " (f16 denormals not kept or rounding not to nearest even) is not implemented");
|
||||
}
|
||||
std::array<IrValue*, 3> args{};
|
||||
for (std::uint32_t index = 0u; index < args.size(); ++index) {
|
||||
const RdnaOperand& operand = accumulator && index == 2u ? accumulatorOperand(inst) : sourceAt(inst, index);
|
||||
|
|
@ -220,6 +223,10 @@ bool TranslationContext::float16Ternary(const RdnaInstruction& inst, IrOpcode op
|
|||
writeF16(inst.destination, fmaF16RoundedToOdd(IrF32(*args[0]), IrF32(*args[1]), IrF32(*args[2])));
|
||||
return true;
|
||||
}
|
||||
if (mix) {
|
||||
writeF16(inst.destination, IrF32(ir.Emit(IrOpcode::FPInterpolateF16, IrType::F32, {args[0], args[1], args[2], &ir.Constant(0u)})));
|
||||
return true;
|
||||
}
|
||||
writeF16(inst.destination, IrF32(ir.Emit(opcode, IrType::F32, {args[0], args[1], args[2]})));
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,6 +86,8 @@ std::uint32_t TranslationContext::f32DenormalFlushFor(const RdnaInstruction& ins
|
|||
case RdnaOpcode::VCvtPkrtzF16F32:
|
||||
case RdnaOpcode::VCvtF16F32:
|
||||
case RdnaOpcode::VDivFixupF32:
|
||||
case RdnaOpcode::VMadMixloF16:
|
||||
case RdnaOpcode::VMadMixhiF16:
|
||||
flush = 1u;
|
||||
break;
|
||||
case RdnaOpcode::VMadLegacyF32:
|
||||
|
|
|
|||
|
|
@ -333,9 +333,10 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- The [shader float mode](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) (`FLOAT_MODE`, `DX10_CLAMP`, `IEEE_MODE` and `FP16_OVFL` of `COMPUTE_PGM_RSRC1` and `SPI_SHADER_PGM_RSRC1_PS/GS/HS`, bit positions from Mesa's `gfx10.json` and `gfx103.json`) is decoded into `GuestContext::floatMode` and is part of the shader cache keys and the serialized request (#981). `IEEE_MODE` is read by `v_min_f32`, `v_max_f32`, `v_min3_f32`, `v_max3_f32` and `v_med3_f32`, `IEEE_MODE` and the `FLOAT_MODE` output denormal bits by the output modifier, `DX10_CLAMP` by the clamp modifier, and the whole mode by the 16-bit interpolation instructions (see their entries). With IEEE=1 a signaling NaN operand returns that NaN quieted (the first operand checked first, min3/max3 as two nested pairs, med3 as min3 when any operand is NaN), measured on RDNA2 (gfx1035) over every combination of ±0, ±1, ±inf, denormals, quiet and signaling NaNs with f32 denormals kept and flushed. IEEE=0 and unknown modes keep the previous lowering, which matches IEEE=0. The f16 and f64 min/max/med3, NaN quieting in f16 arithmetic (the unary instructions and the f32 arithmetic follow it, see below), `FP16_OVFL` and `FLOAT_MODE` (f32 denormals flushed before the min/max compare in Astro's Playroom's 0xc0) still follow a single mode, as the entries below record. The mode comes from the registered shader header's `sh_registers`, the same source at registration and at draw/dispatch so the prepared artifact keys match. A title that rewrites RSRC1 at run time, or a raw compute program with no header, compiles with that mode or none.
|
||||
- [v_mullit_f32](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) follows the hardware measured with the MODE register at IEEE=1 and denormals kept (a signaling NaN src0 is quieted only with IEEE=1, as measured at IEEE=0 too; a positive denormal src2 counts as positive): other denormal settings are not modelled.
|
||||
- [v_mad_legacy_f32](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) (VOP3 0x140) rounds the product to f32 before the add, gives a +0 product for a zero factor, and flushes its f32 denormal sources, its product and its result to a zero of the same sign in every f32 denormal mode, an exception to the `FLOAT_MODE` model in [f32DenormalFlushFor](../../core/shader/recompiler/Translation/src/VectorInstructions.cpp). Measured on gfx1010 (RX 5700 XT) with `tools/hw-oracle/template.s`, assembled by the AMD Windows driver's `amd_comgr_2.dll` and launched through its `amdhip64_6.dll` instead of `oracle.c` and ROCr, over the 32 rows of the five `FmaLegacy.cpp` encodings: identical in wave32 and wave64 at IEEE 0 and 1 with f32 denormal modes 0 to 3, except that the two signaling NaN rows are quieted only with IEEE=1 (#1281). A sum of two zeros takes its sign on the bits (+0 unless both are -0), as lavapipe returned -0 for +0 + -0. The PS5's gfx1013 is not measured; LLVM decodes the encoding as `v_mad_legacy_f32` there, as on gfx1010. RDNA2 differs: on gfx1031 (RX 6700 XT, measured by pandather in #1281) the encoding is `v_fma_legacy_f32` and fused, with the same zero rule and denormal-source flush, so an RDNA2 measurement is not evidence for the console.
|
||||
- [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; shaders without a known float mode follow IEEE=0. `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` follows the same rule with its separately rounded product, as measured on gfx1010 (#1281): 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 arithmetic, `v_fma_mix_f32` with f16 sources, `v_cubema`, `v_mac_legacy_f32` 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` and `v_dot2_f32_f16` always quiet and `v_div_scale_f32` never does.
|
||||
- [v_fma_mixlo_f16 and v_fma_mixhi_f16](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) round the exact product-sum once to f16 with the f64 arithmetic of v_interp_p2_f16, so they need the Float64 capability. On RDNA2 (gfx1036) they follow the `FLOAT_MODE` f16 round and denormal fields, not the f32 round field: -1.5 * 2^-24 + 2^-126 from f32 sources (0xbfc00000, 0x33800000, 0x00800000) gives 0x8001 with f16 denormals kept and round to nearest even, 0x8000 with f16 denormals flushed and 0x8002 with f16 round toward -infinity. Only f16 denormals kept with round to nearest even is translated; another f16 mode throws, as for packed f16. `FP16_OVFL` set is not measured.
|
||||
- [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; shaders without a known float mode follow IEEE=0. `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` follows the same rule with its separately rounded product, as measured on gfx1010 (#1281): 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 arithmetic, `v_fma_mix_f32` with f16 sources, `v_cubema`, `v_mac_legacy_f32` 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_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.
|
||||
- [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/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; 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_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 before rounding: a product whose exact value is below 2^-126 gives zero even when it rounds to 2^-126, computed exactly from the significands for `v_mul_f32`, `v_mul_legacy_f32`, and for `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. `v_fma_mix_f32` was measured with f32 sources only; its f16 sources are not flushed. `v_mad_legacy_f32` flushes its inputs, its product and its result in every mode, see its entry. 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_mac_legacy_f32`, `v_dot2_f32_f16`, `v_div_scale_f32` and `v_div_fmas_f32` (on hardware `v_div_fmas_f32` flushes only its result), f32 interpolation and LDS float atomics keep the previous lowering. 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/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; 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_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 before rounding: a product whose exact value is below 2^-126 gives zero even when it rounds to 2^-126, computed exactly from the significands for `v_mul_f32`, `v_mul_legacy_f32`, and for `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. `v_fma_mix_f32` was measured with f32 sources only; its f16 sources are not flushed. `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` flushes its inputs, its product and its result in every mode, see its entry. 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_mac_legacy_f32`, `v_dot2_f32_f16`, `v_div_scale_f32` and `v_div_fmas_f32` (on hardware `v_div_fmas_f32` flushes only its result), f32 interpolation and LDS float atomics keep the previous lowering. 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.
|
||||
- The executable file that [relinker](../../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) generates opens the console when launched, which is inconvenient for playability.
|
||||
- [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.
|
||||
- [10_11_11 unorm color targets](../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) render into R32_UINT attachments: the pixel shader packs each export, rounded to nearest even like format-30 image stores, which is assumed to match the CB's conversion.
|
||||
|
|
|
|||
Loading…
Reference in New Issue