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