Merge pull request #3895 from CouchRiv/fix-shader-vcmpx-f32-family

fix(shader): keep f32 denormals in compares when denormals are kept
This commit is contained in:
Dean 2026-10-12 19:39:34 +13:00 committed by GitHub
commit d06c065657
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 216 additions and 1 deletions

View File

@ -0,0 +1,173 @@
#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 <stdexcept>
#include <string>
#include <vector>
namespace {
using AgcDriver::Graphics::Require;
using ShaderRecompiler::ShaderStage;
constexpr std::uint32_t Threads = 32;
constexpr std::uint32_t Inputs = 4;
constexpr std::uint32_t ValueCount = 16;
constexpr std::uint32_t Groups = ValueCount * ValueCount / Threads;
alignas(256) std::array<std::uint32_t, Threads * Inputs> Input{};
alignas(256) std::array<std::uint32_t, Threads> Output{};
constexpr std::uint32_t Values[ValueCount] = {
0x00000000u, 0x80000000u, 0x00000001u, 0x80000001u, 0x007fffffu, 0x807fffffu, 0x00800000u, 0x80800000u,
0x3f800000u, 0xbf800000u, 0x7f800000u, 0xff800000u, 0x7fc00000u, 0x7f800001u, 0xffc00000u, 0x7f7fffffu,
};
constexpr std::uint32_t CaptureExec = 0xbe95037eu;
constexpr std::uint32_t CaptureVcc = 0xbe95036au;
alignas(256) std::array<std::uint32_t, 14> Code{
0x34020084u, 0x34060082u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, 0xbe94037eu, 0x7c240b04u,
0xbe95037eu, 0xbefe0314u, 0xd501000au, 0x00550280u, 0xe0701000u, 0x80010a03u, 0xbf810000u,
};
struct Row {
const char* condition;
std::uint32_t cmpx;
std::uint32_t cmp;
std::uint32_t kept[Groups];
std::uint32_t flushed[Groups];
};
constexpr std::uint32_t RowCount = 12;
constexpr Row Rows[RowCount] = {
{"eq", 0x7c240b04u, 0x7c040b04u,
{0x00030003u, 0x00080004u, 0x00200010u, 0x00800040u, 0x02000100u, 0x08000400u, 0x00000000u, 0x80000000u},
{0x003f003fu, 0x003f003fu, 0x003f003fu, 0x00800040u, 0x02000100u, 0x08000400u, 0x00000000u, 0x80000000u}},
{"ge", 0x7c2c0b04u, 0x7c0c0b04u,
{0x0aab0aabu, 0x0aa80aafu, 0x0aa00abfu, 0x0a800affu, 0x0a000bffu, 0x08008fffu, 0x00000000u, 0x8bff0000u},
{0x0abf0abfu, 0x0abf0abfu, 0x0abf0abfu, 0x0a800affu, 0x0a000bffu, 0x08008fffu, 0x00000000u, 0x8bff0000u}},
{"gt", 0x7c280b04u, 0x7c080b04u,
{0x0aa80aa8u, 0x0aa00aabu, 0x0a800aafu, 0x0a000abfu, 0x08000affu, 0x00008bffu, 0x00000000u, 0x0bff0000u},
{0x0a800a80u, 0x0a800a80u, 0x0a800a80u, 0x0a000abfu, 0x08000affu, 0x00008bffu, 0x00000000u, 0x0bff0000u}},
{"le", 0x7c260b04u, 0x7c060b04u,
{0x85578557u, 0x855f8554u, 0x857f8550u, 0x85ff8540u, 0x87ff8500u, 0x8fff0400u, 0x00000000u, 0x84000000u},
{0x857f857fu, 0x857f857fu, 0x857f857fu, 0x85ff8540u, 0x87ff8500u, 0x8fff0400u, 0x00000000u, 0x84000000u}},
{"lg", 0x7c2a0b04u, 0x7c0a0b04u,
{0x8ffc8ffcu, 0x8ff78ffbu, 0x8fdf8fefu, 0x8f7f8fbfu, 0x8dff8effu, 0x87ff8bffu, 0x00000000u, 0x0fff0000u},
{0x8fc08fc0u, 0x8fc08fc0u, 0x8fc08fc0u, 0x8f7f8fbfu, 0x8dff8effu, 0x87ff8bffu, 0x00000000u, 0x0fff0000u}},
{"lt", 0x7c220b04u, 0x7c020b04u,
{0x85548554u, 0x85578550u, 0x855f8540u, 0x857f8500u, 0x85ff8400u, 0x87ff0000u, 0x00000000u, 0x04000000u},
{0x85408540u, 0x85408540u, 0x85408540u, 0x857f8500u, 0x85ff8400u, 0x87ff0000u, 0x00000000u, 0x04000000u}},
{"neq", 0x7c3a0b04u, 0x7c1a0b04u,
{0xfffcfffcu, 0xfff7fffbu, 0xffdfffefu, 0xff7fffbfu, 0xfdfffeffu, 0xf7fffbffu, 0xffffffffu, 0x7fffffffu},
{0xffc0ffc0u, 0xffc0ffc0u, 0xffc0ffc0u, 0xff7fffbfu, 0xfdfffeffu, 0xf7fffbffu, 0xffffffffu, 0x7fffffffu}},
{"nge", 0x7c320b04u, 0x7c120b04u,
{0xf554f554u, 0xf557f550u, 0xf55ff540u, 0xf57ff500u, 0xf5fff400u, 0xf7ff7000u, 0xffffffffu, 0x7400ffffu},
{0xf540f540u, 0xf540f540u, 0xf540f540u, 0xf57ff500u, 0xf5fff400u, 0xf7ff7000u, 0xffffffffu, 0x7400ffffu}},
{"ngt", 0x7c360b04u, 0x7c160b04u,
{0xf557f557u, 0xf55ff554u, 0xf57ff550u, 0xf5fff540u, 0xf7fff500u, 0xffff7400u, 0xffffffffu, 0xf400ffffu},
{0xf57ff57fu, 0xf57ff57fu, 0xf57ff57fu, 0xf5fff540u, 0xf7fff500u, 0xffff7400u, 0xffffffffu, 0xf400ffffu}},
{"nle", 0x7c380b04u, 0x7c180b04u,
{0x7aa87aa8u, 0x7aa07aabu, 0x7a807aafu, 0x7a007abfu, 0x78007affu, 0x7000fbffu, 0xffffffffu, 0x7bffffffu},
{0x7a807a80u, 0x7a807a80u, 0x7a807a80u, 0x7a007abfu, 0x78007affu, 0x7000fbffu, 0xffffffffu, 0x7bffffffu}},
{"nlg", 0x7c340b04u, 0x7c140b04u,
{0x70037003u, 0x70087004u, 0x70207010u, 0x70807040u, 0x72007100u, 0x78007400u, 0xffffffffu, 0xf000ffffu},
{0x703f703fu, 0x703f703fu, 0x703f703fu, 0x70807040u, 0x72007100u, 0x78007400u, 0xffffffffu, 0xf000ffffu}},
{"nlt", 0x7c3c0b04u, 0x7c1c0b04u,
{0x7aab7aabu, 0x7aa87aafu, 0x7aa07abfu, 0x7a807affu, 0x7a007bffu, 0x7800ffffu, 0xffffffffu, 0xfbffffffu},
{0x7abf7abfu, 0x7abf7abfu, 0x7abf7abfu, 0x7a807affu, 0x7a007bffu, 0x7800ffffu, 0xffffffffu, 0xfbffffffu}},
};
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;
}
std::uint32_t Run(AgcDriver::VulkanDevice& device, ShaderRecompiler::ShaderFloatMode mode, std::uint32_t compare, std::uint32_t capture, std::uint32_t group) {
Code[6] = compare;
Code[7] = capture;
for (std::uint32_t lane = 0; lane < Threads; ++lane) {
const std::uint32_t pair = group * Threads + lane;
Input[lane * Inputs] = Values[pair / ValueCount];
Input[lane * Inputs + 1u] = Values[pair % ValueCount];
Input[lane * Inputs + 2u] = 0u;
Input[lane * Inputs + 3u] = 0u;
}
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}, 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;
request.context.floatMode = mode;
const auto result = ShaderRecompiler::Recompile(request);
device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
device.WaitIdle();
std::uint32_t mask = 0u;
for (std::uint32_t lane = 0; lane < Threads; ++lane) {
Require(Output[lane] <= 1u, "v_cmp f32 denormals: lane " + std::to_string(lane) + " stored " + Hex(Output[lane]));
mask |= Output[lane] << lane;
}
return mask;
}
void Check(AgcDriver::VulkanDevice& device, const char* modeName, ShaderRecompiler::ShaderFloatMode mode, const Row& row, bool exec, const std::uint32_t* expected) {
const std::string name = std::string(exec ? "v_cmpx_" : "v_cmp_") + row.condition + "_f32";
for (std::uint32_t group = 0; group < Groups; ++group) {
const std::uint32_t actual = Run(device, mode, exec ? row.cmpx : row.cmp, exec ? CaptureExec : CaptureVcc, group);
if (actual == expected[group]) continue;
const std::uint32_t diff = actual ^ expected[group];
for (std::uint32_t lane = 0; lane < Threads; ++lane) {
if ((diff >> lane & 1u) == 0u) continue;
const std::uint32_t pair = group * Threads + lane;
Require(false,
name + " " + modeName + " of " + Hex(Values[pair / ValueCount]) + " and " +
Hex(Values[pair % ValueCount]) + " is " + std::to_string(actual >> lane & 1u) + ", expected " +
std::to_string(expected[group] >> lane & 1u));
}
}
}
}
int main() {
try {
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
for (const Row& row : Rows) {
for (const bool exec : {true, false}) {
Check(*device, "denormals kept, IEEE mode on", ShaderRecompiler::ShaderFloatMode{0xf0u, true, true, false}, row, exec, row.kept);
Check(*device, "denormals kept, IEEE mode off", ShaderRecompiler::ShaderFloatMode{0xf0u, false, false, false}, row, exec, row.kept);
Check(*device, "denormals flushed, IEEE mode on", ShaderRecompiler::ShaderFloatMode{0xc0u, true, true, false}, row, exec, row.flushed);
Check(*device, "denormals flushed, IEEE mode off", ShaderRecompiler::ShaderFloatMode{0xc0u, false, false, false}, row, exec, row.flushed);
}
}
std::puts("v_cmp and v_cmpx f32 denormals tests passed");
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}

View File

@ -165,6 +165,7 @@ private:
void emitInteger16Compare(const RdnaInstruction& inst, IrOpcode opcode, bool signedValue, bool cmpx);
void emitFloatCompare(const RdnaInstruction& inst, IrOpcode opcode, bool half, bool cmpx, bool swap = false);
IrU1 floatCompareWithConstant(IrOpcode opcode, IrU32 variableBits, std::uint32_t constant);
IrU1 floatCompareKeepingDenormals(IrOpcode opcode, IrU32 lhsBits, IrU32 rhsBits);
void emitInteger64Order(const RdnaInstruction& inst, bool signedValue, bool swap, bool negate, bool cmpx);
void emitFloatOrderedCompare(const RdnaInstruction& inst, bool ordered, bool half, bool cmpx);
void emitFloatClassCompare(const RdnaInstruction& inst, bool cmpx);

View File

@ -50,6 +50,29 @@ bool unorderedInfinityCompareIsTrue(IrOpcode opcode, bool constantIsLhs, std::ui
return false;
}
struct OrderedRelation {
IrOpcode integerOpcode;
bool unordered;
};
OrderedRelation orderedRelationOf(IrOpcode opcode) {
switch (opcode) {
case IrOpcode::FPOrdEqual32: return {IrOpcode::IEqual32, false};
case IrOpcode::FPUnordEqual32: return {IrOpcode::IEqual32, true};
case IrOpcode::FPOrdNotEqual32: return {IrOpcode::INotEqual32, false};
case IrOpcode::FPUnordNotEqual32: return {IrOpcode::INotEqual32, true};
case IrOpcode::FPOrdLessThan32: return {IrOpcode::SLessThan32, false};
case IrOpcode::FPUnordLessThan32: return {IrOpcode::SLessThan32, true};
case IrOpcode::FPOrdLessThanEqual32: return {IrOpcode::SLessThanEqual32, false};
case IrOpcode::FPUnordLessThanEqual32: return {IrOpcode::SLessThanEqual32, true};
case IrOpcode::FPOrdGreaterThan32: return {IrOpcode::SGreaterThan32, false};
case IrOpcode::FPUnordGreaterThan32: return {IrOpcode::SGreaterThan32, true};
case IrOpcode::FPOrdGreaterThanEqual32: return {IrOpcode::SGreaterThanEqual32, false};
case IrOpcode::FPUnordGreaterThanEqual32: return {IrOpcode::SGreaterThanEqual32, true};
default: throw std::runtime_error("f32 compare keeping denormals: unexpected compare opcode");
}
}
}
void TranslateCompareInstruction(IrBuilder& builder, const RdnaInstruction& instruction) {
@ -113,6 +136,10 @@ void TranslationContext::emitFloatCompare(const RdnaInstruction& inst, IrOpcode
emitCompareResult(inst, floatCompareWithConstant(oriented, IrU32(lhsConstant.has_value() ? *rhsBits : *lhsBits), lhsConstant.has_value() ? *lhsConstant : *rhsConstant), false, cmpx);
return;
}
if ((f32DenormalFlush & 1u) == 0u) {
emitCompareResult(inst, floatCompareKeepingDenormals(opcode, IrU32(*lhsBits), IrU32(*rhsBits)), false, cmpx);
return;
}
lhs = &ir.BitCastF32(flushF32Denormal(IrU32(*lhsBits)).Value());
rhs = &ir.BitCastF32(flushF32Denormal(IrU32(*rhsBits)).Value());
emitCompareResult(inst, IrU1(ir.Emit(opcode, IrType::U1, {lhs, rhs})), false, cmpx);
@ -122,6 +149,20 @@ void TranslationContext::emitFloatCompare(const RdnaInstruction& inst, IrOpcode
emitCompareResult(inst, IrU1(ir.Emit(opcode, IrType::U1, {lhs, rhs})), false, cmpx);
}
IrU1 TranslationContext::floatCompareKeepingDenormals(IrOpcode opcode, IrU32 lhsBits, IrU32 rhsBits) {
const auto magnitude = [&](IrU32 bits) -> IrValue& { return ir.BitwiseAnd(bits.Value(), ir.Constant(0x7fffffffu)); };
const auto order = [&](IrU32 bits) -> IrValue& {
IrValue& absolute = magnitude(bits);
IrValue& negative = ir.INotEqual(ir.BitwiseAnd(bits.Value(), ir.Constant(0x80000000u)), ir.Constant(0u));
return ir.Select(negative, ir.ISub(ir.Constant(0u), absolute), absolute);
};
const auto isNan = [&](IrU32 bits) -> IrValue& { return ir.UGreaterThan(magnitude(bits), ir.Constant(0x7f800000u)); };
const OrderedRelation relation = orderedRelationOf(opcode);
IrValue& ordering = ir.Emit(relation.integerOpcode, IrType::U1, {&order(lhsBits), &order(rhsBits)});
IrValue& eitherNan = ir.LogicalOr(isNan(lhsBits), isNan(rhsBits));
return IrU1(relation.unordered ? ir.LogicalOr(eitherNan, ordering) : ir.LogicalAnd(ir.LogicalNot(eitherNan), ordering));
}
IrU1 TranslationContext::floatCompareWithConstant(IrOpcode opcode, IrU32 variableBits, std::uint32_t constant) {
IrValue& value = ir.BitCastF32(variableBits.Value());
const auto compare = [&](IrOpcode compareOpcode, IrValue& lhs, std::uint32_t rhs) { return IrU1(ir.Emit(compareOpcode, IrType::U1, {&lhs, &ir.BitCastF32(ir.Constant(rhs))})); };

File diff suppressed because one or more lines are too long