| /* | |
| * Copyright 2022 Google LLC | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #ifndef skgpu_graphite_Resource_DEFINED | |
| #define skgpu_graphite_Resource_DEFINED | |
| #include "include/gpu/GpuTypes.h" | |
| #include "include/private/SkMutex.h" | |
| #include "src/gpu/GpuTypesPriv.h" | |
| #include "src/gpu/graphite/GraphiteResourceKey.h" | |
| #include "src/gpu/graphite/ResourceTypes.h" | |
| #include <atomic> | |
| #include <functional> | |
| #include <string> | |
| #include <string_view> | |
| class SkMutex; | |
| class SkTraceMemoryDump; | |
| namespace skgpu::graphite { | |
| class GlobalCache; | |
| class ResourceCache; | |
| class SharedContext; | |
| class Texture; | |
| /** | |
| * Base class for objects that can be kept in the ResourceCache and created or fetched by a | |
| * ResourceProvider. A ResourceProvider controls the creation of a Resource and determines how | |
| * its lifecycle is managed (e.g. is it cached, shareable, etc.). Once created, however, the | |
| * Resource's lifecycle is controlled by the provider's ResourceCache so that GPU resources can | |
| * be reused when ref counts reach 0; a Resource that has no more refs and does not belong to a | |
| * cache is simply deleted. | |
| * | |
| * Resource Threading Model | |
| * ======================== | |
| * | |
| * A Resource is allowed to be used in multiple threads and be deleted from any thread safely. | |
| * Resource refs are broken into several categories: usage (public C++ references), command buffer | |
| * (representing active GPU work), and internal (managed by the ResourceCache). Non-cache refs can | |
| * be removed and reach zero from any thread and be safely returned to the ResourceCache. Unlike | |
| * Resource, ResourceCache and ResourceProvider are not thread safe and are only used by a single | |
| * thread. | |
| * | |
| * Active Threads: | |
| * 1. Context thread: this is the primary thread where Recordings are inserted and GPU work is | |
| * scheduled. | |
| * 2. Cache thread: this is the thread that the ResourceCache is associated with. This can be the | |
| * same as the context thread if the ResourceCache and ResourceProvider belong to the Context. | |
| * However, a Recorder's ResourceCache/Provider can be used on an arbitrary single thread the | |
| * client is using for that specific Recorder. | |
| * 3. Usage threads: once created on the cache thread, a Resource is not restricted to just the | |
| * cache thread and context thread. The Resource can have references held on any other | |
| * Recorder's thread if multiple work streams use the resource, or may end up held in other | |
| * client threads naturally as part of their lifecycle management of Skia's API objects. | |
| * - A Resource could be accessed by an arbitrary number of usage threads. | |
| * | |
| * Reference Types: | |
| * 1. Usage refs: a Resource starts with a single usage ref when created by the ResourceProvider, | |
| * which is then given to the caller as an `sk_sp<R>`. Once it escapes the ResourceProvider, | |
| * operations on the `sk_sp` can add more usage refs. Usage refs track the liveness of the | |
| * Resource from C++ code external to the ResourceCache (either higher-level Skia/Graphite | |
| * objects or client objects keeping it alive). | |
| * 2. Command buffer refs: every Resource used in a command buffer has command buffer refs added | |
| * when referencing work (e.g. a Recording) is inserted into the Context. These refs are | |
| * released when the command buffer's finish procs are called and it's known that the GPU-side | |
| * operations are completely done with the Resources. | |
| * 3. Cache ref: A ResourceProvider can choose to register a Resource with its ResourceCache at | |
| * creation time before returning the Resource. The ResourceCache holds a single ref on every | |
| * Resource registered with it until the cache is shutdown. | |
| * 4. Return queue ref: At most one ref held while the Resource is being added to the cache's | |
| * return queue and held until it's removed from the queue on the cache thread. | |
| * | |
| * Lifecycle: | |
| * 1. Initialization: all Resources are created via the ResourceProvider, which also coordinates | |
| * with its ResourceCache. If an existing resource can't be used, a new one is created. The | |
| * initialization phase is the period between when the provider creates the Resource object and | |
| * when it's returned to the caller. The resource can be assigned a key and added to the cache | |
| * at this point. | |
| * 2. Reusable: when all usage refs are removed, some Resources can become reusable and move into | |
| * the return queue (on any thread). On the cache thread, the queue is regularly processed and | |
| * resources that remain reusable are tracked appropriately within the cache. | |
| * - Resources that are only modified via command buffer operations can be reusable when | |
| * usage refs reach 0 because future modifications won't conflict with any outstanding | |
| * work that is holding current command buffer refs. | |
| * - Resources that can be modified directly (e.g. mapped buffers) are not reusable until | |
| * there are no usage or command buffer refs to ensure modifications don't become visible | |
| * to the GPU before intended. | |
| * 3. Purgeable: when all usage refs and command buffer refs are removed, a Resource is purgeable. | |
| * Resources that weren't considered reusable while having outstanding command buffer refs are | |
| * now also reusable. Purgeable resources are also added to the return queue so that the cache | |
| * can decide whether or not to keep the resource alive (based on budget limits). | |
| * 4. Destruction: if a Resource has no refs at all, it can be destroyed. A purgeable resource | |
| * that was not registered with a cache will never have a cache ref or return queue ref so is | |
| * immediately destroyed. When a cache processes a purgeable resource from the return queue, it | |
| * can choose to drop its cache ref (and return queue ref) so the resource will be destroyed. | |
| * Purgeable resources that had been kept alive for reuse can also be dropped by the cache when | |
| * it is being purged (for budget or idleness reasons). | |
| * 5. Shutdown: when a Context or Recorder are deleted, its ResourceCache is shutdown. The cache | |
| * removes all of its cache refs and blocks any further changes to the return queue. This | |
| * allows Resources to be destroyed ASAP when they become purgeable instead of going back to | |
| * the shutdown cache. However, every Resource registered with the cache keeps the cache alive. | |
| * Once every cached resource is destroyed, the cache itself will be destroyed. | |
| * | |
| * Ref Counting and State Transition Properties: | |
| * - The cache ref can only be added during initialization (it is uniquely held and on the cache | |
| * thread). | |
| * - The cache ref can only be removed by the ResourceCache on the cache thread (purging | |
| * operations and shutdown are all single-threaded functions on the cache thread). | |
| * - The return queue ref can be added by any thread when the Resource transitions to reusable | |
| * and/or purgeable. | |
| * - The return queue ref cannot be added more than once; if the Resource goes through multiple | |
| * transitions before being processed by the cache, the second transition does not need to be | |
| * put into the queue. | |
| * - The return queue ref cannot be added directly, but only as part of a usage/CB unref that | |
| * transitions the resource to being reusable or purgeable. The return queue ref is added | |
| * atomically with the other unref. | |
| * - The return queue ref can only be removed by the resource cache on the cache thread, unless | |
| * the cache was shutdown already. In that case the ref is removed by the adding thread. The | |
| * cache will only remove the return queue ref *after* the resource is removed from the queue. | |
| * - If the resource was not registered with a cache during initialization, its cache and return | |
| * queue refs will always be zero. | |
| * - Regular usage refs can only be added when there was prior usage ref held by the caller. This | |
| * can happen on any thread. | |
| * - Command buffer refs can only be added if the resource has a usage ref (that is held through | |
| * the CB ref'ing operation). This can happen on any thread (although in practice just the | |
| * context thread). | |
| * - A command buffer ref cannot be promoted back to a usage ref. | |
| * - Both command buffer and usage refs can be removed from any thread (although CB refs are | |
| * likely to be unreffed on the context thread). | |
| * - When the usage ref count reaches zero, it can only become non-zero via the cache thread by | |
| * actions of its ResourceCache. | |
| * - Actions predicated on usage refs being 0 on the cache thread are safe. | |
| * - Such actions on other threads are not safe because the cache thread could process the | |
| * return queue and reuse the resource, or skip the return queue entirely for shareable | |
| * resources. | |
| * - When the resource becomes purgeable, it can only become non-purgeable on the cache thread | |
| * by action of the ResourceCache. | |
| * - Since there are no other usage refs (beyond what the cache might re-add), no other | |
| * thread can re-add a command buffer ref. | |
| * | |
| * | |
| * Resource Thread Safe Labeling Model | |
| * =================================== | |
| * | |
| * Every Resource object should have a label. Backend objects may additionally store a backend | |
| * resource label which can be useful for memory dumps or other debugging. A Caps flag controls | |
| * whether or not backend labels are enabled. | |
| * | |
| * A resource's label can change over time as it gets reused. This can happen on any thread at | |
| * almost any time. These label updates must be synchronized to its backend resource label, and both | |
| * update steps must be thread safe. | |
| * | |
| * To enforce this, a Resource's label can only be updated at certain points within its lifecycle | |
| * (depending upon its shareability as outlined below). Outside of initial resource creation and | |
| * cache insertion (at which point there is no contention for explicitly and immediately syncing a | |
| * new backend resource's label), propagating label updates to existing backend objects will be | |
| * deferred until recording insertion. This way, previously-set backend labels are guaranteed to | |
| * remain stable during a GPU frame capture. | |
| * | |
| * Resource thread safe label update policy based on shareability: | |
| * TODO(b/387505250): Currently, these policies are enforced for shareable resources. Implement and | |
| * enforce backend resource label update policies for non-shareable and scratch resources. | |
| * - Any resource can have its label set when it is first created or inserted into the cache. | |
| * - A shareable resource can be returned from the cache at any time, and its label should not | |
| * change upon reuse. Therefore, shareable resource labels are effectively only set once. | |
| * - A non-shareable resource will never returned from the cache for simultaneous usage. | |
| * Therefore, its label can be set by the ResourceCache when it returns a resource from | |
| * findOrCreate(...), re-adding an initial usage ref. | |
| * - Scratch resource labels have complex label management since they can be changed across | |
| * recorder threads and within the same recorder thread as it is used for different purposes. | |
| * - Within a recording, a scratch resource's label is the union of all TextureProxy labels | |
| * instantiated with the same resource. | |
| * - Scratch resources are only ever used for Graphite-internal rendering purposes, so we | |
| * know all of their possible usages at compile time. These usages can be represented as a | |
| * bitmask. The flags within the mask can then be mapped to constant strings which | |
| * represent the various possible scratch resource usages in order to generate a label. | |
| * - Storing usages as a bitmask allows us to quickly determine whether a resource's usage | |
| * is actually different than before. If it does not differ, then we can avoid the | |
| * overhead of generating a union of strings and updating the label. | |
| * - Across recorder threads, we don’t want to intermingle proxy labels on the resource’s | |
| * final label. To manage this while making it such that backend scratch resource labels | |
| * can be synchronized during recording insertion, the ScratchResourceManager will | |
| * maintain a label map for all the scratch resources it creates. This map will only | |
| * be modifiable on the recorder thread, becoming immutable after snap() and being | |
| * transferred on to the Recording. | |
| */ | |
| class Resource { | |
| enum class RefType { | |
| kUsage, // Counts controlled by `sk_sp` and tracks liveness from external C++ code. | |
| kCommandBuffer, // Incremented in Context::insertRecording, decremented by finish procs. | |
| kCache, // At most 1 ref, added in registerWithCache(), removed on cache shutdown or purge. | |
| kReturnQueue, // At most 1 ref, held while in the cache's return queue. | |
| }; | |
| public: | |
| Resource(const Resource&) = delete; | |
| Resource(Resource&&) = delete; | |
| Resource& operator=(const Resource&) = delete; | |
| Resource& operator=(Resource&&) = delete; | |
| // Adds a usage ref to the resource. Named ref so we can easily manage usage refs with sk_sp. | |
| void ref() const { | |
| // Only the cache should be able to add the first usage ref to a resource. | |
| this->addRef<RefType::kUsage>(); | |
| } | |
| // Removes a usage ref from the resource | |
| void unref() const { | |
| this->removeRef<RefType::kUsage>(); | |
| } | |
| // Adds a command buffer ref to the resource | |
| void refCommandBuffer() const { | |
| this->addRef<RefType::kCommandBuffer>(); | |
| } | |
| // Removes a command buffer ref from the resource | |
| void unrefCommandBuffer() const { | |
| this->removeRef<RefType::kCommandBuffer>(); | |
| } | |
| // Whether the resource is currently in use by the GPU: any resource that is used in a command | |
| // buffer is considered in use by the GPU. | |
| bool isBusyOnGPU() const { | |
| return (fRefs.load(std::memory_order_acquire) & RefMask(RefType::kCommandBuffer)) != 0; | |
| } | |
| Ownership ownership() const { return fOwnership; } | |
| bool requiresPrepareForReturnToCache() const { return fRequiresPrepareForReturnToCache; } | |
| Budgeted budgeted() const { return fBudgeted; } | |
| Shareable shareable() const { return fShareable; } | |
| const GraphiteResourceKey& key() const { return fKey; } | |
| virtual Protected isProtected() const { return Protected::kNo; } | |
| // Retrieves the amount of GPU memory used by this resource in bytes. It is approximate since we | |
| // aren't aware of additional padding or copies made by the driver. | |
| size_t gpuMemorySize() const { return fGpuMemorySize; } | |
| class UniqueID { | |
| public: | |
| UniqueID() = default; | |
| explicit UniqueID(uint32_t id) : fID(id) {} | |
| uint32_t asUInt() const { return fID; } | |
| bool operator==(const UniqueID& other) const { return fID == other.fID; } | |
| bool operator!=(const UniqueID& other) const { return !(*this == other); } | |
| private: | |
| uint32_t fID = SK_InvalidUniqueID; | |
| }; | |
| // Gets an id that is unique for this Resource object. It is static in that it does not change | |
| // when the content of the Resource object changes. This will never return 0. | |
| UniqueID uniqueID() const { return fUniqueID; } | |
| const std::string& getLabel() const { return fLabel; } | |
| // Tests whether a object has been abandoned or released. All objects will be in this state | |
| // after their creating Context is destroyed or abandoned. | |
| // | |
| // @return true if the object has been released or abandoned, | |
| // false otherwise. | |
| // TODO: As of now this function isn't really needed because in freeGpuData we are always | |
| // deleting this object. However, I want to implement all the purging logic first to make sure | |
| // we don't have a use case for calling internalDispose but not wanting to delete the actual | |
| // object yet. | |
| bool wasDestroyed() const { return fSharedContext == nullptr; } | |
| // Describes the type of gpu resource that is represented by the implementing | |
| // class (e.g. texture, buffer, etc). This data is used for diagnostic | |
| // purposes by dumpMemoryStatistics(). | |
| // | |
| // The value returned is expected to be long lived and will not be copied by the caller. | |
| virtual const char* getResourceType() const = 0; | |
| virtual const Texture* asTexture() const { return nullptr; } | |
| #if defined(GPU_TEST_UTILS) | |
| bool testingShouldDeleteASAP() const { return fDeleteASAP == DeleteASAP::kYes; } | |
| #endif | |
| protected: | |
| Resource(const SharedContext*, | |
| Ownership, | |
| size_t gpuMemorySize, | |
| std::string_view label = {}, | |
| bool reusableRequiresPurgeable = false, | |
| bool requiresPrepareForReturnToCache = false); | |
| virtual ~Resource(); | |
| const SharedContext* sharedContext() const { return fSharedContext; } | |
| // Update the backend GPU resource label to match fLabel. This should only ever be called by the | |
| // ResourceCache or Resource subclass constructors. | |
| void synchronizeBackendLabel() { | |
| if (!fLabel.empty()) { | |
| const std::string fullLabel = "Skia_" + fLabel; | |
| this->setBackendLabel(fullLabel.c_str()); | |
| } | |
| fBackendLabelDirty = false; | |
| } | |
| // Needs to be protected for DawnBuffer's emscripten prepareForReturnToCache | |
| void setDeleteASAP() { fDeleteASAP = DeleteASAP::kYes; } | |
| using TakeRefFunc = void (*)(void* ctx); | |
| private: | |
| /////////////////////////////////////////////////////////////////////////////////////////////// | |
| // The following set of functions are only meant to be called by the [Global|Proxy]Cache. We | |
| // don't want them public general users of a Resource, but they also aren't purely internal. | |
| /////////////////////////////////////////////////////////////////////////////////////////////// | |
| friend class ProxyCache; // for setDeleteASAP and updateAccessTime | |
| friend GlobalCache; // for lastAccessTime and updateAccessTime | |
| enum class DeleteASAP : bool { | |
| kNo = false, | |
| kYes = true, | |
| }; | |
| DeleteASAP shouldDeleteASAP() const { return fDeleteASAP; } | |
| // In the ResourceCache this is called whenever a Resource is moved into the purgeableQueue. It | |
| // may also be called by the ProxyCache and GlobalCache to track the time on Resources they are | |
| // holding on to. | |
| void updateAccessTime() { fLastAccess = skgpu::StdSteadyClock::now(); } | |
| skgpu::StdSteadyClock::time_point lastAccessTime() const { return fLastAccess; } | |
| /////////////////////////////////////////////////////////////////////////////////////////////// | |
| // The following set of functions are only meant to be called by the ResourceCache. We don't | |
| // want them public general users of a Resource, but they also aren't purely internal calls. | |
| /////////////////////////////////////////////////////////////////////////////////////////////// | |
| friend class ResourceCache; | |
| void setBudgeted(Budgeted budgeted) { | |
| SkASSERT(budgeted == Budgeted::kNo || fOwnership == Ownership::kOwned); | |
| fBudgeted = budgeted; | |
| } | |
| void setShareable(Shareable shareable) { | |
| SkASSERT(shareable == Shareable::kNo || fBudgeted == Budgeted::kYes); | |
| fShareable = shareable; | |
| } | |
| void setAvailableForReuse(bool avail) { fAvailableForReuse = avail; } | |
| bool isAvailableForReuse() const { return fAvailableForReuse; } | |
| uint32_t lastUseToken() const { return fLastUseToken; } | |
| void setLastUseToken(uint32_t token) { fLastUseToken = token; } | |
| void setNextInReturnQueue(Resource* next) { | |
| SkASSERT(this->hasReturnQueueRef()); | |
| fNextInReturnQueue = next; | |
| } | |
| int* accessCacheIndex() const { return &fCacheArrayIndex; } | |
| const ResourceCache* cache() const { return fReturnCache.get(); } | |
| // If possible, queries the backend API to check the current allocation size of the gpu | |
| // resource and updates the tracked value. This is specifically useful for Vulkan backends which | |
| // use lazy allocated memory for "memoryless" resources. Ideally that memory should stay zero | |
| // throughout its usage, but certain usage patterns can trigger the device to commit real memory | |
| // to the resource. So this will allow us to have a more accurate tracking of our memory usage. | |
| void updateGpuMemorySize() { fGpuMemorySize = this->onUpdateGpuMemorySize(); } | |
| // Dumps memory usage information for this Resource to traceMemoryDump. | |
| void dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump, bool inPurgeableQueue) const; | |
| /** | |
| * If the resource has a non-shareable key then this gives the resource subclass an opportunity | |
| * to prepare itself to re-enter the cache. The ResourceCache extends its privilege to take the | |
| * first UsageRef to this function via takeRef. If takeRef is called this resource will not | |
| * immediately enter the cache but will be re-reprocessed when the usage ref count again reaches | |
| * zero. | |
| * | |
| * Return true if takeRef() was invoked. | |
| */ | |
| virtual bool prepareForReturnToCache(TakeRefFunc takeRef, void* takeRefCtx) { return false; } | |
| // Adds a cache ref to the resource. May only be called once. | |
| void registerWithCache(sk_sp<ResourceCache>, const GraphiteResourceKey&, Budgeted, Shareable); | |
| // This version of ref allows adding a ref when the usage count is 0. This should only be called | |
| // from the ResourceCache. | |
| void initialUsageRef() const { | |
| this->addRef<RefType::kUsage, /*MustHaveUsageRefs=*/false>(); | |
| } | |
| // Removes a cache ref from the resource. The unref here should only ever be called from the | |
| // ResourceCache and only in the Recorder/Context thread the ResourceCache is part of. | |
| void unrefCache() const { | |
| SkASSERT(fReturnCache); | |
| this->removeRef<RefType::kCache>(); | |
| } | |
| // Removes the return queue ref that was held while the Resource was in the queue. This can only | |
| // be called by the ResourceCache on its thread. It should not be called after unrefCache(). | |
| // It must only be called after the cache has removed the resource from its return queue. | |
| // | |
| // Returns {isReusable, isPurgeable} atomically based on the reference state when the return | |
| // queue ref was removed. `isReusable` is true if all refs affecting reusability were zero | |
| // when the queue ref was removed. `isPurgeable` is true if all usage and command buffer refs | |
| // were zero. | |
| // | |
| // If true is returned there are no other refs that could trigger a reusable or purgeable state | |
| // change. A resource that entered the return queue due to becoming reusable or purgeable only | |
| // happens if that ref count reached zero, so there should be no external ref holder (other than | |
| // the ResourceCache with its separate cache ref). However, unrefReturnQueue() is only called by | |
| // the ResourceCache so it won't be simultaneously handing out usage refs. | |
| // | |
| // If false is returned, it is possible for the resource to immediately become purgeable on | |
| // another thread but since this thread has released its return queue ref, the Resource will | |
| // simply go back in the next return queue. | |
| // | |
| // The cache should track the Resource based on this return value instead of re-checking the | |
| // ref counts as that would not be an atomic operation. | |
| std::tuple<bool, bool, Resource*> unrefReturnQueue() { | |
| // We must reset the fNextInReturnQueue value *before* removing the return queue ref, but we | |
| // need to return the old value to the ResourceCache so that it can continue iterating over | |
| // the linked list. | |
| Resource* next = fNextInReturnQueue; | |
| fNextInReturnQueue = nullptr; | |
| uint64_t origRefs = this->removeRef<RefType::kReturnQueue>(); | |
| // Since we should always have a cache ref when this is called, the Resource will never be | |
| // transitioning to having zero refs, although if `true` is returned the cache may choose to | |
| // then drop its cache ref. | |
| SkASSERT((origRefs & RefMask(RefType::kCache)) != 0); | |
| // `fReusableRefMask` always includes the ReturnQueue ref mask, and since we just removed | |
| // the return ref value, `origRefs` also includes the the ReturnQueue ref mask bit. We have | |
| // to compare to the ref mask to detect the case when the actual reusable refs are all zero. | |
| // Since PurgeableMask() does not add the ReturnQueue ref mask, it *can* compare to zero. | |
| return {(origRefs & fReusableRefMask) == RefMask(RefType::kReturnQueue), | |
| (origRefs & PurgeableMask()) == 0, | |
| next}; | |
| } | |
| // We allow the label on a Resource to change when used for a different function (e.g. when | |
| // reusing a scratch Texture, we can change the label to reflect the caller's current usage). | |
| // This method is only expected to be called when returning a non-shareable or scratch resource | |
| // from the cache. | |
| void setLabel(std::string_view label) { | |
| if (fLabel == label) { | |
| return; | |
| } | |
| fLabel = label; | |
| // It is not always safe to immediately update the backend GPU resource label. Mark it | |
| // as dirty so it can be updated when appropriate. | |
| fBackendLabelDirty = true; | |
| } | |
| #if defined(SK_DEBUG) || defined(GPU_TEST_UTILS) | |
| bool hasCacheRef() const { | |
| return (fRefs.load(std::memory_order_acquire) & RefMask(RefType::kCache)) != 0; | |
| } | |
| bool hasReturnQueueRef() const { | |
| return (fRefs.load(std::memory_order_acquire) & RefMask(RefType::kReturnQueue)) != 0; | |
| } | |
| bool inReturnQueue() const { | |
| return this->hasReturnQueueRef() && SkToBool(fNextInReturnQueue); | |
| } | |
| bool isUsableAsScratch() const { | |
| // This is only called by the ResourceCache, so the state of the Resource's refs won't | |
| // be changed by another thread when isReusable is true. | |
| uint64_t origRefs = fRefs.load(std::memory_order_acquire) & ~RefMask(RefType::kReturnQueue); | |
| bool isReusable = (origRefs & fReusableRefMask) == 0; | |
| return fShareable == Shareable::kScratch || (fShareable == Shareable::kNo && isReusable); | |
| } | |
| bool isPurgeable() const { | |
| // This is only called by the ResourceCache on its thread; if the usage and CB ref counts | |
| // are 0, the ResourceCache is the only way in which they can become non-zero again. | |
| return (fRefs.load(std::memory_order_acquire) & PurgeableMask()) == 0; | |
| } | |
| bool isUniquelyHeld() const { | |
| // This intentionally checks that the cache ref and return queue refs are 0, so that fRefs | |
| // is compared to the value it is initialized with. | |
| return fRefs.load(std::memory_order_acquire) == RefIncrement(RefType::kUsage); | |
| } | |
| bool hasAnyRefs() const { | |
| // Because all ref counts are packed into the same atomic, when this load is actually 0 | |
| // there are no other threads that can reach the object and add new refs (assuming a raw | |
| // pointer has never leaked). | |
| return fRefs.load(std::memory_order_acquire) != 0; | |
| } | |
| #endif | |
| /////////////////////////////////////////////////////////////////////////////////////////////// | |
| // The remaining calls are meant to be truly private (including virtuals for subclasses) | |
| /////////////////////////////////////////////////////////////////////////////////////////////// | |
| // Overridden to free GPU resources in the backend API. | |
| virtual void freeGpuData() = 0; | |
| // Overridden to call any release callbacks, if necessary | |
| virtual void invokeReleaseProc() {} | |
| // Overridden to set the label on the underlying GPU resource. This method is private to help | |
| // enforce that backend label updates are performed in a threadsafe manner. This should only | |
| // ever be called from within synchronizeBackendLabel(). | |
| virtual void setBackendLabel(char const* label) {} | |
| // Overridden to add extra information to the memory dump. | |
| virtual void onDumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump, | |
| const char* dumpName) const {} | |
| // Overridden to calculate a more up-to-date size in bytes. | |
| virtual size_t onUpdateGpuMemorySize() { return fGpuMemorySize; } | |
| // Try to add the Resource to the cache's return queue for pending reuse. | |
| // This should only be called when there is a cache to return to, and the calling thread | |
| // successfully transitioned from no "return queue" ref to setting the return queue ref. | |
| // | |
| // Returns true if the cache accepted the Resource (in which case the set return ref should | |
| // remain set for the cache to remove). If false is returned, the caller should clear the | |
| // return queue ref (and possible dispose of the object). | |
| bool returnToCache() const; | |
| // Frees the object in the underlying 3D API *and* deletes the object itself. | |
| void internalDispose(); | |
| // Resource tracks its different ref counts packed into a single atomic 64-bit value. | |
| // The bits are split into subfields: | |
| // commandBufferRefs:31 e.g. RefMask(kCB) | |
| // usageRefs:31 RefMask(kUsage) | |
| // returnQueueRef: 1 RefMask(kReturnQueue) | |
| // cacheRefs:1 RefMask(kCache) | |
| // | |
| // RefIncrement() and RefMask() help access specific ref type's values. | |
| static constexpr uint64_t RefIncrement(RefType refType) { | |
| switch (refType) { | |
| case RefType::kCommandBuffer: return (uint64_t) 1 << 33; | |
| case RefType::kUsage: return (uint64_t) 1 << 2; | |
| case RefType::kCache: return (uint64_t) 1 << 1; | |
| case RefType::kReturnQueue: return (uint64_t) 1 << 0; | |
| } | |
| SkUNREACHABLE; | |
| } | |
| static inline constexpr uint64_t RefMask(RefType refType) { | |
| switch (refType) { | |
| case RefType::kCommandBuffer: return (((uint64_t)1 << 31) - 1) << 33; | |
| case RefType::kUsage: return (((uint64_t)1 << 31) - 1) << 2; | |
| case RefType::kCache: return 0b10; | |
| case RefType::kReturnQueue: return 0b01; | |
| } | |
| SkUNREACHABLE; | |
| } | |
| static inline constexpr uint64_t PurgeableMask() { | |
| return RefMask(RefType::kUsage) | RefMask(RefType::kCommandBuffer); | |
| } | |
| template <RefType kType, bool MustHaveUsageRefs=true> | |
| void addRef() const { | |
| static_assert(kType != RefType::kReturnQueue, "return queue refs cannot be added directly"); | |
| static constexpr uint64_t kRefIncrement = RefIncrement(kType); | |
| // No barrier required | |
| [[maybe_unused]] uint64_t origCnt = | |
| fRefs.fetch_add(kRefIncrement, std::memory_order_relaxed); | |
| // Require that there was an already held usage ref in order to add this new ref, | |
| // e.g. to add a command buffer ref, a usage ref must already be held; calling code can't | |
| // add usage refs if it wasn't explicitly handed out by the cache. | |
| SkASSERT(!MustHaveUsageRefs || (origCnt & RefMask(RefType::kUsage)) > 0); | |
| // And make sure that the specific type of ref did not overflow into another field | |
| SkASSERT((RefMask(kType) - (origCnt & RefMask(kType))) >= kRefIncrement); | |
| } | |
| template <RefType kType> | |
| uint64_t removeRef() const { | |
| static constexpr uint64_t kRefIncrement = RefIncrement(kType); | |
| uint64_t origRefs; | |
| if (kType == RefType::kCache || kType == RefType::kReturnQueue || !fReturnCache) { | |
| // Without a ResourceCache, or when it's a cache/return-queue unref, there is no | |
| // non-atomic work that has to happen so simply update the ref count. If the net ref | |
| // count reaches 0 we can safely delete the resource because no other thread will | |
| // increase the refs. | |
| origRefs = fRefs.fetch_sub(kRefIncrement, std::memory_order_acq_rel); | |
| SkASSERT((origRefs & RefMask(kType)) >= kRefIncrement); // had a ref to remove | |
| if (origRefs == kRefIncrement) { | |
| SkASSERT(!this->hasAnyRefs()); | |
| Resource* mutableThis = const_cast<Resource*>(this); | |
| mutableThis->internalDispose(); | |
| } | |
| } else { | |
| SkASSERT(kType == RefType::kCommandBuffer || kType == RefType::kUsage); | |
| // When removing a usage or CB ref and the resource is registered with the cache, | |
| // it may need to be returned to the cache. A resource can only be in the return queue | |
| // a single time and must remain alive until cache removes it from the queue. A CAS | |
| // loop is used to atomically decrement the ref and add the return queue ref. | |
| uint64_t nextRefs; | |
| bool needsReturn; | |
| do { | |
| origRefs = fRefs.load(std::memory_order_acquire); | |
| SkASSERT((origRefs & RefMask(kType)) >= kRefIncrement); // have a ref to remove | |
| // When unreffing a usage or command buffer ref, the Resource needs to return to | |
| // the queue when: | |
| // - it's not already in the return queue (return queue ref is 0) AND | |
| // - it's transitioning from non-reusable -> reusable OR non-purgeable -> purgeable | |
| // | |
| // Including RefMask(kReturnQueue) in the bitwise &'s before comparing to the | |
| // ref increment ensures that the return queue ref was 0 in origRefs. | |
| static constexpr uint64_t kPurgeableReturnMask = PurgeableMask() | | |
| RefMask(RefType::kReturnQueue); | |
| // fReusableRefMask should have added this bit added during construction. | |
| SkASSERT((fReusableRefMask & RefMask(RefType::kReturnQueue)) != 0); | |
| // This expression matches the above logic for returning because: | |
| // - Both kPurgeableReturnMask and fReusableRefMask include the return queue bit, | |
| // but kRefIncrement does not. The only way the comparisons can be true is if | |
| // the return queue bit is unset. | |
| // - When the resource is reusable only when purgeable, then both sides of the || | |
| // are identical because fReusableRefMask will equal kPurgeableReturnMask. | |
| // And if the == returns true, we know origRefs was non-zero and nextRefs will | |
| // be zero since it subtracts kRefIncrement. | |
| // - When the resource is reusable when just the usage refs reach 0, the purgeable | |
| // state transition works like before. But fReusableRefMask will mask out any | |
| // non-zero bits in the command buffer subfield. | |
| // - When kRefType==kUsage, the right-hand == will be true when origRefs had | |
| // one usage ref left and nextRefs holds zero. | |
| // - When kRefType==kCommandBuffer, the right-hand side of the || will always | |
| // be false because kRefIncrement will hold bits outside of fReusableRefMask. | |
| // This ensures that the non-reusable -> reusable transition occurs solely | |
| // on removing a usage ref. | |
| SkASSERT((kRefIncrement & RefMask(RefType::kReturnQueue)) == 0); | |
| needsReturn = ((origRefs & kPurgeableReturnMask) == kRefIncrement) || | |
| ((origRefs & fReusableRefMask) == kRefIncrement); | |
| nextRefs = (origRefs - kRefIncrement) | | |
| (needsReturn ? RefMask(RefType::kReturnQueue) : 0); | |
| // If origRefs already included a return queue ref, nextRefs hasn't changed that | |
| SkASSERT((origRefs & RefMask(RefType::kReturnQueue)) == | |
| (nextRefs & RefMask(RefType::kReturnQueue)) || needsReturn); | |
| } while (!fRefs.compare_exchange_weak(origRefs, nextRefs, | |
| std::memory_order_release, | |
| std::memory_order_relaxed)); | |
| // NOTE: because RefMask(RefType::kReturnQueue) was included in the `needsReturn` check, | |
| // we know that it was unset in `origRefs`, and was added to `nextRefs`. The CAS ensures | |
| // that this was the thread that added the return queue ref if `needsReturn` is true | |
| // when the do-while loop exits. | |
| if (needsReturn && !this->returnToCache()) { | |
| // The cache rejected the resource, so we need to unset the "return queue" ref that | |
| // we added above, which may be the last ref keeping the object alive. | |
| SkASSERT(!fNextInReturnQueue); | |
| origRefs = this->removeRef<RefType::kReturnQueue>(); | |
| // so do not access *this* after this point! | |
| } | |
| // else we weren't returning the resource yet, or the cache is maintaining the return | |
| // ref until the return queue has been drained. | |
| } | |
| return origRefs; | |
| } | |
| static constexpr size_t kInvalidGpuMemorySize = ~static_cast<size_t>(0); | |
| // See RefIncrement() for how the bits in this field are interpreted. | |
| mutable std::atomic<uint64_t> fRefs; | |
| // Depending on when the resource can be reused, there are two base values: | |
| // 1. RefMask(kUsage): reused while there is outstanding GPU work (CB ref count is ignored). | |
| // 2. RefMask(kUsage) | RefMask(kCB): cannot be reused until it is also purgeable. | |
| // To simplify logic in removeRef(), this value always includes RefMask(kReturnQueue). | |
| // See removeRef() for rationale. | |
| // | |
| // NOTE: Reusability is related but distinct from shareability. Shareability takes into account | |
| // external information about when and for how long the state of the resource must remain stable | |
| // We track when all resources become "reusable" again even if they were fully shareable because | |
| // that marks when the resource can also change its Shareable type. | |
| const uint64_t fReusableRefMask; | |
| // This is not ref'ed but internalDispose() will be called before the Gpu object is destroyed. | |
| // That call will set this to nullptr. | |
| const SharedContext* fSharedContext; | |
| const UniqueID fUniqueID; | |
| const Ownership fOwnership; | |
| const bool fRequiresPrepareForReturnToCache; | |
| // The resource key and return cache are both set at most once, during registerWithCache(). | |
| /*const*/ GraphiteResourceKey fKey; | |
| /*const*/ sk_sp<ResourceCache> fReturnCache; | |
| // Resources added to their return cache's queue are tracked in a lock-free thread-safe | |
| // singly-linked list whose head element is stored on the cache, and next elements are stored | |
| // inline in Resource. This can only be modified by the thread that set the return queue ref, | |
| // or by the thread that is removing said ref. | |
| // | |
| // A null value means the Resource is not in the return queue. A non-null value means it is in | |
| // the queue, although the ResourceCache assigns a special sentinel value for the tail address. | |
| Resource* fNextInReturnQueue = nullptr; | |
| // The remaining fields are mutable state that is only modified by the ResourceCache on the | |
| // cache's thread, guarded by `fReturnCache::fSingleOwner`. | |
| size_t fGpuMemorySize = kInvalidGpuMemorySize; | |
| // All resources created internally by Graphite that are held in the ResourceCache as shared or | |
| // available scratch resources are considered budgeted. Resources that back client-owned objects | |
| // (e.g. SkSurface or SkImage) and wrapper objects (e.g. BackendTexture) do not count against | |
| // cache limits and therefore should never be budgeted. | |
| Budgeted fBudgeted = Budgeted::kNo; | |
| // All resources start out as non-shareable (the strictest mode) and revert to non-shareable | |
| // when they are returned to the cache and have no more usage refs. An available resource can | |
| // be returned if its shareable type matches the request, or if it was non-shareable at which | |
| // point the resource is upgraded to the more permissive mode (until all shared usages are | |
| // dropped at which point it can be used for any purpose again). | |
| Shareable fShareable = Shareable::kNo; | |
| // This is only used by ProxyCache::purgeProxiesNotUsedSince which is called from | |
| // ResourceCache::purgeResourcesNotUsedSince. When kYes, this signals that the Resource | |
| // should've been purged based on its timestamp at some point regardless of what its | |
| // current timestamp may indicate (since the timestamp will be updated when the Resource | |
| // is returned to the ResourceCache). | |
| DeleteASAP fDeleteASAP = DeleteASAP::kNo; | |
| // Set to true when the resource is contained in its cache's `fResourceMap`, which allows it to | |
| // be returned from findAndRefResource(). | |
| bool fAvailableForReuse = false; | |
| // An index into a heap when this resource is purgeable or an array when not. This is maintained | |
| // by the cache. Must be mutable to fit SkTDPQueue's access API. | |
| mutable int fCacheArrayIndex = -1; | |
| // This value reflects how recently this resource was accessed in the cache. This is maintained | |
| // by the cache. It defines a total order over resources, even if their fLastAccess times are | |
| // the same (i.e. returned at time points less than the system's granularity). | |
| uint32_t fLastUseToken = 0; | |
| skgpu::StdSteadyClock::time_point fLastAccess; | |
| // String used to describe the current use of this Resource. | |
| std::string fLabel; | |
| // Flag to signal whether this Resource's label has been updated and its backend label is now | |
| // out of sync. This attribute does not need to be atomic or otherwise explicitly threadsafe | |
| // because labels will only ever be read from or written to when either: | |
| // A) The current thread owns the sole ref to the resource (e.g. upon creation, or return from | |
| // findAndRefResource()) | |
| // B) Inserting a recording on the Context thread, which cannot overlap with A) because there is | |
| // a usage ref held during insertion. | |
| bool fBackendLabelDirty = false; | |
| }; | |
| } // namespace skgpu::graphite | |
| #endif // skgpu_graphite_Resource_DEFINED |