1 /* 2 * Copyright 2015 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 GrDrawOpAtlas_DEFINED 9 #define GrDrawOpAtlas_DEFINED 10 11 #include "SkPoint.h" 12 #include "SkTDArray.h" 13 #include "SkTInternalLList.h" 14 15 #include "ops/GrDrawOp.h" 16 17 class GrOnFlushResourceProvider; 18 class GrRectanizer; 19 20 struct GrDrawOpAtlasConfig { 21 int numPlotsX() const { return fWidth / fPlotWidth; } 22 int numPlotsY() const { return fHeight / fPlotWidth; } 23 int fWidth; 24 int fHeight; 25 int fPlotWidth; 26 int fPlotHeight; 27 }; 28 29 /** 30 * This class manages one or more atlas textures on behalf of GrDrawOps. The draw ops that use the 31 * atlas perform texture uploads when preparing their draws during flush. The class provides 32 * facilities for using GrDrawOpUploadToken to detect data hazards. Op's uploads are performed in 33 * "ASAP" mode until it is impossible to add data without overwriting texels read by draws that 34 * have not yet executed on the gpu. At that point, the atlas will attempt to allocate a new 35 * atlas texture (or "page") of the same size, up to a maximum number of textures, and upload 36 * to that texture. If that's not possible, the uploads are performed "inline" between draws. If a 37 * single draw would use enough subimage space to overflow the atlas texture then the atlas will 38 * fail to add a subimage. This gives the op the chance to end the draw and begin a new one. 39 * Additional uploads will then succeed in inline mode. 40 * 41 * When the atlas has multiple pages, new uploads are prioritized to the lower index pages, i.e., 42 * it will try to upload to page 0 before page 1 or 2. To keep the atlas from continually using 43 * excess space, periodic garbage collection is needed to shift data from the higher index pages to 44 * the lower ones, and then eventually remove any pages that are no longer in use. "In use" is 45 * determined by using the GrDrawUploadToken system: After a flush each subarea of the page 46 * is checked to see whether it was used in that flush; if it is not, a counter is incremented. 47 * Once that counter reaches a threshold that subarea is considered to be no longer in use. 48 * 49 * Garbage collection is initiated by the GrDrawOpAtlas's client via the compact() method. One 50 * solution is to make the client a subclass of GrOnFlushCallbackObject, register it with the 51 * GrContext via addOnFlushCallbackObject(), and the client's postFlush() method calls compact() 52 * and passes in the given GrDrawUploadToken. 53 */ 54 class GrDrawOpAtlas { 55 private: 56 static constexpr auto kMaxMultitexturePages = 4; 57 58 public: 59 /** Is the atlas allowed to use more than one texture? */ 60 enum class AllowMultitexturing : bool { kNo, kYes }; 61 62 /** 63 * An AtlasID is an opaque handle which callers can use to determine if the atlas contains 64 * a specific piece of data. 65 */ 66 typedef uint64_t AtlasID; 67 static const uint32_t kInvalidAtlasID = 0; 68 static const uint64_t kInvalidAtlasGeneration = 0; 69 70 /** 71 * A function pointer for use as a callback during eviction. Whenever GrDrawOpAtlas evicts a 72 * specific AtlasID, it will call all of the registered listeners so they can process the 73 * eviction. 74 */ 75 typedef void (*EvictionFunc)(GrDrawOpAtlas::AtlasID, void*); 76 77 /** 78 * Returns a GrDrawOpAtlas. This function can be called anywhere, but the returned atlas 79 * should only be used inside of GrMeshDrawOp::onPrepareDraws. 80 * @param GrPixelConfig The pixel config which this atlas will store 81 * @param width width in pixels of the atlas 82 * @param height height in pixels of the atlas 83 * @param numPlotsX The number of plots the atlas should be broken up into in the X 84 * direction 85 * @param numPlotsY The number of plots the atlas should be broken up into in the Y 86 * direction 87 * @param allowMultitexturing Can the atlas use more than one texture. 88 * @param func An eviction function which will be called whenever the atlas has to 89 * evict data 90 * @param data User supplied data which will be passed into func whenever an 91 * eviction occurs 92 * @return An initialized GrDrawOpAtlas, or nullptr if creation fails 93 */ 94 static std::unique_ptr<GrDrawOpAtlas> Make(GrProxyProvider*, GrPixelConfig, 95 int width, int height, 96 int numPlotsX, int numPlotsY, 97 AllowMultitexturing allowMultitexturing, 98 GrDrawOpAtlas::EvictionFunc func, void* data); 99 100 /** 101 * Adds a width x height subimage to the atlas. Upon success it returns an ID and the subimage's 102 * coordinates in the backing texture. False is returned if the subimage cannot fit in the 103 * atlas without overwriting texels that will be read in the current draw. This indicates that 104 * the op should end its current draw and begin another before adding more data. Upon success, 105 * an upload of the provided image data will have been added to the GrDrawOp::Target, in "asap" 106 * mode if possible, otherwise in "inline" mode. Successive uploads in either mode may be 107 * consolidated. 108 * NOTE: When the GrDrawOp prepares a draw that reads from the atlas, it must immediately call 109 * 'setUseToken' with the currentToken from the GrDrawOp::Target, otherwise the next call to 110 * addToAtlas might cause the previous data to be overwritten before it has been read. 111 */ 112 bool addToAtlas(GrResourceProvider*, AtlasID*, GrDeferredUploadTarget*, int width, int height, 113 const void* image, SkIPoint16* loc); 114 115 const sk_sp<GrTextureProxy>* getProxies() const { return fProxies; } 116 117 uint64_t atlasGeneration() const { return fAtlasGeneration; } 118 119 inline bool hasID(AtlasID id) { 120 if (kInvalidAtlasID == id) { 121 return false; 122 } 123 uint32_t plot = GetPlotIndexFromID(id); 124 SkASSERT(plot < fNumPlots); 125 uint32_t page = GetPageIndexFromID(id); 126 SkASSERT(page < fNumActivePages); 127 return fPages[page].fPlotArray[plot]->genID() == GetGenerationFromID(id); 128 } 129 130 /** To ensure the atlas does not evict a given entry, the client must set the last use token. */ 131 inline void setLastUseToken(AtlasID id, GrDeferredUploadToken token) { 132 SkASSERT(this->hasID(id)); 133 uint32_t plotIdx = GetPlotIndexFromID(id); 134 SkASSERT(plotIdx < fNumPlots); 135 uint32_t pageIdx = GetPageIndexFromID(id); 136 SkASSERT(pageIdx < fNumActivePages); 137 Plot* plot = fPages[pageIdx].fPlotArray[plotIdx].get(); 138 this->makeMRU(plot, pageIdx); 139 plot->setLastUseToken(token); 140 } 141 142 inline void registerEvictionCallback(EvictionFunc func, void* userData) { 143 EvictionData* data = fEvictionCallbacks.append(); 144 data->fFunc = func; 145 data->fData = userData; 146 } 147 148 uint32_t numActivePages() { return fNumActivePages; } 149 150 /** 151 * A class which can be handed back to GrDrawOpAtlas for updating last use tokens in bulk. The 152 * current max number of plots per page the GrDrawOpAtlas can handle is 32. If in the future 153 * this is insufficient then we can move to a 64 bit int. 154 */ 155 class BulkUseTokenUpdater { 156 public: 157 BulkUseTokenUpdater() { 158 memset(fPlotAlreadyUpdated, 0, sizeof(fPlotAlreadyUpdated)); 159 } 160 BulkUseTokenUpdater(const BulkUseTokenUpdater& that) 161 : fPlotsToUpdate(that.fPlotsToUpdate) { 162 memcpy(fPlotAlreadyUpdated, that.fPlotAlreadyUpdated, sizeof(fPlotAlreadyUpdated)); 163 } 164 165 void add(AtlasID id) { 166 int index = GrDrawOpAtlas::GetPlotIndexFromID(id); 167 int pageIdx = GrDrawOpAtlas::GetPageIndexFromID(id); 168 if (!this->find(pageIdx, index)) { 169 this->set(pageIdx, index); 170 } 171 } 172 173 void reset() { 174 fPlotsToUpdate.reset(); 175 memset(fPlotAlreadyUpdated, 0, sizeof(fPlotAlreadyUpdated)); 176 } 177 178 struct PlotData { 179 PlotData(int pageIdx, int plotIdx) : fPageIndex(pageIdx), fPlotIndex(plotIdx) {} 180 uint32_t fPageIndex; 181 uint32_t fPlotIndex; 182 }; 183 184 private: 185 bool find(int pageIdx, int index) const { 186 SkASSERT(index < kMaxPlots); 187 return (fPlotAlreadyUpdated[pageIdx] >> index) & 1; 188 } 189 190 void set(int pageIdx, int index) { 191 SkASSERT(!this->find(pageIdx, index)); 192 fPlotAlreadyUpdated[pageIdx] |= (1 << index); 193 fPlotsToUpdate.push_back(PlotData(pageIdx, index)); 194 } 195 196 static constexpr int kMinItems = 4; 197 static constexpr int kMaxPlots = 32; 198 SkSTArray<kMinItems, PlotData, true> fPlotsToUpdate; 199 uint32_t fPlotAlreadyUpdated[kMaxMultitexturePages]; 200 201 friend class GrDrawOpAtlas; 202 }; 203 204 void setLastUseTokenBulk(const BulkUseTokenUpdater& updater, GrDeferredUploadToken token) { 205 int count = updater.fPlotsToUpdate.count(); 206 for (int i = 0; i < count; i++) { 207 const BulkUseTokenUpdater::PlotData& pd = updater.fPlotsToUpdate[i]; 208 // it's possible we've added a plot to the updater and subsequently the plot's page 209 // was deleted -- so we check to prevent a crash 210 if (pd.fPageIndex < fNumActivePages) { 211 Plot* plot = fPages[pd.fPageIndex].fPlotArray[pd.fPlotIndex].get(); 212 this->makeMRU(plot, pd.fPageIndex); 213 plot->setLastUseToken(token); 214 } 215 } 216 } 217 218 void compact(GrDeferredUploadToken startTokenForNextFlush); 219 220 static constexpr auto kGlyphMaxDim = 256; 221 static bool GlyphTooLargeForAtlas(int width, int height) { 222 return width > kGlyphMaxDim || height > kGlyphMaxDim; 223 } 224 225 static uint32_t GetPageIndexFromID(AtlasID id) { 226 return id & 0xff; 227 } 228 229 void instantiate(GrOnFlushResourceProvider*); 230 231 uint32_t maxPages() const { 232 return AllowMultitexturing::kYes == fAllowMultitexturing ? kMaxMultitexturePages : 1; 233 } 234 235 int numAllocated_TestingOnly() const; 236 237 private: 238 GrDrawOpAtlas(GrProxyProvider*, GrPixelConfig, int width, int height, int numPlotsX, 239 int numPlotsY, AllowMultitexturing allowMultitexturing); 240 241 /** 242 * The backing GrTexture for a GrDrawOpAtlas is broken into a spatial grid of Plots. The Plots 243 * keep track of subimage placement via their GrRectanizer. A Plot manages the lifetime of its 244 * data using two tokens, a last use token and a last upload token. Once a Plot is "full" (i.e. 245 * there is no room for the new subimage according to the GrRectanizer), it can no longer be 246 * used unless the last use of the Plot has already been flushed through to the gpu. 247 */ 248 class Plot : public SkRefCnt { 249 SK_DECLARE_INTERNAL_LLIST_INTERFACE(Plot); 250 251 public: 252 /** index() is a unique id for the plot relative to the owning GrAtlas and page. */ 253 uint32_t index() const { return fPlotIndex; } 254 /** 255 * genID() is incremented when the plot is evicted due to a atlas spill. It is used to know 256 * if a particular subimage is still present in the atlas. 257 */ 258 uint64_t genID() const { return fGenID; } 259 GrDrawOpAtlas::AtlasID id() const { 260 SkASSERT(GrDrawOpAtlas::kInvalidAtlasID != fID); 261 return fID; 262 } 263 SkDEBUGCODE(size_t bpp() const { return fBytesPerPixel; }) 264 265 bool addSubImage(int width, int height, const void* image, SkIPoint16* loc); 266 267 /** 268 * To manage the lifetime of a plot, we use two tokens. We use the last upload token to 269 * know when we can 'piggy back' uploads, i.e. if the last upload hasn't been flushed to 270 * the gpu, we don't need to issue a new upload even if we update the cpu backing store. We 271 * use lastUse to determine when we can evict a plot from the cache, i.e. if the last use 272 * has already flushed through the gpu then we can reuse the plot. 273 */ 274 GrDeferredUploadToken lastUploadToken() const { return fLastUpload; } 275 GrDeferredUploadToken lastUseToken() const { return fLastUse; } 276 void setLastUploadToken(GrDeferredUploadToken token) { fLastUpload = token; } 277 void setLastUseToken(GrDeferredUploadToken token) { fLastUse = token; } 278 279 void uploadToTexture(GrDeferredTextureUploadWritePixelsFn&, GrTextureProxy*); 280 void resetRects(); 281 282 int flushesSinceLastUsed() { return fFlushesSinceLastUse; } 283 void resetFlushesSinceLastUsed() { fFlushesSinceLastUse = 0; } 284 void incFlushesSinceLastUsed() { fFlushesSinceLastUse++; } 285 286 private: 287 Plot(int pageIndex, int plotIndex, uint64_t genID, int offX, int offY, int width, int height, 288 GrPixelConfig config); 289 290 ~Plot() override; 291 292 /** 293 * Create a clone of this plot. The cloned plot will take the place of the current plot in 294 * the atlas 295 */ 296 Plot* clone() const { 297 return new Plot(fPageIndex, fPlotIndex, fGenID + 1, fX, fY, fWidth, fHeight, fConfig); 298 } 299 300 static GrDrawOpAtlas::AtlasID CreateId(uint32_t pageIdx, uint32_t plotIdx, 301 uint64_t generation) { 302 SkASSERT(pageIdx < (1 << 8)); 303 SkASSERT(pageIdx < kMaxMultitexturePages); 304 SkASSERT(plotIdx < (1 << 8)); 305 SkASSERT(generation < ((uint64_t)1 << 48)); 306 return generation << 16 | plotIdx << 8 | pageIdx; 307 } 308 309 GrDeferredUploadToken fLastUpload; 310 GrDeferredUploadToken fLastUse; 311 // the number of flushes since this plot has been last used 312 int fFlushesSinceLastUse; 313 314 struct { 315 const uint32_t fPageIndex : 16; 316 const uint32_t fPlotIndex : 16; 317 }; 318 uint64_t fGenID; 319 GrDrawOpAtlas::AtlasID fID; 320 unsigned char* fData; 321 const int fWidth; 322 const int fHeight; 323 const int fX; 324 const int fY; 325 GrRectanizer* fRects; 326 const SkIPoint16 fOffset; // the offset of the plot in the backing texture 327 const GrPixelConfig fConfig; 328 const size_t fBytesPerPixel; 329 SkIRect fDirtyRect; 330 SkDEBUGCODE(bool fDirty); 331 332 friend class GrDrawOpAtlas; 333 334 typedef SkRefCnt INHERITED; 335 }; 336 337 typedef SkTInternalLList<Plot> PlotList; 338 339 static uint32_t GetPlotIndexFromID(AtlasID id) { 340 return (id >> 8) & 0xff; 341 } 342 343 // top 48 bits are reserved for the generation ID 344 static uint64_t GetGenerationFromID(AtlasID id) { 345 return (id >> 16) & 0xffffffffffff; 346 } 347 348 inline bool updatePlot(GrDeferredUploadTarget*, AtlasID*, Plot*); 349 350 inline void makeMRU(Plot* plot, int pageIdx) { 351 if (fPages[pageIdx].fPlotList.head() == plot) { 352 return; 353 } 354 355 fPages[pageIdx].fPlotList.remove(plot); 356 fPages[pageIdx].fPlotList.addToHead(plot); 357 358 // No MRU update for pages -- since we will always try to add from 359 // the front and remove from the back there is no need for MRU. 360 } 361 362 bool createPages(GrProxyProvider*); 363 bool activateNewPage(GrResourceProvider*); 364 void deactivateLastPage(); 365 366 void processEviction(AtlasID); 367 inline void processEvictionAndResetRects(Plot* plot) { 368 this->processEviction(plot->id()); 369 plot->resetRects(); 370 } 371 372 GrPixelConfig fPixelConfig; 373 int fTextureWidth; 374 int fTextureHeight; 375 int fPlotWidth; 376 int fPlotHeight; 377 unsigned int fNumPlots; 378 379 uint64_t fAtlasGeneration; 380 // nextTokenToFlush() value at the end of the previous flush 381 GrDeferredUploadToken fPrevFlushToken; 382 383 struct EvictionData { 384 EvictionFunc fFunc; 385 void* fData; 386 }; 387 388 SkTDArray<EvictionData> fEvictionCallbacks; 389 390 struct Page { 391 // allocated array of Plots 392 std::unique_ptr<sk_sp<Plot>[]> fPlotArray; 393 // LRU list of Plots (MRU at head - LRU at tail) 394 PlotList fPlotList; 395 }; 396 // proxies kept separate to make it easier to pass them up to client 397 sk_sp<GrTextureProxy> fProxies[kMaxMultitexturePages]; 398 Page fPages[kMaxMultitexturePages]; 399 AllowMultitexturing fAllowMultitexturing; 400 401 uint32_t fNumActivePages; 402 }; 403 404 #endif 405