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