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 { numPlotsXGrDrawOpAtlasConfig20 int numPlotsX() const { return fWidth / fPlotWidth; } numPlotsYGrDrawOpAtlasConfig21 int numPlotsY() const { return fHeight / fPlotWidth; } 22 int fWidth; 23 int fHeight; 24 int fLog2Width; 25 int fLog2Height; 26 int fPlotWidth; 27 int fPlotHeight; 28 }; 29 30 /** 31 * This class manages an atlas texture on behalf of GrDrawOps. The draw ops that use the atlas 32 * perform texture uploads when preparing their draws during flush. The class provides facilities 33 * for using GrDrawOpUploadToken to detect data hazards. Op's uploads are performed in "asap" mode 34 * until it is impossible to add data without overwriting texels read by draws that have not yet 35 * executed on the gpu. At that point the uploads are performed "inline" between draws. If a single 36 * draw would use enough subimage space to overflow the atlas texture then the atlas will fail to 37 * add a subimage. This gives the op the chance to end the draw and begin a new one. Additional 38 * uploads will then succeed in inline mode. 39 */ 40 class GrDrawOpAtlas { 41 public: 42 /** 43 * An AtlasID is an opaque handle which callers can use to determine if the atlas contains 44 * a specific piece of data. 45 */ 46 typedef uint64_t AtlasID; 47 static const uint32_t kInvalidAtlasID = 0; 48 static const uint64_t kInvalidAtlasGeneration = 0; 49 50 /** 51 * A function pointer for use as a callback during eviction. Whenever GrDrawOpAtlas evicts a 52 * specific AtlasID, it will call all of the registered listeners so they can process the 53 * eviction. 54 */ 55 typedef void (*EvictionFunc)(GrDrawOpAtlas::AtlasID, void*); 56 57 /** 58 * Returns a GrDrawOpAtlas. This function can be called anywhere, but the returned atlas 59 * should only be used inside of GrMeshDrawOp::onPrepareDraws. 60 * @param GrPixelConfig The pixel config which this atlas will store 61 * @param width width in pixels of the atlas 62 * @param height height in pixels of the atlas 63 * @param numPlotsX The number of plots the atlas should be broken up into in the X 64 * direction 65 * @param numPlotsY The number of plots the atlas should be broken up into in the Y 66 * direction 67 * @param func An eviction function which will be called whenever the atlas has to 68 * evict data 69 * @param data User supplied data which will be passed into func whenver an 70 * eviction occurs 71 * @return An initialized GrDrawOpAtlas, or nullptr if creation fails 72 */ 73 static std::unique_ptr<GrDrawOpAtlas> Make(GrContext*, GrPixelConfig, 74 int width, int height, 75 int numPlotsX, int numPlotsY, 76 GrDrawOpAtlas::EvictionFunc func, void* data); 77 78 /** 79 * Adds a width x height subimage to the atlas. Upon success it returns an ID and the subimage's 80 * coordinates in the backing texture. False is returned if the subimage cannot fit in the 81 * atlas without overwriting texels that will be read in the current draw. This indicates that 82 * the op should end its current draw and begin another before adding more data. Upon success, 83 * an upload of the provided image data will have been added to the GrDrawOp::Target, in "asap" 84 * mode if possible, otherwise in "inline" mode. Successive uploads in either mode may be 85 * consolidated. 86 * NOTE: When the GrDrawOp prepares a draw that reads from the atlas, it must immediately call 87 * 'setUseToken' with the currentToken from the GrDrawOp::Target, otherwise the next call to 88 * addToAtlas might cause the previous data to be overwritten before it has been read. 89 */ 90 bool addToAtlas(AtlasID*, GrDrawOp::Target*, int width, int height, const void* image, 91 SkIPoint16* loc); 92 context()93 GrContext* context() const { return fContext; } getProxy()94 sk_sp<GrTextureProxy> getProxy() const { return fProxy; } 95 atlasGeneration()96 uint64_t atlasGeneration() const { return fAtlasGeneration; } 97 hasID(AtlasID id)98 inline bool hasID(AtlasID id) { 99 uint32_t index = GetIndexFromID(id); 100 SkASSERT(index < fNumPlots); 101 return fPlotArray[index]->genID() == GetGenerationFromID(id); 102 } 103 104 /** To ensure the atlas does not evict a given entry, the client must set the last use token. */ setLastUseToken(AtlasID id,GrDrawOpUploadToken token)105 inline void setLastUseToken(AtlasID id, GrDrawOpUploadToken token) { 106 SkASSERT(this->hasID(id)); 107 uint32_t index = GetIndexFromID(id); 108 SkASSERT(index < fNumPlots); 109 this->makeMRU(fPlotArray[index].get()); 110 fPlotArray[index]->setLastUseToken(token); 111 } 112 registerEvictionCallback(EvictionFunc func,void * userData)113 inline void registerEvictionCallback(EvictionFunc func, void* userData) { 114 EvictionData* data = fEvictionCallbacks.append(); 115 data->fFunc = func; 116 data->fData = userData; 117 } 118 119 /** 120 * A class which can be handed back to GrDrawOpAtlas for updating last use tokens in bulk. The 121 * current max number of plots the GrDrawOpAtlas can handle is 32. If in the future this is 122 * insufficient then we can move to a 64 bit int. 123 */ 124 class BulkUseTokenUpdater { 125 public: BulkUseTokenUpdater()126 BulkUseTokenUpdater() : fPlotAlreadyUpdated(0) {} BulkUseTokenUpdater(const BulkUseTokenUpdater & that)127 BulkUseTokenUpdater(const BulkUseTokenUpdater& that) 128 : fPlotsToUpdate(that.fPlotsToUpdate) 129 , fPlotAlreadyUpdated(that.fPlotAlreadyUpdated) { 130 } 131 add(AtlasID id)132 void add(AtlasID id) { 133 int index = GrDrawOpAtlas::GetIndexFromID(id); 134 if (!this->find(index)) { 135 this->set(index); 136 } 137 } 138 reset()139 void reset() { 140 fPlotsToUpdate.reset(); 141 fPlotAlreadyUpdated = 0; 142 } 143 144 private: find(int index)145 bool find(int index) const { 146 SkASSERT(index < kMaxPlots); 147 return (fPlotAlreadyUpdated >> index) & 1; 148 } 149 set(int index)150 void set(int index) { 151 SkASSERT(!this->find(index)); 152 fPlotAlreadyUpdated = fPlotAlreadyUpdated | (1 << index); 153 fPlotsToUpdate.push_back(index); 154 } 155 156 static const int kMinItems = 4; 157 static const int kMaxPlots = 32; 158 SkSTArray<kMinItems, int, true> fPlotsToUpdate; 159 uint32_t fPlotAlreadyUpdated; 160 161 friend class GrDrawOpAtlas; 162 }; 163 setLastUseTokenBulk(const BulkUseTokenUpdater & updater,GrDrawOpUploadToken token)164 void setLastUseTokenBulk(const BulkUseTokenUpdater& updater, GrDrawOpUploadToken token) { 165 int count = updater.fPlotsToUpdate.count(); 166 for (int i = 0; i < count; i++) { 167 Plot* plot = fPlotArray[updater.fPlotsToUpdate[i]].get(); 168 this->makeMRU(plot); 169 plot->setLastUseToken(token); 170 } 171 } 172 173 static const int kGlyphMaxDim = 256; GlyphTooLargeForAtlas(int width,int height)174 static bool GlyphTooLargeForAtlas(int width, int height) { 175 return width > kGlyphMaxDim || height > kGlyphMaxDim; 176 } 177 178 private: 179 GrDrawOpAtlas(GrContext*, sk_sp<GrTextureProxy>, int numPlotsX, int numPlotsY); 180 181 /** 182 * The backing GrTexture for a GrDrawOpAtlas is broken into a spatial grid of Plots. The Plots 183 * keep track of subimage placement via their GrRectanizer. A Plot manages the lifetime of its 184 * data using two tokens, a last use token and a last upload token. Once a Plot is "full" (i.e. 185 * there is no room for the new subimage according to the GrRectanizer), it can no longer be 186 * used unless the last use of the Plot has already been flushed through to the gpu. 187 */ 188 class Plot : public SkRefCnt { 189 SK_DECLARE_INTERNAL_LLIST_INTERFACE(Plot); 190 191 public: 192 /** index() is a unique id for the plot relative to the owning GrAtlas. */ index()193 uint32_t index() const { return fIndex; } 194 /** 195 * genID() is incremented when the plot is evicted due to a atlas spill. It is used to know 196 * if a particular subimage is still present in the atlas. 197 */ genID()198 uint64_t genID() const { return fGenID; } id()199 GrDrawOpAtlas::AtlasID id() const { 200 SkASSERT(GrDrawOpAtlas::kInvalidAtlasID != fID); 201 return fID; 202 } 203 SkDEBUGCODE(size_t bpp() const { return fBytesPerPixel; }) 204 205 bool addSubImage(int width, int height, const void* image, SkIPoint16* loc); 206 207 /** 208 * To manage the lifetime of a plot, we use two tokens. We use the last upload token to 209 * know when we can 'piggy back' uploads, i.e. if the last upload hasn't been flushed to 210 * the gpu, we don't need to issue a new upload even if we update the cpu backing store. We 211 * use lastUse to determine when we can evict a plot from the cache, i.e. if the last use 212 * has already flushed through the gpu then we can reuse the plot. 213 */ lastUploadToken()214 GrDrawOpUploadToken lastUploadToken() const { return fLastUpload; } lastUseToken()215 GrDrawOpUploadToken lastUseToken() const { return fLastUse; } setLastUploadToken(GrDrawOpUploadToken token)216 void setLastUploadToken(GrDrawOpUploadToken token) { fLastUpload = token; } setLastUseToken(GrDrawOpUploadToken token)217 void setLastUseToken(GrDrawOpUploadToken token) { fLastUse = token; } 218 219 void uploadToTexture(GrDrawOp::WritePixelsFn&, GrTexture* texture); 220 void resetRects(); 221 222 private: 223 Plot(int index, uint64_t genID, int offX, int offY, int width, int height, 224 GrPixelConfig config); 225 226 ~Plot() override; 227 228 /** 229 * Create a clone of this plot. The cloned plot will take the place of the current plot in 230 * the atlas 231 */ clone()232 Plot* clone() const { 233 return new Plot(fIndex, fGenID + 1, fX, fY, fWidth, fHeight, fConfig); 234 } 235 CreateId(uint32_t index,uint64_t generation)236 static GrDrawOpAtlas::AtlasID CreateId(uint32_t index, uint64_t generation) { 237 SkASSERT(index < (1 << 16)); 238 SkASSERT(generation < ((uint64_t)1 << 48)); 239 return generation << 16 | index; 240 } 241 242 GrDrawOpUploadToken fLastUpload; 243 GrDrawOpUploadToken fLastUse; 244 245 const uint32_t fIndex; 246 uint64_t fGenID; 247 GrDrawOpAtlas::AtlasID fID; 248 unsigned char* fData; 249 const int fWidth; 250 const int fHeight; 251 const int fX; 252 const int fY; 253 GrRectanizer* fRects; 254 const SkIPoint16 fOffset; // the offset of the plot in the backing texture 255 const GrPixelConfig fConfig; 256 const size_t fBytesPerPixel; 257 SkIRect fDirtyRect; 258 SkDEBUGCODE(bool fDirty); 259 260 friend class GrDrawOpAtlas; 261 262 typedef SkRefCnt INHERITED; 263 }; 264 265 typedef SkTInternalLList<Plot> PlotList; 266 GetIndexFromID(AtlasID id)267 static uint32_t GetIndexFromID(AtlasID id) { 268 return id & 0xffff; 269 } 270 271 // top 48 bits are reserved for the generation ID GetGenerationFromID(AtlasID id)272 static uint64_t GetGenerationFromID(AtlasID id) { 273 return (id >> 16) & 0xffffffffffff; 274 } 275 276 inline bool updatePlot(GrDrawOp::Target*, AtlasID*, Plot*); 277 makeMRU(Plot * plot)278 inline void makeMRU(Plot* plot) { 279 if (fPlotList.head() == plot) { 280 return; 281 } 282 283 fPlotList.remove(plot); 284 fPlotList.addToHead(plot); 285 } 286 287 inline void processEviction(AtlasID); 288 289 GrContext* fContext; 290 sk_sp<GrTextureProxy> fProxy; 291 int fPlotWidth; 292 int fPlotHeight; 293 SkDEBUGCODE(uint32_t fNumPlots;) 294 295 uint64_t fAtlasGeneration; 296 297 struct EvictionData { 298 EvictionFunc fFunc; 299 void* fData; 300 }; 301 302 SkTDArray<EvictionData> fEvictionCallbacks; 303 // allocated array of Plots 304 std::unique_ptr<sk_sp<Plot>[]> fPlotArray; 305 // LRU list of Plots (MRU at head - LRU at tail) 306 PlotList fPlotList; 307 }; 308 309 #endif 310