| /* |
| * 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 |