fix(agc): drain library builds before device replacement

This commit is contained in:
MarcZX100 committed 2026-10-16 13:02:11 +00:00
1 parent 82ddae75b6
commit 81e3db853c
6 files changed
+90 -1

No files matched your search

@@ -1565,7 +1565,9 @@ if(BUILD_TESTING)
endif()
set_tests_properties(agc_sparse_buffer_holes PROPERTIES TIMEOUT 30)
add_test(NAME agc_pipeline_journal COMMAND agc_driver_graphics_tests --pipeline-journal)
add_test(NAME agc_pipeline_library_clear_waits_for_build COMMAND agc_driver_graphics_tests --pipeline-library-clear-waits-for-build)
set_tests_properties(agc_pipeline_journal PROPERTIES TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
set_tests_properties(agc_pipeline_library_clear_waits_for_build PROPERTIES TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
if(WIN32)
set_tests_properties(agc_pipeline_journal PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
@@ -117,6 +117,7 @@ void SubmitRegistrationPreparation(std::function<void()> task, const PreparedSha
void PrioritizeRegistrationPreparation(PreparedShaders& prepared);
void AwaitRegistrationPreparation();
void SubmitLibraryPrebuild(std::function<void()> task);
void AwaitLibraryPrebuilds();
void CloseRegistrationPreparation();
void ResolvePreparedGraphics(const ShaderSnapshot& front, const std::shared_ptr<const ShaderSnapshot>& fragment, std::uint32_t primitiveType, const ShaderRecompiler::SpirvTarget& target);
@@ -1119,6 +1119,10 @@ void SubmitLibraryPrebuild(std::function<void()> task) {
RegistrationPreparePool::Prebuild().Submit(std::move(task));
}
void AwaitLibraryPrebuilds() {
if (auto* pool = RegistrationPreparePool::ExistingPrebuild()) pool->AwaitSubmitted();
}
void CloseRegistrationPreparation() {
RegistrationPreparePool::Instance().Close();
if (auto* prebuild = RegistrationPreparePool::ExistingPrebuild()) prebuild->Close();
@@ -9,6 +9,7 @@
#include "prx/libSceAgcDriver/Graphics/include/PipelineJournal.hpp"
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp"
#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp"
#include "prx/libSceAgcDriver/Execution/include/BdaFeatures.hpp"
#include "prx/libSceAgcDriver/Execution/include/DeviceFault.hpp"
@@ -2027,6 +2028,7 @@ void VulkanDevice::PrepareForReplacement() {
state->journalReplay->Wait();
state->journalReplay.reset();
}
DriverDetail::AwaitLibraryPrebuilds();
Graphics::FlushCachedTextures(state->device);
Graphics::PublishAllShadows(state->context, Graphics::PublishReason::Teardown);
WaitIdle();
@@ -53,6 +53,7 @@ struct LibraryStore {
std::condition_variable work;
std::deque<std::pair<VkDevice, std::function<void()>>> jobs;
std::map<VkDevice, std::size_t> pending;
std::map<VkDevice, std::size_t> activeBuilds;
bool started = false;
};
@@ -109,6 +110,16 @@ VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPip
Require(!split || (layout.setLayoutCount == 2u && layout.pSetLayouts != nullptr && ((keys.preRasterizationSets | keys.fragmentSets) & ~3u) == 0u), "split pipeline library layout requires two descriptor set slots and a valid mask");
auto& store = Libraries();
std::unique_lock lock(store.mutex);
++store.activeBuilds[context.device];
struct ActiveBuild {
LibraryStore& store;
VkDevice device;
std::unique_lock<std::mutex>& lock;
~ActiveBuild() {
if (!lock.owns_lock()) lock.lock();
if (--store.activeBuilds[device] == 0) store.idle.notify_all();
}
} activeBuild{store, context.device, lock};
auto& device = store.devices[context.device];
if (device.destroyPipeline == nullptr) {
device.destroyPipeline = context.Function<PFN_vkDestroyPipeline>("vkDestroyPipeline");
@@ -312,7 +323,7 @@ void WaitForOptimizedPipelines(VkDevice device) {
void ClearPipelineLibraries(VkDevice device) {
auto& store = Libraries();
std::unique_lock lock(store.mutex);
store.idle.wait(lock, [&] { return store.pending[device] == 0; });
store.idle.wait(lock, [&] { return store.pending[device] == 0 && store.activeBuilds[device] == 0; });
const auto found = store.devices.find(device);
if (found == store.devices.end()) return;
auto& libraries = found->second;
@@ -32,6 +32,7 @@
#include <array>
#include <bit>
#include <chrono>
#include <condition_variable>
#include <cstddef>
#include <cstdlib>
#include <cstring>
@@ -3488,6 +3489,11 @@ struct MockVulkan {
};
MockVulkan mock;
std::mutex pipelineCreateMutex;
std::condition_variable pipelineCreateCondition;
bool blockLibraryCreate = false;
bool libraryCreateEntered = false;
bool releaseLibraryCreate = false;
template<typename THandle>
THandle makeHandle() {
@@ -3672,6 +3678,14 @@ VKAPI_ATTR VkResult VKAPI_CALL mockCreatePipelineLayout(VkDevice, const VkPipeli
}
VKAPI_ATTR VkResult VKAPI_CALL mockCreateGraphicsPipelines(VkDevice, VkPipelineCache, std::uint32_t count, const VkGraphicsPipelineCreateInfo* infos, const VkAllocationCallbacks*, VkPipeline* pipelines) {
{
std::unique_lock lock(pipelineCreateMutex);
if (blockLibraryCreate) {
libraryCreateEntered = true;
pipelineCreateCondition.notify_all();
pipelineCreateCondition.wait(lock, [] { return releaseLibraryCreate; });
}
}
Require(count == 1, "mock expects exactly one graphics pipeline per call");
if ((infos[0].flags & VK_PIPELINE_CREATE_LIBRARY_BIT_KHR) == 0) {
const auto* linked = static_cast<const VkPipelineLibraryCreateInfoKHR*>(infos[0].pNext);
@@ -5140,6 +5154,55 @@ void pipelineLibrarySetTests() {
}
}
void pipelineLibraryClearWaitsForBuildTests() {
using namespace AgcDriver::Graphics;
mock = MockVulkan{};
auto context = mockContext();
context.graphicsPipelineLibrary = true;
context.limits.framebufferNoAttachmentsSampleCounts = VK_SAMPLE_COUNT_1_BIT;
State state{};
ShaderRecompiler::RecompileResult vertex, fragment;
vertex.spirv = makeModule({});
fragment.spirv = makeModule({.fragment = true});
vertex.variantId = 101;
fragment.variantId = 102;
const std::array<CompiledShader, 2> shaders{{{ShaderRecompiler::ShaderStage::Vertex, &vertex, 0}, {ShaderRecompiler::ShaderStage::Fragment, &fragment, PipelinePushSlotBytes}}};
std::array<std::uint32_t, 1> mainKey{1}, fragmentKey{2};
PipelineSetLayouts resources{};
resources.main = makeHandle<VkDescriptorSetLayout>();
resources.fragment = makeHandle<VkDescriptorSetLayout>();
resources.mainKey = mainKey;
resources.fragmentKey = fragmentKey;
{
std::lock_guard lock(pipelineCreateMutex);
blockLibraryCreate = true;
libraryCreateEntered = false;
releaseLibraryCreate = false;
}
std::thread build([&] { Require(Pipeline::Prebuild(context, state, VertexInputLayout{}, resources, shaders), "graphics pipeline library prebuild was skipped"); });
{
std::unique_lock lock(pipelineCreateMutex);
pipelineCreateCondition.wait(lock, [] { return libraryCreateEntered; });
}
std::atomic<bool> cleared{false};
std::thread clear([&] {
ClearCachedPipelines(context.device);
cleared.store(true, std::memory_order_release);
});
std::this_thread::sleep_for(std::chrono::milliseconds(20));
const bool waited = !cleared.load(std::memory_order_acquire);
{
std::lock_guard lock(pipelineCreateMutex);
releaseLibraryCreate = true;
}
pipelineCreateCondition.notify_all();
build.join();
clear.join();
Require(waited, "clearing pipeline libraries did not wait for an active part build");
Require(cleared.load(std::memory_order_acquire), "clearing pipeline libraries did not complete after the part build");
Require(mock.live == 0, "clearing during a library build leaked Vulkan objects");
}
void barycentricEmulationTests() {
using namespace ShaderRecompiler;
using namespace AgcDriver::Graphics;
@@ -7694,6 +7757,11 @@ int main(int argc, char** argv) {
std::cout << "pipeline journal recipe, record and replay tests passed\n";
return 0;
}
if (argc == 2 && std::string_view(argv[1]) == "--pipeline-library-clear-waits-for-build") {
pipelineLibraryClearWaitsForBuildTests();
std::cout << "pipeline library clear waits for active part builds\n";
return 0;
}
if (argc == 2 && std::string_view(argv[1]) == "--sparse-buffer-holes") {
bool passed = false;
{
@@ -7806,6 +7874,7 @@ int main(int argc, char** argv) {
stateTests();
retainedPixelStageTests();
pipelineLibrarySetTests();
pipelineLibraryClearWaitsForBuildTests();
hdrComponentSwapTests();
depthMaintenanceTests();
hardwareScreenOffsetTests();