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/core/SkTHash.h" 12 13 #include "src/gpu/ganesh/GrHashMapWithCache.h" 14 #include "src/gpu/ganesh/GrSurface.h" 15 #include "src/gpu/ganesh/GrSurfaceProxy.h" 16 17 #include "src/base/SkArenaAlloc.h" 18 #include "src/core/SkTMultiMap.h" 19 20 class GrDirectContext; 21 22 // Print out explicit allocation information 23 #define GR_ALLOCATION_SPEW 0 24 25 // Print out information about interval creation 26 #define GR_TRACK_INTERVAL_CREATION 0 27 28 /* 29 * The ResourceAllocator explicitly distributes GPU resources at flush time. It operates by 30 * being given the usage intervals of the various proxies. It keeps these intervals in a singly 31 * linked list sorted by increasing start index. (It also maintains a hash table from proxyID 32 * to interval to find proxy reuse). The ResourceAllocator uses Registers (in the sense of register 33 * allocation) to represent a future surface that will be used for each proxy during 34 * `planAssignment`, and then assigns actual surfaces during `assign`. 35 * 36 * Note: the op indices (used in the usage intervals) come from the order of the ops in 37 * their opsTasks after the opsTask DAG has been linearized. 38 * 39 * The planAssignment method traverses the sorted list and: 40 * moves intervals from the active list that have completed (returning their registers 41 * to the free pool) into the finished list (sorted by increasing start) 42 * 43 * allocates a new register (preferably from the free pool) for the new interval 44 * adds the new interval to the active list (that is sorted by increasing end index) 45 * 46 * After assignment planning, the user can choose to call `makeBudgetHeadroom` which: 47 * computes how much VRAM would be needed for new resources for all extant Registers 48 * 49 * asks the resource cache to purge enough resources to get that much free space 50 * 51 * if it's not possible, do nothing and return false. The user may opt to reset 52 * the allocator and start over with a different DAG. 53 * 54 * If the user wants to commit to the current assignment plan, they call `assign` which: 55 * instantiates lazy proxies 56 * 57 * instantantiates new surfaces for all registers that need them 58 * 59 * assigns the surface for each register to all the proxies that will use it 60 * 61 ************************************************************************************************* 62 * How does instantiation failure handling work when explicitly allocating? 63 * 64 * In the gather usage intervals pass all the GrSurfaceProxies used in the flush should be 65 * gathered (i.e., in OpsTask::gatherProxyIntervals). 66 * 67 * During addInterval, read-only lazy proxies are instantiated. If that fails, the resource 68 * allocator will note the failure and ignore pretty much anything else until `reset`. 69 * 70 * During planAssignment, fully-lazy proxies are instantiated so that we can know their size for 71 * budgeting purposes. If this fails, return false. 72 * 73 * During assign, partially-lazy proxies are instantiated and new surfaces are created for all other 74 * proxies. If any of these fails, return false. 75 * 76 * The drawing manager will drop the flush if any proxies fail to instantiate. 77 */ 78 class GrResourceAllocator { 79 public: GrResourceAllocator(GrDirectContext * dContext)80 GrResourceAllocator(GrDirectContext* dContext) 81 : fDContext(dContext) {} 82 83 ~GrResourceAllocator(); 84 curOp()85 unsigned int curOp() const { return fNumOps; } incOps()86 void incOps() { fNumOps++; } 87 88 /** Indicates whether a given call to addInterval represents an actual usage of the 89 * provided proxy. This is mainly here to accommodate deferred proxies attached to opsTasks. 90 * In that case we need to create an extra long interval for them (due to the upload) but 91 * don't want to count that usage/reference towards the proxy's recyclability. 92 */ 93 enum class ActualUse : bool { 94 kNo = false, 95 kYes = true 96 }; 97 98 /** Indicates whether we allow a gpu texture assigned to a register to be recycled or not. This 99 * comes up when dealing with with Vulkan Secondary CommandBuffers since offscreens sampled 100 * into the scb will all be drawn before being sampled in the scb. This is because the scb 101 * will get submitted in a later command buffer. Thus offscreens cannot share an allocation or 102 * later reuses will overwrite earlier ones. 103 */ 104 enum class AllowRecycling : bool { 105 kNo = false, 106 kYes = true 107 }; 108 109 // Add a usage interval from 'start' to 'end' inclusive. This is usually used for renderTargets. 110 // If an existing interval already exists it will be expanded to include the new range. 111 void addInterval(GrSurfaceProxy*, unsigned int start, unsigned int end, ActualUse actualUse, 112 AllowRecycling SkDEBUGCODE(, bool isDirectDstRead = false)); 113 failedInstantiation()114 bool failedInstantiation() const { return fFailedInstantiation; } 115 116 // Generate an internal plan for resource allocation. After this you can optionally call 117 // `makeBudgetHeadroom` to check whether that plan would go over our memory budget. 118 // Fully-lazy proxies are also instantiated at this point so that their size can 119 // be known accurately. Returns false if any lazy proxy failed to instantiate, true otherwise. 120 bool planAssignment(); 121 122 // Figure out how much VRAM headroom this plan requires. If there's enough purgeable resources, 123 // purge them and return true. Otherwise return false. 124 bool makeBudgetHeadroom(); 125 126 // Clear all internal state in preparation for a new set of intervals. 127 void reset(); 128 129 // Instantiate and assign resources to all proxies. 130 bool assign(); 131 132 #if GR_ALLOCATION_SPEW 133 void dumpIntervals(); 134 #endif 135 136 private: 137 class Interval; 138 class Register; 139 140 // Remove dead intervals from the active list 141 void expire(unsigned int curIndex); 142 143 // These two methods wrap the interactions with the free pool 144 void recycleRegister(Register* r); 145 Register* findOrCreateRegisterFor(GrSurfaceProxy* proxy); 146 147 struct FreePoolTraits { GetKeyFreePoolTraits148 static const skgpu::ScratchKey& GetKey(const Register& r) { 149 return r.scratchKey(); 150 } 151 HashFreePoolTraits152 static uint32_t Hash(const skgpu::ScratchKey& key) { return key.hash(); } OnFreeFreePoolTraits153 static void OnFree(Register* r) { } 154 }; 155 typedef SkTMultiMap<Register, skgpu::ScratchKey, FreePoolTraits> FreePoolMultiMap; 156 157 typedef skia_private::THashMap<uint32_t, Interval*, GrCheapHash> IntvlHash; 158 159 struct UniqueKeyHash { operatorUniqueKeyHash160 uint32_t operator()(const skgpu::UniqueKey& key) const { return key.hash(); } 161 }; 162 typedef skia_private::THashMap<skgpu::UniqueKey, Register*, UniqueKeyHash> 163 UniqueKeyRegisterHash; 164 165 // Each proxy – with some exceptions – is assigned a register. After all assignments are made, 166 // another pass is performed to instantiate and assign actual surfaces to the proxies. Right 167 // now these are performed in one call, but in the future they will be separable and the user 168 // will be able to query re: memory cost before committing to surface creation. 169 class Register { 170 public: 171 // It's OK to pass an invalid scratch key iff the proxy has a unique key. 172 Register(GrSurfaceProxy* originatingProxy, skgpu::ScratchKey, GrResourceProvider*); 173 scratchKey()174 const skgpu::ScratchKey& scratchKey() const { return fScratchKey; } uniqueKey()175 const skgpu::UniqueKey& uniqueKey() const { return fOriginatingProxy->getUniqueKey(); } 176 accountedForInBudget()177 bool accountedForInBudget() const { return fAccountedForInBudget; } setAccountedForInBudget()178 void setAccountedForInBudget() { fAccountedForInBudget = true; } 179 existingSurface()180 GrSurface* existingSurface() const { return fExistingSurface.get(); } 181 182 // Can this register be used by other proxies after this one? 183 bool isRecyclable(const GrCaps&, GrSurfaceProxy* proxy, int knownUseCount, 184 AllowRecycling) const; 185 186 // Resolve the register allocation to an actual GrSurface. 'fOriginatingProxy' 187 // is used to cache the allocation when a given register is used by multiple 188 // proxies. 189 bool instantiateSurface(GrSurfaceProxy*, GrResourceProvider*); 190 191 SkDEBUGCODE(uint32_t uniqueID() const { return fUniqueID; }) 192 193 private: 194 GrSurfaceProxy* fOriginatingProxy; 195 skgpu::ScratchKey fScratchKey; // free pool wants a reference to this. 196 sk_sp<GrSurface> fExistingSurface; // queried from resource cache. may be null. 197 bool fAccountedForInBudget = false; 198 199 #ifdef SK_DEBUG 200 uint32_t fUniqueID; 201 202 static uint32_t CreateUniqueID(); 203 #endif 204 }; 205 206 class Interval { 207 public: Interval(GrSurfaceProxy * proxy,unsigned int start,unsigned int end)208 Interval(GrSurfaceProxy* proxy, unsigned int start, unsigned int end) 209 : fProxy(proxy) 210 , fStart(start) 211 , fEnd(end) { 212 SkASSERT(proxy); 213 SkDEBUGCODE(fUniqueID = CreateUniqueID()); 214 #if GR_TRACK_INTERVAL_CREATION 215 SkString proxyStr = proxy->dump(); 216 SkDebugf("New intvl %d: %s [%d, %d]\n", fUniqueID, proxyStr.c_str(), start, end); 217 #endif 218 } 219 proxy()220 const GrSurfaceProxy* proxy() const { return fProxy; } proxy()221 GrSurfaceProxy* proxy() { return fProxy; } 222 start()223 unsigned int start() const { return fStart; } end()224 unsigned int end() const { return fEnd; } 225 setNext(Interval * next)226 void setNext(Interval* next) { fNext = next; } next()227 const Interval* next() const { return fNext; } next()228 Interval* next() { return fNext; } 229 getRegister()230 Register* getRegister() const { return fRegister; } setRegister(Register * r)231 void setRegister(Register* r) { fRegister = r; } 232 addUse()233 void addUse() { fUses++; } uses()234 int uses() const { return fUses; } 235 extendEnd(unsigned int newEnd)236 void extendEnd(unsigned int newEnd) { 237 if (newEnd > fEnd) { 238 fEnd = newEnd; 239 #if GR_TRACK_INTERVAL_CREATION 240 SkDebugf("intvl %d: extending from %d to %d\n", fUniqueID, fEnd, newEnd); 241 #endif 242 } 243 } 244 disallowRecycling()245 void disallowRecycling() { 246 fAllowRecycling = AllowRecycling::kNo; 247 } allowRecycling()248 AllowRecycling allowRecycling() const { return fAllowRecycling; } 249 250 SkDEBUGCODE(uint32_t uniqueID() const { return fUniqueID; }) 251 252 private: 253 GrSurfaceProxy* fProxy; 254 unsigned int fStart; 255 unsigned int fEnd; 256 Interval* fNext = nullptr; 257 unsigned int fUses = 0; 258 Register* fRegister = nullptr; 259 AllowRecycling fAllowRecycling = AllowRecycling::kYes; 260 261 #ifdef SK_DEBUG 262 uint32_t fUniqueID; 263 264 static uint32_t CreateUniqueID(); 265 #endif 266 }; 267 268 class IntervalList { 269 public: 270 IntervalList() = default; 271 // N.B. No need for a destructor – the arena allocator will clean up for us. 272 empty()273 bool empty() const { 274 SkASSERT(SkToBool(fHead) == SkToBool(fTail)); 275 return !SkToBool(fHead); 276 } peekHead()277 const Interval* peekHead() const { return fHead; } peekHead()278 Interval* peekHead() { return fHead; } 279 Interval* popHead(); 280 void insertByIncreasingStart(Interval*); 281 void insertByIncreasingEnd(Interval*); 282 283 private: 284 SkDEBUGCODE(void validate() const;) 285 286 Interval* fHead = nullptr; 287 Interval* fTail = nullptr; 288 }; 289 290 // Compositing use cases can create > 80 intervals. 291 static const int kInitialArenaSize = 128 * sizeof(Interval); 292 293 GrDirectContext* fDContext; 294 FreePoolMultiMap fFreePool; // Recently created/used GrSurfaces 295 IntvlHash fIntvlHash; // All the intervals, hashed by proxyID 296 297 IntervalList fIntvlList; // All the intervals sorted by increasing start 298 IntervalList fActiveIntvls; // List of live intervals during assignment 299 // (sorted by increasing end) 300 IntervalList fFinishedIntvls; // All the completed intervals 301 // (sorted by increasing start) 302 UniqueKeyRegisterHash fUniqueKeyRegisters; 303 unsigned int fNumOps = 0; 304 305 SkDEBUGCODE(bool fPlanned = false;) 306 SkDEBUGCODE(bool fAssigned = false;) 307 308 SkSTArenaAllocWithReset<kInitialArenaSize> fInternalAllocator; // intervals & registers 309 bool fFailedInstantiation = false; 310 }; 311 312 #endif // GrResourceAllocator_DEFINED 313