From 5942b8c7f146d2af26aef7caa073ebcc228a3f7b Mon Sep 17 00:00:00 2001 From: TibixDev Date: Wed, 7 Oct 2026 18:08:57 +0300 Subject: [PATCH 1/3] perf(graphics): track draw register fingerprints incrementally Track mutations against the current draw dependency mask and preserve device and shader registry identity in the draw key. Validate mutations, projected masks, PM4 packets and prepared graphics against full recomputation. Written with AI assistance. --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 15 ++ .../include/Driver/Draw/DrawRegisterKey.hpp | 12 ++ .../Execution/include/QueueState.hpp | 78 +++++++++- .../src/Driver/Draw/DrawRegisterKey.cpp | 80 +++++++--- .../prx/libSceAgcDriver/tests/Graphics.cpp | 2 +- core/libs/prx/libSceAgcDriver/tests/Pm4.cpp | 3 + .../prx/libSceAgcDriver/tests/RegisterKey.cpp | 140 ++++++++++++++++++ 7 files changed, 301 insertions(+), 29 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawRegisterKey.hpp create mode 100644 core/libs/prx/libSceAgcDriver/tests/RegisterKey.cpp diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index d0bf61b2a..b1461d2ab 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -325,6 +325,21 @@ foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") endforeach() +add_test_executable(agc_driver_register_key_tests EXCLUDE_FROM_ALL tests/RegisterKey.cpp) +target_include_directories(agc_driver_register_key_tests PRIVATE + ${LIBS_INCLUDE_DIR} + ${CMAKE_SOURCE_DIR}/core/shader/recompiler + ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR} +) +target_link_libraries(agc_driver_register_key_tests PRIVATE libSceAgcDriver) +set_target_properties(agc_driver_register_key_tests PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(agc_driver_register_key_tests) +if(BUILD_TESTING) + set_target_properties(agc_driver_register_key_tests PROPERTIES EXCLUDE_FROM_ALL OFF) + add_test(NAME agc_driver_register_key COMMAND agc_driver_register_key_tests) + set_tests_properties(agc_driver_register_key PROPERTIES TIMEOUT 30 ENVIRONMENT "APS5_VERIFY_REGISTER_KEYS=1") +endif() + add_test_executable(agc_driver_tests EXCLUDE_FROM_ALL tests/Submit.cpp ) diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawRegisterKey.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawRegisterKey.hpp new file mode 100644 index 000000000..74a562c8b --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawRegisterKey.hpp @@ -0,0 +1,12 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_DRIVER_DRAW_DRAWREGISTERKEY_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_DRIVER_DRAW_DRAWREGISTERKEY_HPP + +#include "prx/libSceAgcDriver/Execution/include/QueueState.hpp" + +namespace AgcDriver::DriverDetail { + +std::uint64_t RegisterStateKey(const QueueState& queue, bool allUserWords = false, bool incremental = true); + +} + +#endif diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp index 4e9743773..f270c9451 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -23,6 +24,29 @@ public: using mapped_type = std::uint32_t; using value_type = std::pair; + class Mask { + public: + explicit Mask(std::vector bits) : bits(std::move(bits)) {} + bool Contains(std::uint32_t offset) const { return offset / 64 < bits.size() && ((bits[offset / 64] >> (offset % 64)) & 1u) != 0; } + private: + std::vector bits; + }; + + class Reference { + public: + Reference(Registers& owner, std::uint32_t offset) : owner(owner), offset(offset) {} + operator std::uint32_t() const { return owner.values[offset]; } + Reference& operator=(std::uint32_t value) { owner.insert_or_assign(offset, value); return *this; } + Reference& operator=(const Reference& other) { return *this = static_cast(other); } + Reference& operator|=(std::uint32_t value) { return *this = static_cast(*this) | value; } + Reference& operator&=(std::uint32_t value) { return *this = static_cast(*this) & value; } + Reference& operator^=(std::uint32_t value) { return *this = static_cast(*this) ^ value; } + Reference& operator+=(std::uint32_t value) { return *this = static_cast(*this) + value; } + private: + Registers& owner; + std::uint32_t offset; + }; + class const_iterator { public: using iterator_category = std::forward_iterator_tag; @@ -57,6 +81,18 @@ public: using iterator = const_iterator; Registers() = default; + Registers(const Registers&) = default; + Registers& operator=(const Registers&) = default; + Registers(Registers&& other) noexcept : values(std::move(other.values)), present(std::move(other.present)), entries(std::exchange(other.entries, 0)), mask(std::move(other.mask)), fingerprint(std::exchange(other.fingerprint, 0)) {} + Registers& operator=(Registers&& other) noexcept { + if (this == &other) return *this; + values = std::move(other.values); + present = std::move(other.present); + entries = std::exchange(other.entries, 0); + mask = std::move(other.mask); + fingerprint = std::exchange(other.fingerprint, 0); + return *this; + } Registers(std::initializer_list> entries) { for (const auto& [offset, value] : entries) emplace(offset, value); } @@ -83,27 +119,33 @@ public: values.clear(); present.clear(); entries = 0; + fingerprint = 0; } std::pair emplace(std::uint32_t offset, std::uint32_t value) { if (contains(offset)) return {const_iterator{this, offset}, false}; mark(offset) = value; + changeFingerprint(offset, value); return {const_iterator{this, offset}, true}; } std::pair insert_or_assign(std::uint32_t offset, std::uint32_t value) { if (contains(offset)) { + if (values[offset] == value) return {const_iterator{this, offset}, false}; + changeFingerprint(offset, values[offset]); values[offset] = value; + changeFingerprint(offset, value); return {const_iterator{this, offset}, false}; } mark(offset) = value; + changeFingerprint(offset, value); return {const_iterator{this, offset}, true}; } - std::uint32_t& operator[](std::uint32_t offset) { - if (contains(offset)) return values[offset]; - return mark(offset) = 0; + Reference operator[](std::uint32_t offset) { + if (!contains(offset)) emplace(offset, 0); + return {*this, offset}; } - std::uint32_t& at(std::uint32_t offset) { + Reference at(std::uint32_t offset) { if (!contains(offset)) throw std::out_of_range("register is not set"); - return values[offset]; + return {*this, offset}; } const std::uint32_t& at(std::uint32_t offset) const { if (!contains(offset)) throw std::out_of_range("register is not set"); @@ -111,6 +153,7 @@ public: } std::size_t erase(std::uint32_t offset) { if (!contains(offset)) return 0; + changeFingerprint(offset, values[offset]); present[offset / 64] &= ~(std::uint64_t{1} << (offset % 64)); --entries; return 1; @@ -123,8 +166,31 @@ public: } return a == end() && b == other.end(); } + std::uint64_t Fingerprint(const std::shared_ptr& selected) const { + if (mask != selected) { + fingerprint = RecomputeFingerprint(*selected); + mask = selected; + } + return fingerprint; + } + std::uint64_t RecomputeFingerprint(const Mask& selected) const { + std::uint64_t result = 0; + for (const auto [offset, value] : *this) { + if (selected.Contains(offset)) result ^= contribution(offset, value); + } + return result; + } private: + static std::uint64_t contribution(std::uint32_t offset, std::uint32_t value) { + auto hash = ((static_cast(offset) << 32u) | value) + 0x9e3779b97f4a7c15ull; + hash = (hash ^ (hash >> 30u)) * 0xbf58476d1ce4e5b9ull; + hash = (hash ^ (hash >> 27u)) * 0x94d049bb133111ebull; + return hash ^ (hash >> 31u); + } + void changeFingerprint(std::uint32_t offset, std::uint32_t value) { + if (mask != nullptr && mask->Contains(offset)) fingerprint ^= contribution(offset, value); + } static constexpr std::size_t End = ~std::size_t{0}; std::uint32_t& mark(std::uint32_t offset) { if (offset >= values.size()) { @@ -147,6 +213,8 @@ private: std::vector values; std::vector present; std::size_t entries = 0; + mutable std::shared_ptr mask; + mutable std::uint64_t fingerprint = 0; }; inline Registers BuildInitialContextRegisters() { diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp index 3612fb3c3..c19e71439 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp @@ -1,4 +1,5 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawRegisterKey.hpp" #include #include #include @@ -6,36 +7,69 @@ namespace AgcDriver::DriverDetail { +namespace { + +bool runtimeRegister(std::uint32_t offset) { + return offset - 0x00cu < 32u || offset - 0x08cu < 32u || offset - 0x10cu < 32u || offset == 0x082u || offset == 0x083u || offset == 0x102u || offset == 0x103u; +} + +const auto& registerMasks() { + static const auto masks = [] { + std::array, 3> bits; + for (const auto& range : Graphics::DrawKeyRegisters) { + auto& bank = bits[static_cast(range.bank)]; + bank.resize(std::max(bank.size(), (static_cast(range.first) + range.count + 63) / 64)); + for (auto offset = range.first; offset < range.first + range.count; ++offset) { + if (range.bank == Graphics::RegisterBank::Shader && runtimeRegister(offset)) continue; + bank[offset / 64] |= std::uint64_t{1} << (offset % 64); + } + } + std::array, 3> result; + for (std::size_t i = 0; i < bits.size(); ++i) result[i] = std::make_shared(std::move(bits[i])); + return result; + }(); + return masks; +} + +} + +std::uint64_t RegisterStateKey(const QueueState& queue, bool allUserWords, bool incremental) { + const auto& masks = registerMasks(); + const auto fingerprint = [&](const Registers& registers, Graphics::RegisterBank bank) { + const auto& mask = masks[static_cast(bank)]; + return incremental ? registers.Fingerprint(mask) : registers.RecomputeFingerprint(*mask); + }; + auto shape = (0xcbf29ce484222325ull ^ fingerprint(queue.context, Graphics::RegisterBank::Context)) * 0x100000001b3ull; + shape = (shape ^ fingerprint(queue.shader, Graphics::RegisterBank::Shader)) * 0x100000001b3ull; + shape = (shape ^ fingerprint(queue.userConfig, Graphics::RegisterBank::UserConfig)) * 0x100000001b3ull; + auto exact = shape; + const auto words = [&](std::uint32_t base, std::uint32_t count) { + for (auto it = queue.shader.lower_bound(base); it != queue.shader.end() && it->first < base + count; ++it) { + exact = (exact ^ it->first) * 0x100000001b3ull; + exact = (exact ^ it->second) * 0x100000001b3ull; + } + }; + for (const auto base : {0x00cu, 0x08cu, 0x10cu}) { + const auto resources = queue.shader.find(base - 1); + const auto count = resources == queue.shader.end() ? 0u : ((resources->second >> 1u) & 0x1fu) | (((resources->second >> 27u) & 1u) << 5u); + words(base, allUserWords ? 32u : std::min(count, 32u)); + } + words(0x082u, 2); + words(0x102u, 2); + return exact; +} + + std::uint64_t Driver::drawRegisterKey(const QueueState& queue, const ShaderRegistry& registry, std::uint64_t deviceSerial) { static const bool allUserWords = std::getenv("APS5_DRAW_KEY_ALL_USER_WORDS") != nullptr; - std::uint64_t key = 0xcbf29ce484222325ull; + static const bool verify = std::getenv("APS5_VERIFY_REGISTER_KEYS") != nullptr; + auto key = RegisterStateKey(queue, allUserWords); + if (verify && key != RegisterStateKey(queue, allUserWords, false)) throw std::runtime_error("incremental register fingerprint disagrees with full state"); const auto mix = [&](std::uint64_t value) { key ^= value; key *= 0x100000001b3ull; }; - const auto userEnd = [&](std::uint32_t base) { - if (allUserWords) return base + 32u; - const auto resources = queue.shader.find(base - 1); - if (resources == queue.shader.end()) return base; - const auto count = ((resources->second >> 1u) & 0x1fu) | (((resources->second >> 27u) & 1u) << 5u); - return base + std::min(count, 32u); - }; - const std::array, 3> users{{{0x00cu, userEnd(0x00cu)}, {0x08cu, userEnd(0x08cu)}, {0x10cu, userEnd(0x10cu)}}}; - const auto unread = [&](std::uint32_t offset) { - return std::any_of(users.begin(), users.end(), [&](const auto& user) { return offset >= user.second && offset < user.first + 32u; }); - }; mix(deviceSerial); - for (const auto& range : Graphics::DrawKeyRegisters) { - const auto& bank = range.bank == Graphics::RegisterBank::Context ? queue.context : range.bank == Graphics::RegisterBank::Shader ? queue.shader : queue.userConfig; - mix((static_cast(range.bank) << 32u) | range.first); - const auto end = range.first + range.count; - const bool shader = range.bank == Graphics::RegisterBank::Shader; - for (auto it = bank.lower_bound(range.first); it != bank.end() && it->first < end; ++it) { - if (shader && unread(it->first)) continue; - mix(it->first); - mix(it->second); - } - } for (const auto base : {0x008u, 0x088u, 0x0c8u, 0x108u, 0x148u}) { const auto low = queue.shader.find(base); const auto high = queue.shader.find(base + 1); diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index a6c4230d0..78598011e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -2378,7 +2378,7 @@ void AlphaToCoverageTests() { Require(AgcDriver::Graphics::DrawRejection(queue, false).find("alpha-to-coverage") != std::string::npos, "the precheck accepted unsupported alpha-to-coverage"); } queue.context[0x2dc] = 0xaa01; - const auto exportFormat = queue.context.at(0x1c5); + const std::uint32_t exportFormat = queue.context.at(0x1c5); for (const auto format : {4u, 5u, 6u, 9u}) { queue.context[0x1c5] = (exportFormat & ~0xfu) | format; Require(AgcDriver::Graphics::DecodeState(queue).alphaToCoverage, "alpha-to-coverage rejected an MRT0 export format with a floating-point alpha"); diff --git a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp index 5c6394789..490ba22a3 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp @@ -2,6 +2,7 @@ #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawRegisterKey.hpp" #include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" #include "prx/libSceAgcDriver/Submit/include/Acb.hpp" #include "prx/libc/include/Shutdown.hpp" @@ -50,8 +51,10 @@ std::uint32_t low(const void* pointer) { return static_cast(reint std::uint32_t high(const void* pointer) { return static_cast(reinterpret_cast(pointer) >> 32u); } void execute(AgcDriver::QueueState& state, const std::vector& packet) { + AgcDriver::DriverDetail::RegisterStateKey(state); AgcDriver::Pm4::Validate(packet, 0); AgcDriver::Pm4::Execute(packet, state); + check(AgcDriver::DriverDetail::RegisterStateKey(state) == AgcDriver::DriverDetail::RegisterStateKey(state, false, false), "PM4 changed registers without updating their fingerprint"); } void testCatalog() { diff --git a/core/libs/prx/libSceAgcDriver/tests/RegisterKey.cpp b/core/libs/prx/libSceAgcDriver/tests/RegisterKey.cpp new file mode 100644 index 000000000..448d3455a --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/RegisterKey.cpp @@ -0,0 +1,140 @@ +#include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawRegisterKey.hpp" +#include "prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp" +#include "prx/libSceAgcDriver/Graphics/include/State.hpp" +#include +#include +#include +#include +#include +#include + +using namespace AgcDriver; +using namespace AgcDriver::DriverDetail; + +namespace { + +void check(bool condition, const char* message) { + if (!condition) throw std::runtime_error(message); +} + +std::uint64_t baselineRegisterStateKey(const QueueState& queue, bool allUserWords = false) { + std::uint64_t key = 0xcbf29ce484222325ull; + const auto mix = [&](std::uint64_t value) { + key ^= value; + key *= 0x100000001b3ull; + }; + const auto userEnd = [&](std::uint32_t base) { + if (allUserWords) return base + 32u; + const auto resources = queue.shader.find(base - 1); + if (resources == queue.shader.end()) return base; + const auto count = ((resources->second >> 1u) & 0x1fu) | (((resources->second >> 27u) & 1u) << 5u); + return base + std::min(count, 32u); + }; + const std::array, 3> users{{{0x00cu, userEnd(0x00cu)}, {0x08cu, userEnd(0x08cu)}, {0x10cu, userEnd(0x10cu)}}}; + const auto unread = [&](std::uint32_t offset) { + return std::any_of(users.begin(), users.end(), [&](const auto& user) { return offset >= user.second && offset < user.first + 32u; }); + }; + for (const auto& range : Graphics::DrawKeyRegisters) { + const auto& bank = range.bank == Graphics::RegisterBank::Context ? queue.context : range.bank == Graphics::RegisterBank::Shader ? queue.shader : queue.userConfig; + mix((static_cast(range.bank) << 32u) | range.first); + const auto end = range.first + range.count; + const bool shader = range.bank == Graphics::RegisterBank::Shader; + for (auto it = bank.lower_bound(range.first); it != bank.end() && it->first < end; ++it) { + if (shader && unread(it->first)) continue; + mix(it->first); + mix(it->second); + } + } + return key; +} + +void testFingerprints() { + static_assert(!std::is_convertible_v); + std::mt19937 random(42); + QueueState queue; + auto reference = baselineRegisterStateKey(queue); + auto tracked = RegisterStateKey(queue); + const auto compare = [&] { + const auto expected = baselineRegisterStateKey(queue); + const auto actual = RegisterStateKey(queue); + check(actual == RegisterStateKey(queue, false, false), "incremental fingerprint missed a mutation"); + check(RegisterStateKey(queue, true) == RegisterStateKey(queue, true, false), "all-user fingerprint missed a mutation"); + check((expected == reference) == (actual == tracked), "exact key changed its dependency set"); + reference = expected; + tracked = actual; + }; + for (const auto& range : Graphics::DrawKeyRegisters) { + auto& bank = range.bank == Graphics::RegisterBank::Context ? queue.context : range.bank == Graphics::RegisterBank::Shader ? queue.shader : queue.userConfig; + for (auto offset = range.first; offset < range.first + range.count; ++offset) { + bank.erase(offset); compare(); + bank.emplace(offset, 0); compare(); + bank.insert_or_assign(offset, 0); compare(); + bank[offset] = random(); compare(); + bank.at(offset) ^= 1; compare(); + } + } + for (std::uint32_t i = 0; i < 30000; ++i) { + auto& bank = i % 3 == 0 ? queue.context : i % 3 == 1 ? queue.shader : queue.userConfig; + const auto offset = random() % 0x500; + switch (i % 11) { + case 0: bank.erase(offset); break; + case 1: bank.emplace(offset, random()); break; + case 2: bank[offset] |= random(); break; + case 3: bank[offset] &= random(); break; + case 6: bank[offset] += random(); break; + case 4: { + auto retained = bank[offset]; + bank.insert_or_assign(0x1000, random()); + compare(); + retained = random(); + break; + } + case 5: { + auto copy = bank; + copy.insert_or_assign(offset, random()); + compare(); + bank = copy; + break; + } + default: bank.insert_or_assign(offset, random()); break; + } + compare(); + } + queue.shader[0x8b] = 0; compare(); + queue.shader[0x8c] = random(); compare(); + queue.shader[0x8b] = 2; compare(); + queue.shader[0x8b] = 1u << 27u; compare(); + queue.shader.erase(0x8b); compare(); + queue.savedContext = queue.context; + queue.ClearContext(); compare(); + queue.context = *queue.savedContext; compare(); + auto moved = std::move(queue.context); + check(queue.context.empty(), "moved register bank retained its population"); + queue.context.emplace(0x200, 1); compare(); + queue.context = std::move(moved); compare(); + queue.context = queue.context; compare(); + queue.context.clear(); compare(); + queue.context.emplace(0x200, 0); compare(); + queue = QueueState{}; compare(); + Registers registers{{0, 7}, {64, 9}}; + auto low = std::make_shared(std::vector{1}); + auto high = std::make_shared(std::vector{0, 1}); + const auto lowHash = registers.Fingerprint(low); + check(registers.Fingerprint(high) != lowHash && registers.Fingerprint(low) == lowHash, "projection switch reused another mask's hash"); + registers[64] = 8; + check(registers.Fingerprint(low) == lowHash && registers.Fingerprint(high) == registers.RecomputeFingerprint(*high), "projection switch lost a masked write"); +} + + +} + +int main() { + try { + testFingerprints(); + std::puts("Register fingerprint dependencies and mutations passed"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; + } +} From 89414d30391bad3d59dc249e3762e775022c4694 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Tue, 13 Oct 2026 12:43:16 +0200 Subject: [PATCH 2/3] build(agc): link register key test with libc and libkernel like its sibling tests The Windows build failed to start it (0xc0000135, DLL not found). --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index b1461d2ab..fd7b3c4c5 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -331,7 +331,11 @@ target_include_directories(agc_driver_register_key_tests PRIVATE ${CMAKE_SOURCE_DIR}/core/shader/recompiler ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR} ) -target_link_libraries(agc_driver_register_key_tests PRIVATE libSceAgcDriver) +target_link_libraries(agc_driver_register_key_tests PRIVATE + libSceAgcDriver + libc + libkernel +) set_target_properties(agc_driver_register_key_tests PROPERTIES CXX_EXTENSIONS OFF) configure_windows_unwind(agc_driver_register_key_tests) if(BUILD_TESTING) From dd49e1df93b2a84ca98a9b86d47e2556b691eb4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Tue, 13 Oct 2026 14:49:31 +0200 Subject: [PATCH 3/3] build(agc): put libc's directory on PATH for the register key test on Windows The sibling AGC driver tests get this through the agcTest loop; without it the test exits 0xc0000135 (DLL not found) on Windows. --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index fd7b3c4c5..852b8659c 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -342,6 +342,10 @@ if(BUILD_TESTING) set_target_properties(agc_driver_register_key_tests PROPERTIES EXCLUDE_FROM_ALL OFF) add_test(NAME agc_driver_register_key COMMAND agc_driver_register_key_tests) set_tests_properties(agc_driver_register_key PROPERTIES TIMEOUT 30 ENVIRONMENT "APS5_VERIFY_REGISTER_KEYS=1") + if(WIN32) + set_tests_properties(agc_driver_register_key PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() add_test_executable(agc_driver_tests EXCLUDE_FROM_ALL