michael@0: michael@0: /* michael@0: * Copyright 2011 Google Inc. michael@0: * michael@0: * Use of this source code is governed by a BSD-style license that can be michael@0: * found in the LICENSE file. michael@0: */ michael@0: michael@0: michael@0: michael@0: #ifndef GrResourceCache_DEFINED michael@0: #define GrResourceCache_DEFINED michael@0: michael@0: #include "GrConfig.h" michael@0: #include "GrTypes.h" michael@0: #include "GrTMultiMap.h" michael@0: #include "GrBinHashKey.h" michael@0: #include "SkMessageBus.h" michael@0: #include "SkTInternalLList.h" michael@0: michael@0: class GrResource; michael@0: class GrResourceEntry; michael@0: michael@0: class GrResourceKey { michael@0: public: michael@0: static GrCacheID::Domain ScratchDomain() { michael@0: static const GrCacheID::Domain gDomain = GrCacheID::GenerateDomain(); michael@0: return gDomain; michael@0: } michael@0: michael@0: /** Uniquely identifies the GrResource subclass in the key to avoid collisions michael@0: across resource types. */ michael@0: typedef uint8_t ResourceType; michael@0: michael@0: /** Flags set by the GrResource subclass. */ michael@0: typedef uint8_t ResourceFlags; michael@0: michael@0: /** Generate a unique ResourceType */ michael@0: static ResourceType GenerateResourceType(); michael@0: michael@0: /** Creates a key for resource */ michael@0: GrResourceKey(const GrCacheID& id, ResourceType type, ResourceFlags flags) { michael@0: this->init(id.getDomain(), id.getKey(), type, flags); michael@0: }; michael@0: michael@0: GrResourceKey(const GrResourceKey& src) { michael@0: fKey = src.fKey; michael@0: } michael@0: michael@0: GrResourceKey() { michael@0: fKey.reset(); michael@0: } michael@0: michael@0: void reset(const GrCacheID& id, ResourceType type, ResourceFlags flags) { michael@0: this->init(id.getDomain(), id.getKey(), type, flags); michael@0: } michael@0: michael@0: uint32_t getHash() const { michael@0: return fKey.getHash(); michael@0: } michael@0: michael@0: bool isScratch() const { michael@0: return ScratchDomain() == michael@0: *reinterpret_cast(fKey.getData() + michael@0: kCacheIDDomainOffset); michael@0: } michael@0: michael@0: ResourceType getResourceType() const { michael@0: return *reinterpret_cast(fKey.getData() + michael@0: kResourceTypeOffset); michael@0: } michael@0: michael@0: ResourceFlags getResourceFlags() const { michael@0: return *reinterpret_cast(fKey.getData() + michael@0: kResourceFlagsOffset); michael@0: } michael@0: michael@0: bool operator==(const GrResourceKey& other) const { return fKey == other.fKey; } michael@0: michael@0: private: michael@0: enum { michael@0: kCacheIDKeyOffset = 0, michael@0: kCacheIDDomainOffset = kCacheIDKeyOffset + sizeof(GrCacheID::Key), michael@0: kResourceTypeOffset = kCacheIDDomainOffset + sizeof(GrCacheID::Domain), michael@0: kResourceFlagsOffset = kResourceTypeOffset + sizeof(ResourceType), michael@0: kPadOffset = kResourceFlagsOffset + sizeof(ResourceFlags), michael@0: kKeySize = SkAlign4(kPadOffset), michael@0: kPadSize = kKeySize - kPadOffset michael@0: }; michael@0: michael@0: void init(const GrCacheID::Domain domain, michael@0: const GrCacheID::Key& key, michael@0: ResourceType type, michael@0: ResourceFlags flags) { michael@0: union { michael@0: uint8_t fKey8[kKeySize]; michael@0: uint32_t fKey32[kKeySize / 4]; michael@0: } keyData; michael@0: michael@0: uint8_t* k = keyData.fKey8; michael@0: memcpy(k + kCacheIDKeyOffset, key.fData8, sizeof(GrCacheID::Key)); michael@0: memcpy(k + kCacheIDDomainOffset, &domain, sizeof(GrCacheID::Domain)); michael@0: memcpy(k + kResourceTypeOffset, &type, sizeof(ResourceType)); michael@0: memcpy(k + kResourceFlagsOffset, &flags, sizeof(ResourceFlags)); michael@0: memset(k + kPadOffset, 0, kPadSize); michael@0: fKey.setKeyData(keyData.fKey32); michael@0: } michael@0: GrBinHashKey fKey; michael@0: }; michael@0: michael@0: // The cache listens for these messages to purge junk resources proactively. michael@0: struct GrResourceInvalidatedMessage { michael@0: GrResourceKey key; michael@0: }; michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: class GrResourceEntry { michael@0: public: michael@0: GrResource* resource() const { return fResource; } michael@0: const GrResourceKey& key() const { return fKey; } michael@0: michael@0: static const GrResourceKey& GetKey(const GrResourceEntry& e) { return e.key(); } michael@0: static uint32_t Hash(const GrResourceKey& key) { return key.getHash(); } michael@0: static bool Equal(const GrResourceEntry& a, const GrResourceKey& b) { michael@0: return a.key() == b; michael@0: } michael@0: #ifdef SK_DEBUG michael@0: void validate() const; michael@0: #else michael@0: void validate() const {} michael@0: #endif michael@0: michael@0: private: michael@0: GrResourceEntry(const GrResourceKey& key, GrResource* resource); michael@0: ~GrResourceEntry(); michael@0: michael@0: GrResourceKey fKey; michael@0: GrResource* fResource; michael@0: michael@0: // Linked list for the LRU ordering. michael@0: SK_DECLARE_INTERNAL_LLIST_INTERFACE(GrResourceEntry); michael@0: michael@0: friend class GrResourceCache; michael@0: }; michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: /** michael@0: * Cache of GrResource objects. michael@0: * michael@0: * These have a corresponding GrResourceKey, built from 128bits identifying the michael@0: * resource. Multiple resources can map to same GrResourceKey. michael@0: * michael@0: * The cache stores the entries in a double-linked list, which is its LRU. michael@0: * When an entry is "locked" (i.e. given to the caller), it is moved to the michael@0: * head of the list. If/when we must purge some of the entries, we walk the michael@0: * list backwards from the tail, since those are the least recently used. michael@0: * michael@0: * For fast searches, we maintain a hash map based on the GrResourceKey. michael@0: * michael@0: * It is a goal to make the GrResourceCache the central repository and bookkeeper michael@0: * of all resources. It should replace the linked list of GrResources that michael@0: * GrGpu uses to call abandon/release. michael@0: */ michael@0: class GrResourceCache { michael@0: public: michael@0: GrResourceCache(int maxCount, size_t maxBytes); michael@0: ~GrResourceCache(); michael@0: michael@0: /** michael@0: * Return the current resource cache limits. michael@0: * michael@0: * @param maxResource If non-null, returns maximum number of resources michael@0: * that can be held in the cache. michael@0: * @param maxBytes If non-null, returns maximum number of bytes of michael@0: * gpu memory that can be held in the cache. michael@0: */ michael@0: void getLimits(int* maxResources, size_t* maxBytes) const; michael@0: michael@0: /** michael@0: * Specify the resource cache limits. If the current cache exceeds either michael@0: * of these, it will be purged (LRU) to keep the cache within these limits. michael@0: * michael@0: * @param maxResources The maximum number of resources that can be held in michael@0: * the cache. michael@0: * @param maxBytes The maximum number of bytes of resource memory that michael@0: * can be held in the cache. michael@0: */ michael@0: void setLimits(int maxResources, size_t maxResourceBytes); michael@0: michael@0: /** michael@0: * The callback function used by the cache when it is still over budget michael@0: * after a purge. The passed in 'data' is the same 'data' handed to michael@0: * setOverbudgetCallback. The callback returns true if some resources michael@0: * have been freed. michael@0: */ michael@0: typedef bool (*PFOverbudgetCB)(void* data); michael@0: michael@0: /** michael@0: * Set the callback the cache should use when it is still over budget michael@0: * after a purge. The 'data' provided here will be passed back to the michael@0: * callback. Note that the cache will attempt to purge any resources newly michael@0: * freed by the callback. michael@0: */ michael@0: void setOverbudgetCallback(PFOverbudgetCB overbudgetCB, void* data) { michael@0: fOverbudgetCB = overbudgetCB; michael@0: fOverbudgetData = data; michael@0: } michael@0: michael@0: /** michael@0: * Returns the number of bytes consumed by cached resources. michael@0: */ michael@0: size_t getCachedResourceBytes() const { return fEntryBytes; } michael@0: michael@0: // For a found or added resource to be completely exclusive to the caller michael@0: // both the kNoOtherOwners and kHide flags need to be specified michael@0: enum OwnershipFlags { michael@0: kNoOtherOwners_OwnershipFlag = 0x1, // found/added resource has no other owners michael@0: kHide_OwnershipFlag = 0x2 // found/added resource is hidden from future 'find's michael@0: }; michael@0: michael@0: /** michael@0: * Search for an entry with the same Key. If found, return it. michael@0: * If not found, return null. michael@0: * If ownershipFlags includes kNoOtherOwners and a resource is returned michael@0: * then that resource has no other refs to it. michael@0: * If ownershipFlags includes kHide and a resource is returned then that michael@0: * resource will not be returned from future 'find' calls until it is michael@0: * 'freed' (and recycled) or makeNonExclusive is called. michael@0: * For a resource to be completely exclusive to a caller both kNoOtherOwners michael@0: * and kHide must be specified. michael@0: */ michael@0: GrResource* find(const GrResourceKey& key, michael@0: uint32_t ownershipFlags = 0); michael@0: michael@0: /** michael@0: * Add the new resource to the cache (by creating a new cache entry based michael@0: * on the provided key and resource). michael@0: * michael@0: * Ownership of the resource is transferred to the resource cache, michael@0: * which will unref() it when it is purged or deleted. michael@0: * michael@0: * If ownershipFlags includes kHide, subsequent calls to 'find' will not michael@0: * return 'resource' until it is 'freed' (and recycled) or makeNonExclusive michael@0: * is called. michael@0: */ michael@0: void addResource(const GrResourceKey& key, michael@0: GrResource* resource, michael@0: uint32_t ownershipFlags = 0); michael@0: michael@0: /** michael@0: * Determines if the cache contains an entry matching a key. If a matching michael@0: * entry exists but was detached then it will not be found. michael@0: */ michael@0: bool hasKey(const GrResourceKey& key) const { return NULL != fCache.find(key); } michael@0: michael@0: /** michael@0: * Hide 'entry' so that future searches will not find it. Such michael@0: * hidden entries will not be purged. The entry still counts against michael@0: * the cache's budget and should be made non-exclusive when exclusive access michael@0: * is no longer needed. michael@0: */ michael@0: void makeExclusive(GrResourceEntry* entry); michael@0: michael@0: /** michael@0: * Restore 'entry' so that it can be found by future searches. 'entry' michael@0: * will also be purgeable (provided its lock count is now 0.) michael@0: */ michael@0: void makeNonExclusive(GrResourceEntry* entry); michael@0: michael@0: /** michael@0: * Remove a resource from the cache and delete it! michael@0: */ michael@0: void deleteResource(GrResourceEntry* entry); michael@0: michael@0: /** michael@0: * Removes every resource in the cache that isn't locked. michael@0: */ michael@0: void purgeAllUnlocked(); michael@0: michael@0: /** michael@0: * Allow cache to purge unused resources to obey resource limitations michael@0: * Note: this entry point will be hidden (again) once totally ref-driven michael@0: * cache maintenance is implemented. Note that the overbudget callback michael@0: * will be called if the initial purge doesn't get the cache under michael@0: * its budget. michael@0: * michael@0: * extraCount and extraBytes are added to the current resource allocation michael@0: * to make sure enough room is available for future additions (e.g, michael@0: * 10MB across 10 textures is about to be added). michael@0: */ michael@0: void purgeAsNeeded(int extraCount = 0, size_t extraBytes = 0); michael@0: michael@0: #ifdef SK_DEBUG michael@0: void validate() const; michael@0: #else michael@0: void validate() const {} michael@0: #endif michael@0: michael@0: #if GR_CACHE_STATS michael@0: void printStats(); michael@0: #endif michael@0: michael@0: private: michael@0: enum BudgetBehaviors { michael@0: kAccountFor_BudgetBehavior, michael@0: kIgnore_BudgetBehavior michael@0: }; michael@0: michael@0: void internalDetach(GrResourceEntry*, BudgetBehaviors behavior = kAccountFor_BudgetBehavior); michael@0: void attachToHead(GrResourceEntry*, BudgetBehaviors behavior = kAccountFor_BudgetBehavior); michael@0: michael@0: void removeInvalidResource(GrResourceEntry* entry); michael@0: michael@0: GrTMultiMap fCache; michael@0: michael@0: // We're an internal doubly linked list michael@0: typedef SkTInternalLList EntryList; michael@0: EntryList fList; michael@0: michael@0: #ifdef SK_DEBUG michael@0: // These objects cannot be returned by a search michael@0: EntryList fExclusiveList; michael@0: #endif michael@0: michael@0: // our budget, used in purgeAsNeeded() michael@0: int fMaxCount; michael@0: size_t fMaxBytes; michael@0: michael@0: // our current stats, related to our budget michael@0: #if GR_CACHE_STATS michael@0: int fHighWaterEntryCount; michael@0: size_t fHighWaterEntryBytes; michael@0: int fHighWaterClientDetachedCount; michael@0: size_t fHighWaterClientDetachedBytes; michael@0: #endif michael@0: michael@0: int fEntryCount; michael@0: size_t fEntryBytes; michael@0: int fClientDetachedCount; michael@0: size_t fClientDetachedBytes; michael@0: michael@0: // prevents recursive purging michael@0: bool fPurging; michael@0: michael@0: PFOverbudgetCB fOverbudgetCB; michael@0: void* fOverbudgetData; michael@0: michael@0: void internalPurge(int extraCount, size_t extraBytes); michael@0: michael@0: // Listen for messages that a resource has been invalidated and purge cached junk proactively. michael@0: SkMessageBus::Inbox fInvalidationInbox; michael@0: void purgeInvalidated(); michael@0: michael@0: #ifdef SK_DEBUG michael@0: static size_t countBytes(const SkTInternalLList& list); michael@0: #endif michael@0: }; michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: #ifdef SK_DEBUG michael@0: class GrAutoResourceCacheValidate { michael@0: public: michael@0: GrAutoResourceCacheValidate(GrResourceCache* cache) : fCache(cache) { michael@0: cache->validate(); michael@0: } michael@0: ~GrAutoResourceCacheValidate() { michael@0: fCache->validate(); michael@0: } michael@0: private: michael@0: GrResourceCache* fCache; michael@0: }; michael@0: #else michael@0: class GrAutoResourceCacheValidate { michael@0: public: michael@0: GrAutoResourceCacheValidate(GrResourceCache*) {} michael@0: }; michael@0: #endif michael@0: michael@0: #endif