• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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