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