• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2022 Google LLC
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #ifndef skgpu_graphite_Resource_DEFINED
9 #define skgpu_graphite_Resource_DEFINED
10 
11 #include "include/gpu/GpuTypes.h"
12 #include "include/private/base/SkMutex.h"
13 #include "src/gpu/GpuTypesPriv.h"
14 #include "src/gpu/graphite/GraphiteResourceKey.h"
15 #include "src/gpu/graphite/ResourceTypes.h"
16 
17 #include <atomic>
18 #include <functional>
19 #include <string>
20 #include <string_view>
21 
22 class SkMutex;
23 class SkTraceMemoryDump;
24 
25 namespace skgpu::graphite {
26 
27 class GlobalCache;
28 class ResourceCache;
29 class SharedContext;
30 class Texture;
31 
32 /**
33  * Base class for objects that can be kept in the ResourceCache and created or fetched by a
34  * ResourceProvider. A ResourceProvider controls the creation of a Resource and determines how
35  * its lifecycle is managed (e.g. is it cached, shareable, etc.). Once created, however, the
36  * Resource's lifecycle is controlled by the provider's ResourceCache so that GPU resources can
37  * be reused when ref counts reach 0; a Resource that has no more refs and does not belong to a
38  * cache is simply deleted.
39  *
40  * Resource Threading Model
41  * ========================
42  *
43  * A Resource is allowed to be used in multiple threads and be deleted from any thread safely.
44  * Resource refs are broken into several categories: usage (public C++ references), command buffer
45  * (representing active GPU work), and internal (managed by the ResourceCache). Non-cache refs can
46  * be removed and reach zero from any thread and be safely returned to the ResourceCache. Unlike
47  * Resource, ResourceCache and ResourceProvider are not thread safe and are only used by a single
48  * thread.
49  *
50  * Active Threads:
51  *   1. Context thread: this is the primary thread where Recordings are inserted and GPU work is
52  *      scheduled.
53  *   2. Cache thread: this is the thread that the ResourceCache is associated with. This can be the
54  *      same as the context thread if the ResourceCache and ResourceProvider belong to the Context.
55  *      However, a Recorder's ResourceCache/Provider can be used on an arbitrary single thread the
56  *      client is using for that specific Recorder.
57  *   3. Usage threads: once created on the cache thread, a Resource is not restricted to just the
58  *      cache thread and context thread. The Resource can have references held on any other
59  *      Recorder's thread if multiple work streams use the resource, or may end up held in other
60  *      client threads naturally as part of their lifecycle management of Skia's API objects.
61  *         - A Resource could be accessed by an arbitrary number of usage threads.
62  *
63  * Reference Types:
64  *   1. Usage refs: a Resource starts with a single usage ref when created by the ResourceProvider,
65  *      which is then given to the caller as an `sk_sp<R>`. Once it escapes the ResourceProvider,
66  *      operations on the `sk_sp` can add more usage refs. Usage refs track the liveness of the
67  *      Resource from C++ code external to the ResourceCache (either higher-level Skia/Graphite
68  *      objects or client objects keeping it alive).
69  *   2. Command buffer refs: every Resource used in a command buffer has command buffer refs added
70  *      when referencing work (e.g. a Recording) is inserted into the Context. These refs are
71  *      released when the command buffer's finish procs are called and it's known that the GPU-side
72  *      operations are completely done with the Resources.
73  *   3. Cache ref: A ResourceProvider can choose to register a Resource with its ResourceCache at
74  *      creation time before returning the Resource. The ResourceCache holds a single ref on every
75  *      Resource registered with it until the cache is shutdown.
76  *   4. Return queue ref: At most one ref held while the Resource is being added to the cache's
77  *      return queue and held until it's removed from the queue on the cache thread.
78 
79  *
80  * Lifecycle:
81  *   1. Initialization: all Resources are created via the ResourceProvider, which also coordinates
82  *      with its ResourceCache. If an existing resource can't be used, a new one is created. The
83  *      initialization phase is the period between when the provider creates the Resource object and
84  *      when it's returned to the caller. The resource can be assigned a key and added to the cache
85  *      at this point.
86  *   2. Reusable: when all usage refs are removed, some Resources can become reusable and move into
87  *      the return queue (on any thread). On the cache thread, the queue is regularly processed and
88  *      resources that remain reusable are tracked appropriately within the cache.
89  *         - Resources that are only modified via command buffer operations can be reusable when
90  *           usage refs reach 0 because future modifications won't conflict with any outstanding
91  *           work that is holding current command buffer refs.
92  *         - Resources that can be modified directly (e.g. mapped buffers) are not reusable until
93  *           there are no usage or command buffer refs to ensure modifications don't become visible
94  *           to the GPU before intended.
95  *   3. Purgeable: when all usage refs and command buffer refs are removed, a Resource is purgeable.
96  *      Resources that weren't considered reusable while having outstanding command buffer refs are
97  *      now also reusable. Purgeable resources are also added to the return queue so that the cache
98  *      can decide whether or not to keep the resource alive (based on budget limits).
99  *   4. Destruction: if a Resource has no refs at all, it can be destroyed. A purgeable resource
100  *      that was not registered with a cache will never have a cache ref or return queue ref so is
101  *      immediately destroyed. When a cache processes a purgeable resource from the return queue, it
102  *      can choose to drop its cache ref (and return queue ref) so the resource will be destroyed.
103  *      Purgeable resources that had been kept alive for reuse can also be dropped by the cache when
104  *      it is being purged (for budget or idleness reasons).
105  *   5. Shutdown: when a Context or Recorder are deleted, its ResourceCache is shutdown. The cache
106  *      removes all of its cache refs and blocks any further changes to the return queue. This
107  *      allows Resources to be destroyed ASAP when they become purgeable instead of going back to
108  *      the shutdown cache. However, every Resource registered with the cache keeps the cache alive.
109  *      Once every cached resource is destroyed, the cache itself will be destroyed.
110  *
111  * Ref Counting and State Transition Properties:
112  *   - The cache ref can only be added during initialization (it is uniquely held and on the cache
113  *     thread).
114  *   - The cache ref can only be removed by the ResourceCache on the cache thread (purging
115  *     operations and shutdown are all single-threaded functions on the cache thread).
116  *   - The return queue ref can be added by any thread when the Resource transitions to reusable
117  *     and/or purgeable.
118  *   - The return queue ref cannot be added more than once; if the Resource goes through multiple
119  *     transitions before being processed by the cache, the second transition does not need to be
120  *     put into the queue.
121  *   - The return queue ref cannot be added directly, but only as part of a usage/CB unref that
122  *     transitions the resource to being reusable or purgeable. The return queue ref is added
123  *     atomically with the other unref.
124  *   - The return queue ref can only be removed by the resource cache on the cache thread, unless
125  *     the cache was shutdown already. In that case the ref is removed by the adding thread. The
126  *     cache will only remove the return queue ref *after* the resource is removed from the queue.
127  *   - If the resource was not registered with a cache during initialization, its cache and return
128  *     queue refs will always be zero.
129  *   - Regular usage refs can only be added when there was prior usage ref held by the caller. This
130  *     can happen on any thread.
131  *   - Command buffer refs can only be added if the resource has a usage ref (that is held through
132  *     the CB ref'ing operation). This can happen on any thread (although in practice just the
133  *     context thread).
134  *   - A command buffer ref cannot be promoted back to a usage ref.
135  *   - Both command buffer and usage refs can be removed from any thread (although CB refs are
136  *     likely to be unreffed on the context thread).
137  *   - When the usage ref count reaches zero, it can only become non-zero via the cache thread by
138  *     actions of its ResourceCache.
139  *        - Actions predicated on usage refs being 0 on the cache thread are safe.
140  *        - Such actions on other threads are not safe because the cache thread could process the
141  *          return queue and reuse the resource, or skip the return queue entirely for shareable
142  *          resources.
143  *   - When the resource becomes purgeable, it can only become non-purgeable on the cache thread
144  *     by action of the ResourceCache.
145  *        - Since there are no other usage refs (beyond what the cache might re-add), no other
146  *          thread can re-add a command buffer ref.
147  *
148  */
149 class Resource {
150     enum class RefType {
151         kUsage, // Counts controlled by `sk_sp` and tracks liveness from external C++ code.
152         kCommandBuffer, // Incremented in Context::insertRecording, decremented by finish procs.
153         kCache, // At most 1 ref, added in registerWithCache(), removed on cache shutdown or purge.
154         kReturnQueue, // At most 1 ref, held while in the cache's return queue.
155     };
156 
157 public:
158     Resource(const Resource&) = delete;
159     Resource(Resource&&) = delete;
160     Resource& operator=(const Resource&) = delete;
161     Resource& operator=(Resource&&) = delete;
162 
163     // Adds a usage ref to the resource. Named ref so we can easily manage usage refs with sk_sp.
ref()164     void ref() const {
165         // Only the cache should be able to add the first usage ref to a resource.
166         this->addRef<RefType::kUsage>();
167     }
168 
169     // Removes a usage ref from the resource
unref()170     void unref() const {
171         this->removeRef<RefType::kUsage>();
172     }
173 
174     // Adds a command buffer ref to the resource
refCommandBuffer()175     void refCommandBuffer() const {
176         this->addRef<RefType::kCommandBuffer>();
177     }
178 
179     // Removes a command buffer ref from the resource
unrefCommandBuffer()180     void unrefCommandBuffer() const {
181         this->removeRef<RefType::kCommandBuffer>();
182     }
183 
ownership()184     Ownership ownership() const { return fOwnership; }
185 
budgeted()186     Budgeted budgeted() const { return fBudgeted; }
shareable()187     Shareable shareable() const { return fShareable; }
key()188     const GraphiteResourceKey& key() const { return fKey; }
189 
190     // Retrieves the amount of GPU memory used by this resource in bytes. It is approximate since we
191     // aren't aware of additional padding or copies made by the driver.
gpuMemorySize()192     size_t gpuMemorySize() const { return fGpuMemorySize; }
193 
194     class UniqueID {
195     public:
196         UniqueID() = default;
197 
UniqueID(uint32_t id)198         explicit UniqueID(uint32_t id) : fID(id) {}
199 
asUInt()200         uint32_t asUInt() const { return fID; }
201 
202         bool operator==(const UniqueID& other) const { return fID == other.fID; }
203         bool operator!=(const UniqueID& other) const { return !(*this == other); }
204 
205     private:
206         uint32_t fID = SK_InvalidUniqueID;
207     };
208 
209     // Gets an id that is unique for this Resource object. It is static in that it does not change
210     // when the content of the Resource object changes. This will never return 0.
uniqueID()211     UniqueID uniqueID() const { return fUniqueID; }
212 
getLabel()213     std::string getLabel() const { return fLabel; }
214 
215     // We allow the label on a Resource to change when used for a different function. For example
216     // when reusing a scratch Texture we can change the label to match callers current use.
setLabel(std::string_view label)217     void setLabel(std::string_view label) {
218         fLabel = label;
219 
220         if (!fLabel.empty()) {
221             const std::string fullLabel = "Skia_" + fLabel;
222             this->setBackendLabel(fullLabel.c_str());
223         }
224     }
225 
226     // Tests whether a object has been abandoned or released. All objects will be in this state
227     // after their creating Context is destroyed or abandoned.
228     //
229     // @return true if the object has been released or abandoned,
230     //         false otherwise.
231     // TODO: As of now this function isn't really needed because in freeGpuData we are always
232     // deleting this object. However, I want to implement all the purging logic first to make sure
233     // we don't have a use case for calling internalDispose but not wanting to delete the actual
234     // object yet.
wasDestroyed()235     bool wasDestroyed() const { return fSharedContext == nullptr; }
236 
237     // Describes the type of gpu resource that is represented by the implementing
238     // class (e.g. texture, buffer, etc).  This data is used for diagnostic
239     // purposes by dumpMemoryStatistics().
240     //
241     // The value returned is expected to be long lived and will not be copied by the caller.
242     virtual const char* getResourceType() const = 0;
243 
asTexture()244     virtual const Texture* asTexture() const { return nullptr; }
245 
246 #if defined(GPU_TEST_UTILS)
testingShouldDeleteASAP()247     bool testingShouldDeleteASAP() const { return fDeleteASAP == DeleteASAP::kYes; }
248 #endif
249 
250 protected:
251     Resource(const SharedContext*,
252              Ownership,
253              size_t gpuMemorySize,
254              bool reusableRequiresPurgeable = false);
255     virtual ~Resource();
256 
sharedContext()257     const SharedContext* sharedContext() const { return fSharedContext; }
258 
259     // Needs to be protected for DawnBuffer's emscripten prepareForReturnToCache
setDeleteASAP()260     void setDeleteASAP() { fDeleteASAP = DeleteASAP::kYes; }
261 
262 private:
263     ///////////////////////////////////////////////////////////////////////////////////////////////
264     // The following set of functions are only meant to be called by the [Global|Proxy]Cache. We
265     // don't want them public general users of a Resource, but they also aren't purely internal.
266     ///////////////////////////////////////////////////////////////////////////////////////////////
267     friend class ProxyCache; // for setDeleteASAP and updateAccessTime
268     friend GlobalCache; // for lastAccessTime and updateAccessTime
269 
270     enum class DeleteASAP : bool {
271         kNo = false,
272         kYes = true,
273     };
274 
shouldDeleteASAP()275     DeleteASAP shouldDeleteASAP() const { return fDeleteASAP; }
276 
277     // In the ResourceCache this is called whenever a Resource is moved into the purgeableQueue. It
278     // may also be called by the ProxyCache and GlobalCache to track the time on Resources they are
279     // holding on to.
updateAccessTime()280     void updateAccessTime() { fLastAccess = skgpu::StdSteadyClock::now(); }
lastAccessTime()281     skgpu::StdSteadyClock::time_point lastAccessTime() const { return fLastAccess; }
282 
283     ///////////////////////////////////////////////////////////////////////////////////////////////
284     // The following set of functions are only meant to be called by the ResourceCache. We don't
285     // want them public general users of a Resource, but they also aren't purely internal calls.
286     ///////////////////////////////////////////////////////////////////////////////////////////////
287     friend class ResourceCache;
288 
setBudgeted(Budgeted budgeted)289     void setBudgeted(Budgeted budgeted) {
290         SkASSERT(budgeted == Budgeted::kNo || fOwnership == Ownership::kOwned);
291         fBudgeted = budgeted;
292     }
setShareable(Shareable shareable)293     void setShareable(Shareable shareable) {
294         SkASSERT(shareable == Shareable::kNo || fBudgeted == Budgeted::kYes);
295         fShareable = shareable;
296     }
297 
setAvailableForReuse(bool avail)298     void setAvailableForReuse(bool avail) { fAvailableForReuse = avail; }
isAvailableForReuse()299     bool isAvailableForReuse() const { return fAvailableForReuse; }
300 
lastUseToken()301     uint32_t lastUseToken() const { return fLastUseToken; }
setLastUseToken(uint32_t token)302     void setLastUseToken(uint32_t token) { fLastUseToken = token; }
303 
setNextInReturnQueue(Resource * next)304     void setNextInReturnQueue(Resource* next) {
305         SkASSERT(this->hasReturnQueueRef());
306         fNextInReturnQueue = next;
307     }
308 
accessCacheIndex()309     int* accessCacheIndex() const { return &fCacheArrayIndex; }
cache()310     const ResourceCache* cache() const { return fReturnCache.get(); }
311 
312     // If possible, queries the backend API to check the current allocation size of the gpu
313     // resource and updates the tracked value. This is specifically useful for Vulkan backends which
314     // use lazy allocated memory for "memoryless" resources. Ideally that memory should stay zero
315     // throughout its usage, but certain usage patterns can trigger the device to commit real memory
316     // to the resource. So this will allow us to have a more accurate tracking of our memory usage.
updateGpuMemorySize()317     void updateGpuMemorySize() { fGpuMemorySize = this->onUpdateGpuMemorySize(); }
318 
319     // Dumps memory usage information for this Resource to traceMemoryDump.
320     void dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump, bool inPurgeableQueue) const;
321 
322     /**
323      * If the resource has a non-shareable key then this gives the resource subclass an opportunity
324      * to prepare itself to re-enter the cache. The ResourceCache extends its privilege to take the
325      * first UsageRef to this function via takeRef. If takeRef is called this resource will not
326      * immediately enter the cache but will be re-reprocessed when the usage ref count again reaches
327      * zero.
328      *
329      * Return true if takeRef() was invoked.
330      */
prepareForReturnToCache(const std::function<void ()> & takeRef)331     virtual bool prepareForReturnToCache(const std::function<void()>& takeRef) { return false; }
332 
333     // Adds a cache ref to the resource. May only be called once.
334     void registerWithCache(sk_sp<ResourceCache>, const GraphiteResourceKey&, Budgeted, Shareable);
335 
336     // This version of ref allows adding a ref when the usage count is 0. This should only be called
337     // from the ResourceCache.
initialUsageRef()338     void initialUsageRef() const {
339         this->addRef<RefType::kUsage, /*MustHaveUsageRefs=*/false>();
340     }
341 
342     // Removes a cache ref from the resource. The unref here should only ever be called from the
343     // ResourceCache and only in the Recorder/Context thread the ResourceCache is part of.
unrefCache()344     void unrefCache() const {
345         SkASSERT(fReturnCache);
346         this->removeRef<RefType::kCache>();
347     }
348 
349     // Removes the return queue ref that was held while the Resource was in the queue. This can only
350     // be called by the ResourceCache on its thread. It should not be called after unrefCache().
351     // It must only be called after the cache has removed the resource from its return queue.
352     //
353     // Returns {isReusable, isPurgeable} atomically based on the reference state when the return
354     // queue ref was removed. `isReusable` is true if all refs affecting reusability were zero
355     // when the queue ref was removed. `isPurgeable` is true if all usage and command buffer refs
356     // were zero.
357     //
358     // If true is returned there are no other refs that could trigger a reusable or purgeable state
359     // change. A resource that entered the return queue due to becoming reusable or purgeable only
360     // happens if that ref count reached zero, so there should be no external ref holder (other than
361     // the ResourceCache with its separate cache ref). However, unrefReturnQueue() is only called by
362     // the ResourceCache so it won't be simultaneously handing out usage refs.
363     //
364     // If false is returned, it is possible for the resource to immediately become purgeable on
365     // another thread but since this thread has released its return queue ref, the Resource will
366     // simply go back in the next return queue.
367     //
368     // The cache should track the Resource based on this return value instead of re-checking the
369     // ref counts as that would not be an atomic operation.
unrefReturnQueue()370     std::tuple<bool, bool, Resource*> unrefReturnQueue() {
371         // We must reset the fNextInReturnQueue value *before* removing the return queue ref, but we
372         // need to return the old value to the ResourceCache so that it can continue iterating over
373         // the linked list.
374         Resource* next = fNextInReturnQueue;
375         fNextInReturnQueue = nullptr;
376 
377         uint64_t origRefs = this->removeRef<RefType::kReturnQueue>();
378 
379         // Since we should always have a cache ref when this is called, the Resource will never be
380         // transitioning to having zero refs, although if `true` is returned the cache may choose to
381         // then drop its cache ref.
382         SkASSERT((origRefs & RefMask(RefType::kCache)) != 0);
383         // `fReusableRefMask` always includes the ReturnQueue ref mask, and since we just removed
384         // the return ref value, `origRefs` also includes the the ReturnQueue ref mask bit. We have
385         // to compare to the ref mask to detect the case when the actual reusable refs are all zero.
386         // Since PurgeableMask() does not add the ReturnQueue ref mask, it *can* compare to zero.
387         return {(origRefs & fReusableRefMask) == RefMask(RefType::kReturnQueue),
388                 (origRefs & PurgeableMask()) == 0,
389                 next};
390     }
391 
392 #if defined(SK_DEBUG) || defined(GPU_TEST_UTILS)
hasCacheRef()393     bool hasCacheRef() const {
394         return (fRefs.load(std::memory_order_acquire) & RefMask(RefType::kCache)) != 0;
395     }
396 
hasReturnQueueRef()397     bool hasReturnQueueRef() const {
398         return (fRefs.load(std::memory_order_acquire) & RefMask(RefType::kReturnQueue)) != 0;
399     }
400 
inReturnQueue()401     bool inReturnQueue() const {
402         return this->hasReturnQueueRef() && SkToBool(fNextInReturnQueue);
403     }
404 
isUsableAsScratch()405     bool isUsableAsScratch() const {
406         // This is only called by the ResourceCache, so the state of the Resource's refs won't
407         // be changed by another thread when isReusable is true.
408         uint64_t origRefs = fRefs.load(std::memory_order_acquire) & ~RefMask(RefType::kReturnQueue);
409         bool isReusable = (origRefs & fReusableRefMask) == 0;
410         return fShareable == Shareable::kScratch || (fShareable == Shareable::kNo && isReusable);
411     }
412 
isPurgeable()413     bool isPurgeable() const {
414         // This is only called by the ResourceCache on its thread; if the usage and CB ref counts
415         // are 0, the ResourceCache is the only way in which they can become non-zero again.
416         return (fRefs.load(std::memory_order_acquire) & PurgeableMask()) == 0;
417     }
418 
isUniquelyHeld()419     bool isUniquelyHeld() const {
420         // This intentionally checks that the cache ref and return queue refs are 0, so that fRefs
421         // is compared to the value it is initialized with.
422         return fRefs.load(std::memory_order_acquire) == RefIncrement(RefType::kUsage);
423     }
424 
hasAnyRefs()425     bool hasAnyRefs() const {
426         // Because all ref counts are packed into the same atomic, when this load is actually 0
427         // there are no other threads that can reach the object and add new refs (assuming a raw
428         // pointer has never leaked).
429         return fRefs.load(std::memory_order_acquire) != 0;
430     }
431 #endif
432 
433     ///////////////////////////////////////////////////////////////////////////////////////////////
434     // The remaining calls are meant to be truely private (including virtuals for subclasses)
435     ///////////////////////////////////////////////////////////////////////////////////////////////
436 
437     // Overridden to free GPU resources in the backend API.
438     virtual void freeGpuData() = 0;
439 
440     // Overridden to call any release callbacks, if necessary
invokeReleaseProc()441     virtual void invokeReleaseProc() {}
442 
443     // Overridden to set the label on the underlying GPU resource
setBackendLabel(char const * label)444     virtual void setBackendLabel(char const* label) {}
445 
446     // Overridden to add extra information to the memory dump.
onDumpMemoryStatistics(SkTraceMemoryDump * traceMemoryDump,const char * dumpName)447     virtual void onDumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump,
448                                         const char* dumpName) const {}
449 
450 
451     // Overridden to calculate a more up-to-date size in bytes.
onUpdateGpuMemorySize()452     virtual size_t onUpdateGpuMemorySize() { return fGpuMemorySize; }
453 
454     // Try to add the Resource to the cache's return queue for pending reuse.
455     // This should only be called when there is a cache to return to, and the calling thread
456     // successfully transitioned from no "return queue" ref to setting the return queue ref.
457     //
458     // Returns true if the cache accepted the Resource (in which case the set return ref should
459     // remain set for the cache to remove). If false is returned, the caller should clear the
460     // return queue ref (and possible dispose of the object).
461     bool returnToCache() const;
462 
463     // Frees the object in the underlying 3D API *and* deletes the object itself.
464     void internalDispose();
465 
466     // Resource tracks its different ref counts packed into a single atomic 64-bit value.
467     // The bits are split into subfields:
468     // commandBufferRefs:31 e.g. RefMask(kCB)
469     // usageRefs:31              RefMask(kUsage)
470     // returnQueueRef: 1         RefMask(kReturnQueue)
471     // cacheRefs:1               RefMask(kCache)
472     //
473     // RefIncrement() and RefMask() help access specific ref type's values.
RefIncrement(RefType refType)474     static constexpr uint64_t RefIncrement(RefType refType) {
475         switch (refType) {
476             case RefType::kCommandBuffer: return (uint64_t) 1 << 33;
477             case RefType::kUsage:         return (uint64_t) 1 << 2;
478             case RefType::kCache:         return (uint64_t) 1 << 1;
479             case RefType::kReturnQueue:   return (uint64_t) 1 << 0;
480         }
481         SkUNREACHABLE;
482     }
RefMask(RefType refType)483     static inline constexpr uint64_t RefMask(RefType refType) {
484         switch (refType) {
485             case RefType::kCommandBuffer: return (((uint64_t)1 << 31) - 1) << 33;
486             case RefType::kUsage:         return (((uint64_t)1 << 31) - 1) << 2;
487             case RefType::kCache:         return 0b10;
488             case RefType::kReturnQueue:   return 0b01;
489         }
490         SkUNREACHABLE;
491     }
PurgeableMask()492     static inline constexpr uint64_t PurgeableMask() {
493         return RefMask(RefType::kUsage) | RefMask(RefType::kCommandBuffer);
494     }
495 
496     template <RefType kType, bool MustHaveUsageRefs=true>
addRef()497     void addRef() const {
498         static_assert(kType != RefType::kReturnQueue, "return queue refs cannot be added directly");
499         static constexpr uint64_t kRefIncrement = RefIncrement(kType);
500         // No barrier required
501         [[maybe_unused]] uint64_t origCnt =
502                 fRefs.fetch_add(kRefIncrement, std::memory_order_relaxed);
503         // Require that there was an already held usage ref in order to add this new ref,
504         // e.g. to add a command buffer ref, a usage ref must already be held; calling code can't
505         // add usage refs if it wasn't explicitly handed out by the cache.
506         SkASSERT(!MustHaveUsageRefs || (origCnt & RefMask(RefType::kUsage)) > 0);
507         // And make sure that the specific type of ref did not overflow into another field
508         SkASSERT((RefMask(kType) - (origCnt & RefMask(kType))) >= kRefIncrement);
509     }
510 
511     template <RefType kType>
removeRef()512     uint64_t removeRef() const {
513         static constexpr uint64_t kRefIncrement = RefIncrement(kType);
514 
515         uint64_t origRefs;
516         if (kType == RefType::kCache || kType == RefType::kReturnQueue || !fReturnCache) {
517             // Without a ResourceCache, or when it's a cache/return-queue unref, there is no
518             // non-atomic work that has to happen so simply update the ref count. If the net ref
519             // count reaches 0 we can safely delete the resource because no other thread will
520             // increase the refs.
521             origRefs = fRefs.fetch_sub(kRefIncrement, std::memory_order_acq_rel);
522             SkASSERT((origRefs & RefMask(kType)) >= kRefIncrement); // had a ref to remove
523 
524             if (origRefs == kRefIncrement) {
525                 SkASSERT(!this->hasAnyRefs());
526                 Resource* mutableThis = const_cast<Resource*>(this);
527                 mutableThis->internalDispose();
528             }
529         } else {
530             SkASSERT(kType == RefType::kCommandBuffer || kType == RefType::kUsage);
531             // When removing a usage or CB ref and the resource is registered with the cache,
532             // it may need to be returned to the cache. A resource can only be in the return queue
533             // a single time and must remain alive until cache removes it from the queue. A CAS
534             // loop is used to atomically decrement the ref and add the return queue ref.
535             uint64_t nextRefs;
536             bool needsReturn;
537             do {
538                 origRefs = fRefs.load(std::memory_order_acquire);
539                 SkASSERT((origRefs & RefMask(kType)) >= kRefIncrement); // have a ref to remove
540 
541                 // When unreffing a usage or command buffer ref, the Resource needs to return to
542                 // the queue when:
543                 //  - it's not already in the return queue (return queue ref is 0) AND
544                 //  - it's transitioning from non-reusable -> reusable OR non-purgeable -> purgeable
545                 //
546                 // Including RefMask(kReturnQueue) in the bitwise &'s before comparing to the
547                 // ref increment ensures that the return queue ref was 0 in origRefs.
548                 static constexpr uint64_t kPurgeableReturnMask = PurgeableMask() |
549                                                                  RefMask(RefType::kReturnQueue);
550                 // fReusableRefMask should have added this bit added during construction.
551                 SkASSERT((fReusableRefMask & RefMask(RefType::kReturnQueue)) != 0);
552                 // This expression matches the above logic for returning because:
553                 //  - Both kPurgeableReturnMask and fReusableRefMask include the return queue bit,
554                 //    but kRefIncrement does not. The only way the comparisons can be true is if
555                 //    the return queue bit is unset.
556                 //  - When the resource is reusable only when purgeable, then both sides of the ||
557                 //    are identical because fReusableRefMask will equal kPurgeableReturnMask.
558                 //    And if the == returns true, we know origRefs was non-zero and nextRefs will
559                 //    be zero since it subtracts kRefIncrement.
560                 //  - When the resource is reusable when just the usage refs reach 0, the purgeable
561                 //    state transition works like before. But fReusableRefMask will mask out any
562                 //    non-zero bits in the command buffer subfield.
563                 //      - When kRefType==kUsage, the right-hand == will be true when origRefs had
564                 //        one usage ref left and nextRefs holds zero.
565                 //      - When kRefType==kCommandBuffer, the right-hand side of the || will always
566                 //        be false because kRefIncrement will hold bits outside of fReusableRefMask.
567                 //        This ensures that the non-reusable -> reusable transition occurs solely
568                 //        on removing a usage ref.
569                 SkASSERT((kRefIncrement & RefMask(RefType::kReturnQueue)) == 0);
570                 needsReturn = ((origRefs & kPurgeableReturnMask) == kRefIncrement) ||
571                               ((origRefs & fReusableRefMask) == kRefIncrement);
572 
573                 nextRefs = (origRefs - kRefIncrement) |
574                            (needsReturn ? RefMask(RefType::kReturnQueue) : 0);
575                 // If origRefs already included a return queue ref, nextRefs hasn't changed that
576                 SkASSERT((origRefs & RefMask(RefType::kReturnQueue)) ==
577                          (nextRefs & RefMask(RefType::kReturnQueue)) || needsReturn);
578             } while (!fRefs.compare_exchange_weak(origRefs, nextRefs,
579                                                   std::memory_order_release,
580                                                   std::memory_order_relaxed));
581             // NOTE: because RefMask(RefType::kReturnQueue) was included in the `needsReturn` check,
582             // we know that it was unset in `origRefs`, and was added to `nextRefs`. The CAS ensures
583             // that this was the thread that added the return queue ref if `needsReturn` is true
584             // when the do-while loop exits.
585 
586             if (needsReturn && !this->returnToCache()) {
587                 // The cache rejected the resource, so we need to unset the "return queue" ref that
588                 // we added above, which may be the last ref keeping the object alive.
589                 SkASSERT(!fNextInReturnQueue);
590                 origRefs = this->removeRef<RefType::kReturnQueue>();
591                 // so do not access *this* after this point!
592             }
593             // else we weren't returning the resource yet, or the cache is maintaining the return
594             // ref until the return queue has been drained.
595         }
596 
597         return origRefs;
598     }
599 
600     static constexpr size_t kInvalidGpuMemorySize = ~static_cast<size_t>(0);
601 
602     // See RefIncrement() for how the bits in this field are interpreted.
603     mutable std::atomic<uint64_t> fRefs;
604 
605     // Depending on when the resource can be reused, there are two base values:
606     // 1. RefMask(kUsage): reused while there is outstanding GPU work (CB ref count is ignored).
607     // 2. RefMask(kUsage) | RefMask(kCB): cannot be reused until it is also purgeable.
608     // To simplify logic in removeRef(), this value always includes RefMask(kReturnQueue).
609     // See removeRef() for rationale.
610     //
611     // NOTE: Reusability is related but distinct from shareability. Shareability takes into account
612     // external information about when and for how long the state of the resource must remain stable
613     // We track when all resources become "reusable" again even if they were fully shareable because
614     // that marks when the resource can also change its Shareable type.
615     const uint64_t fReusableRefMask;
616 
617     // This is not ref'ed but internalDispose() will be called before the Gpu object is destroyed.
618     // That call will set this to nullptr.
619     const SharedContext* fSharedContext;
620 
621     const Ownership fOwnership;
622     const UniqueID fUniqueID;
623 
624     // The resource key and return cache are both set at most once, during registerWithCache().
625     /*const*/ GraphiteResourceKey  fKey;
626     /*const*/ sk_sp<ResourceCache> fReturnCache;
627 
628     // Resources added to their return cache's queue are tracked in a lock-free thread-safe
629     // singly-linked list whose head element is stored on the cache, and next elements are stored
630     // inline in Resource. This can only be modified by the thread that set the return queue ref,
631     // or by the thread that is removing said ref.
632     //
633     // A null value means the Resource is not in the return queue. A non-null value means it is in
634     // the queue, although the ResourceCache assigns a special sentinel value for the tail address.
635     Resource* fNextInReturnQueue = nullptr;
636 
637     // The remaining fields are mutable state that is only modified by the ResourceCache on the
638     // cache's thread, guarded by `fReturnCache::fSingleOwner`.
639 
640     size_t fGpuMemorySize = kInvalidGpuMemorySize;
641 
642     // All resources created internally by Graphite that are held in the ResourceCache as shared or
643     // available scratch resources are considered budgeted. Resources that back client-owned objects
644     // (e.g. SkSurface or SkImage) and wrapper objects (e.g. BackendTexture) do not count against
645     // cache limits and therefore should never be budgeted.
646     Budgeted fBudgeted = Budgeted::kNo;
647     // All resources start out as non-shareable (the strictest mode) and revert to non-shareable
648     // when they are returned to the cache and have no more usage refs. An available resource can
649     // be returned if its shareable type matches the request, or if it was non-shareable at which
650     // point the resource is upgraded to the more permissive mode (until all shared usages are
651     // dropped at which point it can be used for any purpose again).
652     Shareable fShareable = Shareable::kNo;
653 
654     // This is only used by ProxyCache::purgeProxiesNotUsedSince which is called from
655     // ResourceCache::purgeResourcesNotUsedSince. When kYes, this signals that the Resource
656     // should've been purged based on its timestamp at some point regardless of what its
657     // current timestamp may indicate (since the timestamp will be updated when the Resource
658     // is returned to the ResourceCache).
659     DeleteASAP fDeleteASAP = DeleteASAP::kNo;
660 
661     // Set to true when the resource is contained in its cache's `fResourceMap`, which allows it to
662     // be returned from findAndRefResource().
663     bool fAvailableForReuse = false;
664 
665     // An index into a heap when this resource is purgeable or an array when not. This is maintained
666     // by the cache. Must be mutable to fit SkTDPQueue's access API.
667     mutable int fCacheArrayIndex = -1;
668 
669     // This value reflects how recently this resource was accessed in the cache. This is maintained
670     // by the cache. It defines a total order over resources, even if their fLastAccess times are
671     // the same (i.e. returned at time points less than the system's granularity).
672     uint32_t fLastUseToken;
673     skgpu::StdSteadyClock::time_point fLastAccess;
674 
675     // String used to describe the current use of this Resource.
676     std::string fLabel;
677 };
678 
679 } // namespace skgpu::graphite
680 
681 #endif // skgpu_graphite_Resource_DEFINED
682