1/*
2 * Copyright 2017 Google Inc.
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 GrResourceAllocator_DEFINED
9#define GrResourceAllocator_DEFINED
10
11#include "src/gpu/GrGpuResourcePriv.h"
12#include "src/gpu/GrSurface.h"
13#include "src/gpu/GrSurfaceProxy.h"
14
15#include "src/core/SkArenaAlloc.h"
16#include "src/core/SkTDynamicHash.h"
17#include "src/core/SkTMultiMap.h"
18
19class GrResourceProvider;
20
21// Print out explicit allocation information
22#define GR_ALLOCATION_SPEW 0
23
24// Print out information about interval creation
25#define GR_TRACK_INTERVAL_CREATION 0
26
27/*
28 * The ResourceAllocator explicitly distributes GPU resources at flush time. It operates by
29 * being given the usage intervals of the various proxies. It keeps these intervals in a singly
30 * linked list sorted by increasing start index. (It also maintains a hash table from proxyID
31 * to interval to find proxy reuse). When it comes time to allocate the resources it
32 * traverses the sorted list and:
33 * removes intervals from the active list that have completed (returning their GrSurfaces
34 * to the free pool)
35
36 * allocates a new resource (preferably from the free pool) for the new interval
37 * adds the new interval to the active list (that is sorted by increasing end index)
38 *
39 * Note: the op indices (used in the usage intervals) come from the order of the ops in
40 * their opsTasks after the opsTask DAG has been linearized.
41 *
42 *************************************************************************************************
43 * How does instantiation failure handling work when explicitly allocating?
44 *
45 * In the gather usage intervals pass all the GrSurfaceProxies used in the flush should be
46 * gathered (i.e., in GrOpsTask::gatherProxyIntervals).
47 *
48 * The allocator will churn through this list but could fail anywhere.
49 *
50 * Allocation failure handling occurs at two levels:
51 *
52 * 1) If the GrSurface backing an opsTask fails to allocate then the entire opsTask is dropped.
53 *
54 * 2) If an individual GrSurfaceProxy fails to allocate then any ops that use it are dropped
55 * (via GrOpsTask::purgeOpsWithUninstantiatedProxies)
56 *
57 * The pass to determine which ops to drop is a bit laborious so we only check the opsTasks and
58 * individual ops when something goes wrong in allocation (i.e., when the return code from
59 * GrResourceAllocator::assign is bad)
60 *
61 * All together this means we should never attempt to draw an op which is missing some
62 * required GrSurface.
63 *
64 * One wrinkle in this plan is that promise images are fulfilled during the gather interval pass.
65 * If any of the promise images fail at this stage then the allocator is set into an error
66 * state and all allocations are then scanned for failures during the main allocation pass.
67 */
68class GrResourceAllocator {
69public:
70 GrResourceAllocator(GrResourceProvider* resourceProvider SkDEBUGCODE(, int numOpsTasks))
71 : fResourceProvider(resourceProvider) SkDEBUGCODE(, fNumOpsTasks(numOpsTasks)) {}
72
73 ~GrResourceAllocator();
74
75 unsigned int curOp() const { return fNumOps; }
76 void incOps() { fNumOps++; }
77
78 /** Indicates whether a given call to addInterval represents an actual usage of the
79 * provided proxy. This is mainly here to accomodate deferred proxies attached to opsTasks.
80 * In that case we need to create an extra long interval for them (due to the upload) but
81 * don't want to count that usage/reference towards the proxy's recyclability.
82 */
83 enum class ActualUse : bool {
84 kNo = false,
85 kYes = true
86 };
87
88 // Add a usage interval from 'start' to 'end' inclusive. This is usually used for renderTargets.
89 // If an existing interval already exists it will be expanded to include the new range.
90 void addInterval(GrSurfaceProxy*, unsigned int start, unsigned int end, ActualUse actualUse
91 SkDEBUGCODE(, bool isDirectDstRead = false));
92
93 enum class AssignError {
94 kNoError,
95 kFailedProxyInstantiation
96 };
97
98 // Returns true when the opsTasks from 'startIndex' to 'stopIndex' should be executed;
99 // false when nothing remains to be executed.
100 // If any proxy fails to instantiate, the AssignError will be set to kFailedProxyInstantiation.
101 // If this happens, the caller should remove all ops which reference an uninstantiated proxy.
102 // This is used to execute a portion of the queued opsTasks in order to reduce the total
103 // amount of GPU resources required.
104 bool assign(int* startIndex, int* stopIndex, AssignError* outError);
105
106 void determineRecyclability();
107 void markEndOfOpsTask(int opsTaskIndex);
108
109#if GR_ALLOCATION_SPEW
110 void dumpIntervals();
111#endif
112
113private:
114 class Interval;
115
116 // Remove dead intervals from the active list
117 void expire(unsigned int curIndex);
118
119 bool onOpsTaskBoundary() const;
120 void forceIntermediateFlush(int* stopIndex);
121
122 // These two methods wrap the interactions with the free pool
123 void recycleSurface(sk_sp<GrSurface> surface);
124 sk_sp<GrSurface> findSurfaceFor(const GrSurfaceProxy* proxy);
125
126 struct FreePoolTraits {
127 static const GrScratchKey& GetKey(const GrSurface& s) {
128 return s.resourcePriv().getScratchKey();
129 }
130
131 static uint32_t Hash(const GrScratchKey& key) { return key.hash(); }
132 static void OnFree(GrSurface* s) { s->unref(); }
133 };
134 typedef SkTMultiMap<GrSurface, GrScratchKey, FreePoolTraits> FreePoolMultiMap;
135
136 typedef SkTDynamicHash<Interval, unsigned int> IntvlHash;
137
138 class Interval {
139 public:
140 Interval(GrSurfaceProxy* proxy, unsigned int start, unsigned int end)
141 : fProxy(proxy)
142 , fProxyID(proxy->uniqueID().asUInt())
143 , fStart(start)
144 , fEnd(end)
145 , fNext(nullptr) {
146 SkASSERT(proxy);
147#if GR_TRACK_INTERVAL_CREATION
148 fUniqueID = CreateUniqueID();
149 SkDebugf("New intvl %d: proxyID: %d [ %d, %d ]\n",
150 fUniqueID, proxy->uniqueID().asUInt(), start, end);
151#endif
152 }
153
154 // Used when recycling an interval
155 void resetTo(GrSurfaceProxy* proxy, unsigned int start, unsigned int end) {
156 SkASSERT(proxy);
157 SkASSERT(!fProxy && !fNext);
158
159 fUses = 0;
160 fProxy = proxy;
161 fProxyID = proxy->uniqueID().asUInt();
162 fStart = start;
163 fEnd = end;
164 fNext = nullptr;
165#if GR_TRACK_INTERVAL_CREATION
166 fUniqueID = CreateUniqueID();
167 SkDebugf("New intvl %d: proxyID: %d [ %d, %d ]\n",
168 fUniqueID, proxy->uniqueID().asUInt(), start, end);
169#endif
170 }
171
172 ~Interval() {
173 SkASSERT(!fAssignedSurface);
174 }
175
176 const GrSurfaceProxy* proxy() const { return fProxy; }
177 GrSurfaceProxy* proxy() { return fProxy; }
178
179 unsigned int start() const { return fStart; }
180 unsigned int end() const { return fEnd; }
181
182 void setNext(Interval* next) { fNext = next; }
183 const Interval* next() const { return fNext; }
184 Interval* next() { return fNext; }
185
186 void markAsRecyclable() { fIsRecyclable = true;}
187 bool isRecyclable() const { return fIsRecyclable; }
188
189 void addUse() { fUses++; }
190 int uses() { return fUses; }
191
192 void extendEnd(unsigned int newEnd) {
193 if (newEnd > fEnd) {
194 fEnd = newEnd;
195#if GR_TRACK_INTERVAL_CREATION
196 SkDebugf("intvl %d: extending from %d to %d\n", fUniqueID, fEnd, newEnd);
197#endif
198 }
199 }
200
201 void assign(sk_sp<GrSurface>);
202 bool wasAssignedSurface() const { return fAssignedSurface != nullptr; }
203 sk_sp<GrSurface> detachSurface() { return std::move(fAssignedSurface); }
204
205 // for SkTDynamicHash
206 static const uint32_t& GetKey(const Interval& intvl) {
207 return intvl.fProxyID;
208 }
209 static uint32_t Hash(const uint32_t& key) { return key; }
210
211 private:
212 sk_sp<GrSurface> fAssignedSurface;
213 GrSurfaceProxy* fProxy;
214 uint32_t fProxyID; // This is here b.c. DynamicHash requires a ref to the key
215 unsigned int fStart;
216 unsigned int fEnd;
217 Interval* fNext;
218 unsigned int fUses = 0;
219 bool fIsRecyclable = false;
220
221#if GR_TRACK_INTERVAL_CREATION
222 uint32_t fUniqueID;
223
224 uint32_t CreateUniqueID();
225#endif
226 };
227
228 class IntervalList {
229 public:
230 IntervalList() = default;
231 ~IntervalList() {
232 // The only time we delete an IntervalList is in the GrResourceAllocator dtor.
233 // Since the arena allocator will clean up for us we don't bother here.
234 }
235
236 bool empty() const {
237 SkASSERT(SkToBool(fHead) == SkToBool(fTail));
238 return !SkToBool(fHead);
239 }
240 const Interval* peekHead() const { return fHead; }
241 Interval* peekHead() { return fHead; }
242 Interval* popHead();
243 void insertByIncreasingStart(Interval*);
244 void insertByIncreasingEnd(Interval*);
245 Interval* detachAll();
246
247 private:
248 SkDEBUGCODE(void validate() const;)
249
250 Interval* fHead = nullptr;
251 Interval* fTail = nullptr;
252 };
253
254 // Compositing use cases can create > 80 intervals.
255 static const int kInitialArenaSize = 128 * sizeof(Interval);
256
257 GrResourceProvider* fResourceProvider;
258 FreePoolMultiMap fFreePool; // Recently created/used GrSurfaces
259 IntvlHash fIntvlHash; // All the intervals, hashed by proxyID
260
261 IntervalList fIntvlList; // All the intervals sorted by increasing start
262 IntervalList fActiveIntvls; // List of live intervals during assignment
263 // (sorted by increasing end)
264 unsigned int fNumOps = 0;
265 SkTArray<unsigned int> fEndOfOpsTaskOpIndices;
266 int fCurOpsTaskIndex = 0;
267 SkDEBUGCODE(const int fNumOpsTasks = -1;)
268
269 SkDEBUGCODE(bool fAssigned = false;)
270
271 char fStorage[kInitialArenaSize];
272 SkArenaAlloc fIntervalAllocator{fStorage, kInitialArenaSize, kInitialArenaSize};
273 Interval* fFreeIntervalList = nullptr;
274 bool fLazyInstantiationError = false;
275};
276
277#endif // GrResourceAllocator_DEFINED
278