blob: 5b03aad2814916cc9357f5a22b2f52ee66b0b48e [file] [log] [blame] [edit]
/*
* Copyright 2023 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "src/gpu/graphite/ProxyCache.h"
#include "include/core/SkBitmap.h"
#include "include/core/SkPixelRef.h"
#include "include/gpu/GpuTypes.h"
#include "src/core/SkMipmap.h"
#include "src/gpu/ResourceKey.h"
#include "src/gpu/graphite/Image_Graphite.h"
#include "src/gpu/graphite/RecorderPriv.h"
#include "src/gpu/graphite/Texture.h"
#include "src/gpu/graphite/TextureProxy.h"
#include "src/gpu/graphite/TextureUtils.h"
using namespace skia_private;
DECLARE_SKMESSAGEBUS_MESSAGE(skgpu::UniqueKeyInvalidatedMsg_Graphite, uint32_t,
/* AllowCopyableMessage= */ true)
namespace {
void make_bitmap_key(skgpu::UniqueKey* key, const SkBitmap& bm) {
SkASSERT(key);
SkIPoint origin = bm.pixelRefOrigin();
SkIRect subset = SkIRect::MakePtSize(origin, bm.dimensions());
static const skgpu::UniqueKey::Domain kProxyCacheDomain = skgpu::UniqueKey::GenerateDomain();
skgpu::UniqueKey::Builder builder(key, kProxyCacheDomain, 5, "ProxyCache");
builder[0] = bm.pixelRef()->getGenerationID();
builder[1] = subset.fLeft;
builder[2] = subset.fTop;
builder[3] = subset.fRight;
builder[4] = subset.fBottom;
}
sk_sp<SkIDChangeListener> make_unique_key_invalidation_listener(const skgpu::UniqueKey& key,
uint32_t recorderID) {
class Listener : public SkIDChangeListener {
public:
Listener(const skgpu::UniqueKey& key, uint32_t recorderUniqueID)
: fMsg(key, recorderUniqueID) {}
void changed() override {
SkMessageBus<skgpu::UniqueKeyInvalidatedMsg_Graphite, uint32_t>::Post(fMsg);
}
private:
skgpu::UniqueKeyInvalidatedMsg_Graphite fMsg;
};
return sk_make_sp<Listener>(key, recorderID);
}
} // anonymous namespace
namespace skgpu::graphite {
ProxyCache::ProxyCache(uint32_t recorderID) : fInvalidUniqueKeyInbox(recorderID) {
SkASSERT(recorderID != SK_InvalidGenID);
}
ProxyCache::~ProxyCache() {}
uint32_t ProxyCache::UniqueKeyHash::operator()(const UniqueKey& key) const {
return key.hash();
}
template <typename CreateEntryFn>
sk_sp<TextureProxy> ProxyCache::findOrCreateCacheEntry(const UniqueKey& key,
std::string_view label,
CreateEntryFn fn) {
this->processInvalidKeyMsgs();
if (CacheEntry* cached = fCache.find(key)) {
SkASSERT(cached->fProxy);
if (Resource* resource = cached->fProxy->texture()) {
resource->updateAccessTime();
}
return cached->fProxy;
}
CacheEntry newEntry = fn(label.empty() ? key.tag() : label);
if (newEntry.fProxy) {
// Success, add it to the cache
fCache.set(key, newEntry);
}
return newEntry.fProxy;
}
sk_sp<TextureProxy> ProxyCache::findOrCreateCachedProxy(Recorder* recorder,
const SkBitmap& bitmap,
std::string_view label) {
skgpu::UniqueKey key;
make_bitmap_key(&key, bitmap);
return this->findOrCreateCachedProxy(
recorder, key, &bitmap,
[](const void* context) { return *static_cast<const SkBitmap*>(context); },
label);
}
sk_sp<TextureProxy> ProxyCache::findOrCreateCachedProxy(Recorder* recorder,
const UniqueKey& key,
GeneratorContext context,
BitmapGeneratorFn generator,
std::string_view label) {
return this->findOrCreateCacheEntry(key, label, [&](std::string_view finalLabel) {
SkBitmap bitmap = generator(context);
if (bitmap.empty()) {
return CacheEntry{};
}
auto [ view, ct ] = MakeBitmapProxyView(recorder, bitmap, nullptr, Mipmapped::kNo,
Budgeted::kYes, finalLabel);
if (!view) {
return CacheEntry{};
}
// Since if the bitmap is held by more than just this function call (e.g. it likely came
// from findOrCreateCachedProxy() that takes an existing SkBitmap), it's worth adding a
// listener to remove them from the cache automatically when no one holds on to it anymore.
// NOTE: We add listeners even if the bitmap is immutable because the listener triggers when
// the bitmap is destroyed. We avoid leaking listeners when the proxy cache is purged due
// to unuse by marking old listeners for cleanup.
sk_sp<SkIDChangeListener> listener = nullptr;
const bool addListener = !bitmap.pixelRef()->unique();
if (addListener) {
listener = make_unique_key_invalidation_listener(key, recorder->priv().uniqueID());
bitmap.pixelRef()->addGenIDChangeListener(listener);
}
return CacheEntry{view.refProxy(), std::move(listener)};
});
}
sk_sp<TextureProxy> ProxyCache::findOrCreateCachedProxy(Recorder* recorder,
const UniqueKey& key,
GeneratorContext context,
GPUGeneratorFn fn,
std::string_view label) {
return this->findOrCreateCacheEntry(key, label, [&](std::string_view finalLabel) {
sk_sp<Image> textureImage = fn(recorder, context);
if (!textureImage) {
return CacheEntry{};
}
// Force `textureImage`'s TextureProxy to be instantiated so that it's not treated by a
// Recorder as a scratch image that can have a temporary scratch texture assignment.
textureImage->textureProxyView().proxy()->instantiate(recorder->priv().resourceProvider());
// Flush pending work defining the image's content, which also adds these tasks to the root
// task list.
// TODO(b/409888039): These added tasks need to be preserved so that later Recordings that
// get cache hits in the ProxyCache can also initialize the texture if they are added out
// of order relative to this triggering Recording.
textureImage->notifyInUse(recorder, /*drawContext=*/nullptr);
// GPU created proxys never have SkIDChangeListeners.
return CacheEntry{textureImage->textureProxyView().refProxy(),
/*listener=*/nullptr};
});
}
void ProxyCache::purgeAll() {
// removeEntriesAndListeners() without having to copy out all of the keys
fCache.foreach([](const skgpu::UniqueKey&, const CacheEntry* entry) {
if (entry->fListener) {
entry->fListener->markShouldDeregister();
}
});
fCache.reset();
}
void ProxyCache::processInvalidKeyMsgs() {
TArray<skgpu::UniqueKeyInvalidatedMsg_Graphite> invalidKeyMsgs;
fInvalidUniqueKeyInbox.poll(&invalidKeyMsgs);
if (!invalidKeyMsgs.empty()) {
for (int i = 0; i < invalidKeyMsgs.size(); ++i) {
// NOTE(crbug.com/1480570): A change listener is only invoked once, so we shouldn't see
// an invalid message added twice for the same entry. However, we can remove the entry
// due to other reasons while the bitmap listener owner is still alive. While we mark
// the listener to de-register itself, there is still a race where we could decide to
// remove the entry on one thread but haven't de-registered it yet, then another thread
// cleans up the bitmap and posts a message, then the first thread removes the entry the
// posted message also wants to remove.
if (fCache.find(invalidKeyMsgs[i].key())) {
fCache.remove(invalidKeyMsgs[i].key());
}
}
}
}
void ProxyCache::removeEntriesAndListeners(SkSpan<const UniqueKey> toRemove) {
// This assumes that the entry removal is coming from not polling the invalid key
// messages, so it's necessary to mark the listeners as done. Removing the listeners also means
// we don't leak change listeners if the bitmap is ever re-cached.
for (const UniqueKey& k : toRemove) {
CacheEntry* e = fCache.find(k);
if (e->fListener) {
e->fListener->markShouldDeregister();
}
fCache.remove(k);
}
}
void ProxyCache::freeUniquelyHeld() {
this->processInvalidKeyMsgs();
skia_private::TArray<skgpu::UniqueKey> toRemove;
fCache.foreach([&](const skgpu::UniqueKey& key, const CacheEntry* entry) {
SkASSERT(entry->fProxy);
if (entry->fProxy->unique()) {
toRemove.push_back(key);
}
});
this->removeEntriesAndListeners(toRemove);
}
void ProxyCache::purgeProxiesNotUsedSince(const skgpu::StdSteadyClock::time_point* purgeTime) {
this->processInvalidKeyMsgs();
skia_private::TArray<skgpu::UniqueKey> toRemove;
fCache.foreach([&](const skgpu::UniqueKey& key, const CacheEntry* entry) {
SkASSERT(entry->fProxy);
if (Resource* resource = entry->fProxy->texture();
resource &&
(!purgeTime || resource->lastAccessTime() < *purgeTime)) {
toRemove.push_back(key);
}
});
this->removeEntriesAndListeners(toRemove);
}
#if defined(GPU_TEST_UTILS)
int ProxyCache::numCached() const {
return fCache.count();
}
sk_sp<TextureProxy> ProxyCache::find(const SkBitmap& bitmap) {
skgpu::UniqueKey key;
make_bitmap_key(&key, bitmap);
if (CacheEntry* cached = fCache.find(key)) {
SkASSERT(cached->fProxy);
return cached->fProxy;
}
return nullptr;
}
void ProxyCache::forceProcessInvalidKeyMsgs() {
this->processInvalidKeyMsgs();
}
void ProxyCache::forceFreeUniquelyHeld() {
this->freeUniquelyHeld();
}
void ProxyCache::forcePurgeProxiesNotUsedSince(skgpu::StdSteadyClock::time_point purgeTime) {
this->purgeProxiesNotUsedSince(&purgeTime);
}
#endif // defined(GPU_TEST_UTILS)
} // namespace skgpu::graphite