From 1b70938052c8de0064a6e42ad184d6a1d4a1efda Mon Sep 17 00:00:00 2001 From: Claxten Date: Tue, 27 Oct 2026 00:36:40 +0100 Subject: [PATCH] agc: emit async memory semaphores and execute them in the CP --- .../command_processor/commandProcessor.h | 1 + .../command_processor/pm4Dispatch.cpp | 1 + .../guest_gpu/command_processor/pm4Dispatch.h | 1 + .../command_processor/pm4Handlers.cpp | 15 ++++ src/graphics/guest_gpu/graphicsRun.cpp | 10 +++ src/libs/agc.cpp | 44 +++++++++++- src/libs/agc.h | 2 + src/libs/libAgcDriver.cpp | 1 + tests/ShaderRecompilerComputeTests.cpp | 71 +++++++++++++++++++ 9 files changed, 144 insertions(+), 2 deletions(-) diff --git a/src/graphics/guest_gpu/command_processor/commandProcessor.h b/src/graphics/guest_gpu/command_processor/commandProcessor.h index 382608bf2..dacc3eeb9 100644 --- a/src/graphics/guest_gpu/command_processor/commandProcessor.h +++ b/src/graphics/guest_gpu/command_processor/commandProcessor.h @@ -98,6 +98,7 @@ public: template 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, diff --git a/src/graphics/guest_gpu/command_processor/pm4Dispatch.cpp b/src/graphics/guest_gpu/command_processor/pm4Dispatch.cpp index 84d949fe8..fec9b6de9 100644 --- a/src/graphics/guest_gpu/command_processor/pm4Dispatch.cpp +++ b/src/graphics/guest_gpu/command_processor/pm4Dispatch.cpp @@ -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; diff --git a/src/graphics/guest_gpu/command_processor/pm4Dispatch.h b/src/graphics/guest_gpu/command_processor/pm4Dispatch.h index bcc9aa57f..8efd99be2 100644 --- a/src/graphics/guest_gpu/command_processor/pm4Dispatch.h +++ b/src/graphics/guest_gpu/command_processor/pm4Dispatch.h @@ -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); diff --git a/src/graphics/guest_gpu/command_processor/pm4Handlers.cpp b/src/graphics/guest_gpu/command_processor/pm4Handlers.cpp index 06a408e0e..42aadd569 100644 --- a/src/graphics/guest_gpu/command_processor/pm4Handlers.cpp +++ b/src/graphics/guest_gpu/command_processor/pm4Handlers.cpp @@ -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( + (buffer[0] & ~0x7u) | (static_cast(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(); diff --git a/src/graphics/guest_gpu/graphicsRun.cpp b/src/graphics/guest_gpu/graphicsRun.cpp index f52d24af4..b429a91b5 100644 --- a/src/graphics/guest_gpu/graphicsRun.cpp +++ b/src/graphics/guest_gpu/graphicsRun.cpp @@ -281,6 +281,16 @@ template void CommandProcessor::WaitRegMem(uint32_t, const uint32_t*, template void CommandProcessor::WaitRegMem(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); diff --git a/src/libs/agc.cpp b/src/libs/agc.cpp index 2b6576f17..c3aa0e58d 100644 --- a/src/libs/agc.cpp +++ b/src/libs/agc.cpp @@ -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(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((address_value >> 32u) & 0xffffu); + cmd[3] = static_cast(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(address_value) & ~0x7u; + semaphore[2] = static_cast(address_value >> 32u); + semaphore[3] = (static_cast(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); } diff --git a/src/libs/agc.h b/src/libs/agc.h index ac4337c9d..8cbcab88d 100644 --- a/src/libs/agc.h +++ b/src/libs/agc.h @@ -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, diff --git a/src/libs/libAgcDriver.cpp b/src/libs/libAgcDriver.cpp index 02b40351d..d7d12794d 100644 --- a/src/libs/libAgcDriver.cpp +++ b/src/libs/libAgcDriver.cpp @@ -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); diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 04c4875c7..df50c09da 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -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(&Gen5::AgcAcbMemSemaphore), + "async semaphore import did not resolve to the emitter"); + + GraphicsInitJmpTables(); + CommandProcessor processor(renderer, 0x20); + const auto execute = [&](std::span commands) { + Pm4Execution execution; + return processor.Process(execution, commands) == Pm4ProcessResult::Complete; + }; + + alignas(8) uint64_t counter = 0; + const auto address = reinterpret_cast(&counter); + const auto low = static_cast(address); + const auto high = static_cast(address >> 32u); + std::array 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(&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();