shader: implement BUFFER_ATOMIC_FMAX

This commit is contained in:
nmzik
2026-08-05 02:33:26 +02:00
parent df44ad3fac
commit ac64d202af
15 changed files with 244 additions and 14 deletions
+103
View File
@@ -9051,6 +9051,7 @@ CoverageClass ClassifyOpcode(ShaderOpcode opcode, const std::set<ShaderOpcode>&
case Opcode::BufferAtomicOr:
case Opcode::BufferAtomicXor:
case Opcode::BufferAtomicFMin:
case Opcode::BufferAtomicFMax:
case Opcode::FlatLoadUbyte:
case Opcode::FlatLoadSbyte:
case Opcode::FlatLoadUshort:
@@ -14017,6 +14018,105 @@ TestCase BufferAtomicFMinContendedWorkgroup() {
return test;
}
TestCase BufferAtomicFMaxExactRawGlcModes() {
using O = ShaderOpcode;
std::vector<u32> code;
AppendVMovLiteral(&code, 3, 0x40000000u); // 2.0
code.push_back(0xe100000cu);
code.push_back(0x80010300u); // exact failing buffer_atomic_fmax v3, offset 12, GLC=0
AppendStoreVgpr(&code, 3, 4);
AppendVMovLiteral(&code, 0, 0x40800000u); // 4.0
code.push_back(0xe100400cu);
code.push_back(0x80010000u); // same address with GLC=1
AppendStoreVgpr(&code, 0, 5);
AppendEnd(&code);
TestCase test;
test.name = "BufferAtomicFMaxExactRawGlcModes";
test.code = code;
test.initial = {0, 0, 0, 0x3f800000u, 0, 0}; // memory[3] = 1.0
test.expected = {0, 0, 0, 0x40800000u, 0x40000000u, 0x40000000u};
test.opcodes = {O::VMovB32, O::BufferAtomicFMax, O::BufferStoreDword, O::SEndpgm};
const auto descriptor =
MakeStructuredStorageBufferData(0, static_cast<u32>(test.initial.size() * sizeof(u32)));
std::copy_n(descriptor.begin(), 4, test.user_data.begin() + 4);
test.user_data[50] = 1u << 20u;
test.has_user_data = true;
return test;
}
TestCase BufferAtomicFMaxSpecialValues() {
using O = ShaderOpcode;
const u32 values[] = {
0x40000000u, // 2.0, finite update
0x40000000u, // 2.0, finite no-update
0x7f800000u, // incoming +infinity
0xff800000u, // incoming -infinity
0x3f800000u, // finite against old +infinity
0x3f800000u, // finite against old -infinity
0x3f800000u, // finite against old quiet NaN
0x7fcabcdeu, // incoming quiet NaN
0x3f800000u, // finite against old signaling NaN
0x7faabcdeu, // incoming signaling NaN
0x00000000u, // +0.0 against old -0.0
0x80000000u, // -0.0 against old +0.0
0x80000000u, // -0.0 against a negative denorm
0x80000001u, // negative denorm against -0.0
0x00000001u, // positive denorm against +0.0
0x00000000u, // +0.0 against a positive denorm
};
std::vector<u32> code;
for (u32 i = 0; i < static_cast<u32>(std::size(values)); i++) {
AppendVMovU32(&code, 20, i * 4u);
AppendVMovLiteral(&code, i, values[i]);
AppendBufferStoreOpcode(&code, 0x40, i, 20, true);
}
for (u32 i = 0; i < static_cast<u32>(std::size(values)); i++) {
AppendStoreVgpr(&code, i, i + static_cast<u32>(std::size(values)));
}
AppendEnd(&code);
return {"BufferAtomicFMaxSpecialValues",
code,
{0x3f800000u, 0x40800000u, 0xbf800000u, 0x3f800000u, 0x7f800000u,
0xff800000u, 0x7fc12345u, 0x3f800000u, 0x7fa54321u, 0x3f800000u,
0x80000000u, 0x00000000u, 0x80000001u, 0x80000000u, 0x00000000u,
0x00000001u, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0x40000000u, 0x40800000u, 0x7f800000u, 0x3f800000u, 0x7f800000u,
0x3f800000u, 0x7fc12345u, 0x3f800000u, 0x7fa54321u, 0x3f800000u,
0x80000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000001u,
0x00000001u, 0x3f800000u, 0x40800000u, 0xbf800000u, 0x3f800000u,
0x7f800000u, 0xff800000u, 0x7fc12345u, 0x3f800000u, 0x7fa54321u,
0x3f800000u, 0x80000000u, 0x00000000u, 0x80000001u, 0x80000000u,
0x00000000u, 0x00000001u},
{O::VMovB32, O::BufferAtomicFMax, O::BufferStoreDword, O::SEndpgm}};
}
TestCase BufferAtomicFMaxContendedWorkgroup() {
using O = ShaderOpcode;
std::vector<u32> code;
code.push_back(EncodeVop1(0x06, 1, Vgpr(0))); // v_cvt_f32_u32 v1, thread_id.x
AppendVMovU32(&code, 20, 0);
AppendBufferStoreOpcode(&code, 0x40, 1, 20);
AppendEnd(&code);
TestCase test;
test.name = "BufferAtomicFMaxContendedWorkgroup";
test.code = code;
test.initial = {0xc2c80000u}; // -100.0
test.expected = {0x427c0000u}; // max(-100.0, 0.0 .. 63.0)
test.opcodes = {O::VCvtF32U32, O::VMovB32, O::BufferAtomicFMax, O::SEndpgm};
test.compute_info.threads_num[0] = 64;
test.compute_info.threads_num[1] = 1;
test.compute_info.threads_num[2] = 1;
test.compute_info.thread_ids_num = 1;
test.has_compute_info = true;
return test;
}
std::vector<u32> MakeRgbaImage(u32 width, u32 height, u32 value = 0) {
return std::vector<u32>(static_cast<size_t>(width) * height * 4u, value);
}
@@ -15370,6 +15470,9 @@ std::vector<TestCase> MakeCases() {
AddCase(BufferAtomicFMinExactRawGlcModes);
AddCase(BufferAtomicFMinSpecialValues);
AddCase(BufferAtomicFMinContendedWorkgroup);
AddCase(BufferAtomicFMaxExactRawGlcModes);
AddCase(BufferAtomicFMaxSpecialValues);
AddCase(BufferAtomicFMaxContendedWorkgroup);
AddCase(ImageLoadVariants);
AddCase(ImageLoadR32UintUsesIntegerSampledImage);
AddCase(ImageLoad1DUsesScalarCoordinate);
+44
View File
@@ -2959,6 +2959,15 @@ void TestNewShaderRecompilerIrLookupMissFailsExplicitly() {
Check(Common::ContainsStr(error, "no IR lowering"),
"missing decoder-to-IR mapping did not report an explicit error");
Check(ir.blocks.empty(), "missing decoder-to-IR mapping emitted a fallback IR block");
decoded.instructions.front().family = ShaderRecompiler::Decoder::Family::MUBUF;
decoded.instructions.front().opcode = ShaderRecompiler::Decoder::Opcode::VAddNcU32;
error.clear();
Check(!ShaderRecompiler::IR::LowerProgram(decoded, cfg, ShaderType::Compute, 64u, ir, &error),
"memory-family opcode bypassed specialized memory lowering");
Check(Common::ContainsStr(error, "memory-family opcode") &&
Common::ContainsStr(error, "specialized IR lowering"),
"memory-family lowering bypass did not report an explicit error");
}
void TestNewShaderRecompilerMemoryFamilyLowering() {
@@ -4790,6 +4799,8 @@ void TestNewShaderRecompilerAtomicLowering() {
EncodeMubuf1(6, 0, 1), // buffer_atomic_xor
0xe0fc0000u,
0x80010000u, // exact buffer_atomic_fmin v0, s[4:7], 0 (GLC=0)
0xe100000cu,
0x80010300u, // exact failing buffer_atomic_fmax v3, s[4:7], 12 (GLC=0)
EncodeDs0(0x00),
EncodeDs1(0, 2, 1), // ds_add_u32
EncodeDs0(0x01),
@@ -4846,6 +4857,8 @@ void TestNewShaderRecompilerAtomicLowering() {
"new decoder did not decode buffer atomic xor");
Check(Common::ContainsStr(result.decoded_dump, "buffer_atomic_fmin"),
"new decoder did not decode buffer atomic float min");
Check(Common::ContainsStr(result.decoded_dump, "buffer_atomic_fmax"),
"new decoder did not decode buffer atomic float max");
Check(Common::ContainsStr(result.decoded_dump, "ds_add_u32"),
"new decoder did not decode DS atomic add");
Check(Common::ContainsStr(result.decoded_dump, "ds_sub_u32"),
@@ -4880,6 +4893,8 @@ void TestNewShaderRecompilerAtomicLowering() {
"buffer atomic xor did not lower to IR");
Check(Common::ContainsStr(result.ir_dump, "AtomicFMinF32 null, v0"),
"buffer atomic float min did not lower to IR without a GLC return");
Check(Common::ContainsStr(result.ir_dump, "AtomicFMaxF32 null, v3"),
"buffer atomic float max did not lower to IR without a GLC return");
Check(Common::ContainsStr(result.ir_dump, "AtomicAddU32 null, v2"),
"DS no-return atomic add did not lower to IR");
Check(Common::ContainsStr(result.ir_dump, "AtomicSubU32 null, v10"),
@@ -6712,6 +6727,35 @@ void TestNewShaderRecompilerNativeBindingPlan() {
rejected_spirv == rejected_before && rejected_error.find("atomic") != std::string::npos,
"malformed atomic instruction reached a fatal descriptor path");
auto malformed_float_atomic = result.program;
atomic_inst = FindBufferInstruction(&malformed_float_atomic);
Check(atomic_inst != nullptr, "native validation fixture lacks a float atomic candidate");
atomic_inst->op = ShaderRecompiler::IR::Opcode::AtomicFMaxF32;
atomic_inst->memory.kind = ShaderRecompiler::IR::ResourceKind::Lds;
atomic_inst->memory.resource = 0;
atomic_inst->src_count = 2;
rejected_spirv = rejected_before;
rejected_error.clear();
Check(!ShaderRecompiler::Spirv::EmitProgram(malformed_float_atomic, result.resources, nullptr,
nullptr, nullptr, rejected_spirv,
&rejected_error) &&
rejected_spirv == rejected_before &&
rejected_error.find("floating-point atomic") != std::string::npos,
"buffer-only floating-point atomic accepted a non-buffer resource");
auto invalid_ir_opcode = result.program;
Check(!invalid_ir_opcode.blocks.empty() && !invalid_ir_opcode.blocks.front().instructions.empty(),
"native validation fixture lacks an invalid-opcode candidate");
invalid_ir_opcode.blocks.front().instructions.front().op =
ShaderRecompiler::IR::Opcode::Count;
rejected_spirv = rejected_before;
rejected_error.clear();
Check(!ShaderRecompiler::Spirv::EmitProgram(invalid_ir_opcode, result.resources, nullptr, nullptr,
nullptr, rejected_spirv, &rejected_error) &&
rejected_spirv == rejected_before &&
rejected_error.find("opcode is out of range") != std::string::npos,
"out-of-range IR opcode was not rejected transactionally before emission");
auto overwide_atomic = result.program;
atomic_inst = FindBufferInstruction(&overwide_atomic);
Check(atomic_inst != nullptr, "native validation fixture lacks an overwide atomic candidate");