Merge pull request #4795 from MarcZX100/codex/pipeline-cache-snapshot-lock-4781
fix(agc): serialize pipeline cache snapshots
This commit is contained in:
commit
1f1115dc90
|
|
@ -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<unsigned long long>(programAddress));
|
||||
CreateComputePipeline(state->device, state->DeviceFunction<PFN_vkCreateComputePipelines>("vkCreateComputePipelines"), context.pipelineCache, objects->module, objects->layout, pipelineFlags, 0, specialization.Info(), subgroupSize, &objects->pipeline);
|
||||
CreateComputePipeline(state->device, state->DeviceFunction<PFN_vkCreateComputePipelines>("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);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@
|
|||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
|
@ -28,6 +30,19 @@ class SamplerCache;
|
|||
class ShaderResources;
|
||||
class PlaceholderImages;
|
||||
|
||||
class PipelineCacheSynchronization {
|
||||
public:
|
||||
using PipelineUseLock = std::shared_lock<std::shared_mutex>;
|
||||
using SnapshotLock = std::unique_lock<std::shared_mutex>;
|
||||
|
||||
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<typename TFunction>
|
||||
TFunction Function(const char* name) const {
|
||||
|
|
|
|||
|
|
@ -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<std::byte>& 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;
|
||||
|
|
|
|||
|
|
@ -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<PFN_vkCreateComputePipelines>("vkCreateComputePipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &pipeline), "vkCreateComputePipelines color transfer");
|
||||
{
|
||||
auto pipelineCacheLock = context.LockPipelineCacheForUse();
|
||||
Check(context.Function<PFN_vkCreateComputePipelines>("vkCreateComputePipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &pipeline), "vkCreateComputePipelines color transfer");
|
||||
}
|
||||
context.Function<PFN_vkDestroyShaderModule>("vkDestroyShaderModule")(context.device, module, nullptr);
|
||||
module = VK_NULL_HANDLE;
|
||||
const VkDescriptorPoolSize size{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 3};
|
||||
|
|
|
|||
|
|
@ -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<PFN_vkCreateGraphicsPipelines>("vkCreateGraphicsPipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &pipeline), "vkCreateGraphicsPipelines");
|
||||
else {
|
||||
auto pipelineCacheLock = context.LockPipelineCacheForUse();
|
||||
Check(context.Function<PFN_vkCreateGraphicsPipelines>("vkCreateGraphicsPipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &pipeline), "vkCreateGraphicsPipelines");
|
||||
}
|
||||
if (alternateAlpha) {
|
||||
blend.pAttachments = secondChannels.data();
|
||||
auto pipelineCacheLock = context.LockPipelineCacheForUse();
|
||||
Check(context.Function<PFN_vkCreateGraphicsPipelines>("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<PFN_vkCreateGraphicsPipelines>("vkCreateGraphicsPipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &backFaces), "vkCreateGraphicsPipelines");
|
||||
}
|
||||
for (const auto module : _modules) context.Function<PFN_vkDestroyShaderModule>("vkDestroyShaderModule")(context.device, module, nullptr);
|
||||
|
|
|
|||
|
|
@ -106,11 +106,15 @@ void PipelineCache::load(std::vector<std::byte>& initialData) {
|
|||
void PipelineCache::save(bool final) {
|
||||
const auto getData = context.Function<PFN_vkGetPipelineCacheData>("vkGetPipelineCacheData");
|
||||
std::size_t size = 0;
|
||||
if (getData(context.device, cache, &size, nullptr) != VK_SUCCESS || size == 0 || size == savedBytes) return;
|
||||
std::vector<std::byte> 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<std::byte> 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();
|
||||
|
|
|
|||
|
|
@ -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<PFN_vkCreateGraphicsPipelines>("vkCreateGraphicsPipelines")(context.device, context.pipelineCache, 1, &create, nullptr, &handle);
|
||||
VkResult created;
|
||||
{
|
||||
auto pipelineCacheLock = context.LockPipelineCacheForUse();
|
||||
created = context.Function<PFN_vkCreateGraphicsPipelines>("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<PFN_vkCreateGraphicsPipelines>("vkCreateGraphicsPipelines")(context.device, context.pipelineCache, 1, &create, nullptr, &pipeline), "vkCreateGraphicsPipelines link");
|
||||
{
|
||||
auto pipelineCacheLock = context.LockPipelineCacheForUse();
|
||||
Check(context.Function<PFN_vkCreateGraphicsPipelines>("vkCreateGraphicsPipelines")(context.device, context.pipelineCache, 1, &create, nullptr, &pipeline), "vkCreateGraphicsPipelines link");
|
||||
}
|
||||
++device.counters.linked;
|
||||
if (optimize && optimized != nullptr) {
|
||||
auto slot = std::make_shared<OptimizedPipeline>();
|
||||
|
|
@ -243,8 +250,9 @@ VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPip
|
|||
*optimized = slot;
|
||||
const auto createPipelines = context.Function<PFN_vkCreateGraphicsPipelines>("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<std::uint32_t>(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);
|
||||
|
|
|
|||
|
|
@ -1998,7 +1998,10 @@ bool Recorder::gpuSampleCounter() {
|
|||
pipelineInfo.stage.module = module;
|
||||
pipelineInfo.stage.pName = "main";
|
||||
pipelineInfo.layout = sampleLayout;
|
||||
Check(context.Function<PFN_vkCreateComputePipelines>("vkCreateComputePipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &samplePipeline), "vkCreateComputePipelines sample counter");
|
||||
{
|
||||
auto pipelineCacheLock = context.LockPipelineCacheForUse();
|
||||
Check(context.Function<PFN_vkCreateComputePipelines>("vkCreateComputePipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &samplePipeline), "vkCreateComputePipelines sample counter");
|
||||
}
|
||||
context.Function<PFN_vkDestroyShaderModule>("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<PFN_vkCreateShaderModule>("vkCreateShaderModule")(context.device, &moduleInfo, nullptr, &module), "vkCreateShaderModule sample dumps");
|
||||
pipelineInfo.stage.module = module;
|
||||
pipelineInfo.layout = dumpLayout;
|
||||
Check(context.Function<PFN_vkCreateComputePipelines>("vkCreateComputePipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &dumpPipeline), "vkCreateComputePipelines sample dumps");
|
||||
{
|
||||
auto pipelineCacheLock = context.LockPipelineCacheForUse();
|
||||
Check(context.Function<PFN_vkCreateComputePipelines>("vkCreateComputePipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &dumpPipeline), "vkCreateComputePipelines sample dumps");
|
||||
}
|
||||
context.Function<PFN_vkDestroyShaderModule>("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<PFN_vkCreateComputePipelines>("vkCreateComputePipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &meshArgumentPipeline), "vkCreateComputePipelines mesh arguments");
|
||||
{
|
||||
auto pipelineCacheLock = context.LockPipelineCacheForUse();
|
||||
Check(context.Function<PFN_vkCreateComputePipelines>("vkCreateComputePipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &meshArgumentPipeline), "vkCreateComputePipelines mesh arguments");
|
||||
}
|
||||
context.Function<PFN_vkDestroyShaderModule>("vkDestroyShaderModule")(context.device, module, nullptr);
|
||||
meshArgumentState = 1;
|
||||
} catch (const std::exception& error) {
|
||||
|
|
|
|||
|
|
@ -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<PFN_vkCreateComputePipelines>("vkCreateComputePipelines")(context.device, context.pipelineCache, 1, &createInfo, nullptr, &result), "vkCreateComputePipelines");
|
||||
{
|
||||
auto pipelineCacheLock = context.LockPipelineCacheForUse();
|
||||
Check(context.Function<PFN_vkCreateComputePipelines>("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<PFN_vkCreateComputePipelines>("vkCreateComputePipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &cmaskPipeline);
|
||||
VkResult created;
|
||||
{
|
||||
auto pipelineCacheLock = context.LockPipelineCacheForUse();
|
||||
created = context.Function<PFN_vkCreateComputePipelines>("vkCreateComputePipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &cmaskPipeline);
|
||||
}
|
||||
context.Function<PFN_vkDestroyShaderModule>("vkDestroyShaderModule")(context.device, cmaskModule, nullptr);
|
||||
Check(created, "vkCreateComputePipelines CMASK clear");
|
||||
cmaskErrors = std::make_unique<Buffer>(context, 4, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
Loading…
Reference in New Issue