Compare commits

...

3 Commits

Author SHA1 Message Date
nmzik 099de8dea6 graphics: publish each PM4 WRITE_DATA word exactly once 2026-10-18 16:17:51 +02:00
opcod3 f4bdb52922 agc: restore verified fused shader size name 2026-10-18 16:12:28 +02:00
nmzik 22ef16a044 shader: implement PS5 vector data cache invalidation 2026-10-18 15:57:14 +02:00
15 changed files with 89 additions and 9 deletions
+5 -6
View File
@@ -343,12 +343,11 @@ void CommandProcessor::WriteData(uint32_t* dst, const uint32_t* src, uint32_t dw
return;
}
if (write_one_address) {
for (uint32_t i = 0; i < dw_num; i++) {
dst[0] = src[i];
}
} else {
memcpy(dst, src, static_cast<size_t>(dw_num) * sizeof(uint32_t));
// Completion labels can be reused immediately; memcpy may write a destination twice.
auto* output = static_cast<volatile uint32_t*>(dst);
for (uint32_t i = 0; i < dw_num; i++) {
*output = src[i];
output += !write_one_address;
}
}
@@ -655,6 +655,14 @@ void EmitStoreCompletion(EmitterState& state) {
spv::MemorySemanticsImageMemoryMask));
}
void EmitDataCacheInvalidate(EmitterState& state) {
// RDNA2 GL0/GL1 invalidation makes vector memory visible without a workgroup rendezvous.
state.builder.AddFunction(
spv::OpMemoryBarrier, ConstantU32(state, spv::ScopeDevice),
ConstantU32(state, spv::MemorySemanticsAcquireMask | spv::MemorySemanticsUniformMemoryMask |
spv::MemorySemanticsImageMemoryMask));
}
void EmitBarrier(EmitterState& state) {
const auto tessellation = state.program.stage == ShaderType::TessellationControl;
if (!tessellation && ShaderWorkgroupInput(state.program.stage, state.input_info) == nullptr) {
@@ -218,6 +218,7 @@ inline constexpr auto EmitTtraceData = EmitVoid;
inline constexpr auto EmitInstPrefetch = EmitVoid;
void EmitBarrier(EmitterState& state);
void EmitStoreCompletion(EmitterState& state);
void EmitDataCacheInvalidate(EmitterState& state);
void EmitMeshAllocate(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitMeshDrawParameter(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitGetTessellationAttribute(ValueEmitContext& ctx, const IR::Inst& inst);
@@ -307,6 +307,7 @@ void EmitBlock(ValueEmitContext& ctx, const IR::Block* block, EmitInstruction&&
ctx.state.lane_half = half;
if (half == 0 || (inst.GetOpcode() != IR::ValueOpcode::Barrier &&
inst.GetOpcode() != IR::ValueOpcode::StoreCompletion &&
inst.GetOpcode() != IR::ValueOpcode::DataCacheInvalidate &&
inst.GetOpcode() != IR::ValueOpcode::MeshAllocate)) {
emit_instruction(lane, inst);
}
@@ -70,6 +70,8 @@ constexpr MemoryOpcodeInfo MUBUF_OPCODE_LIST[] = {
{0x52u, Opcode::BUFFER_ATOMIC_ADD_X2, 2, 32},
{0x59u, Opcode::BUFFER_ATOMIC_AND_X2, 2, 32},
{0x5au, Opcode::BUFFER_ATOMIC_OR_X2, 2, 32},
{0x71u, Opcode::BUFFER_GL0_INV, 0, 0},
{0x72u, Opcode::BUFFER_GL1_INV, 0, 0},
{0x80u, Opcode::BUFFER_LOAD_FORMAT_D16_X, 1, 16, false, false, true},
{0x83u, Opcode::BUFFER_LOAD_FORMAT_D16_XYZW, 2, 16, false, false, true},
{0x84u, Opcode::BUFFER_STORE_FORMAT_D16_X, 1, 16, false, false, true},
@@ -312,6 +314,9 @@ void DecodeMubuf(uint32_t pc, std::span<const uint32_t> code, uint32_t word_inde
if (inst.opcode == Opcode::UNSUPPORTED) {
SetUnsupported(inst, Family::MUBUF, opcode, "MUBUF opcode is not implemented");
}
if (inst.opcode == Opcode::BUFFER_GL0_INV || inst.opcode == Opcode::BUFFER_GL1_INV) {
return;
}
DecodeVectorGpr(vdata, inst.dst);
if (inst.opcode == Opcode::BUFFER_LOAD_FORMAT_D16_XYZW ||
@@ -566,6 +566,9 @@ std::string InstructionToString(const Instruction& inst) {
OperandToString(inst.src0).c_str()));
case Opcode::S_BARRIER:
return WithUnsupportedReason(inst, fmt::format("0x{:08x}: s_barrier", inst.pc));
case Opcode::BUFFER_GL0_INV:
case Opcode::BUFFER_GL1_INV:
return fmt::format("0x{:08x}: {}", inst.pc, magic_enum::enum_name(inst.opcode));
case Opcode::V_NOP:
return WithUnsupportedReason(inst, fmt::format("0x{:08x}: v_nop", inst.pc));
case Opcode::S_ENDPGM:
@@ -599,6 +599,8 @@ enum class Opcode {
BUFFER_ATOMIC_XOR,
BUFFER_ATOMIC_FMIN,
BUFFER_ATOMIC_FMAX,
BUFFER_GL0_INV,
BUFFER_GL1_INV,
FLAT_LOAD_UBYTE,
FLAT_LOAD_SBYTE,
FLAT_LOAD_USHORT,
@@ -940,6 +940,10 @@ void Translator::DS_PERMUTE(const Decoder::Instruction& inst, bool backward) {
void Translator::EmitMemory(const Decoder::Instruction& inst) {
switch (inst.opcode) {
case Decoder::Opcode::BUFFER_GL0_INV:
case Decoder::Opcode::BUFFER_GL1_INV:
ir.Emit(IR::ValueOpcode::DataCacheInvalidate);
return;
case Decoder::Opcode::IMAGE_BVH_INTERSECT_RAY: {
const auto result = ir.Emit(IR::ValueOpcode::BvhIntersect,
{ConstructU32x4(inst.src1, 4), MakeImageAddress(inst, inst.src0), ir.GetExec()},
@@ -94,6 +94,7 @@ bool HasSideEffects(ValueOpcode opcode) {
case ValueOpcode::SetTessellationAttribute:
case ValueOpcode::MeshAllocate:
case ValueOpcode::StoreCompletion:
case ValueOpcode::DataCacheInvalidate:
case ValueOpcode::Barrier: return true;
default: return false;
}
@@ -350,6 +350,7 @@ VALUE_OPCODE(SetAttribute, Void, U32x4, U1)
VALUE_OPCODE(ControlNop, Void)
VALUE_OPCODE(StoreCompletion, Void)
VALUE_OPCODE(DataCacheInvalidate, Void)
VALUE_OPCODE(Barrier, Void)
VALUE_OPCODE(Sendmsg, Void)
VALUE_OPCODE(TtraceData, Void)
@@ -509,6 +509,7 @@ void Visit(Program& program, ShaderStageInputInfo input_info, InputUsage& inputs
case ValueOpcode::ReadConst: info.uses_flattened_srt = true; break;
case ValueOpcode::BitwiseXor32: info.has_bitwise_xor = true; break;
case ValueOpcode::StoreCompletion: info.subgroup_barrier = true; break;
case ValueOpcode::DataCacheInvalidate: info.coherent_buffers = true; break;
case ValueOpcode::BvhIntersect: info.bvh = true; break;
case ValueOpcode::ConditionRef:
info.subgroup_vote |=
+1 -1
View File
@@ -670,7 +670,7 @@ int KYTY_SYSV_ABI AgcCreateShader(Shader** dst, void* header, const volatile voi
return OK;
}
int KYTY_SYSV_ABI AgcUnknownGetFusedShaderSize(SizeAlign* dst, const Shader* front,
int KYTY_SYSV_ABI AgcGetFusedShaderSize(SizeAlign* dst, const Shader* front,
const Shader* back) {
PRINT_NAME();
+1 -1
View File
@@ -40,7 +40,7 @@ int KYTY_SYSV_ABI AgcInit(uint32_t* state, uint32_t ver);
void* KYTY_SYSV_ABI AgcGetRegisterDefaults2(uint32_t ver);
void* KYTY_SYSV_ABI AgcGetRegisterDefaults2Internal(uint32_t ver);
int KYTY_SYSV_ABI AgcCreateShader(Shader** dst, void* header, const volatile void* code);
int KYTY_SYSV_ABI AgcUnknownGetFusedShaderSize(SizeAlign* dst, const Shader* front,
int KYTY_SYSV_ABI AgcGetFusedShaderSize(SizeAlign* dst, const Shader* front,
const Shader* back);
int KYTY_SYSV_ABI AgcFuseShaderHalves_0200(Shader* fused_result, const Shader* front,
const Shader* back, void* scratch_mem);
+1 -1
View File
@@ -18,7 +18,7 @@ LIB_DEFINE(InitAgcDriver_1) {
LIB_FUNC("2JtWUUiYBXs", Gen5::AgcGetRegisterDefaults2);
LIB_FUNC("wRbq6ZjNop4", Gen5::AgcGetRegisterDefaults2Internal);
LIB_FUNC("f3dg2CSgRKY", Gen5::AgcCreateShader);
LIB_FUNC("dolOmWH+huQ", Gen5::AgcUnknownGetFusedShaderSize);
LIB_FUNC("dolOmWH+huQ", Gen5::AgcGetFusedShaderSize);
LIB_FUNC("fd5Bp5tGTgo", Gen5::AgcFuseShaderHalves_0200);
LIB_FUNC("nApJjpKNBl4", Gen5::AgcFuseShaderHalves);
LIB_FUNC("vcmNN+AAXnY", Gen5::AgcSetCxRegIndirectPatchSetAddress);
+54
View File
@@ -1967,6 +1967,59 @@ void TestNggVertexEntryState() {
}
}
void TestNativeDataCacheInvalidation() {
using namespace ShaderRecompiler;
// Captured f5edf70dbb1f6057:0x194 and 0x1a4, each followed by S_WAITCNT.
const std::array cache_ops = {0xe1c40000u, 0u, 0xbf8c0000u,
0xe1c80000u, 0u, 0xbf8c0000u, EncodeSopp(0x01)};
Decoder::Program decoded;
Decoder::DecodeProgram(cache_ops, decoded);
for (const uint32_t index : {0u, 2u}) {
const auto &inst = decoded.instructions[index];
Check(inst.opcode == (index == 0 ? Decoder::Opcode::BUFFER_GL0_INV
: Decoder::Opcode::BUFFER_GL1_INV) &&
inst.word_count == 2 && inst.src_count == 0 && inst.data_dwords == 0 &&
inst.dst.kind == Decoder::OperandKind::Unknown,
"RDNA2 cache invalidation acquired a buffer operand or lost its encoding");
}
auto options = MakeCompileOptions(ShaderType::Compute);
options.dump_ir = true;
ShaderComputeInputInfo compute{};
compute.threads_num[1] = compute.threads_num[2] = 1;
options.input_info.compute = &compute;
for (const uint32_t wave_size : {32u, 64u}) {
compute.threads_num[0] = compute.wave_size = options.wave_size = wave_size;
const auto minimal = RecompileForTest(cache_ops, options);
CheckSpirvBinaryValidates(minimal.spirv);
Check(minimal.program.info.buffers.empty(),
"cache invalidation introduced a phantom buffer binding");
const auto source = DisassembleSpirvBinary(minimal.spirv);
const auto first = source.find("OpMemoryBarrier %uint_1 %uint_2114");
const auto second = source.find("OpMemoryBarrier %uint_1 %uint_2114", first + 1);
Check(first != std::string::npos && second != std::string::npos &&
source.find("OpMemoryBarrier", second + 1) == std::string::npos &&
source.find("OpControlBarrier") == std::string::npos,
"cache invalidation must fence device memory once per wave without convergence");
const std::array reload = {
EncodeMubuf0(0x1c, 0, false), EncodeMubuf1(0, 0, 0),
0xe1c40000u, 0u, 0xbf8c0000u,
EncodeMubuf0(0x0c, 0, false), EncodeMubuf1(1, 0, 0),
EncodeMubuf0(0x1c, 4, false), EncodeMubuf1(1, 0, 0), EncodeSopp(0x01),
};
const auto result = RecompileForTest(reload, options);
CheckSpirvBinaryValidates(result.spirv);
const auto store = result.ir_dump.find("StoreBufferU32");
const auto fence = result.ir_dump.find("DataCacheInvalidate");
const auto load = result.ir_dump.find("LoadBufferU32");
Check(store < fence && fence < load && load != std::string::npos &&
result.program.info.coherent_buffers &&
DisassembleSpirvBinary(result.spirv).find(" Coherent") != std::string::npos,
"plain buffer reload lost its cache fence order or coherent visibility");
}
}
void TestNativeStoreCompletion() {
using namespace ShaderRecompiler;
const std::array register_wait = {EncodeSopk(0x17, 4, static_cast<int16_t>(0x9234)), EncodeSopp(0x01)};
@@ -16523,6 +16576,7 @@ int main() {
TestNewShaderDecoderArchitecture();
TestImageAddressOperands();
TestSopkCompareImmediateExtension();
TestNativeDataCacheInvalidation();
TestNativeStoreCompletion();
TestNewShaderRecompilerCapturedVopcSdwaCmpxClass();
TestVopcCmpxClassF16Decoder();