agc: emit async memory semaphores and execute them in the CP

This commit is contained in:
Claxten
2026-10-27 00:36:40 +01:00
committed by nmzik
parent 914a3bb4b8
commit 1b70938052
9 changed files with 144 additions and 2 deletions
@@ -98,6 +98,7 @@ public:
template <typename T>
void WaitRegMem(uint32_t func, const T* addr, T ref, T mask, uint32_t poll, uint32_t wait_op);
void MemSemaphore(uint64_t* counter, bool signal, bool write);
void WriteData(uint32_t* dst, const uint32_t* src, uint32_t dw_num, uint32_t write_control);
void WriteReferenceClock(uint64_t dst_address, uint32_t num_bytes);
void DmaData(uint8_t engine, uint8_t dst_sel, uint8_t dst_cache_policy,
@@ -202,6 +202,7 @@ constexpr auto MakeOpcodeDispatchTable() {
g_cp_op_func[Pm4::IT_DMA_DATA] = CpOpDmaData;
g_cp_op_func[Pm4::IT_ACQUIRE_MEM] = CpOpAcquireMem;
g_cp_op_func[Pm4::IT_RELEASE_MEM] = CpOpReleaseMem;
g_cp_op_func[Pm4::IT_MEM_SEMAPHORE] = CpOpMemSemaphore;
g_cp_op_func[Pm4::IT_REWIND] = CpOpRewind;
g_cp_op_func[Pm4::IT_SET_CONTEXT_REG] = CpOpSetContextReg;
g_cp_op_func[Pm4::IT_SET_SH_REG] = CpOpSetShaderReg;
@@ -136,6 +136,7 @@ uint32_t CpOpEventWrite(CommandProcessor&, uint32_t, const uint32_t*, uint32_t,
uint32_t CpOpDmaData(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpAcquireMem(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpReleaseMem(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpMemSemaphore(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpSetContextReg(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpSetShaderReg(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpSetUconfigReg(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
@@ -2060,6 +2060,21 @@ KYTY_CP_OP_PARSER(CpOpReleaseMem) {
return 7;
}
KYTY_CP_OP_PARSER(CpOpMemSemaphore) {
KYTY_PROFILER_FUNCTION();
const auto control = buffer[2];
EXIT_NOT_IMPLEMENTED(cmd_id != 0xc0023900 || (control & ~0xe0110000u) != 0 ||
(control >> 29u) < 6u);
auto* counter = reinterpret_cast<uint64_t*>(
(buffer[0] & ~0x7u) | (static_cast<uint64_t>(buffer[1] & 0x3ffffu) << 32u));
// A signal updates the counter before the next packet, so its mailbox stall never waits.
cp.MemSemaphore(counter, (control >> 29u) == 6u, ((control >> 20u) & 0x1u) != 0);
return 3;
}
KYTY_CP_OP_PARSER(CpOpSetContextReg) {
KYTY_PROFILER_FUNCTION();
+10
View File
@@ -281,6 +281,16 @@ template void CommandProcessor::WaitRegMem<uint32_t>(uint32_t, const uint32_t*,
template void CommandProcessor::WaitRegMem<uint64_t>(uint32_t, const uint64_t*, uint64_t, uint64_t,
uint32_t, uint32_t);
void CommandProcessor::MemSemaphore(uint64_t* counter, bool signal, bool write) {
if (signal) {
*counter = write ? 1u : *counter + 1u;
} else if (*counter == 0) {
SuspendPm4();
} else {
(*counter)--;
}
}
void CommandProcessor::WriteData(uint32_t* dst, const uint32_t* src, uint32_t dw_num,
uint32_t write_control) {
const uint32_t dst_sel = ((write_control >> 30u) & 0x1u) | ((write_control >> 7u) & 0x1eu);
+42 -2
View File
@@ -2480,6 +2480,9 @@ static constexpr uint32_t AGC_DRAW_INDIRECT_MULTI_END_TAG = 0xc6000000u;
static constexpr uint32_t AGC_WAIT_USER_DATA_BEGIN_32_TAG = 0xc8010000u;
static constexpr uint32_t AGC_WAIT_USER_DATA_BEGIN_64_TAG = 0xc8020000u;
static constexpr uint32_t AGC_WAIT_USER_DATA_END_TAG = 0xc8000000u;
static constexpr uint32_t AGC_MEM_SEMAPHORE_SIGNAL_BEGIN_TAG = 0xc9010000u;
static constexpr uint32_t AGC_MEM_SEMAPHORE_WAIT_BEGIN_TAG = 0xc9020000u;
static constexpr uint32_t AGC_MEM_SEMAPHORE_END_TAG = 0xc9000000u;
static constexpr uint32_t AGC_WAIT_USER_DATA_BEGIN_DW = 4u;
static constexpr uint32_t AGC_INTERNAL_DATA_PACKET_SIZE_DW = 3u;
@@ -3077,6 +3080,42 @@ uint32_t* KYTY_SYSV_ABI AgcAcbWaitRegMem(CommandBuffer* buf, uint8_t size, uint8
poll_cycles);
}
uint32_t* KYTY_SYSV_ABI AgcAcbMemSemaphore(CommandBuffer* buf, const volatile void* address,
uint8_t mailbox, uint8_t signal_type, uint8_t op) {
EXIT_NOT_IMPLEMENTED(op != 6u && op != 7u);
auto* cmd =
buf->AllocateDW(AGC_WAIT_USER_DATA_BEGIN_DW + AGC_INTERNAL_DATA_PACKET_SIZE_DW + 4u);
if (cmd == nullptr) {
return nullptr;
}
const auto address_value = reinterpret_cast<uint64_t>(address);
const bool signal = (op == 6u);
cmd[0] = KYTY_PM4(AGC_WAIT_USER_DATA_BEGIN_DW, Pm4::IT_SET_UCONFIG_REG, 1u);
cmd[1] = AGC_INTERNAL_DATA_REGISTER;
cmd[2] = (signal ? AGC_MEM_SEMAPHORE_SIGNAL_BEGIN_TAG : AGC_MEM_SEMAPHORE_WAIT_BEGIN_TAG) |
static_cast<uint32_t>((address_value >> 32u) & 0xffffu);
cmd[3] = static_cast<uint32_t>(address_value);
// A signal pads with a NOP header where a wait ends with its end marker.
auto* semaphore = cmd + AGC_WAIT_USER_DATA_BEGIN_DW;
if (signal) {
semaphore[0] = KYTY_PM4(AGC_INTERNAL_DATA_PACKET_SIZE_DW, Pm4::IT_NOP, 0u);
semaphore += AGC_INTERNAL_DATA_PACKET_SIZE_DW;
}
semaphore[0] = KYTY_PM4(4, Pm4::IT_MEM_SEMAPHORE, 0u);
semaphore[1] = static_cast<uint32_t>(address_value) & ~0x7u;
semaphore[2] = static_cast<uint32_t>(address_value >> 32u);
semaphore[3] = (static_cast<uint32_t>(op) << 29u) | ((signal_type & 0x1u) << 20u) |
((mailbox & 0x1u) << 16u);
if (!signal) {
write_agc_internal_data_packet(semaphore + 4, AGC_MEM_SEMAPHORE_END_TAG);
}
return cmd;
}
uint64_t KYTY_SYSV_ABI AgcAcbWaitOnAddressGetSize(uint8_t size) {
PRINT_NAME();
@@ -3840,11 +3879,12 @@ bool AgcIsInternalDataPacket(uint32_t cmd_id, const uint32_t* payload) {
const auto size_dw = KYTY_PM4_LEN(cmd_id);
if (size_dw == AGC_WAIT_USER_DATA_BEGIN_DW) {
const auto tag = payload[1] & 0xffff0000u;
return tag == AGC_WAIT_USER_DATA_BEGIN_32_TAG || tag == AGC_WAIT_USER_DATA_BEGIN_64_TAG;
return tag == AGC_WAIT_USER_DATA_BEGIN_32_TAG || tag == AGC_WAIT_USER_DATA_BEGIN_64_TAG ||
tag == AGC_MEM_SEMAPHORE_SIGNAL_BEGIN_TAG || tag == AGC_MEM_SEMAPHORE_WAIT_BEGIN_TAG;
}
return size_dw == AGC_INTERNAL_DATA_PACKET_SIZE_DW &&
(payload[1] == AGC_WAIT_USER_DATA_END_TAG ||
(payload[1] == AGC_WAIT_USER_DATA_END_TAG || payload[1] == AGC_MEM_SEMAPHORE_END_TAG ||
payload[1] == AGC_DRAW_INDIRECT_MULTI_BEGIN_TAG ||
payload[1] == AGC_DRAW_INDIRECT_MULTI_END_TAG);
}
+2
View File
@@ -216,6 +216,8 @@ uint32_t* KYTY_SYSV_ABI AgcAcbWaitRegMem(CommandBuffer* buf, uint8_t size, uint8
uint8_t cache_policy, const volatile void* address,
uint64_t reference, uint64_t mask, uint32_t poll_cycles);
uint64_t KYTY_SYSV_ABI AgcAcbWaitOnAddressGetSize(uint8_t size);
uint32_t* KYTY_SYSV_ABI AgcAcbMemSemaphore(CommandBuffer* buf, const volatile void* address,
uint8_t mailbox, uint8_t signal_type, uint8_t op);
uint32_t* KYTY_SYSV_ABI AgcAcbDmaData(CommandBuffer* buf, uint8_t dst, uint8_t dst_cache_policy,
uint64_t dst_address_or_offset, uint8_t src,
uint8_t src_cache_policy,
+1
View File
@@ -78,6 +78,7 @@ LIB_DEFINE(InitAgcDriver_1) {
LIB_FUNC("b-oySn+G2tE", Gen5::AgcAcbJumpGetSize);
LIB_FUNC("DwICrVxerkY", Gen5::AgcAcbRewind);
LIB_FUNC("htn36gPnBk4", Gen5::AgcAcbWaitRegMem);
LIB_FUNC("q4VuU-QsLOE", Gen5::AgcAcbMemSemaphore);
LIB_FUNC("idlaArvdXEs", Gen5::AgcAcbWaitOnAddressGetSize);
LIB_FUNC("-RnpfpxIhec", Gen5::AgcAcbDmaData);
LIB_FUNC("qzMN2XKGA4k", Gen5::AgcAcbCopyData);
+71
View File
@@ -51691,6 +51691,75 @@ void CheckPm4WaitResume(RenderContext &renderer) {
std::printf("[host] %-32s ok\n", "Pm4WaitResume");
}
void CheckPm4MemSemaphore(RenderContext &renderer) {
constexpr const char *name = "Pm4MemSemaphore";
Loader::SymbolDatabase symbols;
Libs::InitAgcDriver_1(&symbols);
const auto *symbol = symbols.FindByNid("q4VuU-QsLOE", Loader::SymbolType::Func);
Require(name, "async export",
symbol != nullptr &&
symbol->vaddr == reinterpret_cast<uint64_t>(&Gen5::AgcAcbMemSemaphore),
"async semaphore import did not resolve to the emitter");
GraphicsInitJmpTables();
CommandProcessor processor(renderer, 0x20);
const auto execute = [&](std::span<const uint32_t> commands) {
Pm4Execution execution;
return processor.Process(execution, commands) == Pm4ProcessResult::Complete;
};
alignas(8) uint64_t counter = 0;
const auto address = reinterpret_cast<uint64_t>(&counter);
const auto low = static_cast<uint32_t>(address);
const auto high = static_cast<uint32_t>(address >> 32u);
std::array<uint32_t, 11> packet{};
auto *const end = packet.data() + packet.size();
CommandBufferLayout acb{packet.data(), end, packet.data(), end, nullptr, nullptr, 0};
// A packet header in unwritten DWORDs shows the CP skips the signal's pad by its length.
constexpr uint32_t poison = 0xc0023900u;
const auto emit = [&](uint8_t signal_type, uint8_t op) {
packet.fill(poison);
acb.cursor_up = packet.data();
return Gen5::AgcAcbMemSemaphore(reinterpret_cast<Gen5::CommandBuffer *>(&acb), &counter, 1,
signal_type, op) == packet.data() &&
acb.cursor_up == end;
};
Require(name, "wait stream",
emit(0, 7) &&
packet == decltype(packet){0xc0027904u, 0x342u, 0xc9020000u | (high & 0xffffu), low,
0xc0023900u, low & ~0x7u, high, 0xe0010000u,
0xc0017904u, 0x342u, 0xc9000000u} &&
Gen5::AgcIsInternalDataPacket(packet[0], packet.data() + 1) &&
Gen5::AgcIsInternalDataPacket(packet[8], packet.data() + 9),
"semaphore wait differs from the native stream");
const auto wait = packet;
Pm4Execution waiting;
Require(name, "empty wait",
processor.Process(waiting, wait) == Pm4ProcessResult::Blocked &&
processor.Process(waiting, wait) == Pm4ProcessResult::Blocked &&
!waiting.MadeProgress() && counter == 0,
"semaphore wait passed or advanced on a zero counter");
Require(name, "increment stream",
emit(0, 6) &&
packet == decltype(packet){0xc0027904u, 0x342u, 0xc9010000u | (high & 0xffffu), low,
0xc0011000u, poison, poison,
0xc0023900u, low & ~0x7u, high, 0xc0010000u} &&
Gen5::AgcIsInternalDataPacket(packet[0], packet.data() + 1) &&
execute(packet) && counter == 1 && execute(packet) && counter == 2,
"semaphore increment differs from the native stream or count");
Require(name, "resume",
processor.Process(waiting, wait) == Pm4ProcessResult::Complete && counter == 1,
"semaphore wait did not consume one count after a signal");
counter = 5;
Require(name, "write",
emit(1, 6) && packet[10] == 0xc0110000u && execute(packet) && counter == 1,
"semaphore write signal did not set the counter to one");
std::printf("[host] %-32s ok\n", name);
}
void CheckPm4RewindResume(RenderContext &renderer) {
constexpr const char *name = "Pm4RewindResume";
Loader::SymbolDatabase symbols;
@@ -52681,6 +52750,7 @@ int main(int argc, char **argv) {
CheckPm4AcquireMemPackets(vulkan.RuntimeRenderer());
CheckPm4WaitPackets(vulkan.RuntimeRenderer());
CheckPm4WaitResume(vulkan.RuntimeRenderer());
CheckPm4MemSemaphore(vulkan.RuntimeRenderer());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--pm4-draw-multi-only") == 0) {
@@ -52989,6 +53059,7 @@ int main(int argc, char **argv) {
CheckPm4ContextStateOperations(vulkan.RuntimeRenderer());
CheckPm4IndirectControlFlow(vulkan.RuntimeRenderer());
CheckPm4WaitResume(vulkan.RuntimeRenderer());
CheckPm4MemSemaphore(vulkan.RuntimeRenderer());
CheckPm4RewindResume(vulkan.RuntimeRenderer());
CheckEmbeddedFetchVertexOffset();
CheckEmbeddedFetchLaneSpill();