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