1 /* 2 * Copyright 2024 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_ScratchResourceManager_DEFINED 9 #define skgpu_graphite_ScratchResourceManager_DEFINED 10 11 #include "include/core/SkRefCnt.h" 12 #include "include/core/SkSize.h" 13 #include "include/private/base/SkTArray.h" 14 #include "src/core/SkTHash.h" 15 16 #include <string_view> 17 18 namespace skgpu::graphite { 19 20 class Resource; 21 class ResourceProvider; 22 class Texture; 23 class TextureInfo; 24 class TextureProxy; 25 26 // NOTE: This is temporary while atlas management requires flushing an entire Recorder. That 27 // can break a scratch Device into multiple DrawTasks and the proxy read count needs to count 28 // all reads regardless of which DrawTask is referenced. Once scratch devices only produce a 29 // single DrawTask, DrawTask can hold the pending read count directly. 30 class ProxyReadCountMap { 31 public: 32 ProxyReadCountMap() = default; 33 increment(const TextureProxy * proxy)34 void increment(const TextureProxy* proxy) { 35 int* count = fCounts.find(proxy); 36 if (!count) { 37 count = fCounts.set(proxy, 0); 38 } 39 (*count)++; 40 } 41 decrement(const TextureProxy * proxy)42 bool decrement(const TextureProxy* proxy) { 43 int* count = fCounts.find(proxy); 44 SkASSERT(count && *count > 0); 45 (*count)--; 46 return *count == 0; 47 } 48 get(const TextureProxy * proxy)49 int get(const TextureProxy* proxy) const { 50 const int* count = fCounts.find(proxy); 51 return count ? *count : 0; 52 } 53 54 SkDEBUGCODE(bool hasPendingReads() const;) 55 56 private: 57 skia_private::THashMap<const TextureProxy*, int> fCounts; 58 }; 59 60 /** 61 * ScratchResourceManager helps coordinate the reuse of resources *within* a Recording that would 62 * not otherwise be returned from the ResourceProvider/Cache because the Recorder is holds usage 63 * refs on the resources and they are typically not Shareable. 64 * 65 * A ScratchResourceManager maintains a pool of resources that have been handed out for some use 66 * case and then been explicitly returned by the original holder. It is up to the callers to 67 * return resources in an optimal manner (for best reuse) and not use them after they've been 68 * returned for a later task's use. To help callers manage when they can return resources, 69 * the manager maintains a stack that corresponds with the depth-first traversal of the tasks 70 * during prepareResources() and provides hooks to register listeners that are invoked when tasks 71 * read or sample resources. 72 * 73 * Once all uninstantiated resources are assigned and prepareResources() succeeds, the 74 * ScratchResourceManager can be discarded. The reuse within a Recording's task graph is fixed at 75 * that point and remains valid even if the recording is replayed. 76 */ 77 class ScratchResourceManager { 78 public: 79 ScratchResourceManager(ResourceProvider* resourceProvider, 80 std::unique_ptr<ProxyReadCountMap>); 81 ~ScratchResourceManager(); 82 83 // Get a scratch texture with the given size and texture info. The returned texture will 84 // not be reusable until the caller invokes `returnResource()`. At that point, subsequent 85 // compatible calls to getScratchTexture() may return the texture. If there is no compatible 86 // available texture to be reused, the ResourceProvider will be used to find or create one. 87 // 88 // It is the caller's responsibility to determine when it's acceptable to return a resource. 89 // That said, it's not mandatory that the scratch resources be returned. In that case, they just 90 // stop being available for reuse for later tasks in a Recording. 91 sk_sp<Texture> getScratchTexture(SkISize, const TextureInfo&, std::string_view label); 92 93 // TODO: Eventually update ScratchBuffer and DrawBufferManager to leverage the 94 // ScratchResourceManager. There are a few open issues to address first: 95 // - ScratchBuffer uses RAII to return the resource; ScratchResourceManager could adopt this 96 // for buffers but that may only make sense if textures could also operate that way. 97 // Alternatively, ScratchBuffer remains an RAII abstraction on top of ScratchResourceManager. 98 // - ScratchResourceManager is currently only available in snap(), but DrawBufferManager needs 99 // to be available at all times because a DrawPass could be created whenever. b/335644795 100 // considers moving all DrawPass creation into snap() so that would avoid this issue. 101 // Alternatively, ScratchResourceManager could have the same lifetime as the buffer manager. 102 103 // Mark the resource as available for reuse. Must have been previously returned by this manager. 104 // If the caller does not ensure that all of its uses of the resource are prepared before 105 // tasks that are processed after this call, then undefined results can occur. 106 void returnTexture(sk_sp<Texture>); 107 108 // Graphite accumulates tasks into a graph (implicit dependencies defined by the order they are 109 // added to the root task list, or explicitly when appending child tasks). The depth-first 110 // traversal of this graph helps impose constraints on the read/write windows of resources. To 111 // help Tasks with this tracking, ScratchResourceManager maintains a stack of lists of "pending 112 // uses". 113 // 114 // Each recursion in the depth-first traversal of the task graph pushes the stack. Going up 115 // pops the stack. A "pending use" allows a task that modifies a resource to register a 116 // listener that is triggered when either its scope is popped off or a consuming task that 117 // reads that resource notifies the ScratchResourceManager (e.g. a RenderPassTask or CopyTask 118 // that sample a scratch texture). Internally, the listeners can decrement a pending read count 119 // or otherwise determine when to call returnResource() without having to be coupled directly to 120 // the consuming tasks. 121 // 122 // When a task calls notifyResourcesConsumed(), all "pending use" listeners in the current 123 // scope are invoked and removed from the list. This means that tasks must be externally 124 // organized such that only the tasks that prepare the scratch resources for that consuming task 125 // are at the same depth. Intermingling writes to multiple scratch textures before they are 126 // sampled by separate renderpasses would mean that all the scratch textures could be returned 127 // for reuse at the first renderpass. Instead, a TaskList can be used to group the scratch 128 // writes with the renderpass that samples it to introduce a scope in the stack. Alternatively, 129 // if the caller constructs a single list directly to avoid this issue, the extra stack 130 // manipulation can be avoided. 131 class PendingUseListener { 132 public: ~PendingUseListener()133 virtual ~PendingUseListener() {} 134 135 virtual void onUseCompleted(ScratchResourceManager*) = 0; 136 }; 137 138 // Push a new scope onto the stack, preventing previously added pending listeners from being 139 // invoked when a task consumes resources. 140 void pushScope(); 141 142 // Pop the current scope off the stack. This does not invoke any pending listeners that were 143 // not consumed by a task within the ending scope. This can happen if an offscreen layer is 144 // flushed in a Recording snap() before it's actually been drawn to its target. That final draw 145 // can then happen in a subsequent Recording even. By not invoking the pending listener, it will 146 // not return the scratch resource, correctly keeping it in use across multiple Recordings. 147 // TODO: Eventually, the above scenario should not happen, but that requires atlasing to not 148 // force a flush of every Device. Once that is the case, popScope() can ideally assert that 149 // there are no more pending listeners to invoke (otherwise it means the tasks were linked 150 // incorrectly). 151 void popScope(); 152 153 // Invoked by tasks that sample from or read from resources. All pending listeners that were 154 // marked in the current scope will be invoked. 155 void notifyResourcesConsumed(); 156 157 // Register a listener that will be invoked on the next call to notifyResourcesConsumed() or 158 // popScope() within the current scope. Registering the same listener multiple times will invoke 159 // it multiple times. 160 // 161 // The ScratchResourceManager does not take ownership of these listeners; they are assumed to 162 // live for as long as the prepareResources() phase of snapping a Recording. 163 void markResourceInUse(PendingUseListener* listener); 164 165 // Temporary access to the proxy read counts stored in the ScratchResourceManager pendingReadCount(const TextureProxy * proxy)166 int pendingReadCount(const TextureProxy* proxy) const { 167 return fProxyReadCounts->get(proxy); 168 } 169 170 // Returns true if the read count reached zero; must only be called if it was > 0 previously. removePendingRead(const TextureProxy * proxy)171 bool removePendingRead(const TextureProxy* proxy) { 172 return fProxyReadCounts->decrement(proxy); 173 } 174 175 private: 176 ResourceProvider* fResourceProvider; 177 178 // ScratchResourceManager holds a pointer to each un-returned scratch resource that it's 179 // fetched from the ResourceProvider that should be considered unavailable when making 180 // additional resource requests from the cache with compatible keys. They are bare pointers 181 // because the resources are kept alive by the proxies and tasks that queried the 182 // ScratchResourceManager. 183 // NOTE: This is the same type as ResourceCache::ScratchResourceSet but cannot be forward 184 // declared because it's both a template and an inner type. 185 skia_private::THashSet<const Resource*> fUnavailable; 186 187 // This single list is organized into a stack of sublists by using null pointers to mark the 188 // start of a new scope. 189 skia_private::TArray<PendingUseListener*> fListenerStack; 190 191 std::unique_ptr<ProxyReadCountMap> fProxyReadCounts; 192 }; 193 194 } // namespace skgpu::graphite 195 196 #endif // skgpu_graphite_ResourceReuseManager_DEFINED 197