Implement monolithic VkPipelineCache toggle
Dawn/Vulkan was using a VkPipelineCache per unique pipeline previously.
This hurts pipeline/shader compilation performance since many pipelines
produce the same binary blobs. Add a VulkanMonolithicPipelineCache
toggle that switches to using a single VkPipelineCache per wgpu::Device.
The existing behaviour to serialize VkPipelineCache into BlobCache
immediately after pipeline compilation is more problematic with a single
VkPipelineCache since it can get quite large. With monolithic cache it
will just mark that serialization is needed. PerformIdleWork() is added
to dawn native device to trigger this serialization at a time of the
embedders choosing, preferably when it's not going to hinder
performance.
Bug: 370343334
Change-Id: I92f826e98c57580d5ee24e895d4ded3920495d2d
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/209816
Commit-Queue: Kyle Charbonneau <kylechar@google.com>
Reviewed-by: Loko Kung <lokokung@google.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/include/dawn/native/DawnNative.h b/include/dawn/native/DawnNative.h
index 79376d6..b98913f 100644
--- a/include/dawn/native/DawnNative.h
+++ b/include/dawn/native/DawnNative.h
@@ -308,6 +308,10 @@
// Free any unused GPU memory like staging buffers, cached resources, etc.
DAWN_NATIVE_EXPORT void ReduceMemoryUsage(WGPUDevice device);
+// Perform tasks that are appropriate to do when idle like serializing pipeline
+// caches, etc.
+DAWN_NATIVE_EXPORT void PerformIdleTasks(const wgpu::Device& device);
+
} // namespace dawn::native
#endif // INCLUDE_DAWN_NATIVE_DAWNNATIVE_H_
diff --git a/src/dawn/native/DawnNative.cpp b/src/dawn/native/DawnNative.cpp
index 087f62b..300c216 100644
--- a/src/dawn/native/DawnNative.cpp
+++ b/src/dawn/native/DawnNative.cpp
@@ -312,4 +312,10 @@
FromAPI(device)->ReduceMemoryUsage();
}
+void PerformIdleTasks(const wgpu::Device& device) {
+ auto* deviceBase = FromAPI(device.Get());
+ auto deviceLock(deviceBase->GetScopedLock());
+ deviceBase->PerformIdleTasks();
+}
+
} // namespace dawn::native
diff --git a/src/dawn/native/Device.cpp b/src/dawn/native/Device.cpp
index 4b92d19..7ea30103 100644
--- a/src/dawn/native/Device.cpp
+++ b/src/dawn/native/Device.cpp
@@ -2445,6 +2445,8 @@
void DeviceBase::SetLabelImpl() {}
+void DeviceBase::PerformIdleTasksImpl() {}
+
bool DeviceBase::ShouldDuplicateNumWorkgroupsForDispatchIndirect(
ComputePipelineBase* computePipeline) const {
return false;
@@ -2564,6 +2566,11 @@
mTemporaryUniformBuffer = nullptr;
}
+void DeviceBase::PerformIdleTasks() {
+ DAWN_ASSERT(IsLockedByCurrentThreadIfNeeded());
+ PerformIdleTasksImpl();
+}
+
ResultOrError<Ref<BufferBase>> DeviceBase::GetOrCreateTemporaryUniformBuffer(size_t size) {
if (!mTemporaryUniformBuffer || mTemporaryUniformBuffer->GetSize() != size) {
BufferDescriptor desc;
diff --git a/src/dawn/native/Device.h b/src/dawn/native/Device.h
index ebb92c2..a93516d 100644
--- a/src/dawn/native/Device.h
+++ b/src/dawn/native/Device.h
@@ -435,6 +435,7 @@
void DumpMemoryStatistics(dawn::native::MemoryDump* dump) const;
uint64_t ComputeEstimatedMemoryUsage() const;
void ReduceMemoryUsage();
+ void PerformIdleTasks();
ResultOrError<Ref<BufferBase>> GetOrCreateTemporaryUniformBuffer(size_t size);
@@ -502,6 +503,7 @@
virtual ResultOrError<Ref<SharedFenceBase>> ImportSharedFenceImpl(
const SharedFenceDescriptor* descriptor);
virtual void SetLabelImpl();
+ virtual void PerformIdleTasksImpl();
virtual MaybeError TickImpl() = 0;
void FlushCallbackTaskQueue();
diff --git a/src/dawn/native/PipelineCache.cpp b/src/dawn/native/PipelineCache.cpp
index d65c2b4..9a0bba1 100644
--- a/src/dawn/native/PipelineCache.cpp
+++ b/src/dawn/native/PipelineCache.cpp
@@ -29,8 +29,8 @@
namespace dawn::native {
-PipelineCacheBase::PipelineCacheBase(BlobCache* cache, const CacheKey& key)
- : mCache(cache), mKey(key) {}
+PipelineCacheBase::PipelineCacheBase(BlobCache* cache, const CacheKey& key, bool storeOnIdle)
+ : mCache(cache), mKey(key), mStoreOnIdle(storeOnIdle) {}
Blob PipelineCacheBase::Initialize() {
DAWN_ASSERT(!mInitialized);
@@ -57,10 +57,27 @@
return {};
}
-MaybeError PipelineCacheBase::FlushIfNeeded() {
+MaybeError PipelineCacheBase::DidCompilePipeline() {
DAWN_ASSERT(mInitialized);
- if (!CacheHit()) {
- return Flush();
+ if (mStoreOnIdle) {
+ // Assume pipeline cache was modified by compiling a pipeline. It will be stored in
+ // BlobCache at some later point in StoreOnIdle() if necessary.
+ mNeedsStore = true;
+ } else {
+ // TODO(dawn:549): Flush is currently synchronously happening on the same thread as pipeline
+ // compilation, but it's perhaps deferrable.
+ if (!CacheHit()) {
+ return Flush();
+ }
+ }
+ return {};
+}
+
+MaybeError PipelineCacheBase::StoreOnIdle() {
+ DAWN_ASSERT(mStoreOnIdle);
+ if (mNeedsStore) {
+ mNeedsStore = false;
+ DAWN_TRY(Flush());
}
return {};
}
diff --git a/src/dawn/native/PipelineCache.h b/src/dawn/native/PipelineCache.h
index 3b0ef04..5b9015c 100644
--- a/src/dawn/native/PipelineCache.h
+++ b/src/dawn/native/PipelineCache.h
@@ -47,12 +47,18 @@
// with more monolithic-like caches where we expect overwriting sometimes.
MaybeError Flush();
- // Serializes and writes the current contents of the backend cache object into the backing
- // blob cache iff the initial read from the backend cache did not result in a hit.
- MaybeError FlushIfNeeded();
+ // Called after pipeline was compiled. The default implementation serializes and writes the
+ // current contents of the backend cache object into the backing blob cache iff the initial read
+ // from the backend cache did not result in a hit.
+ MaybeError DidCompilePipeline();
+
+ // Trigger storing pipeline cache data in BlobCache if necessary.
+ MaybeError StoreOnIdle();
protected:
- PipelineCacheBase(BlobCache* cache, const CacheKey& key);
+ // If `storeOnIdle` is true then pipeline cache will only stored in BlobCache when
+ // StoreOnIdle() is called.
+ PipelineCacheBase(BlobCache* cache, const CacheKey& key, bool storeOnIdle);
// Initializes and returns the cached blob given the cache and keys. Used by backend
// implementations to get the cache and set the cache hit state. Should only be called once.
@@ -69,8 +75,10 @@
// the blob cache is guaranteed to be valid throughout the lifetime of the object.
raw_ptr<BlobCache> mCache;
CacheKey mKey;
+ const bool mStoreOnIdle;
bool mInitialized = false;
bool mCacheHit = false;
+ bool mNeedsStore = false;
};
} // namespace dawn::native
diff --git a/src/dawn/native/Toggles.cpp b/src/dawn/native/Toggles.cpp
index 4ea8bd7..9f5cd7f 100644
--- a/src/dawn/native/Toggles.cpp
+++ b/src/dawn/native/Toggles.cpp
@@ -564,6 +564,11 @@
"while the WebGPU CTS is expecting n. Scale the depth bias value by multiple 0.5 on certain "
"backends to achieve conformant result.",
"https://crbug.com/42241017", ToggleStage::Device}},
+ {Toggle::VulkanMonolithicPipelineCache,
+ {"vulkan_monolithic_pipeline_cache",
+ "Use a monolithic VkPipelineCache per device. The embedder is responsible for calling "
+ "PerformIdleTasks() on the device to serialize VkPipelineCache to BlobCache if needed.",
+ "crbug.com/370343334", ToggleStage::Device}},
{Toggle::NoWorkaroundSampleMaskBecomesZeroForAllButLastColorTarget,
{"no_workaround_sample_mask_becomes_zero_for_all_but_last_color_target",
"MacOS 12.0+ Intel has a bug where the sample mask is only applied for the last color "
diff --git a/src/dawn/native/Toggles.h b/src/dawn/native/Toggles.h
index c40770f..aa0e087 100644
--- a/src/dawn/native/Toggles.h
+++ b/src/dawn/native/Toggles.h
@@ -135,6 +135,7 @@
D3D12ForceStencilComponentReplicateSwizzle,
D3D12ExpandShaderResourceStateTransitionsToCopySource,
GLDepthBiasModifier,
+ VulkanMonolithicPipelineCache,
// Unresolved issues.
NoWorkaroundSampleMaskBecomesZeroForAllButLastColorTarget,
diff --git a/src/dawn/native/vulkan/ComputePipelineVk.cpp b/src/dawn/native/vulkan/ComputePipelineVk.cpp
index 4085b22..013989b 100644
--- a/src/dawn/native/vulkan/ComputePipelineVk.cpp
+++ b/src/dawn/native/vulkan/ComputePipelineVk.cpp
@@ -138,8 +138,7 @@
"CreateComputePipelines"));
cacheTimer.RecordMicroseconds("Vulkan.CreateComputePipelines.CacheMiss");
}
- // TODO(dawn:549): Flush is currently in the same thread, but perhaps deferrable.
- DAWN_TRY(cache->FlushIfNeeded());
+ DAWN_TRY(cache->DidCompilePipeline());
SetLabelImpl();
diff --git a/src/dawn/native/vulkan/DeviceVk.cpp b/src/dawn/native/vulkan/DeviceVk.cpp
index 22a4645..560ee7a 100644
--- a/src/dawn/native/vulkan/DeviceVk.cpp
+++ b/src/dawn/native/vulkan/DeviceVk.cpp
@@ -30,6 +30,7 @@
#include "dawn/common/Log.h"
#include "dawn/common/NonCopyable.h"
#include "dawn/common/Platform.h"
+#include "dawn/common/Version_autogen.h"
#include "dawn/native/BackendConnection.h"
#include "dawn/native/ChainUtils.h"
#include "dawn/native/CreatePipelineAsyncEvent.h"
@@ -233,6 +234,19 @@
return TextureView::Create(texture, descriptor);
}
Ref<PipelineCacheBase> Device::GetOrCreatePipelineCacheImpl(const CacheKey& key) {
+ if (IsToggleEnabled(Toggle::VulkanMonolithicPipelineCache)) {
+ if (!mMonolithicPipelineCache) {
+ CacheKey cacheKey = GetCacheKey();
+ // `pipelineCacheUUID` is supposed to change if anything in the driver changes such that
+ // the serialized VkPipelineCache is no longer valid.
+ auto& deviceProperties = GetDeviceInfo().properties;
+ StreamIn(&cacheKey, deviceProperties.pipelineCacheUUID);
+
+ mMonolithicPipelineCache = PipelineCache::CreateMonolithic(this, cacheKey);
+ }
+ return mMonolithicPipelineCache;
+ }
+
return PipelineCache::Create(this, key);
}
void Device::InitializeComputePipelineAsyncImpl(Ref<CreateComputePipelineAsyncEvent> event) {
@@ -994,4 +1008,15 @@
SetDebugName(this, VK_OBJECT_TYPE_DEVICE, mVkDevice, "Dawn_Device", GetLabel());
}
+void Device::PerformIdleTasksImpl() {
+ if (mMonolithicPipelineCache) {
+ MaybeError maybeError = mMonolithicPipelineCache->StoreOnIdle();
+ if (maybeError.IsError()) {
+ std::unique_ptr<ErrorData> error = maybeError.AcquireError();
+ EmitLog(WGPULoggingType_Error, error->GetFormattedMessage().c_str());
+ return;
+ }
+ }
+}
+
} // namespace dawn::native::vulkan
diff --git a/src/dawn/native/vulkan/DeviceVk.h b/src/dawn/native/vulkan/DeviceVk.h
index ba12063..75df610 100644
--- a/src/dawn/native/vulkan/DeviceVk.h
+++ b/src/dawn/native/vulkan/DeviceVk.h
@@ -118,6 +118,7 @@
float GetTimestampPeriodInNS() const override;
void SetLabelImpl() override;
+ void PerformIdleTasksImpl() override;
void OnDebugMessage(std::string message);
@@ -199,6 +200,8 @@
const std::string mDebugPrefix;
std::vector<std::string> mDebugMessages;
+ Ref<PipelineCache> mMonolithicPipelineCache;
+
MaybeError ImportExternalImage(const ExternalImageDescriptorVk* descriptor,
ExternalMemoryHandle memoryHandle,
VkImage image,
diff --git a/src/dawn/native/vulkan/PipelineCacheVk.cpp b/src/dawn/native/vulkan/PipelineCacheVk.cpp
index 40fdfa0..9841077 100644
--- a/src/dawn/native/vulkan/PipelineCacheVk.cpp
+++ b/src/dawn/native/vulkan/PipelineCacheVk.cpp
@@ -31,6 +31,7 @@
#include "dawn/native/Device.h"
#include "dawn/native/Error.h"
+#include "dawn/native/Toggles.h"
#include "dawn/native/vulkan/DeviceVk.h"
#include "dawn/native/vulkan/FencedDeleter.h"
#include "dawn/native/vulkan/VulkanError.h"
@@ -39,13 +40,22 @@
// static
Ref<PipelineCache> PipelineCache::Create(DeviceBase* device, const CacheKey& key) {
- Ref<PipelineCache> cache = AcquireRef(new PipelineCache(device, key));
+ Ref<PipelineCache> cache =
+ AcquireRef(new PipelineCache(device, key, /*isMonolithicCache=*/false));
cache->Initialize();
return cache;
}
-PipelineCache::PipelineCache(DeviceBase* device, const CacheKey& key)
- : PipelineCacheBase(device->GetBlobCache(), key), mDevice(device) {}
+// static
+Ref<PipelineCache> PipelineCache::CreateMonolithic(DeviceBase* device, const CacheKey& key) {
+ Ref<PipelineCache> cache =
+ AcquireRef(new PipelineCache(device, key, /*isMonolithicCache=*/true));
+ cache->Initialize();
+ return cache;
+}
+
+PipelineCache::PipelineCache(DeviceBase* device, const CacheKey& key, bool isMonolithicCache)
+ : PipelineCacheBase(device->GetBlobCache(), key, isMonolithicCache), mDevice(device) {}
PipelineCache::~PipelineCache() {
if (mHandle == VK_NULL_HANDLE) {
@@ -75,13 +85,18 @@
DAWN_TRY(CheckVkSuccess(
device->fn.GetPipelineCacheData(device->GetVkDevice(), mHandle, &bufferSize, nullptr),
"GetPipelineCacheData"));
- if (bufferSize == 0) {
+
+ if (bufferSize == 0 || bufferSize == mStoredDataSize) {
+ // If current VkPipelineCache data size is same as `mCachedDataSize` assume nothing has
+ // changed vs what is stored in the BlobCache.
return {};
}
*blob = CreateBlob(bufferSize);
DAWN_TRY(CheckVkSuccess(
device->fn.GetPipelineCacheData(device->GetVkDevice(), mHandle, &bufferSize, blob->Data()),
"GetPipelineCacheData"));
+ mStoredDataSize = bufferSize;
+
return {};
}
@@ -107,7 +122,10 @@
if (maybeError.IsError()) {
std::unique_ptr<ErrorData> error = maybeError.AcquireError();
GetDevice()->EmitLog(WGPULoggingType_Info, error->GetFormattedMessage().c_str());
+ return;
}
+
+ mStoredDataSize = blob.Size();
}
} // namespace dawn::native::vulkan
diff --git a/src/dawn/native/vulkan/PipelineCacheVk.h b/src/dawn/native/vulkan/PipelineCacheVk.h
index 68ef783..8a260f9 100644
--- a/src/dawn/native/vulkan/PipelineCacheVk.h
+++ b/src/dawn/native/vulkan/PipelineCacheVk.h
@@ -44,11 +44,15 @@
public:
static Ref<PipelineCache> Create(DeviceBase* device, const CacheKey& key);
+ // Creates a pipeline cache that is intended to be monolithic. The cache will only be serialized
+ // and stored to BlobCache when StoreOnIdle() is called.
+ static Ref<PipelineCache> CreateMonolithic(DeviceBase* device, const CacheKey& key);
+
DeviceBase* GetDevice() const;
VkPipelineCache GetHandle() const;
private:
- explicit PipelineCache(DeviceBase* device, const CacheKey& key);
+ explicit PipelineCache(DeviceBase* device, const CacheKey& key, bool isMonolithicCache);
~PipelineCache() override;
void Initialize();
@@ -56,6 +60,8 @@
raw_ptr<DeviceBase> mDevice;
VkPipelineCache mHandle = VK_NULL_HANDLE;
+
+ size_t mStoredDataSize = 0;
};
} // namespace dawn::native::vulkan
diff --git a/src/dawn/native/vulkan/RenderPipelineVk.cpp b/src/dawn/native/vulkan/RenderPipelineVk.cpp
index 4037536..151bec7 100644
--- a/src/dawn/native/vulkan/RenderPipelineVk.cpp
+++ b/src/dawn/native/vulkan/RenderPipelineVk.cpp
@@ -594,8 +594,7 @@
cacheTimer.RecordMicroseconds("Vulkan.CreateGraphicsPipelines.CacheMiss");
}
- // TODO(dawn:549): Flush is currently in the same thread, but perhaps deferrable.
- DAWN_TRY(cache->FlushIfNeeded());
+ DAWN_TRY(cache->DidCompilePipeline());
SetLabelImpl();