diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index 5d3ddfec4..4e72b41c0 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -94,7 +94,7 @@ bool CoreVulkanCapability(std::uint32_t capability) { } } -void CreateComputePipeline(VkDevice device, PFN_vkCreateComputePipelines create, VkPipelineCache cache, VkShaderModule module, VkPipelineLayout layout, VkPipelineCreateFlags flags, VkPipelineShaderStageCreateFlags stageFlags, const VkSpecializationInfo* specialization, std::uint32_t subgroupSize, VkPipeline* pipeline) { +void CreateComputePipeline(VkDevice device, PFN_vkCreateComputePipelines create, VkPipelineCache cache, VkShaderModule module, VkPipelineLayout layout, VkPipelineCreateFlags flags, VkPipelineShaderStageCreateFlags stageFlags, const VkSpecializationInfo* specialization, std::uint32_t subgroupSize, VkPipeline* pipeline, Graphics::PipelineCacheSynchronization* synchronization = nullptr) { VkComputePipelineCreateInfo info{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO}; info.flags = flags; info.stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; @@ -107,6 +107,7 @@ void CreateComputePipeline(VkDevice device, PFN_vkCreateComputePipelines create, subgroup.requiredSubgroupSize = subgroupSize; if (subgroupSize != 0) info.stage.pNext = &subgroup; info.layout = layout; + auto cacheLock = synchronization != nullptr ? synchronization->LockForPipelineUse() : Graphics::PipelineCacheSynchronization::PipelineUseLock{}; check(create(device, cache, 1, &info, nullptr, pipeline), "vkCreateComputePipelines"); } @@ -3120,6 +3121,7 @@ Graphics::Context VulkanDevice::buildContext() const { state->depthClamp, state->hostImportAlignment }; + context.pipelineCacheSynchronization = state->pipelineCache ? &state->pipelineCache->Synchronization() : nullptr; context.dmaBufImport = state->dmaBufImport; context.recorder = state->recorder.get(); context.descriptorCache = state->descriptorCache.get(); @@ -4275,7 +4277,7 @@ VulkanDevice::IndirectOutcome VulkanDevice::dispatch(const ShaderRecompiler::Rec std::fprintf(stderr, "[compute-capture] %s\n", path.string().c_str()); } if (profile && shader.spirv.size() > 100000) std::fprintf(stderr, "[dispatch] creating a pipeline for %zu SPIR-V words (program 0x%llx)\n", shader.spirv.size(), static_cast(programAddress)); - CreateComputePipeline(state->device, state->DeviceFunction("vkCreateComputePipelines"), context.pipelineCache, objects->module, objects->layout, pipelineFlags, 0, specialization.Info(), subgroupSize, &objects->pipeline); + CreateComputePipeline(state->device, state->DeviceFunction("vkCreateComputePipelines"), context.pipelineCache, objects->module, objects->layout, pipelineFlags, 0, specialization.Info(), subgroupSize, &objects->pipeline, context.pipelineCacheSynchronization); objects->destroyModule(objects->device, objects->module, nullptr); objects->module = VK_NULL_HANDLE; Graphics::LogPipelineStatistics_nid_no_patch(context, objects->pipeline); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp index 68ba7ea94..c83858906 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include #include #include @@ -28,6 +30,19 @@ class SamplerCache; class ShaderResources; class PlaceholderImages; +class PipelineCacheSynchronization { +public: + using PipelineUseLock = std::shared_lock; + using SnapshotLock = std::unique_lock; + + PipelineUseLock LockForPipelineUse() const { return PipelineUseLock(mutex); } + SnapshotLock LockForSnapshot() const { return SnapshotLock(mutex); } + SnapshotLock TryLockForSnapshot() const { return SnapshotLock(mutex, std::try_to_lock); } + +private: + mutable std::shared_mutex mutex; +}; + inline void Require(bool condition, const std::string& reason) { if (!condition) throw std::runtime_error("AGC graphics: " + reason); } @@ -176,6 +191,11 @@ struct Context { bool provokingVertexModePerPipeline = false; bool singlePassStorage = false; VkSampleCountFlags sampleLocationSampleCounts = 0; + PipelineCacheSynchronization* pipelineCacheSynchronization = nullptr; + + PipelineCacheSynchronization::PipelineUseLock LockPipelineCacheForUse() const { + return pipelineCacheSynchronization != nullptr ? pipelineCacheSynchronization->LockForPipelineUse() : PipelineCacheSynchronization::PipelineUseLock{}; + } template TFunction Function(const char* name) const { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/PipelineCache.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/PipelineCache.hpp index 54727e945..6398fde6d 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/PipelineCache.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/PipelineCache.hpp @@ -21,6 +21,7 @@ public: PipelineCache(const PipelineCache&) = delete; PipelineCache& operator=(const PipelineCache&) = delete; VkPipelineCache Handle() const { return cache; } + PipelineCacheSynchronization& Synchronization() { return synchronization; } private: void load(std::vector& initialData); @@ -30,6 +31,7 @@ private: Context context; VkPhysicalDeviceProperties properties{}; VkPipelineCache cache = VK_NULL_HANDLE; + PipelineCacheSynchronization synchronization; std::filesystem::path path; std::size_t savedBytes = 0; std::mutex mutex; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GpuColorTransfer.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GpuColorTransfer.cpp index 4aec57b1d..b7d132af9 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GpuColorTransfer.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GpuColorTransfer.cpp @@ -35,7 +35,10 @@ GpuColorTransfer::GpuColorTransfer(const Context& context) : context(context) { pipelineInfo.stage.module = module; pipelineInfo.stage.pName = "main"; pipelineInfo.layout = pipelineLayout; - Check(context.Function("vkCreateComputePipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &pipeline), "vkCreateComputePipelines color transfer"); + { + auto pipelineCacheLock = context.LockPipelineCacheForUse(); + Check(context.Function("vkCreateComputePipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &pipeline), "vkCreateComputePipelines color transfer"); + } context.Function("vkDestroyShaderModule")(context.device, module, nullptr); module = VK_NULL_HANDLE; const VkDescriptorPoolSize size{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 3}; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp index 6eeefefa2..2a7abf526 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp @@ -499,13 +499,18 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput return; } if (libraries) pipeline = LinkPipelineFromLibraries(context, pipelineInfo, rendering, layoutInfo, keys, &optimized, false, _modules); - else Check(context.Function("vkCreateGraphicsPipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &pipeline), "vkCreateGraphicsPipelines"); + else { + auto pipelineCacheLock = context.LockPipelineCacheForUse(); + Check(context.Function("vkCreateGraphicsPipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &pipeline), "vkCreateGraphicsPipelines"); + } if (alternateAlpha) { blend.pAttachments = secondChannels.data(); + auto pipelineCacheLock = context.LockPipelineCacheForUse(); Check(context.Function("vkCreateGraphicsPipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &secondChannel), "vkCreateGraphicsPipelines"); } if (state.depthBiasPerFace) { raster.cullMode = VK_CULL_MODE_FRONT_BIT; + auto pipelineCacheLock = context.LockPipelineCacheForUse(); Check(context.Function("vkCreateGraphicsPipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &backFaces), "vkCreateGraphicsPipelines"); } for (const auto module : _modules) context.Function("vkDestroyShaderModule")(context.device, module, nullptr); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineCache.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineCache.cpp index b3f1de78c..ec51c5a20 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineCache.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineCache.cpp @@ -106,11 +106,15 @@ void PipelineCache::load(std::vector& initialData) { void PipelineCache::save(bool final) { const auto getData = context.Function("vkGetPipelineCacheData"); std::size_t size = 0; - if (getData(context.device, cache, &size, nullptr) != VK_SUCCESS || size == 0 || size == savedBytes) return; - std::vector file(sizeof(FileHeader) + size); - const auto result = getData(context.device, cache, &size, file.data() + sizeof(FileHeader)); - if (result != VK_SUCCESS && result != VK_INCOMPLETE) return; - file.resize(sizeof(FileHeader) + size); + std::vector file; + { + auto synchronizationLock = synchronization.LockForSnapshot(); + if (getData(context.device, cache, &size, nullptr) != VK_SUCCESS || size == 0 || size == savedBytes) return; + file.resize(sizeof(FileHeader) + size); + const auto result = getData(context.device, cache, &size, file.data() + sizeof(FileHeader)); + if (result != VK_SUCCESS && result != VK_INCOMPLETE) return; + file.resize(sizeof(FileHeader) + size); + } const FileHeader header{FileMagic, FileFormat, size, ShaderRecompiler::HashBytes(std::span(file).subspan(sizeof(FileHeader)))}; std::memcpy(file.data(), &header, sizeof(header)); const auto started = std::chrono::steady_clock::now(); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp index bfdb538df..bd2a12ed3 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp @@ -205,7 +205,11 @@ VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPip } VkPipeline handle = VK_NULL_HANDLE; if (partsOnly) lock.unlock(); - const auto created = context.Function("vkCreateGraphicsPipelines")(context.device, context.pipelineCache, 1, &create, nullptr, &handle); + VkResult created; + { + auto pipelineCacheLock = context.LockPipelineCacheForUse(); + created = context.Function("vkCreateGraphicsPipelines")(context.device, context.pipelineCache, 1, &create, nullptr, &handle); + } if (partsOnly) lock.lock(); Check(created, "vkCreateGraphicsPipelines library"); if (const auto raced = cache.find(key); raced != cache.end()) { @@ -234,7 +238,10 @@ VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPip VkGraphicsPipelineCreateInfo create{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, &linked}; create.layout = info.layout; VkPipeline pipeline = VK_NULL_HANDLE; - Check(context.Function("vkCreateGraphicsPipelines")(context.device, context.pipelineCache, 1, &create, nullptr, &pipeline), "vkCreateGraphicsPipelines link"); + { + auto pipelineCacheLock = context.LockPipelineCacheForUse(); + Check(context.Function("vkCreateGraphicsPipelines")(context.device, context.pipelineCache, 1, &create, nullptr, &pipeline), "vkCreateGraphicsPipelines link"); + } ++device.counters.linked; if (optimize && optimized != nullptr) { auto slot = std::make_shared(); @@ -243,8 +250,9 @@ VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPip *optimized = slot; const auto createPipelines = context.Function("vkCreateGraphicsPipelines"); const auto cache = context.pipelineCache; + const auto cacheSynchronization = context.pipelineCacheSynchronization; const auto libraryLayout = fullLayout; - enqueue(store, context.device, [slot, createPipelines, cache, libraryLayout, libraries, &store, devicePointer = &device] { + enqueue(store, context.device, [slot, createPipelines, cache, cacheSynchronization, libraryLayout, libraries, &store, devicePointer = &device] { if (slot->released.load()) return; VkPipelineLibraryCreateInfoKHR linked{VK_STRUCTURE_TYPE_PIPELINE_LIBRARY_CREATE_INFO_KHR}; linked.libraryCount = static_cast(libraries.size()); @@ -253,7 +261,11 @@ VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPip create.flags = VK_PIPELINE_CREATE_LINK_TIME_OPTIMIZATION_BIT_EXT; create.layout = libraryLayout; VkPipeline result = VK_NULL_HANDLE; - if (createPipelines(slot->device, cache, 1, &create, nullptr, &result) != VK_SUCCESS) return; + const auto pipelineCreationResult = [&] { + auto pipelineCacheLock = cacheSynchronization != nullptr ? cacheSynchronization->LockForPipelineUse() : PipelineCacheSynchronization::PipelineUseLock{}; + return createPipelines(slot->device, cache, 1, &create, nullptr, &result); + }(); + if (pipelineCreationResult != VK_SUCCESS) return; slot->handle.store(result); if (slot->released.load()) { if (const auto handle = slot->handle.exchange(VK_NULL_HANDLE)) slot->destroy(slot->device, handle, nullptr); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp index 192d1e9c3..269102624 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp @@ -1998,7 +1998,10 @@ bool Recorder::gpuSampleCounter() { pipelineInfo.stage.module = module; pipelineInfo.stage.pName = "main"; pipelineInfo.layout = sampleLayout; - Check(context.Function("vkCreateComputePipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &samplePipeline), "vkCreateComputePipelines sample counter"); + { + auto pipelineCacheLock = context.LockPipelineCacheForUse(); + Check(context.Function("vkCreateComputePipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &samplePipeline), "vkCreateComputePipelines sample counter"); + } context.Function("vkDestroyShaderModule")(context.device, module, nullptr); module = VK_NULL_HANDLE; const VkPushConstantRange dumpPush{VK_SHADER_STAGE_COMPUTE_BIT, 0, SampleDumpPushBytes}; @@ -2009,7 +2012,10 @@ bool Recorder::gpuSampleCounter() { Check(context.Function("vkCreateShaderModule")(context.device, &moduleInfo, nullptr, &module), "vkCreateShaderModule sample dumps"); pipelineInfo.stage.module = module; pipelineInfo.layout = dumpLayout; - Check(context.Function("vkCreateComputePipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &dumpPipeline), "vkCreateComputePipelines sample dumps"); + { + auto pipelineCacheLock = context.LockPipelineCacheForUse(); + Check(context.Function("vkCreateComputePipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &dumpPipeline), "vkCreateComputePipelines sample dumps"); + } context.Function("vkDestroyShaderModule")(context.device, module, nullptr); module = VK_NULL_HANDLE; } catch (const std::exception& error) { @@ -2286,7 +2292,10 @@ bool Recorder::RecordMeshArguments(VkCommandBuffer commands, VkDeviceAddress rec pipelineInfo.stage.module = module; pipelineInfo.stage.pName = "main"; pipelineInfo.layout = meshArgumentLayout; - Check(context.Function("vkCreateComputePipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &meshArgumentPipeline), "vkCreateComputePipelines mesh arguments"); + { + auto pipelineCacheLock = context.LockPipelineCacheForUse(); + Check(context.Function("vkCreateComputePipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &meshArgumentPipeline), "vkCreateComputePipelines mesh arguments"); + } context.Function("vkDestroyShaderModule")(context.device, module, nullptr); meshArgumentState = 1; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/TextureDetiler.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/TextureDetiler.cpp index bed858e7a..95ed37239 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/TextureDetiler.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/TextureDetiler.cpp @@ -181,7 +181,10 @@ VkPipeline TextureDetiler::pipeline(TextureTileMode tileMode, std::uint32_t elem createInfo.stage = stage; createInfo.layout = image ? imagePipelineLayout : pipelineLayout; VkPipeline result = VK_NULL_HANDLE; - Check(context.Function("vkCreateComputePipelines")(context.device, context.pipelineCache, 1, &createInfo, nullptr, &result), "vkCreateComputePipelines"); + { + auto pipelineCacheLock = context.LockPipelineCacheForUse(); + Check(context.Function("vkCreateComputePipelines")(context.device, context.pipelineCache, 1, &createInfo, nullptr, &result), "vkCreateComputePipelines"); + } pipelines.emplace_back(key, result); return result; } @@ -299,7 +302,11 @@ void TextureDetiler::DispatchCmaskClear(VkCommandBuffer commands, VkBuffer cmask pipelineInfo.stage.module = cmaskModule; pipelineInfo.stage.pName = "main"; pipelineInfo.layout = cmaskPipelineLayout; - const auto created = context.Function("vkCreateComputePipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &cmaskPipeline); + VkResult created; + { + auto pipelineCacheLock = context.LockPipelineCacheForUse(); + created = context.Function("vkCreateComputePipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &cmaskPipeline); + } context.Function("vkDestroyShaderModule")(context.device, cmaskModule, nullptr); Check(created, "vkCreateComputePipelines CMASK clear"); cmaskErrors = std::make_unique(context, 4, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 4f1468bbb..4589213eb 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -5628,6 +5628,16 @@ int main(int argc, char** argv) { std::cout << "address-space device teardown releases cached guest allocations\n"; return 0; } + { + AgcDriver::Graphics::PipelineCacheSynchronization synchronization; + auto pipelineUse = synchronization.LockForPipelineUse(); + bool snapshotAcquired = false; + std::thread snapshot([&] { snapshotAcquired = synchronization.TryLockForSnapshot().owns_lock(); }); + snapshot.join(); + Require(!snapshotAcquired, "pipeline cache snapshot overlapped pipeline creation"); + pipelineUse.unlock(); + Require(synchronization.TryLockForSnapshot().owns_lock(), "pipeline cache snapshot stayed blocked after pipeline creation finished"); + } if (argc == 2 && std::string_view(argv[1]) == "--descriptor-layout-flags") { descriptorLayoutFlagTests(); std::cout << "Descriptor layout native and captured binding flags passed\n";