Flutter Engine
The Flutter Engine
ScratchResourceManager.h
Go to the documentation of this file.
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
12#include "include/core/SkSize.h"
14#include "src/core/SkTHash.h"
15
16#include <string_view>
17
18namespace skgpu::graphite {
19
20class Resource;
22class Texture;
23class TextureInfo;
24class 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.
31public:
32 ProxyReadCountMap() = default;
33
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
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
49 int get(const TextureProxy* proxy) const {
50 const int* count = fCounts.find(proxy);
51 return count ? *count : 0;
52 }
53
54private:
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 */
76public:
78 std::unique_ptr<ProxyReadCountMap>);
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.
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.
130 public:
132
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.
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.
162
163 // Temporary access to the proxy read counts stored in the ScratchResourceManager
164 int pendingReadCount(const TextureProxy* proxy) const {
165 return fProxyReadCounts->get(proxy);
166 }
167
168 // Returns true if the read count reached zero; must only be called if it was > 0 previously.
169 bool removePendingRead(const TextureProxy* proxy) {
170 return fProxyReadCounts->decrement(proxy);
171 }
172
173private:
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.
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.
191
192 std::unique_ptr<ProxyReadCountMap> fProxyReadCounts;
193};
194
195} // namespace skgpu::graphite
196
197#endif // skgpu_graphite_ResourceReuseManager_DEFINED
int count
Definition: FontMgrTest.cpp:50
#define SkASSERT(cond)
Definition: SkAssert.h:116
int get(const TextureProxy *proxy) const
bool decrement(const TextureProxy *proxy)
void increment(const TextureProxy *proxy)
virtual void onUseCompleted(ScratchResourceManager *)=0
int pendingReadCount(const TextureProxy *proxy) const
void markResourceInUse(PendingUseListener *listener)
ScratchResourceManager(ResourceProvider *resourceProvider, std::unique_ptr< ProxyReadCountMap >)
bool removePendingRead(const TextureProxy *proxy)
sk_sp< Texture > getScratchTexture(SkISize, const TextureInfo &, std::string_view label)
Definition: SkSize.h:16