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