From 9649cf4e712b5fb68a863068190e729d57ccdaca Mon Sep 17 00:00:00 2001 From: GTTeancum Date: Sat, 5 Sep 2026 23:51:13 -0400 Subject: [PATCH] perf(vu): reuse XGKICK packet storage between transfers --- ps2xRuntime/src/lib/vu/ps2_vu1_core.cpp | 7 +++- ps2xTest/src/ps2_vu1_tests.cpp | 49 +++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/ps2xRuntime/src/lib/vu/ps2_vu1_core.cpp b/ps2xRuntime/src/lib/vu/ps2_vu1_core.cpp index bd75bba31..37bd2cee1 100644 --- a/ps2xRuntime/src/lib/vu/ps2_vu1_core.cpp +++ b/ps2xRuntime/src/lib/vu/ps2_vu1_core.cpp @@ -932,7 +932,12 @@ void VU1Interpreter::startXgkick(uint32_t qwordAddress) return; const uint32_t sourceAddress = (qwordAddress * 16u) % m_activeVuDataSize; - m_xgkick = {}; + // Only the copied prefix is read; each qword is overwritten before publication. + // Keep the packet storage instead of clearing and copying a 64 KiB temporary. + m_xgkick.totalBytes = 0u; + m_xgkick.copiedBytes = 0u; + m_xgkick.currentTagEnd = 0u; + m_xgkick.currentTagEop = false; m_xgkick.active = true; m_xgkick.sourceAddress = sourceAddress; m_xgkick.cycleCredit = 1u; // XGKICK's issue cycle counts toward PATH1. diff --git a/ps2xTest/src/ps2_vu1_tests.cpp b/ps2xTest/src/ps2_vu1_tests.cpp index 0754b69fc..de4fd35ce 100644 --- a/ps2xTest/src/ps2_vu1_tests.cpp +++ b/ps2xTest/src/ps2_vu1_tests.cpp @@ -1311,6 +1311,55 @@ void register_ps2_vu1_tests() "XGKICK resource stalls should consume VU cycles"); }); + tc.Run("XGKICK reuses storage across long short and growing packets", [](TestCase &t) + { + Vu1Fixture fx; + t.IsTrue(fx.initialize(), "VU1 fixture should initialize"); + if (!fx.code || !fx.data) return; + const uint32_t addresses[] = {0u, 8192u, 12288u}; + const uint32_t sizes[] = {4096u, 32u, 80u}; + const std::vector expectedCycles{511u, 514u, 523u}; + std::vector> expected, captured; + std::vector capturedCycles; + for (uint32_t i = 0; i < 3u; ++i) + { + expected.emplace_back(sizes[i], 0u); + auto &packet = expected.back(); + const uint64_t tag = makeGifTag(static_cast(sizes[i] / 16u - 1u), GIF_FMT_IMAGE, 0u, true); + std::memcpy(packet.data(), &tag, sizeof(tag)); + for (uint32_t byte = 16u; byte < sizes[i]; ++byte) + packet[byte] = static_cast(byte * 13u + i * 71u); + std::memcpy(fx.data + addresses[i], packet.data(), packet.size()); + writeVuInstructionPair(fx.code, i * 8u, makeVuLowerSpecial(0x6Cu, static_cast(i + 1u)), kVuUpperNop); + } + writeVuInstructionPair(fx.code, 24u, 0u, kVuUpperNop | 0x40000000u); + writeVuInstructionPair(fx.code, 32u, 0u, kVuUpperNop); + VU1Interpreter *active = nullptr; + fx.mem.setGifPacketCallback([&](const uint8_t *packet, uint32_t size) + { + captured.emplace_back(packet, packet + size); + capturedCycles.push_back(active->state().cycles); + }); + for (const uint32_t budget : {4096u, 1u, 3u, 8u, 64u}) + { + captured.clear(); + capturedCycles.clear(); + VU1Interpreter vu; + active = &vu; + for (uint32_t i = 0; i < 3u; ++i) + vu.state().vi[i + 1u] = static_cast(addresses[i] / 16u); + vu.execute(fx.code, PS2_VU1_CODE_SIZE, fx.data, PS2_VU1_DATA_SIZE, + fx.gs, &fx.mem, 0u, 0u, 0u, budget); + for (uint32_t calls = 0u; captured.size() < 3u && calls < 4096u; ++calls) + vu.resume(fx.code, PS2_VU1_CODE_SIZE, fx.data, PS2_VU1_DATA_SIZE, + fx.gs, &fx.mem, 0u, 0u, budget); + t.IsTrue(captured == expected, "Every packet must have exact new bytes and length, with no stale suffix"); + t.IsTrue(capturedCycles == expectedCycles, "Back-to-back transfers must retain their exact completion cycles"); + t.Equals(vu.state().cycles, uint64_t{523u}, "Slicing must not change final cycle count"); + active = nullptr; + } + }); + tc.Run("synthetic Code Veronica text packet preserves black-frame PATH1 data", [](TestCase &t) { PS2Memory mem;