From 5e150851d0dd5ddb161449b44edf1bf52d18ac5a Mon Sep 17 00:00:00 2001 From: Brian Salomon Date: Wed, 22 Mar 2017 14:53:13 -0400 Subject: [PATCH] Revert "Revert "Add a new GrResourceCache purging mechanism for purging unused resources."" This reverts commit 20c322ef0cd04cf8e2592879d05d9f4e6cb19596. Change-Id: I6df9a8594484837672308dc2c21c7c29b76ffa2c Reviewed-on: https://skia-review.googlesource.com/10013 Commit-Queue: Brian Salomon Reviewed-by: Robert Phillips --- include/gpu/GrContext.h | 6 +++ include/gpu/GrGpuResource.h | 24 +++++---- include/gpu/GrTypesPriv.h | 10 ++++ src/gpu/GrContext.cpp | 5 ++ src/gpu/GrGpuResourceCacheAccess.h | 12 +++++ src/gpu/GrResourceCache.cpp | 19 +++++++ src/gpu/GrResourceCache.h | 8 ++- tests/ResourceCacheTest.cpp | 105 ++++++++++++++++++++++++++++++++++++- 8 files changed, 175 insertions(+), 14 deletions(-) diff --git a/include/gpu/GrContext.h b/include/gpu/GrContext.h index 959a0fa..ea4f422 100644 --- a/include/gpu/GrContext.h +++ b/include/gpu/GrContext.h @@ -160,6 +160,12 @@ public: */ void purgeAllUnlockedResources(); + /** + * Purge GPU resources that haven't been used in the past 'ms' milliseconds, regardless of + * whether the context is currently under budget. + */ + void purgeResourcesNotUsedInMs(std::chrono::milliseconds ms); + /** Access the context capabilities */ const GrCaps* caps() const { return fCaps; } diff --git a/include/gpu/GrGpuResource.h b/include/gpu/GrGpuResource.h index e0a7903..2d84c63 100644 --- a/include/gpu/GrGpuResource.h +++ b/include/gpu/GrGpuResource.h @@ -315,28 +315,30 @@ private: void makeUnbudgeted(); #ifdef SK_DEBUG - friend class GrGpu; // for assert in GrGpu to access getGpu + friend class GrGpu; // for assert in GrGpu to access getGpu #endif + // An index into a heap when this resource is purgeable or an array when not. This is maintained // by the cache. - int fCacheArrayIndex; + int fCacheArrayIndex; // This value reflects how recently this resource was accessed in the cache. This is maintained // by the cache. - uint32_t fTimestamp; - uint32_t fExternalFlushCntWhenBecamePurgeable; + uint32_t fTimestamp; + uint32_t fExternalFlushCntWhenBecamePurgeable; + GrStdSteadyClock::time_point fTimeWhenBecamePurgeable; static const size_t kInvalidGpuMemorySize = ~static_cast(0); - GrScratchKey fScratchKey; - GrUniqueKey fUniqueKey; + GrScratchKey fScratchKey; + GrUniqueKey fUniqueKey; // This is not ref'ed but abandon() or release() will be called before the GrGpu object // is destroyed. Those calls set will this to NULL. - GrGpu* fGpu; - mutable size_t fGpuMemorySize; + GrGpu* fGpu; + mutable size_t fGpuMemorySize; - SkBudgeted fBudgeted; - bool fRefsWrappedObjects; - const UniqueID fUniqueID; + SkBudgeted fBudgeted; + bool fRefsWrappedObjects; + const UniqueID fUniqueID; typedef GrIORef INHERITED; friend class GrIORef; // to access notifyAllCntsAreZero and notifyRefCntIsZero. diff --git a/include/gpu/GrTypesPriv.h b/include/gpu/GrTypesPriv.h index 28c3335..5db1c24 100644 --- a/include/gpu/GrTypesPriv.h +++ b/include/gpu/GrTypesPriv.h @@ -8,9 +8,19 @@ #ifndef GrTypesPriv_DEFINED #define GrTypesPriv_DEFINED +#include #include "GrTypes.h" #include "SkRefCnt.h" +// The old libstdc++ uses the draft name "monotonic_clock" rather than "steady_clock". This might +// not actually be monotonic, depending on how libstdc++ was built. However, this is only currently +// used for idle resource purging so it shouldn't cause a correctness problem. +#if defined(__GLIBCXX__) && (__GLIBCXX__ < 20130000) +using GrStdSteadyClock = std::chrono::monotonic_clock; +#else +using GrStdSteadyClock = std::chrono::steady_clock; +#endif + /** This enum indicates the type of antialiasing to be performed. */ enum class GrAAType : unsigned { /** No antialiasing */ diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp index 93fd324..56d8117 100644 --- a/src/gpu/GrContext.cpp +++ b/src/gpu/GrContext.cpp @@ -193,6 +193,11 @@ void GrContext::freeGpuResources() { fResourceCache->purgeAllUnlocked(); } +void GrContext::purgeResourcesNotUsedInMs(std::chrono::milliseconds ms) { + ASSERT_SINGLE_OWNER + fResourceCache->purgeResourcesNotUsedSince(GrStdSteadyClock::now() - ms); +} + void GrContext::getResourceCacheUsage(int* resourceCount, size_t* resourceBytes) const { ASSERT_SINGLE_OWNER diff --git a/src/gpu/GrGpuResourceCacheAccess.h b/src/gpu/GrGpuResourceCacheAccess.h index e91f899..cfc18e7 100644 --- a/src/gpu/GrGpuResourceCacheAccess.h +++ b/src/gpu/GrGpuResourceCacheAccess.h @@ -63,6 +63,10 @@ private: SkASSERT(fResource->isPurgeable()); fResource->fExternalFlushCntWhenBecamePurgeable = cnt; } + void setTimeWhenResourceBecomePurgeable() { + SkASSERT(fResource->isPurgeable()); + fResource->fTimeWhenBecamePurgeable = GrStdSteadyClock::now(); + } /** * Called by the cache to determine whether this resource has been puregable for more than * a threshold number of external flushes. @@ -71,6 +75,14 @@ private: SkASSERT(fResource->isPurgeable()); return fResource->fExternalFlushCntWhenBecamePurgeable; } + /** + * Called by the cache to determine whether this resource should be purged based on the length + * of time it has been available for purging. + */ + GrStdSteadyClock::time_point timeWhenResourceBecamePurgeable() { + SkASSERT(fResource->isPurgeable()); + return fResource->fTimeWhenBecamePurgeable; + } int* accessCacheIndex() const { return &fResource->fCacheArrayIndex; } diff --git a/src/gpu/GrResourceCache.cpp b/src/gpu/GrResourceCache.cpp index 9462a73..596af6d 100644 --- a/src/gpu/GrResourceCache.cpp +++ b/src/gpu/GrResourceCache.cpp @@ -365,6 +365,7 @@ void GrResourceCache::notifyCntReachedZero(GrGpuResource* resource, uint32_t fla this->removeFromNonpurgeableArray(resource); fPurgeableQueue.insert(resource); resource->cacheAccess().setFlushCntWhenResourceBecamePurgeable(fExternalFlushCnt); + resource->cacheAccess().setTimeWhenResourceBecomePurgeable(); if (SkBudgeted::kNo == resource->resourcePriv().isBudgeted()) { // Check whether this resource could still be used as a scratch resource. @@ -504,6 +505,24 @@ void GrResourceCache::purgeAllUnlocked() { this->validate(); } +void GrResourceCache::purgeResourcesNotUsedSince(GrStdSteadyClock::time_point purgeTime) { + while (fPurgeableQueue.count()) { + const GrStdSteadyClock::time_point resourceTime = + fPurgeableQueue.peek()->cacheAccess().timeWhenResourceBecamePurgeable(); + if (resourceTime >= purgeTime) { + // Resources were given both LRU timestamps and tagged with a frame number when + // they first became purgeable. The LRU timestamp won't change again until the + // resource is made non-purgeable again. So, at this point all the remaining + // resources in the timestamp-sorted queue will have a frame number >= to this + // one. + break; + } + GrGpuResource* resource = fPurgeableQueue.peek(); + SkASSERT(resource->isPurgeable()); + resource->cacheAccess().release(); + } +} + void GrResourceCache::processInvalidUniqueKeys( const SkTArray& msgs) { for (int i = 0; i < msgs.count(); ++i) { diff --git a/src/gpu/GrResourceCache.h b/src/gpu/GrResourceCache.h index 5f08a51..d871c9a 100644 --- a/src/gpu/GrResourceCache.h +++ b/src/gpu/GrResourceCache.h @@ -50,8 +50,9 @@ public: static const int kDefaultMaxCount = 2 * (1 << 12); // Default maximum number of bytes of gpu memory of budgeted resources in the cache. static const size_t kDefaultMaxSize = 96 * (1 << 20); - // Default number of external flushes a budgeted resources can go unused in the cache before it - // is purged. Using a value <= 0 disables this feature. + // Default number of external flushes a budgeted resources can go unused in the cache before it + // is purged. Using a value <= 0 disables this feature. This will be removed once Chrome + // starts using time-based purging. static const int kDefaultMaxUnusedFlushes = 1 * /* flushes per frame */ 60 * /* fps */ @@ -159,6 +160,9 @@ public: /** Purges all resources that don't have external owners. */ void purgeAllUnlocked(); + /** Purge all resources not used since the passed in time. */ + void purgeResourcesNotUsedSince(GrStdSteadyClock::time_point); + /** Returns true if the cache would like a flush to occur in order to make more resources purgeable. */ bool requestsFlush() const { return fRequestFlush; } diff --git a/tests/ResourceCacheTest.cpp b/tests/ResourceCacheTest.cpp index 9573161..a7a2b45 100644 --- a/tests/ResourceCacheTest.cpp +++ b/tests/ResourceCacheTest.cpp @@ -9,7 +9,7 @@ #include "SkTypes.h" #if SK_SUPPORT_GPU - +#include #include "GrContext.h" #include "GrContextFactory.h" #include "GrGpu.h" @@ -1241,6 +1241,108 @@ static void test_flush(skiatest::Reporter* reporter) { REPORTER_ASSERT(reporter, 10 == cache->getResourceCount()); } +static void test_time_purge(skiatest::Reporter* reporter) { + Mock mock(1000000, 1000000); + GrContext* context = mock.context(); + GrResourceCache* cache = mock.cache(); + + static constexpr int kCnts[] = {1, 10, 1024}; + auto nowish = []() { + // We sleep so that we ensure we get a value that is greater than the last call to + // GrStdSteadyClock::now(). + std::this_thread::sleep_for(GrStdSteadyClock::duration(5)); + auto result = GrStdSteadyClock::now(); + // Also sleep afterwards so we don't get this value again. + std::this_thread::sleep_for(GrStdSteadyClock::duration(5)); + return result; + }; + + for (int cnt : kCnts) { + std::unique_ptr timeStamps( + new GrStdSteadyClock::time_point[cnt]); + { + // Insert resources and get time points between each addition. + for (int i = 0; i < cnt; ++i) { + TestResource* r = new TestResource(context->getGpu()); + GrUniqueKey k; + make_unique_key<1>(&k, i); + r->resourcePriv().setUniqueKey(k); + r->unref(); + timeStamps.get()[i] = nowish(); + } + + // Purge based on the time points between resource additions. Each purge should remove + // the oldest resource. + for (int i = 0; i < cnt; ++i) { + cache->purgeResourcesNotUsedSince(timeStamps[i]); + REPORTER_ASSERT(reporter, cnt - i - 1 == cache->getResourceCount()); + for (int j = 0; j < i; ++j) { + GrUniqueKey k; + make_unique_key<1>(&k, j); + GrGpuResource* r = cache->findAndRefUniqueResource(k); + REPORTER_ASSERT(reporter, !SkToBool(r)); + SkSafeUnref(r); + } + } + + REPORTER_ASSERT(reporter, 0 == cache->getResourceCount()); + cache->purgeAllUnlocked(); + } + + // Do a similar test but where we leave refs on some resources to prevent them from being + // purged. + { + std::unique_ptr refedResources(new GrGpuResource*[cnt / 2]); + for (int i = 0; i < cnt; ++i) { + TestResource* r = new TestResource(context->getGpu()); + GrUniqueKey k; + make_unique_key<1>(&k, i); + r->resourcePriv().setUniqueKey(k); + // Leave a ref on every other resource, beginning with the first. + if (SkToBool(i & 0x1)) { + refedResources.get()[i / 2] = r; + } else { + r->unref(); + } + timeStamps.get()[i] = nowish(); + } + + for (int i = 0; i < cnt; ++i) { + // Should get a resource purged every other frame. + cache->purgeResourcesNotUsedSince(timeStamps[i]); + REPORTER_ASSERT(reporter, cnt - i / 2 - 1 == cache->getResourceCount()); + } + + // Unref all the resources that we kept refs on in the first loop. + for (int i = 0; i < (cnt / 2); ++i) { + refedResources.get()[i]->unref(); + cache->purgeResourcesNotUsedSince(nowish()); + REPORTER_ASSERT(reporter, cnt / 2 - i - 1 == cache->getResourceCount()); + } + + cache->purgeAllUnlocked(); + } + + REPORTER_ASSERT(reporter, 0 == cache->getResourceCount()); + + // Verify that calling flush() on a GrContext with nothing to do will not trigger resource + // eviction + context->flush(); + for (int i = 0; i < 10; ++i) { + TestResource* r = new TestResource(context->getGpu()); + GrUniqueKey k; + make_unique_key<1>(&k, i); + r->resourcePriv().setUniqueKey(k); + r->unref(); + } + REPORTER_ASSERT(reporter, 10 == cache->getResourceCount()); + context->flush(); + REPORTER_ASSERT(reporter, 10 == cache->getResourceCount()); + cache->purgeResourcesNotUsedSince(nowish()); + REPORTER_ASSERT(reporter, 0 == cache->getResourceCount()); + } +} + static void test_large_resource_count(skiatest::Reporter* reporter) { // Set the cache size to double the resource count because we're going to create 2x that number // resources, using two different key domains. Add a little slop to the bytes because we resize @@ -1393,6 +1495,7 @@ DEF_GPUTEST(ResourceCacheMisc, reporter, factory) { test_resource_size_changed(reporter); test_timestamp_wrap(reporter); test_flush(reporter); + test_time_purge(reporter); test_large_resource_count(reporter); test_custom_data(reporter); test_abandoned(reporter); -- 2.7.4