mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-08-18 22:42:23 +00:00
shader: implement BUFFER_ATOMIC_FMAX
This commit is contained in:
@@ -85,6 +85,7 @@ bool InstructionMaySplitSpirvBlock(const IR::Instruction& inst) {
|
||||
case IR::Opcode::AtomicOrU32:
|
||||
case IR::Opcode::AtomicXorU32:
|
||||
case IR::Opcode::AtomicFMinF32:
|
||||
case IR::Opcode::AtomicFMaxF32:
|
||||
case IR::Opcode::FlatLoadUbyte:
|
||||
case IR::Opcode::FlatLoadSbyte:
|
||||
case IR::Opcode::FlatLoadUshort:
|
||||
|
||||
@@ -58,6 +58,7 @@ constexpr MemoryOpcodeInfo MUBUF_OPS[] = {
|
||||
{0x3au, Opcode::BufferAtomicOr, 1, 32},
|
||||
{0x3bu, Opcode::BufferAtomicXor, 1, 32},
|
||||
{0x3fu, Opcode::BufferAtomicFMin, 1, 32},
|
||||
{0x40u, Opcode::BufferAtomicFMax, 1, 32},
|
||||
};
|
||||
|
||||
constexpr MemoryOpcodeInfo MTBUF_OPS[] = {
|
||||
|
||||
@@ -836,6 +836,7 @@ std::string OpcodeToString(Opcode opcode) {
|
||||
case Opcode::BufferAtomicOr: return "buffer_atomic_or";
|
||||
case Opcode::BufferAtomicXor: return "buffer_atomic_xor";
|
||||
case Opcode::BufferAtomicFMin: return "buffer_atomic_fmin";
|
||||
case Opcode::BufferAtomicFMax: return "buffer_atomic_fmax";
|
||||
case Opcode::FlatLoadUbyte: return "flat_load_ubyte";
|
||||
case Opcode::FlatLoadSbyte: return "flat_load_sbyte";
|
||||
case Opcode::FlatLoadUshort: return "flat_load_ushort";
|
||||
@@ -1127,6 +1128,7 @@ std::string InstructionToString(const Instruction& inst) {
|
||||
case Opcode::BufferAtomicOr:
|
||||
case Opcode::BufferAtomicXor:
|
||||
case Opcode::BufferAtomicFMin:
|
||||
case Opcode::BufferAtomicFMax:
|
||||
case Opcode::BufferLoadSbyte:
|
||||
case Opcode::BufferLoadSshort:
|
||||
case Opcode::FlatLoadUbyte:
|
||||
|
||||
@@ -435,6 +435,7 @@ enum class Opcode {
|
||||
BufferAtomicOr,
|
||||
BufferAtomicXor,
|
||||
BufferAtomicFMin,
|
||||
BufferAtomicFMax,
|
||||
FlatLoadUbyte,
|
||||
FlatLoadSbyte,
|
||||
FlatLoadUshort,
|
||||
|
||||
@@ -132,7 +132,8 @@ bool IsAtomic(IR::Opcode op) {
|
||||
case IR::Opcode::AtomicAndU32:
|
||||
case IR::Opcode::AtomicOrU32:
|
||||
case IR::Opcode::AtomicXorU32:
|
||||
case IR::Opcode::AtomicFMinF32: return true;
|
||||
case IR::Opcode::AtomicFMinF32:
|
||||
case IR::Opcode::AtomicFMaxF32: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
@@ -142,6 +143,9 @@ uint32_t BufferAddressOperandCount(const IR::Instruction& inst) {
|
||||
}
|
||||
|
||||
bool ValidateInstructionContract(const IR::Instruction& inst, std::string* error) {
|
||||
if (static_cast<size_t>(inst.op) >= static_cast<size_t>(IR::Opcode::Count)) {
|
||||
return Fail(error, "IR instruction opcode is out of range");
|
||||
}
|
||||
if (inst.src_count > std::size(inst.src)) {
|
||||
return Fail(error, "instruction source count exceeds fixed IR storage");
|
||||
}
|
||||
@@ -201,6 +205,12 @@ bool ValidateInstructionContract(const IR::Instruction& inst, std::string* error
|
||||
inst.src_count != 2)) {
|
||||
return Fail(error, "storage image instruction has an invalid resource class");
|
||||
}
|
||||
const auto float_atomic =
|
||||
inst.op == IR::Opcode::AtomicFMinF32 || inst.op == IR::Opcode::AtomicFMaxF32;
|
||||
if (float_atomic &&
|
||||
(kind != IR::ResourceKind::Buffer || inst.src_count != buffer_address_count + 1u)) {
|
||||
return Fail(error, "floating-point atomic instruction must target a buffer");
|
||||
}
|
||||
if (IsAtomic(inst.op) &&
|
||||
!((kind == IR::ResourceKind::Buffer && inst.src_count == buffer_address_count + 1u) ||
|
||||
((kind == IR::ResourceKind::Lds || kind == IR::ResourceKind::Gds) &&
|
||||
@@ -489,6 +499,13 @@ bool EmitProgram(const IR::Program& program, const IR::ResourceSnapshot& resourc
|
||||
AllocateBlockLabels(state, program);
|
||||
AllocateDispatcherState(state, program);
|
||||
EmitFunction(state, program);
|
||||
if (state.unsupported_ir_instruction) {
|
||||
char pc[9] {};
|
||||
std::snprintf(pc, sizeof(pc), "%08x", state.unsupported_ir_pc);
|
||||
return Fail(error, "SPIR-V emitter has no handler for IR opcode " +
|
||||
std::string(IR::OpcodeName(state.unsupported_ir_opcode)) +
|
||||
" at pc 0x" + pc);
|
||||
}
|
||||
|
||||
ReportReserveExceeded(program, "registers", state.registers.size());
|
||||
ReportReserveExceeded(program, "inputs", state.inputs.size());
|
||||
|
||||
@@ -778,6 +778,9 @@ void EmitInstruction(EmitterState& state, const IR::Instruction& inst) {
|
||||
case IR::Opcode::AtomicFMinF32:
|
||||
EmitGuardedByExec(state, [&]() { EmitAtomicFMinF32(state, inst); });
|
||||
break;
|
||||
case IR::Opcode::AtomicFMaxF32:
|
||||
EmitGuardedByExec(state, [&]() { EmitAtomicFMaxF32(state, inst); });
|
||||
break;
|
||||
case IR::Opcode::FlatLoadUbyte: EmitFlatLoadUbyte(state, inst); break;
|
||||
case IR::Opcode::FlatLoadSbyte: EmitFlatLoadSbyte(state, inst); break;
|
||||
case IR::Opcode::FlatLoadUshort: EmitFlatLoadUshort(state, inst); break;
|
||||
@@ -854,7 +857,13 @@ void EmitInstruction(EmitterState& state, const IR::Instruction& inst) {
|
||||
}
|
||||
EmitGuardedByExec(state, [&]() { EmitExport(state, inst); });
|
||||
break;
|
||||
default: break;
|
||||
default:
|
||||
if (!state.unsupported_ir_instruction) {
|
||||
state.unsupported_ir_instruction = true;
|
||||
state.unsupported_ir_opcode = inst.op;
|
||||
state.unsupported_ir_pc = inst.pc;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -414,6 +414,9 @@ struct EmitterState {
|
||||
bool needs_image_gather_extended = false;
|
||||
bool needs_function_lds = false;
|
||||
bool needs_pixel_valid_mask = false;
|
||||
bool unsupported_ir_instruction = false;
|
||||
IR::Opcode unsupported_ir_opcode = IR::Opcode::Count;
|
||||
uint32_t unsupported_ir_pc = 0;
|
||||
std::vector<RegisterBinding> registers;
|
||||
std::vector<InputBinding> inputs;
|
||||
std::vector<OutputBinding> outputs;
|
||||
@@ -1012,6 +1015,7 @@ void EmitDeviceAtomicMemoryBarrier(EmitterState& state);
|
||||
void EmitAtomicU32(EmitterState& state, const IR::Instruction& inst, uint32_t opcode);
|
||||
|
||||
void EmitAtomicFMinF32(EmitterState& state, const IR::Instruction& inst);
|
||||
void EmitAtomicFMaxF32(EmitterState& state, const IR::Instruction& inst);
|
||||
|
||||
void EmitSLoadDword(EmitterState& state, const IR::Instruction& inst);
|
||||
|
||||
|
||||
@@ -1184,7 +1184,8 @@ void EmitAtomicU32(EmitterState& state, const IR::Instruction& inst, uint32_t op
|
||||
|
||||
namespace {
|
||||
|
||||
uint32_t EmitF32BitsOrderedLessThan(EmitterState& state, uint32_t lhs_bits, uint32_t rhs_bits) {
|
||||
uint32_t EmitF32BitsOrderedCompare(EmitterState& state, uint32_t lhs_bits, uint32_t rhs_bits,
|
||||
uint32_t comparison_opcode) {
|
||||
struct ClassifiedBits {
|
||||
uint32_t nan = 0;
|
||||
uint32_t zero = 0;
|
||||
@@ -1219,14 +1220,13 @@ uint32_t EmitF32BitsOrderedLessThan(EmitterState& state, uint32_t lhs_bits, uint
|
||||
const auto both_zero = EmitLogicalAndBool(state, lhs.zero, rhs.zero);
|
||||
const auto ordered_nonzero =
|
||||
EmitLogicalNotBool(state, EmitLogicalOrBool(state, any_nan, both_zero));
|
||||
const auto less = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpULessThan, state.bool_type, less, lhs.key, rhs.key});
|
||||
return EmitLogicalAndBool(state, ordered_nonzero, less);
|
||||
const auto comparison = state.builder.AllocateId();
|
||||
state.builder.AddFunction({comparison_opcode, state.bool_type, comparison, lhs.key, rhs.key});
|
||||
return EmitLogicalAndBool(state, ordered_nonzero, comparison);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void EmitAtomicFMinF32(EmitterState& state, const IR::Instruction& inst) {
|
||||
void EmitAtomicFMinMaxF32(EmitterState& state, const IR::Instruction& inst,
|
||||
uint32_t comparison_opcode) {
|
||||
const auto index =
|
||||
EmitMemoryDwordIndex(state, inst, inst.memory, 1, AddressSourceCount(inst, 1));
|
||||
const auto in_bounds = EmitStorageBufferElementInBounds(state, inst.memory, index, inst.pc);
|
||||
@@ -1235,15 +1235,26 @@ void EmitAtomicFMinF32(EmitterState& state, const IR::Instruction& inst) {
|
||||
const auto pointer = EmitStorageBufferElementPointer(state, inst.memory, index, inst.pc);
|
||||
return EmitAtomicUpdateU32(state, pointer, inst.memory.kind, [&](uint32_t old_u32) {
|
||||
const auto replace_old = state.builder.AllocateId();
|
||||
const auto less = EmitF32BitsOrderedLessThan(state, src_u32, old_u32);
|
||||
const auto replace =
|
||||
EmitF32BitsOrderedCompare(state, src_u32, old_u32, comparison_opcode);
|
||||
state.builder.AddFunction(
|
||||
{OpSelect, state.uint_type, replace_old, less, src_u32, old_u32});
|
||||
{OpSelect, state.uint_type, replace_old, replace, src_u32, old_u32});
|
||||
return replace_old;
|
||||
});
|
||||
});
|
||||
EmitStoreU32(state, inst.dst, old);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void EmitAtomicFMinF32(EmitterState& state, const IR::Instruction& inst) {
|
||||
EmitAtomicFMinMaxF32(state, inst, OpULessThan);
|
||||
}
|
||||
|
||||
void EmitAtomicFMaxF32(EmitterState& state, const IR::Instruction& inst) {
|
||||
EmitAtomicFMinMaxF32(state, inst, OpUGreaterThan);
|
||||
}
|
||||
|
||||
void EmitSLoadDword(EmitterState& state, const IR::Instruction& inst) {
|
||||
if (state.address_memory_variable == 0) {
|
||||
ExitDescriptorBindingFailure(state, IR::DescriptorBindingKind::AddressMemory,
|
||||
|
||||
@@ -31,7 +31,8 @@ bool IsAtomic(Opcode op) {
|
||||
case Opcode::AtomicAndU32:
|
||||
case Opcode::AtomicOrU32:
|
||||
case Opcode::AtomicXorU32:
|
||||
case Opcode::AtomicFMinF32: return true;
|
||||
case Opcode::AtomicFMinF32:
|
||||
case Opcode::AtomicFMaxF32: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -975,7 +975,12 @@ bool LowerControlInstruction(const Decoder::Instruction& decoded, BasicBlock& bl
|
||||
return LowerControlMarker(decoded, block, Opcode::TtraceData, true, error);
|
||||
case Decoder::Opcode::SInstPrefetch:
|
||||
return LowerControlMarker(decoded, block, Opcode::InstPrefetch, true, error);
|
||||
default: return false;
|
||||
default:
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("control opcode has no specialized IR lowering: {}",
|
||||
Decoder::OpcodeToString(decoded.opcode));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1064,6 +1069,18 @@ bool IsTerminatorOpcode(Decoder::Opcode opcode) {
|
||||
|
||||
bool LowerImplemented(const Decoder::Instruction& decoded, BasicBlock& block, std::string* error);
|
||||
|
||||
bool IsMemoryFamily(Decoder::Family family) {
|
||||
switch (family) {
|
||||
case Decoder::Family::SMEM:
|
||||
case Decoder::Family::MUBUF:
|
||||
case Decoder::Family::MTBUF:
|
||||
case Decoder::Family::FLAT:
|
||||
case Decoder::Family::DS:
|
||||
case Decoder::Family::MIMG: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool LowerDecodedInstruction(const Decoder::Instruction& inst, BasicBlock& block,
|
||||
std::string* error) {
|
||||
switch (inst.opcode) {
|
||||
@@ -1117,6 +1134,15 @@ bool LowerDecodedInstruction(const Decoder::Instruction& inst, BasicBlock& block
|
||||
if (IsMemoryOpcode(inst.opcode)) {
|
||||
return LowerMemoryInstruction(inst, block, error);
|
||||
}
|
||||
// Memory instructions require specialized lowering to populate MemoryInfo. Never let a
|
||||
// newly decoded memory-family opcode fall through to generic register-only IR.
|
||||
if (IsMemoryFamily(inst.family)) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("decoded memory-family opcode has no specialized IR lowering: {}",
|
||||
Decoder::OpcodeToString(inst.opcode));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (ScalarShiftLeftAddAmount(inst.opcode) != 0) {
|
||||
return LowerScalarShiftLeftAdd(inst, block, error);
|
||||
}
|
||||
|
||||
@@ -571,6 +571,8 @@ bool LowerMemoryInstruction(const Decoder::Instruction& decoded, BasicBlock& blo
|
||||
return LowerBufferAtomicDword(decoded, block, Opcode::AtomicXorU32, error);
|
||||
case Decoder::Opcode::BufferAtomicFMin:
|
||||
return LowerBufferAtomicDword(decoded, block, Opcode::AtomicFMinF32, error);
|
||||
case Decoder::Opcode::BufferAtomicFMax:
|
||||
return LowerBufferAtomicDword(decoded, block, Opcode::AtomicFMaxF32, error);
|
||||
case Decoder::Opcode::FlatLoadUbyte:
|
||||
case Decoder::Opcode::FlatLoadSbyte:
|
||||
case Decoder::Opcode::FlatLoadUshort:
|
||||
@@ -675,7 +677,12 @@ bool LowerMemoryInstruction(const Decoder::Instruction& decoded, BasicBlock& blo
|
||||
case Decoder::Opcode::ImageAtomicAnd:
|
||||
case Decoder::Opcode::ImageAtomicOr:
|
||||
case Decoder::Opcode::ImageAtomicXor: return LowerImageAtomicU32(decoded, block, error);
|
||||
default: return false;
|
||||
default:
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("memory opcode has no specialized IR lowering: {}",
|
||||
Decoder::OpcodeToString(decoded.opcode));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -732,6 +739,7 @@ bool IsMemoryOpcode(Decoder::Opcode opcode) {
|
||||
case Decoder::Opcode::BufferAtomicOr:
|
||||
case Decoder::Opcode::BufferAtomicXor:
|
||||
case Decoder::Opcode::BufferAtomicFMin:
|
||||
case Decoder::Opcode::BufferAtomicFMax:
|
||||
case Decoder::Opcode::FlatLoadUbyte:
|
||||
case Decoder::Opcode::FlatLoadSbyte:
|
||||
case Decoder::Opcode::FlatLoadUshort:
|
||||
|
||||
@@ -353,6 +353,7 @@ constexpr LowerMap LOWER_OPS[] = {
|
||||
{Decoder::Opcode::BufferAtomicOr, Opcode::AtomicOrU32},
|
||||
{Decoder::Opcode::BufferAtomicXor, Opcode::AtomicXorU32},
|
||||
{Decoder::Opcode::BufferAtomicFMin, Opcode::AtomicFMinF32},
|
||||
{Decoder::Opcode::BufferAtomicFMax, Opcode::AtomicFMaxF32},
|
||||
{Decoder::Opcode::FlatLoadUbyte, Opcode::FlatLoadUbyte},
|
||||
{Decoder::Opcode::FlatLoadSbyte, Opcode::FlatLoadSbyte},
|
||||
{Decoder::Opcode::FlatLoadSshort, Opcode::FlatLoadSshort},
|
||||
|
||||
@@ -308,6 +308,7 @@ IR_OPCODE(AtomicAndU32, General)
|
||||
IR_OPCODE(AtomicOrU32, General)
|
||||
IR_OPCODE(AtomicXorU32, General)
|
||||
IR_OPCODE(AtomicFMinF32, General)
|
||||
IR_OPCODE(AtomicFMaxF32, General)
|
||||
IR_OPCODE(FlatLoadUbyte, General)
|
||||
IR_OPCODE(FlatLoadSbyte, General)
|
||||
IR_OPCODE(FlatLoadUshort, General)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user