From 1831b61ba1a3cce36c362d80afaa896b10d5de61 Mon Sep 17 00:00:00 2001 From: Tamalero <11452991+Tamalero@users.noreply.github.com> Date: Wed, 28 Oct 2026 02:12:08 -0700 Subject: [PATCH 1/3] perf(agc): link pipelines with link-time optimization on half the hardware threads Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 17 +++- .../Graphics/src/PipelineLibrary.cpp | 31 ++++++-- .../tests/PipelineLibraryWorkers.cpp | 78 +++++++++++++++++++ docs/dev/ENVIRONMENT.md | 1 + 4 files changed, 118 insertions(+), 9 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/PipelineLibraryWorkers.cpp diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index c79e5351ca32..6db48b1f3355 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -1350,7 +1350,7 @@ if(BUILD_TESTING) set_target_properties(agc_pipeline_queue_failure_tests PROPERTIES EXCLUDE_FROM_ALL OFF) foreach(mode wait clear stop) add_test(NAME agc_pipeline_queue_failure_${mode} COMMAND agc_pipeline_queue_failure_tests ${mode}) - set_tests_properties(agc_pipeline_queue_failure_${mode} PROPERTIES TIMEOUT 15) + set_tests_properties(agc_pipeline_queue_failure_${mode} PROPERTIES TIMEOUT 15 ENVIRONMENT "APS5_PIPELINE_LTO_THREADS=1") if(WIN32) set_tests_properties(agc_pipeline_queue_failure_${mode} PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") @@ -1358,6 +1358,21 @@ if(BUILD_TESTING) endforeach() endif() +add_test_executable(agc_pipeline_library_workers_tests EXCLUDE_FROM_ALL tests/PipelineLibraryWorkers.cpp Graphics/src/PipelineLibrary.cpp) +target_include_directories(agc_pipeline_library_workers_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/core/shader/recompiler ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR}) +target_link_libraries(agc_pipeline_library_workers_tests PRIVATE libSceAgcDriver libc) +set_target_properties(agc_pipeline_library_workers_tests PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(agc_pipeline_library_workers_tests) +if(BUILD_TESTING) + set_target_properties(agc_pipeline_library_workers_tests PROPERTIES EXCLUDE_FROM_ALL OFF) + add_test(NAME agc_pipeline_library_workers COMMAND agc_pipeline_library_workers_tests) + set_tests_properties(agc_pipeline_library_workers PROPERTIES TIMEOUT 15 ENVIRONMENT "APS5_PIPELINE_LTO_THREADS=2") + if(WIN32) + set_tests_properties(agc_pipeline_library_workers PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + add_test_executable(agc_pass_hazards_tests EXCLUDE_FROM_ALL tests/PassHazards.cpp Graphics/src/PassHazards.cpp) target_include_directories(agc_pass_hazards_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR}) target_link_libraries(agc_pass_hazards_tests PRIVATE libc) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp index 0ccd993ac27f..2a5cd9331fb1 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp @@ -1,6 +1,7 @@ #include "prx/libSceAgcDriver/Graphics/include/PipelineLibrary.hpp" #include "prx/libSceAgcDriver/Graphics/include/PipelineJournal.hpp" #include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp" +#include #include #include #include @@ -12,6 +13,7 @@ #include #include #include +#include namespace AgcDriver::Graphics { @@ -55,12 +57,21 @@ struct LibraryStore { std::deque>> jobs; std::map pending; std::map activeBuilds; - std::thread worker; + std::vector workers; + std::size_t waitingWorkers = 0; bool stopping = false; }; std::atomic runningWorkers{0}; +std::size_t WorkerLimit() { + static const std::size_t limit = [] { + const char* text = std::getenv("APS5_PIPELINE_LTO_THREADS"); + return text != nullptr ? std::max(1, std::strtoul(text, nullptr, 10)) : std::max(2, std::thread::hardware_concurrency() / 2); + }(); + return limit; +} + LibraryStore& Libraries() { static auto* store = new LibraryStore(); return *store; @@ -69,7 +80,9 @@ LibraryStore& Libraries() { void runJobs(LibraryStore& store) { std::unique_lock lock(store.mutex); for (;;) { + ++store.waitingWorkers; store.work.wait(lock, [&] { return !store.jobs.empty() || store.stopping; }); + --store.waitingWorkers; if (store.stopping) break; auto [device, job] = std::move(store.jobs.front()); store.jobs.pop_front(); @@ -83,8 +96,8 @@ void runJobs(LibraryStore& store) { void enqueue(LibraryStore& store, VkDevice device, std::function job) { if (store.stopping) return; - if (!store.worker.joinable()) { - store.worker = std::thread(runJobs, std::ref(store)); + if (store.waitingWorkers == 0 && store.workers.size() < WorkerLimit()) { + store.workers.emplace_back(runJobs, std::ref(store)); runningWorkers.fetch_add(1, std::memory_order_acq_rel); } auto& pending = store.pending[device]; @@ -365,19 +378,21 @@ void ClearPipelineLibraries(VkDevice device) { void StopPipelineLibraryWorker() { auto& store = Libraries(); - std::thread worker; + std::vector workers; { std::lock_guard lock(store.mutex); store.stopping = true; for (const auto& [device, job] : store.jobs) --store.pending[device]; store.jobs.clear(); - worker = std::move(store.worker); + workers = std::move(store.workers); } store.idle.notify_all(); store.work.notify_all(); - if (!worker.joinable()) return; - if (worker.get_id() == std::this_thread::get_id()) worker.detach(); - else worker.join(); + for (auto& worker : workers) { + if (!worker.joinable()) continue; + if (worker.get_id() == std::this_thread::get_id()) worker.detach(); + else worker.join(); + } } unsigned PipelineLibraryWorkers() { diff --git a/core/libs/prx/libSceAgcDriver/tests/PipelineLibraryWorkers.cpp b/core/libs/prx/libSceAgcDriver/tests/PipelineLibraryWorkers.cpp new file mode 100644 index 000000000000..8ee91bccacca --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/PipelineLibraryWorkers.cpp @@ -0,0 +1,78 @@ +#include "prx/libSceAgcDriver/Graphics/include/PipelineLibrary.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace AgcDriver::Graphics; +using namespace std::chrono_literals; + +namespace { + +void Check(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "FAIL: %s\n", message); + std::fflush(stderr); + std::_Exit(1); +} + +void Schedule(VkDevice device, std::function work) { + OptimizedPipeline pipeline; + pipeline.device = device; + pipeline.relink = std::move(work); + pipeline.deferred = true; + pipeline.Schedule(); +} + +} + +int main() { + const auto device = reinterpret_cast(std::uintptr_t{1}); + std::mutex mutex; + std::condition_variable changed; + bool entered = false; + bool released = false; + std::atomic completed{0}; + Schedule(device, [&] { + std::unique_lock lock(mutex); + entered = true; + changed.notify_all(); + changed.wait(lock, [&] { return released; }); + ++completed; + }); + { + std::unique_lock lock(mutex); + Check(changed.wait_for(lock, 3s, [&] { return entered; }), "the first worker did not start"); + } + + Schedule(device, [&] { + ++completed; + changed.notify_all(); + }); + { + std::unique_lock lock(mutex); + Check(changed.wait_for(lock, 3s, [&] { return completed == 1; }), "a job waited behind a held job with a second worker allowed"); + } + Check(PipelineLibraryWorkers() == 2, "the queue did not start a second worker"); + + Schedule(device, [&] { ++completed; }); + { + std::lock_guard lock(mutex); + released = true; + } + changed.notify_all(); + WaitForOptimizedPipelines(device); + Check(completed == 3, "a job was lost or ran twice"); + Check(PipelineLibraryWorkers() == 2, "the queue started more workers than allowed"); + + StopPipelineLibraryWorker(); + ClearPipelineLibraries(device); + Check(PipelineLibraryWorkers() == 0, "the pipeline workers did not stop"); + std::printf("two workers: a job finished while another was held, three jobs ran once each, both workers stopped\n"); + return 0; +} diff --git a/docs/dev/ENVIRONMENT.md b/docs/dev/ENVIRONMENT.md index af605afcfffd..c91a8b011ddd 100644 --- a/docs/dev/ENVIRONMENT.md +++ b/docs/dev/ENVIRONMENT.md @@ -376,6 +376,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_PIPELINE_JOURNAL_ENTRIES` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp` | 4096 | number of journal entries loaded at most; a longer journal keeps its last entries, the ones recorded last | | `APS5_PIPELINE_JOURNAL_MAX_MIB` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp` | 1024 | size cap in MiB on the pipeline journal; the file is rewritten down to three quarters of the cap when it is reached, keeping the newest pipelines and the modules they name and leaving room for later records | | `APS5_PIPELINE_JOURNAL_THREADS` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp` | | threads that replay the journal's pipelines, 0 disables the replay; unset uses half the hardware threads, at least one | +| `APS5_PIPELINE_LTO_THREADS` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp` | | threads that link graphics pipelines with link-time optimization in the background; unset uses half the hardware threads, at least two | | `APS5_PIPELINE_STATS` | string | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | captures and prints driver statistics for each new pipeline, requiring `VK_KHR_pipeline_executable_properties`; `1` enables | | `APS5_PIPELINE_STORE_ENTRIES` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp` | adaptive | fixes the LRU bound on the entries of the pipeline store, which otherwise starts at 1024 and doubles up to 4096 for a journal replay that does not fit and while evicted pipelines keep coming back; an entry is evicted only once no recorded draw holds it | | `APS5_PM4_RING` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/SubmissionRing.cpp` | 16 | number of submissions the PM4 ring keeps for dumps, from 0 to 4096; 0 disables the ring | From c151053cd0983b681258f9dd388f5804f15c6823 Mon Sep 17 00:00:00 2001 From: Tamalero <11452991+Tamalero@users.noreply.github.com> Date: Wed, 28 Oct 2026 02:36:49 -0700 Subject: [PATCH 2/3] fix(agc): reject an APS5_PIPELINE_LTO_THREADS that is not 1 through the hardware thread count Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 14 ++++++++++++-- .../Graphics/src/PipelineLibrary.cpp | 12 +++++++++++- .../tests/PipelineLibraryWorkers.cpp | 17 ++++++++++++++++- docs/dev/ENVIRONMENT.md | 2 +- 4 files changed, 40 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 6db48b1f3355..1bddaa7c0ae4 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -1367,9 +1367,19 @@ if(BUILD_TESTING) set_target_properties(agc_pipeline_library_workers_tests PROPERTIES EXCLUDE_FROM_ALL OFF) add_test(NAME agc_pipeline_library_workers COMMAND agc_pipeline_library_workers_tests) set_tests_properties(agc_pipeline_library_workers PROPERTIES TIMEOUT 15 ENVIRONMENT "APS5_PIPELINE_LTO_THREADS=2") + set(workerTests agc_pipeline_library_workers) + foreach(case negative=-1 text=abc zero=0 suffix=8x above_hardware=100000) + string(REPLACE "=" ";" parts "${case}") + list(GET parts 0 name) + list(GET parts 1 value) + add_test(NAME agc_pipeline_library_workers_invalid_${name} COMMAND agc_pipeline_library_workers_tests invalid) + set_tests_properties(agc_pipeline_library_workers_invalid_${name} PROPERTIES TIMEOUT 15 ENVIRONMENT "APS5_PIPELINE_LTO_THREADS=${value}") + list(APPEND workerTests agc_pipeline_library_workers_invalid_${name}) + endforeach() if(WIN32) - set_tests_properties(agc_pipeline_library_workers PROPERTIES ENVIRONMENT_MODIFICATION - "PATH=path_list_prepend:$") + foreach(test IN LISTS workerTests) + set_tests_properties(${test} PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + endforeach() endif() endif() diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp index 2a5cd9331fb1..665b7c17dad7 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp @@ -4,14 +4,18 @@ #include #include #include +#include #include #include +#include #include #include #include #include #include #include +#include +#include #include #include @@ -66,8 +70,14 @@ std::atomic runningWorkers{0}; std::size_t WorkerLimit() { static const std::size_t limit = [] { + const std::size_t hardware = std::max(1, std::thread::hardware_concurrency()); const char* text = std::getenv("APS5_PIPELINE_LTO_THREADS"); - return text != nullptr ? std::max(1, std::strtoul(text, nullptr, 10)) : std::max(2, std::thread::hardware_concurrency() / 2); + if (text == nullptr) return std::max(2, hardware / 2); + const char* end = text + std::strlen(text); + std::size_t value = 0; + const auto [last, error] = std::from_chars(text, end, value); + if (error != std::errc{} || last != end || value == 0 || value > hardware) throw std::runtime_error("APS5_PIPELINE_LTO_THREADS must be a whole number from 1 through " + std::to_string(hardware) + ", the hardware thread count"); + return value; }(); return limit; } diff --git a/core/libs/prx/libSceAgcDriver/tests/PipelineLibraryWorkers.cpp b/core/libs/prx/libSceAgcDriver/tests/PipelineLibraryWorkers.cpp index 8ee91bccacca..4895c240fc54 100644 --- a/core/libs/prx/libSceAgcDriver/tests/PipelineLibraryWorkers.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/PipelineLibraryWorkers.cpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include using namespace AgcDriver::Graphics; @@ -31,8 +33,21 @@ void Schedule(VkDevice device, std::function work) { } -int main() { +int main(int argc, char** argv) { const auto device = reinterpret_cast(std::uintptr_t{1}); + if (argc == 2 && std::string_view(argv[1]) == "invalid") { + bool rejected = false; + try { + Schedule(device, [] {}); + } catch (const std::runtime_error& error) { + rejected = std::string_view(error.what()).find("APS5_PIPELINE_LTO_THREADS") != std::string_view::npos; + } + Check(rejected, "an invalid worker count was accepted"); + Check(PipelineLibraryWorkers() == 0, "a worker started for an invalid worker count"); + std::printf("invalid worker count rejected before any worker started\n"); + return 0; + } + Check(argc == 1, "expected no argument or invalid"); std::mutex mutex; std::condition_variable changed; bool entered = false; diff --git a/docs/dev/ENVIRONMENT.md b/docs/dev/ENVIRONMENT.md index c91a8b011ddd..c04f8daf2f5d 100644 --- a/docs/dev/ENVIRONMENT.md +++ b/docs/dev/ENVIRONMENT.md @@ -376,7 +376,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_PIPELINE_JOURNAL_ENTRIES` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp` | 4096 | number of journal entries loaded at most; a longer journal keeps its last entries, the ones recorded last | | `APS5_PIPELINE_JOURNAL_MAX_MIB` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp` | 1024 | size cap in MiB on the pipeline journal; the file is rewritten down to three quarters of the cap when it is reached, keeping the newest pipelines and the modules they name and leaving room for later records | | `APS5_PIPELINE_JOURNAL_THREADS` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp` | | threads that replay the journal's pipelines, 0 disables the replay; unset uses half the hardware threads, at least one | -| `APS5_PIPELINE_LTO_THREADS` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp` | | threads that link graphics pipelines with link-time optimization in the background; unset uses half the hardware threads, at least two | +| `APS5_PIPELINE_LTO_THREADS` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp` | | threads that link graphics pipelines with link-time optimization in the background, a whole number from 1 through the hardware thread count (other values fail at the first background link); unset uses half the hardware threads, at least two | | `APS5_PIPELINE_STATS` | string | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | captures and prints driver statistics for each new pipeline, requiring `VK_KHR_pipeline_executable_properties`; `1` enables | | `APS5_PIPELINE_STORE_ENTRIES` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp` | adaptive | fixes the LRU bound on the entries of the pipeline store, which otherwise starts at 1024 and doubles up to 4096 for a journal replay that does not fit and while evicted pipelines keep coming back; an entry is evicted only once no recorded draw holds it | | `APS5_PM4_RING` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/SubmissionRing.cpp` | 16 | number of submissions the PM4 ring keeps for dumps, from 0 to 4096; 0 disables the ring | From e231257e214cee585d8b587294e1a58c1cf58070 Mon Sep 17 00:00:00 2001 From: Tamalero <11452991+Tamalero@users.noreply.github.com> Date: Wed, 28 Oct 2026 10:50:58 -0700 Subject: [PATCH 3/3] perf(agc): start a pipeline worker for a burst behind a woken idle worker Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 4 ++- .../Graphics/src/PipelineLibrary.cpp | 2 +- .../tests/PipelineLibraryWorkers.cpp | 30 ++++++++++++++++++- docs/dev/ENVIRONMENT.md | 2 +- 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 1bddaa7c0ae4..6e72524df2b9 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -1367,7 +1367,9 @@ if(BUILD_TESTING) set_target_properties(agc_pipeline_library_workers_tests PROPERTIES EXCLUDE_FROM_ALL OFF) add_test(NAME agc_pipeline_library_workers COMMAND agc_pipeline_library_workers_tests) set_tests_properties(agc_pipeline_library_workers PROPERTIES TIMEOUT 15 ENVIRONMENT "APS5_PIPELINE_LTO_THREADS=2") - set(workerTests agc_pipeline_library_workers) + add_test(NAME agc_pipeline_library_workers_burst COMMAND agc_pipeline_library_workers_tests burst) + set_tests_properties(agc_pipeline_library_workers_burst PROPERTIES TIMEOUT 15 ENVIRONMENT "APS5_PIPELINE_LTO_THREADS=2") + set(workerTests agc_pipeline_library_workers agc_pipeline_library_workers_burst) foreach(case negative=-1 text=abc zero=0 suffix=8x above_hardware=100000) string(REPLACE "=" ";" parts "${case}") list(GET parts 0 name) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp index 665b7c17dad7..c6b5cbe0f249 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp @@ -106,7 +106,7 @@ void runJobs(LibraryStore& store) { void enqueue(LibraryStore& store, VkDevice device, std::function job) { if (store.stopping) return; - if (store.waitingWorkers == 0 && store.workers.size() < WorkerLimit()) { + if (store.waitingWorkers <= store.jobs.size() && store.workers.size() < WorkerLimit()) { store.workers.emplace_back(runJobs, std::ref(store)); runningWorkers.fetch_add(1, std::memory_order_acq_rel); } diff --git a/core/libs/prx/libSceAgcDriver/tests/PipelineLibraryWorkers.cpp b/core/libs/prx/libSceAgcDriver/tests/PipelineLibraryWorkers.cpp index 4895c240fc54..9ae50cd0b3d7 100644 --- a/core/libs/prx/libSceAgcDriver/tests/PipelineLibraryWorkers.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/PipelineLibraryWorkers.cpp @@ -47,12 +47,40 @@ int main(int argc, char** argv) { std::printf("invalid worker count rejected before any worker started\n"); return 0; } - Check(argc == 1, "expected no argument or invalid"); std::mutex mutex; std::condition_variable changed; bool entered = false; bool released = false; std::atomic completed{0}; + if (argc == 2 && std::string_view(argv[1]) == "burst") { + Schedule(device, [&] { ++completed; }); + WaitForOptimizedPipelines(device); + Check(completed == 1 && PipelineLibraryWorkers() == 1, "the first job did not run on one worker"); + Schedule(device, [&] { + std::unique_lock lock(mutex); + changed.wait(lock, [&] { return released; }); + ++completed; + }); + Schedule(device, [&] { + std::lock_guard lock(mutex); + ++completed; + changed.notify_all(); + }); + { + std::unique_lock lock(mutex); + Check(changed.wait_for(lock, 3s, [&] { return completed == 2; }), "a burst queued behind the idle worker it woke with a second worker allowed"); + released = true; + } + changed.notify_all(); + WaitForOptimizedPipelines(device); + Check(completed == 3 && PipelineLibraryWorkers() == 2, "the burst did not run once each on two workers"); + StopPipelineLibraryWorker(); + ClearPipelineLibraries(device); + Check(PipelineLibraryWorkers() == 0, "the pipeline workers did not stop"); + std::printf("two jobs queued at once behind one idle worker ran on two workers\n"); + return 0; + } + Check(argc == 1, "expected no argument, invalid or burst"); Schedule(device, [&] { std::unique_lock lock(mutex); entered = true; diff --git a/docs/dev/ENVIRONMENT.md b/docs/dev/ENVIRONMENT.md index c04f8daf2f5d..0fdcf4ab285e 100644 --- a/docs/dev/ENVIRONMENT.md +++ b/docs/dev/ENVIRONMENT.md @@ -376,7 +376,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_PIPELINE_JOURNAL_ENTRIES` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp` | 4096 | number of journal entries loaded at most; a longer journal keeps its last entries, the ones recorded last | | `APS5_PIPELINE_JOURNAL_MAX_MIB` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp` | 1024 | size cap in MiB on the pipeline journal; the file is rewritten down to three quarters of the cap when it is reached, keeping the newest pipelines and the modules they name and leaving room for later records | | `APS5_PIPELINE_JOURNAL_THREADS` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp` | | threads that replay the journal's pipelines, 0 disables the replay; unset uses half the hardware threads, at least one | -| `APS5_PIPELINE_LTO_THREADS` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp` | | threads that link graphics pipelines with link-time optimization in the background, a whole number from 1 through the hardware thread count (other values fail at the first background link); unset uses half the hardware threads, at least two | +| `APS5_PIPELINE_LTO_THREADS` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp` | | threads that link graphics pipelines with link-time optimization in the background, a whole number from 1 through the hardware thread count; any other value fails at the first background link, 0 included (it does not disable the link, unlike `APS5_PIPELINE_JOURNAL_THREADS`); unset uses half the hardware threads, at least two | | `APS5_PIPELINE_STATS` | string | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | captures and prints driver statistics for each new pipeline, requiring `VK_KHR_pipeline_executable_properties`; `1` enables | | `APS5_PIPELINE_STORE_ENTRIES` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp` | adaptive | fixes the LRU bound on the entries of the pipeline store, which otherwise starts at 1024 and doubles up to 4096 for a journal replay that does not fit and while evicted pipelines keep coming back; an entry is evicted only once no recorded draw holds it | | `APS5_PM4_RING` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/SubmissionRing.cpp` | 16 | number of submissions the PM4 ring keeps for dumps, from 0 to 4096; 0 disables the ring |