Add TextureView caching.
Built off a suggestion by lokokung@
(https://dawn-review.googlesource.com/c/dawn/+/244496),
adds TextureView caching to the Dawn front end.
Caching only applies to Textures created by API
calls, not those created internally (ie:
temporary textures created for workarounds.)
Bug: 416088623
Change-Id: I9cb94e8b24ac25485febb1b3c442a4c84fcfb20e
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/245014
Auto-Submit: Brandon Jones <bajones@chromium.org>
Commit-Queue: Brandon Jones <bajones@chromium.org>
Reviewed-by: Loko Kung <lokokung@google.com>
diff --git a/src/dawn/common/BUILD.gn b/src/dawn/common/BUILD.gn
index 8c0d76e..a2fa395 100644
--- a/src/dawn/common/BUILD.gn
+++ b/src/dawn/common/BUILD.gn
@@ -284,6 +284,7 @@
"GPUInfo.h",
"HashUtils.h",
"IOKitRef.h",
+ "LRUCache.h",
"LinkedList.h",
"Log.cpp",
"Log.h",
diff --git a/src/dawn/common/CMakeLists.txt b/src/dawn/common/CMakeLists.txt
index 7f723d5..bbf2ccd 100644
--- a/src/dawn/common/CMakeLists.txt
+++ b/src/dawn/common/CMakeLists.txt
@@ -69,6 +69,7 @@
"ityp_vector.h"
"LinkedList.h"
"Log.h"
+ "LRUCache.h"
"MatchVariant.h"
"Math.h"
"Mutex.h"
diff --git a/src/dawn/common/LRUCache.h b/src/dawn/common/LRUCache.h
new file mode 100644
index 0000000..a57c6ac
--- /dev/null
+++ b/src/dawn/common/LRUCache.h
@@ -0,0 +1,161 @@
+// Copyright 2025 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef SRC_DAWN_COMMON_LRUCACHE_H_
+#define SRC_DAWN_COMMON_LRUCACHE_H_
+
+#include <array>
+#include <list>
+#include <utility>
+
+#include "absl/container/flat_hash_map.h"
+#include "dawn/common/MutexProtected.h"
+#include "dawn/common/Result.h"
+
+namespace dawn {
+
+// LRUCache is a Least-Recently-Used cache that retains the most recently queried values (up to
+// mCapacity), and evicts older items from the cache as new values are created.
+// Example usage:
+// class CachedValue {
+// public:
+// CachedValue(uint32_t a, bool b);
+// };
+// struct CachedValueKey {
+// uint32_t a;
+// bool b;
+// };
+//
+// struct CachedValueCacheFuncs {
+// size_t operator()(const CachedValueCacheFuncs& key) const {
+// size_t hash = Hash(key.a);
+// HashCombine(&hash, key.b);
+// return hash;
+// }
+// bool operator()(const CachedValueCacheFuncs& a, const CachedValueCacheFuncs& b) const {
+// return a.a == b.a && a.b == b.b;
+// }
+// };
+//
+// class CacheUser {
+// public:
+// static const kCacheCapacity = 32;
+// CacheUser() : mCache(kCacheCapacity) {}
+//
+// Ref<CachedValue> GetValue(const CachedValueKey& valueKey) {
+// return mCache.GetOrCreate(valueKey, [](const CachedValueKey& key) -> {
+// return Ref<CachedValue>(new CachedValue(key.a, key.b));
+// });
+// }
+//
+// private:
+// using ValueCache = LRUCache<CachedValueKey, CachedValue, ErrorData, CachedValueCacheFuncs>;
+// ValueCache mCache;
+// };
+
+template <typename Key, typename Value, typename Error, typename CacheFuncs>
+class LRUCache {
+ public:
+ explicit LRUCache(size_t capacity) : mCapacity(capacity) {}
+ virtual ~LRUCache() = default;
+
+ template <typename CreateFn>
+ Result<Value, Error> GetOrCreate(Key& key, CreateFn createFn) {
+ // A capacity of 0 means caching is disabled, so just return the result
+ // of the createFn directly.
+ if (mCapacity == 0) {
+ auto result = createFn(key);
+ if (result.IsError()) [[unlikely]] {
+ return Result<Value, Error>(result.AcquireError());
+ }
+ auto value = result.AcquireSuccess();
+ // If caching is disabled treat every value as if it is immediately evicted from the
+ // cache on creation.
+ EvictedFromCache(value);
+ return value;
+ }
+
+ return mCache.Use([&](auto cache) -> Result<Value, Error> {
+ // Try to lookup |Key| to see if we have a cache hit.
+ auto it = cache->map.find(key);
+ if (it != cache->map.end()) {
+ // Using iterators as a stable reference like this works because "Adding, removing,
+ // and moving the elements within the list or across several lists does not
+ // invalidate the iterators or references. An iterator is invalidated only when the
+ // corresponding element is deleted."
+ // (From https://en.cppreference.com/w/cpp/container/list)
+ cache->list.splice(cache->list.begin(), cache->list, it->second);
+ return it->second->second;
+ }
+
+ // Otherwise, we need to try to create the entry.
+ Result<Value, Error> result = createFn(key);
+ if (result.IsError()) [[unlikely]] {
+ return Result<Value, Error>(result.AcquireError());
+ }
+ auto value = result.AcquireSuccess();
+ cache->list.emplace_front(key, value);
+ cache->map.emplace(key, cache->list.begin());
+
+ // If the LRU has exceeded it's capacity, remove the oldest entry.
+ if (cache->list.size() > mCapacity) {
+ auto back = cache->list.back();
+ EvictedFromCache(back.second);
+ cache->map.erase(back.first);
+ cache->list.pop_back();
+ }
+
+ return value;
+ });
+ }
+
+ void Clear() {
+ mCache.Use([](auto cache) {
+ cache->map.clear();
+ cache->list.clear();
+ });
+ }
+
+ // Override if values need to be deleted or otherwise handled as they are evicted from the
+ // cache. Useful for values that are not Ref<> values.
+ virtual void EvictedFromCache(const Value& value) {}
+
+ protected:
+ const size_t mCapacity;
+
+ using RecentList = std::list<std::pair<Key, Value>>;
+ using Map = absl::flat_hash_map<Key, typename RecentList::iterator, CacheFuncs, CacheFuncs>;
+ struct Cache {
+ RecentList list;
+ Map map;
+ };
+ MutexProtected<Cache> mCache;
+};
+
+} // namespace dawn
+
+#endif // SRC_DAWN_COMMON_LRUCACHE_H_
diff --git a/src/dawn/common/RefCountedWithExternalCount.h b/src/dawn/common/RefCountedWithExternalCount.h
index 149fccc..86bd70b 100644
--- a/src/dawn/common/RefCountedWithExternalCount.h
+++ b/src/dawn/common/RefCountedWithExternalCount.h
@@ -36,9 +36,12 @@
// refcount for calls to APIAddRef/APIRelease (refs added/removed by the application).
// The external refcount starts at 0, and the total refcount starts at 1 - i.e. the first
// ref isn't an external ref.
-// When the external refcount drops to zero, WillDropLastExternalRef is called. and it can be called
+// When the external refcount is incremented from 0 to 1, WillAddFirstExternalRef is called, and it
+// can be called more than once.
+// When the external refcount drops to zero, WillDropLastExternalRef is called, and it can be called
// more than once.
-// The derived class must override the behavior of WillDropLastExternalRef.
+// The derived class must override the behavior of WillDropLastExternalRef, overriding
+// WillAddFirstExternalRef is optional.
template <typename T>
class RefCountedWithExternalCount : public T {
public:
@@ -58,13 +61,18 @@
T::APIRelease();
}
- void IncrementExternalRefCount() { mExternalRefCount.Increment(); }
+ void IncrementExternalRefCount() {
+ if (mExternalRefCount.Increment()) {
+ WillAddFirstExternalRef();
+ }
+ }
uint64_t GetExternalRefCountForTesting() const {
return mExternalRefCount.GetValueForTesting();
}
private:
+ virtual void WillAddFirstExternalRef() {}
virtual void WillDropLastExternalRef() = 0;
RefCount mExternalRefCount{/*initCount=*/0, /*payload=*/0};
diff --git a/src/dawn/native/Device.cpp b/src/dawn/native/Device.cpp
index 6ab35e6..2cd37d1 100644
--- a/src/dawn/native/Device.cpp
+++ b/src/dawn/native/Device.cpp
@@ -2256,7 +2256,11 @@
} else {
descriptor = Unpack(&desc);
}
- return CreateTextureViewImpl(texture, descriptor);
+
+ return texture->GetOrCreateViewFromCache(
+ descriptor, [&](TextureViewQuery&) -> ResultOrError<Ref<TextureViewBase>> {
+ return CreateTextureViewImpl(texture, descriptor);
+ });
}
// Other implementation details
diff --git a/src/dawn/native/Surface.cpp b/src/dawn/native/Surface.cpp
index d0b30ae..cf39927 100644
--- a/src/dawn/native/Surface.cpp
+++ b/src/dawn/native/Surface.cpp
@@ -608,6 +608,7 @@
void Surface::APIGetCurrentTexture(SurfaceTexture* surfaceTexture) const {
MaybeError maybeError = GetCurrentTexture(surfaceTexture);
+
if (!GetCurrentDevice()) {
[[maybe_unused]] bool error = mInstance->ConsumedError(std::move(maybeError));
} else {
diff --git a/src/dawn/native/SwapChain.cpp b/src/dawn/native/SwapChain.cpp
index 9af635f..7660eba 100644
--- a/src/dawn/native/SwapChain.cpp
+++ b/src/dawn/native/SwapChain.cpp
@@ -129,7 +129,8 @@
DAWN_ASSERT(mCurrentTextureInfo.texture->GetViewFormats() == ComputeViewFormatSet());
// Calling GetCurrentTexture always returns a new reference.
- surfaceTexture.texture = Ref<TextureBase>(mCurrentTextureInfo.texture).Detach();
+ auto texture = mCurrentTextureInfo.texture;
+ surfaceTexture.texture = ReturnToAPI(std::move(texture));
return surfaceTexture;
}
diff --git a/src/dawn/native/Texture.cpp b/src/dawn/native/Texture.cpp
index ca093b3..29485bd 100644
--- a/src/dawn/native/Texture.cpp
+++ b/src/dawn/native/Texture.cpp
@@ -33,6 +33,7 @@
#include "absl/strings/str_format.h"
#include "dawn/common/Assert.h"
#include "dawn/common/Constants.h"
+#include "dawn/common/HashUtils.h"
#include "dawn/common/Math.h"
#include "dawn/native/Adapter.h"
#include "dawn/native/BlitBufferToTexture.h"
@@ -925,7 +926,7 @@
TextureBase::TextureState::TextureState() : hasAccess(true), destroyed(false) {}
TextureBase::TextureBase(DeviceBase* device, const UnpackedPtr<TextureDescriptor>& descriptor)
- : SharedResource(device, descriptor->label),
+ : RefCountedWithExternalCount<SharedResource>(device, descriptor->label),
mDimension(descriptor->dimension),
mCompatibilityTextureBindingViewDimension(
ResolveDefaultCompatiblityTextureBindingViewDimension(device, descriptor)),
@@ -965,7 +966,7 @@
TextureBase::TextureBase(DeviceBase* device,
const TextureDescriptor* descriptor,
ObjectBase::ErrorTag tag)
- : SharedResource(device, tag, descriptor->label),
+ : RefCountedWithExternalCount<SharedResource>(device, tag, descriptor->label),
mDimension(descriptor->dimension),
mFormat(kUnusedFormat),
mBaseSize(descriptor->size),
@@ -987,6 +988,9 @@
// other threads using the texture since there are no other live refs.
mState.destroyed = true;
+ // Drop all the cache references to TextureViews.
+ mTextureViewCache = nullptr;
+
// Destroy all of the views associated with the texture as well.
mTextureViews.Destroy();
}
@@ -1012,6 +1016,21 @@
}
}
+void TextureBase::WillAddFirstExternalRef() {
+ // Only enable the view cache once an external reference has been added. This prevents textures
+ // created for internal uses, such as workarounds, from being kept alive by the views in the
+ // cache.
+ if (!IsError()) {
+ mTextureViewCache = std::make_unique<TextureViewCache>(kDefaultTextureViewCacheCapacity);
+ }
+}
+
+void TextureBase::WillDropLastExternalRef() {
+ // Drop all the additional references to TextureViews that we were holding as a part of the
+ // cache.
+ mTextureViewCache = nullptr;
+}
+
std::string TextureBase::GetSizeLabel() const {
if (mDimension == wgpu::TextureDimension::e1D) {
return absl::StrFormat("%d px", mBaseSize.width);
@@ -1433,6 +1452,36 @@
return mUsage;
}
+// TextureViewQuery
+
+TextureViewQuery::TextureViewQuery(const UnpackedPtr<TextureViewDescriptor>& desc) {
+ // TextureViewDescriptor fields
+ format = desc->format;
+ dimension = desc->dimension;
+ baseMipLevel = desc->baseMipLevel;
+ mipLevelCount = desc->mipLevelCount;
+ baseArrayLayer = desc->baseArrayLayer;
+ arrayLayerCount = desc->arrayLayerCount;
+ aspect = desc->aspect;
+ usage = desc->usage;
+}
+
+// TextureViewCacheFuncs
+
+size_t TextureViewCacheFuncs::operator()(const TextureViewQuery& desc) const {
+ size_t hash = Hash(desc.format);
+
+ HashCombine(&hash, desc.dimension, desc.aspect, desc.usage);
+ HashCombine(&hash, desc.baseMipLevel, desc.mipLevelCount, desc.baseArrayLayer,
+ desc.arrayLayerCount);
+
+ return hash;
+}
+
+bool TextureViewCacheFuncs::operator()(const TextureViewQuery& a, const TextureViewQuery& b) const {
+ return a == b;
+}
+
// TextureViewBase
TextureViewBase::TextureViewBase(TextureBase* texture,
diff --git a/src/dawn/native/Texture.h b/src/dawn/native/Texture.h
index 19b1d7e..3743655 100644
--- a/src/dawn/native/Texture.h
+++ b/src/dawn/native/Texture.h
@@ -28,9 +28,12 @@
#ifndef SRC_DAWN_NATIVE_TEXTURE_H_
#define SRC_DAWN_NATIVE_TEXTURE_H_
+#include <memory>
#include <string>
#include <vector>
+#include "dawn/common/LRUCache.h"
+#include "dawn/common/RefCountedWithExternalCount.h"
#include "dawn/common/WeakRef.h"
#include "dawn/common/ityp_array.h"
#include "dawn/common/ityp_bitset.h"
@@ -84,7 +87,31 @@
kShaderTextureUsages | kResolveTextureLoadAndStoreUsages |
wgpu::TextureUsage::TransientAttachment | wgpu::TextureUsage::StorageAttachment;
-class TextureBase : public SharedResource {
+// A flattened version of TextureViewDescriptor used to query the texture view cache.
+struct TextureViewQuery {
+ explicit TextureViewQuery(const UnpackedPtr<TextureViewDescriptor>& desc);
+ bool operator==(const TextureViewQuery& b) const = default;
+
+ // TextureViewDescriptor fields (label ignored)
+ wgpu::TextureFormat format;
+ wgpu::TextureViewDimension dimension;
+ uint32_t baseMipLevel;
+ uint32_t mipLevelCount;
+ uint32_t baseArrayLayer;
+ uint32_t arrayLayerCount;
+ wgpu::TextureAspect aspect;
+ wgpu::TextureUsage usage;
+
+ // Update with fields from relevant chained structs as they are added.
+};
+
+static const size_t kDefaultTextureViewCacheCapacity = 4;
+struct TextureViewCacheFuncs {
+ size_t operator()(const TextureViewQuery& desc) const;
+ bool operator()(const TextureViewQuery& a, const TextureViewQuery& b) const;
+};
+
+class TextureBase : public RefCountedWithExternalCount<SharedResource> {
public:
enum class ClearValue { Zero, NonZero };
@@ -164,6 +191,11 @@
uint64_t ComputeEstimatedByteSize() const;
+ template <typename CreateFn>
+ ResultOrError<Ref<TextureViewBase>> GetOrCreateViewFromCache(
+ const UnpackedPtr<TextureViewDescriptor>& desc,
+ CreateFn createFn);
+
// Dawn API
TextureViewBase* APICreateView(const TextureViewDescriptor* descriptor = nullptr);
TextureViewBase* APICreateErrorView(const TextureViewDescriptor* descriptor = nullptr);
@@ -201,6 +233,9 @@
std::string GetSizeLabel() const;
+ void WillAddFirstExternalRef() override;
+ void WillDropLastExternalRef() override;
+
wgpu::TextureDimension mDimension;
// Only used for compatibility mode
wgpu::TextureViewDimension mCompatibilityTextureBindingViewDimension =
@@ -219,10 +254,27 @@
// is destroyed.
ApiObjectList mTextureViews;
+ using TextureViewCache =
+ LRUCache<TextureViewQuery, Ref<TextureViewBase>, ErrorData, TextureViewCacheFuncs>;
+ std::unique_ptr<TextureViewCache> mTextureViewCache;
+
// TODO(crbug.com/dawn/845): Use a more optimized data structure to save space
std::vector<bool> mIsSubresourceContentInitializedAtIndex;
};
+template <typename CreateFn>
+ResultOrError<Ref<TextureViewBase>> TextureBase::GetOrCreateViewFromCache(
+ const UnpackedPtr<TextureViewDescriptor>& desc,
+ CreateFn createFn) {
+ TextureViewQuery query(desc);
+
+ if (!mTextureViewCache) {
+ return createFn(query);
+ }
+
+ return mTextureViewCache->GetOrCreate(query, createFn);
+}
+
class TextureViewBase : public ApiObjectBase {
public:
TextureViewBase(TextureBase* texture, const UnpackedPtr<TextureViewDescriptor>& descriptor);