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