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 <cmath> 12 #include <vector> 13 14 #include "include/gpu/GrBackendSurface.h" 15 #include "include/private/SkTArray.h" 16 #include "src/core/SkIPoint16.h" 17 #include "src/core/SkTInternalLList.h" 18 #include "src/gpu/GrDeferredUpload.h" 19 #include "src/gpu/GrRectanizerSkyline.h" 20 #include "src/gpu/GrSurfaceProxyView.h" 21 #include "src/gpu/geometry/GrRect.h" 22 23 class GrOnFlushResourceProvider; 24 class GrProxyProvider; 25 class GrResourceProvider; 26 class GrTextureProxy; 27 28 /** 29 * This class manages one or more atlas textures on behalf of GrDrawOps. The draw ops that use the 30 * atlas perform texture uploads when preparing their draws during flush. The class provides 31 * facilities for using GrDrawOpUploadToken to detect data hazards. Op's uploads are performed in 32 * "ASAP" mode until it is impossible to add data without overwriting texels read by draws that 33 * have not yet executed on the gpu. At that point, the atlas will attempt to allocate a new 34 * atlas texture (or "page") of the same size, up to a maximum number of textures, and upload 35 * to that texture. If that's not possible, the uploads are performed "inline" between draws. If a 36 * single draw would use enough subimage space to overflow the atlas texture then the atlas will 37 * fail to add a subimage. This gives the op the chance to end the draw and begin a new one. 38 * Additional uploads will then succeed in inline mode. 39 * 40 * When the atlas has multiple pages, new uploads are prioritized to the lower index pages, i.e., 41 * it will try to upload to page 0 before page 1 or 2. To keep the atlas from continually using 42 * excess space, periodic garbage collection is needed to shift data from the higher index pages to 43 * the lower ones, and then eventually remove any pages that are no longer in use. "In use" is 44 * determined by using the GrDrawUploadToken system: After a flush each subarea of the page 45 * is checked to see whether it was used in that flush; if it is not, a counter is incremented. 46 * Once that counter reaches a threshold that subarea is considered to be no longer in use. 47 * 48 * Garbage collection is initiated by the GrDrawOpAtlas's client via the compact() method. One 49 * solution is to make the client a subclass of GrOnFlushCallbackObject, register it with the 50 * GrContext via addOnFlushCallbackObject(), and the client's postFlush() method calls compact() 51 * and passes in the given GrDrawUploadToken. 52 */ 53 class GrDrawOpAtlas { 54 public: 55 /** Is the atlas allowed to use more than one texture? */ 56 enum class AllowMultitexturing : bool { kNo, kYes }; 57 58 // These are both restricted by the space they occupy in the PlotLocator. 59 // maxPages is also limited by being crammed into the glyph uvs. 60 // maxPlots is also limited by the fPlotAlreadyUpdated bitfield in BulkUseTokenUpdater 61 static constexpr auto kMaxMultitexturePages = 4; 62 static constexpr int kMaxPlots = 32; 63 64 /** 65 * A PlotLocator specifies the plot and is analogous to a directory path: 66 * page/plot/plotGeneration 67 * 68 * In fact PlotLocator is a portion of a glyph image location in the atlas fully specified by: 69 * format/atlasGeneration/page/plot/plotGeneration/rect 70 * 71 * TODO: Remove the small path renderer's use of the PlotLocator for eviction. 72 */ 73 class PlotLocator { 74 public: PlotLocator(uint32_t pageIdx,uint32_t plotIdx,uint64_t generation)75 PlotLocator(uint32_t pageIdx, uint32_t plotIdx, uint64_t generation) 76 : fGenID(generation) 77 , fPlotIndex(plotIdx) 78 , fPageIndex(pageIdx) { 79 SkASSERT(pageIdx < kMaxMultitexturePages); 80 SkASSERT(plotIdx < kMaxPlots); 81 SkASSERT(generation < ((uint64_t)1 << 48)); 82 } 83 PlotLocator()84 PlotLocator() : fGenID(0), fPlotIndex(0), fPageIndex(0) {} 85 isValid()86 bool isValid() const { 87 return fGenID != 0 || fPlotIndex != 0 || fPageIndex != 0; 88 } 89 makeInvalid()90 void makeInvalid() { 91 fGenID = 0; 92 fPlotIndex = 0; 93 fPageIndex = 0; 94 } 95 96 bool operator==(const PlotLocator& other) const { 97 return fGenID == other.fGenID && 98 fPlotIndex == other.fPlotIndex && 99 fPageIndex == other.fPageIndex; } 100 pageIndex()101 uint32_t pageIndex() const { return fPageIndex; } plotIndex()102 uint32_t plotIndex() const { return fPlotIndex; } genID()103 uint64_t genID() const { return fGenID; } 104 105 private: 106 uint64_t fGenID:48; 107 uint64_t fPlotIndex:8; 108 uint64_t fPageIndex:8; 109 }; 110 111 static const uint64_t kInvalidAtlasGeneration = 0; 112 113 114 // AtlasLocator handles atlas position information. It keeps a left-top, right-bottom pair of 115 // encoded UV coordinates. The bits 13 & 14 of the U coordinates hold the atlas page index. 116 // This information is handed directly as is from fUVs. This encoding has the nice property 117 // that width = fUVs[2] - fUVs[0]; the page encoding in the top bits subtracts to zero. 118 class AtlasLocator { 119 public: getUVs()120 std::array<uint16_t, 4> getUVs() const { 121 return fUVs; 122 } 123 invalidatePlotLocator()124 void invalidatePlotLocator() { fPlotLocator.makeInvalid(); } 125 126 // TODO: Remove the small path renderer's use of this for eviction plotLocator()127 PlotLocator plotLocator() const { return fPlotLocator; } 128 pageIndex()129 uint32_t pageIndex() const { return fPlotLocator.pageIndex(); } 130 plotIndex()131 uint32_t plotIndex() const { return fPlotLocator.plotIndex(); } 132 genID()133 uint64_t genID() const { return fPlotLocator.genID(); } 134 topLeft()135 SkIPoint topLeft() const { 136 return {fUVs[0] & 0x1FFF, fUVs[1]}; 137 } 138 width()139 uint16_t width() const { 140 return fUVs[2] - fUVs[0]; 141 } 142 height()143 uint16_t height() const { 144 return fUVs[3] - fUVs[1]; 145 } 146 insetSrc(int padding)147 void insetSrc(int padding) { 148 SkASSERT(2 * padding <= this->width()); 149 SkASSERT(2 * padding <= this->height()); 150 151 fUVs[0] += padding; 152 fUVs[1] += padding; 153 fUVs[2] -= padding; 154 fUVs[3] -= padding; 155 } 156 updatePlotLocator(PlotLocator p)157 void updatePlotLocator(PlotLocator p) { 158 fPlotLocator = p; 159 SkASSERT(fPlotLocator.pageIndex() <= 3); 160 uint16_t page = fPlotLocator.pageIndex() << 13; 161 fUVs[0] = (fUVs[0] & 0x1FFF) | page; 162 fUVs[2] = (fUVs[2] & 0x1FFF) | page; 163 } 164 updateRect(GrIRect16 rect)165 void updateRect(GrIRect16 rect) { 166 SkASSERT(rect.fLeft <= rect.fRight); 167 SkASSERT(rect.fRight <= 0x1FFF); 168 fUVs[0] = (fUVs[0] & 0xE000) | rect.fLeft; 169 fUVs[1] = rect.fTop; 170 fUVs[2] = (fUVs[2] & 0xE000) | rect.fRight; 171 fUVs[3] = rect.fBottom; 172 } 173 174 private: 175 PlotLocator fPlotLocator{0, 0, 0}; 176 177 // The inset padded bounds in the atlas in the lower 13 bits, and page index in bits 13 & 178 // 14 of the Us. 179 std::array<uint16_t, 4> fUVs{0, 0, 0, 0}; 180 }; 181 182 /** 183 * An interface for eviction callbacks. Whenever GrDrawOpAtlas evicts a 184 * specific PlotLocator, it will call all of the registered listeners so they can process the 185 * eviction. 186 */ 187 class EvictionCallback { 188 public: 189 virtual ~EvictionCallback() = default; 190 virtual void evict(PlotLocator) = 0; 191 }; 192 193 /** 194 * Keep track of generation number for Atlases and Plots. 195 */ 196 class GenerationCounter { 197 public: 198 static constexpr uint64_t kInvalidGeneration = 0; next()199 uint64_t next() { 200 return fGeneration++; 201 } 202 203 private: 204 uint64_t fGeneration{1}; 205 }; 206 207 /** 208 * Returns a GrDrawOpAtlas. This function can be called anywhere, but the returned atlas 209 * should only be used inside of GrMeshDrawOp::onPrepareDraws. 210 * @param GrColorType The colorType which this atlas will store 211 * @param width width in pixels of the atlas 212 * @param height height in pixels of the atlas 213 * @param numPlotsX The number of plots the atlas should be broken up into in the X 214 * direction 215 * @param numPlotsY The number of plots the atlas should be broken up into in the Y 216 * direction 217 * @param atlasGeneration a pointer to the context's generation counter. 218 * @param allowMultitexturing Can the atlas use more than one texture. 219 * @param evictor A pointer to an eviction callback class. 220 * 221 * @return An initialized GrDrawOpAtlas, or nullptr if creation fails 222 */ 223 static std::unique_ptr<GrDrawOpAtlas> Make(GrProxyProvider*, 224 const GrBackendFormat& format, 225 GrColorType, 226 int width, int height, 227 int plotWidth, int plotHeight, 228 GenerationCounter* generationCounter, 229 AllowMultitexturing allowMultitexturing, 230 EvictionCallback* evictor); 231 232 /** 233 * Adds a width x height subimage to the atlas. Upon success it returns 'kSucceeded' and returns 234 * the ID and the subimage's coordinates in the backing texture. 'kTryAgain' is returned if 235 * the subimage cannot fit in the atlas without overwriting texels that will be read in the 236 * current draw. This indicates that the op should end its current draw and begin another 237 * before adding more data. Upon success, an upload of the provided image data will have 238 * been added to the GrDrawOp::Target, in "asap" mode if possible, otherwise in "inline" mode. 239 * Successive uploads in either mode may be consolidated. 240 * 'kError' will be returned when some unrecoverable error was encountered while trying to 241 * add the subimage. In this case the op being created should be discarded. 242 * 243 * NOTE: When the GrDrawOp prepares a draw that reads from the atlas, it must immediately call 244 * 'setUseToken' with the currentToken from the GrDrawOp::Target, otherwise the next call to 245 * addToAtlas might cause the previous data to be overwritten before it has been read. 246 */ 247 248 enum class ErrorCode { 249 kError, 250 kSucceeded, 251 kTryAgain 252 }; 253 254 ErrorCode addToAtlas(GrResourceProvider*, GrDeferredUploadTarget*, 255 int width, int height, const void* image, AtlasLocator*); 256 getViews()257 const GrSurfaceProxyView* getViews() const { return fViews; } 258 atlasGeneration()259 uint64_t atlasGeneration() const { return fAtlasGeneration; } 260 hasID(const PlotLocator & plotLocator)261 bool hasID(const PlotLocator& plotLocator) { 262 if (!plotLocator.isValid()) { 263 return false; 264 } 265 266 uint32_t plot = plotLocator.plotIndex(); 267 uint32_t page = plotLocator.pageIndex(); 268 uint64_t plotGeneration = fPages[page].fPlotArray[plot]->genID(); 269 uint64_t locatorGeneration = plotLocator.genID(); 270 return plot < fNumPlots && page < fNumActivePages && plotGeneration == locatorGeneration; 271 } 272 273 /** To ensure the atlas does not evict a given entry, the client must set the last use token. */ setLastUseToken(const AtlasLocator & atlasLocator,GrDeferredUploadToken token)274 void setLastUseToken(const AtlasLocator& atlasLocator, GrDeferredUploadToken token) { 275 SkASSERT(this->hasID(atlasLocator.plotLocator())); 276 uint32_t plotIdx = atlasLocator.plotIndex(); 277 SkASSERT(plotIdx < fNumPlots); 278 uint32_t pageIdx = atlasLocator.pageIndex(); 279 SkASSERT(pageIdx < fNumActivePages); 280 Plot* plot = fPages[pageIdx].fPlotArray[plotIdx].get(); 281 this->makeMRU(plot, pageIdx); 282 plot->setLastUseToken(token); 283 } 284 numActivePages()285 uint32_t numActivePages() { return fNumActivePages; } 286 287 /** 288 * A class which can be handed back to GrDrawOpAtlas for updating last use tokens in bulk. The 289 * current max number of plots per page the GrDrawOpAtlas can handle is 32. If in the future 290 * this is insufficient then we can move to a 64 bit int. 291 */ 292 class BulkUseTokenUpdater { 293 public: BulkUseTokenUpdater()294 BulkUseTokenUpdater() { 295 memset(fPlotAlreadyUpdated, 0, sizeof(fPlotAlreadyUpdated)); 296 } BulkUseTokenUpdater(const BulkUseTokenUpdater & that)297 BulkUseTokenUpdater(const BulkUseTokenUpdater& that) 298 : fPlotsToUpdate(that.fPlotsToUpdate) { 299 memcpy(fPlotAlreadyUpdated, that.fPlotAlreadyUpdated, sizeof(fPlotAlreadyUpdated)); 300 } 301 add(const AtlasLocator & atlasLocator)302 bool add(const AtlasLocator& atlasLocator) { 303 int plotIdx = atlasLocator.plotIndex(); 304 int pageIdx = atlasLocator.pageIndex(); 305 if (this->find(pageIdx, plotIdx)) { 306 return false; 307 } 308 this->set(pageIdx, plotIdx); 309 return true; 310 } 311 reset()312 void reset() { 313 fPlotsToUpdate.reset(); 314 memset(fPlotAlreadyUpdated, 0, sizeof(fPlotAlreadyUpdated)); 315 } 316 317 struct PlotData { PlotDataPlotData318 PlotData(int pageIdx, int plotIdx) : fPageIndex(pageIdx), fPlotIndex(plotIdx) {} 319 uint32_t fPageIndex; 320 uint32_t fPlotIndex; 321 }; 322 323 private: find(int pageIdx,int index)324 bool find(int pageIdx, int index) const { 325 SkASSERT(index < kMaxPlots); 326 return (fPlotAlreadyUpdated[pageIdx] >> index) & 1; 327 } 328 set(int pageIdx,int index)329 void set(int pageIdx, int index) { 330 SkASSERT(!this->find(pageIdx, index)); 331 fPlotAlreadyUpdated[pageIdx] |= (1 << index); 332 fPlotsToUpdate.push_back(PlotData(pageIdx, index)); 333 } 334 335 static constexpr int kMinItems = 4; 336 SkSTArray<kMinItems, PlotData, true> fPlotsToUpdate; 337 uint32_t fPlotAlreadyUpdated[kMaxMultitexturePages]; // TODO: increase this to uint64_t 338 // to allow more plots per page 339 340 friend class GrDrawOpAtlas; 341 }; 342 setLastUseTokenBulk(const BulkUseTokenUpdater & updater,GrDeferredUploadToken token)343 void setLastUseTokenBulk(const BulkUseTokenUpdater& updater, GrDeferredUploadToken token) { 344 int count = updater.fPlotsToUpdate.count(); 345 for (int i = 0; i < count; i++) { 346 const BulkUseTokenUpdater::PlotData& pd = updater.fPlotsToUpdate[i]; 347 // it's possible we've added a plot to the updater and subsequently the plot's page 348 // was deleted -- so we check to prevent a crash 349 if (pd.fPageIndex < fNumActivePages) { 350 Plot* plot = fPages[pd.fPageIndex].fPlotArray[pd.fPlotIndex].get(); 351 this->makeMRU(plot, pd.fPageIndex); 352 plot->setLastUseToken(token); 353 } 354 } 355 } 356 357 void compact(GrDeferredUploadToken startTokenForNextFlush); 358 359 void instantiate(GrOnFlushResourceProvider*); 360 maxPages()361 uint32_t maxPages() const { 362 return fMaxPages; 363 } 364 365 int numAllocated_TestingOnly() const; 366 void setMaxPages_TestingOnly(uint32_t maxPages); 367 368 private: 369 GrDrawOpAtlas(GrProxyProvider*, const GrBackendFormat& format, GrColorType, int width, 370 int height, int plotWidth, int plotHeight, GenerationCounter* generationCounter, 371 AllowMultitexturing allowMultitexturing); 372 373 /** 374 * The backing GrTexture for a GrDrawOpAtlas is broken into a spatial grid of Plots. The Plots 375 * keep track of subimage placement via their GrRectanizer. A Plot manages the lifetime of its 376 * data using two tokens, a last use token and a last upload token. Once a Plot is "full" (i.e. 377 * there is no room for the new subimage according to the GrRectanizer), it can no longer be 378 * used unless the last use of the Plot has already been flushed through to the gpu. 379 */ 380 class Plot : public SkRefCnt { 381 SK_DECLARE_INTERNAL_LLIST_INTERFACE(Plot); 382 383 public: pageIndex()384 uint32_t pageIndex() const { return fPageIndex; } 385 386 /** plotIndex() is a unique id for the plot relative to the owning GrAtlas and page. */ plotIndex()387 uint32_t plotIndex() const { return fPlotIndex; } 388 /** 389 * genID() is incremented when the plot is evicted due to a atlas spill. It is used to know 390 * if a particular subimage is still present in the atlas. 391 */ genID()392 uint64_t genID() const { return fGenID; } plotLocator()393 PlotLocator plotLocator() const { 394 SkASSERT(fPlotLocator.isValid()); 395 return fPlotLocator; 396 } 397 SkDEBUGCODE(size_t bpp() const { return fBytesPerPixel; }) 398 399 bool addSubImage(int width, int height, const void* image, AtlasLocator* atlasLocator); 400 401 /** 402 * To manage the lifetime of a plot, we use two tokens. We use the last upload token to 403 * know when we can 'piggy back' uploads, i.e. if the last upload hasn't been flushed to 404 * the gpu, we don't need to issue a new upload even if we update the cpu backing store. We 405 * use lastUse to determine when we can evict a plot from the cache, i.e. if the last use 406 * has already flushed through the gpu then we can reuse the plot. 407 */ lastUploadToken()408 GrDeferredUploadToken lastUploadToken() const { return fLastUpload; } lastUseToken()409 GrDeferredUploadToken lastUseToken() const { return fLastUse; } setLastUploadToken(GrDeferredUploadToken token)410 void setLastUploadToken(GrDeferredUploadToken token) { fLastUpload = token; } setLastUseToken(GrDeferredUploadToken token)411 void setLastUseToken(GrDeferredUploadToken token) { fLastUse = token; } 412 413 void uploadToTexture(GrDeferredTextureUploadWritePixelsFn&, GrTextureProxy*); 414 void resetRects(); 415 flushesSinceLastUsed()416 int flushesSinceLastUsed() { return fFlushesSinceLastUse; } resetFlushesSinceLastUsed()417 void resetFlushesSinceLastUsed() { fFlushesSinceLastUse = 0; } incFlushesSinceLastUsed()418 void incFlushesSinceLastUsed() { fFlushesSinceLastUse++; } 419 420 private: 421 Plot(int pageIndex, int plotIndex, GenerationCounter* generationCounter, 422 int offX, int offY, int width, int height, GrColorType colorType); 423 424 ~Plot() override; 425 426 /** 427 * Create a clone of this plot. The cloned plot will take the place of the current plot in 428 * the atlas 429 */ clone()430 Plot* clone() const { 431 return new Plot( 432 fPageIndex, fPlotIndex, fGenerationCounter, fX, fY, fWidth, fHeight, fColorType); 433 } 434 435 GrDeferredUploadToken fLastUpload; 436 GrDeferredUploadToken fLastUse; 437 // the number of flushes since this plot has been last used 438 int fFlushesSinceLastUse; 439 440 struct { 441 const uint32_t fPageIndex : 16; 442 const uint32_t fPlotIndex : 16; 443 }; 444 GenerationCounter* const fGenerationCounter; 445 uint64_t fGenID; 446 PlotLocator fPlotLocator; 447 unsigned char* fData; 448 const int fWidth; 449 const int fHeight; 450 const int fX; 451 const int fY; 452 GrRectanizerSkyline fRectanizer; 453 const SkIPoint16 fOffset; // the offset of the plot in the backing texture 454 const GrColorType fColorType; 455 const size_t fBytesPerPixel; 456 SkIRect fDirtyRect; 457 SkDEBUGCODE(bool fDirty); 458 459 friend class GrDrawOpAtlas; 460 461 using INHERITED = SkRefCnt; 462 }; 463 464 typedef SkTInternalLList<Plot> PlotList; 465 466 inline bool updatePlot(GrDeferredUploadTarget*, AtlasLocator*, Plot*); 467 makeMRU(Plot * plot,int pageIdx)468 inline void makeMRU(Plot* plot, int pageIdx) { 469 if (fPages[pageIdx].fPlotList.head() == plot) { 470 return; 471 } 472 473 fPages[pageIdx].fPlotList.remove(plot); 474 fPages[pageIdx].fPlotList.addToHead(plot); 475 476 // No MRU update for pages -- since we will always try to add from 477 // the front and remove from the back there is no need for MRU. 478 } 479 480 bool uploadToPage(unsigned int pageIdx, GrDeferredUploadTarget*, int width, int height, 481 const void* image, AtlasLocator*); 482 483 bool createPages(GrProxyProvider*, GenerationCounter*); 484 bool activateNewPage(GrResourceProvider*); 485 void deactivateLastPage(); 486 487 void processEviction(PlotLocator); processEvictionAndResetRects(Plot * plot)488 inline void processEvictionAndResetRects(Plot* plot) { 489 this->processEviction(plot->plotLocator()); 490 plot->resetRects(); 491 } 492 493 GrBackendFormat fFormat; 494 GrColorType fColorType; 495 int fTextureWidth; 496 int fTextureHeight; 497 int fPlotWidth; 498 int fPlotHeight; 499 unsigned int fNumPlots; 500 501 GenerationCounter* const fGenerationCounter; 502 uint64_t fAtlasGeneration; 503 504 // nextTokenToFlush() value at the end of the previous flush 505 GrDeferredUploadToken fPrevFlushToken; 506 507 // the number of flushes since this atlas has been last used 508 int fFlushesSinceLastUse; 509 510 std::vector<EvictionCallback*> fEvictionCallbacks; 511 512 struct Page { 513 // allocated array of Plots 514 std::unique_ptr<sk_sp<Plot>[]> fPlotArray; 515 // LRU list of Plots (MRU at head - LRU at tail) 516 PlotList fPlotList; 517 }; 518 // proxies kept separate to make it easier to pass them up to client 519 GrSurfaceProxyView fViews[kMaxMultitexturePages]; 520 Page fPages[kMaxMultitexturePages]; 521 uint32_t fMaxPages; 522 523 uint32_t fNumActivePages; 524 525 SkDEBUGCODE(void validate(const AtlasLocator& atlasLocator) const;) 526 }; 527 528 // There are three atlases (A8, 565, ARGB) that are kept in relation with one another. In 529 // general, the A8 dimensions are 2x the 565 and ARGB dimensions with the constraint that an atlas 530 // size will always contain at least one plot. Since the ARGB atlas takes the most space, its 531 // dimensions are used to size the other two atlases. 532 class GrDrawOpAtlasConfig { 533 public: 534 // The capabilities of the GPU define maxTextureSize. The client provides maxBytes, and this 535 // represents the largest they want a single atlas texture to be. Due to multitexturing, we 536 // may expand temporarily to use more space as needed. 537 GrDrawOpAtlasConfig(int maxTextureSize, size_t maxBytes); 538 539 // For testing only - make minimum sized atlases -- a single plot for ARGB, four for A8 GrDrawOpAtlasConfig()540 GrDrawOpAtlasConfig() : GrDrawOpAtlasConfig(kMaxAtlasDim, 0) {} 541 542 SkISize atlasDimensions(GrMaskFormat type) const; 543 SkISize plotDimensions(GrMaskFormat type) const; 544 545 private: 546 // On some systems texture coordinates are represented using half-precision floating point, 547 // which limits the largest atlas dimensions to 2048x2048. 548 // For simplicity we'll use this constraint for all of our atlas textures. 549 // This can be revisited later if we need larger atlases. 550 static constexpr int kMaxAtlasDim = 2048; 551 552 SkISize fARGBDimensions; 553 int fMaxTextureSize; 554 }; 555 556 #endif 557