xref: /aosp_15_r20/external/skia/src/gpu/graphite/DrawAtlas.h (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2022 Google LLC
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 skgpu_graphite_DrawAtlas_DEFINED
9 #define skgpu_graphite_DrawAtlas_DEFINED
10 
11 #include <cmath>
12 #include <string>
13 #include <string_view>
14 #include <vector>
15 
16 #include "src/core/SkIPoint16.h"
17 #include "src/core/SkTHash.h"
18 #include "src/gpu/AtlasTypes.h"
19 
20 class SkAutoPixmapStorage;
21 
22 namespace skgpu::graphite {
23 
24 class DrawContext;
25 class Recorder;
26 class TextureProxy;
27 
28 /**
29  * TODO: the process described here is tentative, and this comment revised once locked down.
30  *
31  * This class manages one or more atlas textures on behalf of primitive draws in Device. The
32  * drawing processes that use the atlas add preceding UploadTasks when generating RenderPassTasks.
33  * The class provides facilities for using DrawTokens to detect data hazards. Plots that need
34  * uploads are tracked until it is impossible to add data without overwriting texels read by draws
35  * that have not yet been snapped to a RenderPassTask. At that point, the atlas will attempt to
36  * allocate a new atlas texture (or "page") of the same size, up to a maximum number of textures,
37  * and upload to that texture. If that's not possible, then the atlas will fail to add a subimage.
38  * This gives the Device the chance to end the current draw, snap a RenderpassTask, and begin a new
39  * one. Additional uploads will then succeed.
40  *
41  * When the atlas has multiple pages, new uploads are prioritized to the lower index pages, i.e.,
42  * it will try to upload to page 0 before page 1 or 2. To keep the atlas from continually using
43  * excess space, periodic garbage collection is needed to shift data from the higher index pages to
44  * the lower ones, and then eventually remove any pages that are no longer in use. "In use" is
45  * determined by using the AtlasToken system: After a DrawPass is snapped a subarea of the page, or
46  * "plot" is checked to see whether it was used in that DrawPass. If less than a quarter of the
47  * plots have been used recently (within kPlotRecentlyUsedCount iterations) and there are available
48  * plots in lower index pages, the higher index page will be deactivated, and its glyphs will
49  * gradually migrate to other pages via the usual upload system.
50  *
51  * Garbage collection is initiated by the DrawAtlas's client via the compact() method.
52  */
53 class DrawAtlas {
54 public:
55     /** Is the atlas allowed to use more than one texture? */
56     enum class AllowMultitexturing : bool { kNo, kYes };
57 
58     /** Should the atlas use storage textures? */
59     enum class UseStorageTextures : bool { kNo, kYes };
60 
61     /**
62      * Returns a DrawAtlas.
63      *  @param ct                  The colorType which this atlas will store.
64      *  @param bpp                 Size in bytes of each pixel.
65      *  @param width               Width in pixels of the atlas.
66      *  @param height              Height in pixels of the atlas.
67      *  @param plotWidth           The width of each plot. width/plotWidth should be an integer.
68      *  @param plotWidth           The height of each plot. height/plotHeight should be an integer.
69      *  @param atlasGeneration     A pointer to the context's generation counter.
70      *  @param allowMultitexturing Can the atlas use more than one texture.
71      *  @param useStorageTextures  Should the atlas use storage textures.
72      *  @param evictor             A pointer to an eviction callback class.
73      *  @param label               Label for texture resources.
74      *
75      *  @return                    An initialized DrawAtlas, or nullptr if creation fails.
76      */
77     static std::unique_ptr<DrawAtlas> Make(SkColorType ct, size_t bpp,
78                                            int width, int height,
79                                            int plotWidth, int plotHeight,
80                                            AtlasGenerationCounter* generationCounter,
81                                            AllowMultitexturing allowMultitexturing,
82                                            UseStorageTextures useStorageTextures,
83                                            PlotEvictionCallback* evictor,
84                                            std::string_view label);
85 
86     /**
87      * Adds a width x height subimage to the atlas. Upon success it returns 'kSucceeded' and returns
88      * the ID and the subimage's coordinates in the backing texture. 'kTryAgain' is returned if
89      * the subimage cannot fit in the atlas without overwriting texels that will be read in the
90      * current list of draws. This indicates that the Device should end its current draw, snap a
91      * DrawPass, and begin another before adding more data. 'kError' will be returned when some
92      * unrecoverable error was encountered while trying to add the subimage. In this case the draw
93      * being created should be discarded.
94      *
95      * This tracking does not generate UploadTasks per se. Instead, when the RenderPassTask is
96      * ready to be snapped, recordUploads() will be called by the Device and that will generate the
97      * necessary UploadTasks. If the useCachedUploads argument in recordUploads() is true, this
98      * will generate uploads for the entire area of each Plot that has changed since the last
99      * eviction. Otherwise it will only generate uploads for newly added changes.
100      *
101      * NOTE: When a draw that reads from the atlas is added to the DrawList, the client using this
102      * DrawAtlas must immediately call 'setLastUseToken' with the currentToken from the Recorder,
103      * otherwise the next call to addToAtlas might cause the previous data to be overwritten before
104      * it has been read.
105      */
106 
107     enum class ErrorCode {
108         kError,
109         kSucceeded,
110         kTryAgain
111     };
112 
113     ErrorCode addToAtlas(Recorder*, int width, int height, const void* image, AtlasLocator*);
114     ErrorCode addRect(Recorder*, int width, int height, AtlasLocator*);
115     // Reset Pixmap to point to backing data for the AtlasLocator's Plot.
116     // Return relative location within the Plot, as indicated by the AtlasLocator.
117     SkIPoint prepForRender(const AtlasLocator&, SkAutoPixmapStorage*);
118     bool recordUploads(DrawContext*, Recorder*);
119 
getProxies()120     const sk_sp<TextureProxy>* getProxies() const { return fProxies; }
121 
atlasID()122     uint32_t atlasID() const { return fAtlasID; }
atlasGeneration()123     uint64_t atlasGeneration() const { return fAtlasGeneration; }
numActivePages()124     uint32_t numActivePages() const { return fNumActivePages; }
numPlots()125     unsigned int numPlots() const { return fNumPlots; }
plotSize()126     SkISize plotSize() const { return {fPlotWidth, fPlotHeight}; }
127 
hasID(const PlotLocator & plotLocator)128     bool hasID(const PlotLocator& plotLocator) {
129         if (!plotLocator.isValid()) {
130             return false;
131         }
132 
133         uint32_t plot = plotLocator.plotIndex();
134         uint32_t page = plotLocator.pageIndex();
135         uint64_t plotGeneration = fPages[page].fPlotArray[plot]->genID();
136         uint64_t locatorGeneration = plotLocator.genID();
137         return plot < fNumPlots && page < fNumActivePages && plotGeneration == locatorGeneration;
138     }
139 
140     /** To ensure the atlas does not evict a given entry, the client must set the last use token. */
setLastUseToken(const AtlasLocator & atlasLocator,AtlasToken token)141     void setLastUseToken(const AtlasLocator& atlasLocator, AtlasToken token) {
142         Plot* plot = this->findPlot(atlasLocator);
143         this->internalSetLastUseToken(plot, atlasLocator.pageIndex(), token);
144     }
145 
setLastUseTokenBulk(const BulkUsePlotUpdater & updater,AtlasToken token)146     void setLastUseTokenBulk(const BulkUsePlotUpdater& updater,
147                              AtlasToken token) {
148         int count = updater.count();
149         for (int i = 0; i < count; i++) {
150             const BulkUsePlotUpdater::PlotData& pd = updater.plotData(i);
151             // it's possible we've added a plot to the updater and subsequently the plot's page
152             // was deleted -- so we check to prevent a crash
153             if (pd.fPageIndex < fNumActivePages) {
154                 Plot* plot = fPages[pd.fPageIndex].fPlotArray[pd.fPlotIndex].get();
155                 this->internalSetLastUseToken(plot, pd.fPageIndex, token);
156             }
157         }
158     }
159 
160     void compact(AtlasToken startTokenForNextFlush, bool forceCompact);
161 
162     // Mark all plots with any content as full. Used only with Vello because it can't do
163     // new renders to a texture without a clear.
164     void markUsedPlotsAsFull();
165 
166     void evictAllPlots();
167 
maxPages()168     uint32_t maxPages() const {
169         return fMaxPages;
170     }
171 
172     int numAllocated_TestingOnly() const;
173     void setMaxPages_TestingOnly(uint32_t maxPages);
174 
175 private:
176     DrawAtlas(SkColorType, size_t bpp,
177               int width, int height, int plotWidth, int plotHeight,
178               AtlasGenerationCounter* generationCounter,
179               AllowMultitexturing allowMultitexturing,
180               UseStorageTextures useStorageTextures,
181               std::string_view label);
182 
183     bool addRectToPage(unsigned int pageIdx, int width, int height, AtlasLocator*);
184 
185     void updatePlot(Plot* plot, AtlasLocator*);
186 
makeMRU(Plot * plot,int pageIdx)187     inline void makeMRU(Plot* plot, int pageIdx) {
188         if (fPages[pageIdx].fPlotList.head() == plot) {
189             return;
190         }
191 
192         fPages[pageIdx].fPlotList.remove(plot);
193         fPages[pageIdx].fPlotList.addToHead(plot);
194 
195         // No MRU update for pages -- since we will always try to add from
196         // the front and remove from the back there is no need for MRU.
197     }
198 
findPlot(const AtlasLocator & atlasLocator)199     Plot* findPlot(const AtlasLocator& atlasLocator) {
200         SkASSERT(this->hasID(atlasLocator.plotLocator()));
201         uint32_t pageIdx = atlasLocator.pageIndex();
202         uint32_t plotIdx = atlasLocator.plotIndex();
203         return fPages[pageIdx].fPlotArray[plotIdx].get();
204     }
205 
internalSetLastUseToken(Plot * plot,uint32_t pageIdx,AtlasToken token)206     void internalSetLastUseToken(Plot* plot, uint32_t pageIdx, AtlasToken token) {
207         this->makeMRU(plot, pageIdx);
208         plot->setLastUseToken(token);
209     }
210 
211     bool createPages(AtlasGenerationCounter*);
212     bool activateNewPage(Recorder*);
213     void deactivateLastPage();
214 
215     void processEviction(PlotLocator);
processEvictionAndResetRects(Plot * plot)216     inline void processEvictionAndResetRects(Plot* plot) {
217         this->processEviction(plot->plotLocator());
218         plot->resetRects();
219     }
220 
221     SkColorType           fColorType;
222     size_t                fBytesPerPixel;
223     int                   fTextureWidth;
224     int                   fTextureHeight;
225     int                   fPlotWidth;
226     int                   fPlotHeight;
227     unsigned int          fNumPlots;
228     UseStorageTextures    fUseStorageTextures;
229     const std::string     fLabel;
230     uint32_t              fAtlasID;   // unique identifier for this atlas
231 
232     // A counter to track the atlas eviction state for Glyphs. Each Glyph has a PlotLocator
233     // which contains its current generation. When the atlas evicts a plot, it increases
234     // the generation counter. If a Glyph's generation is less than the atlas's
235     // generation, then it knows it's been evicted and is either free to be deleted or
236     // re-added to the atlas if necessary.
237     AtlasGenerationCounter* const fGenerationCounter;
238     uint64_t                      fAtlasGeneration;
239 
240     // nextFlushToken() value at the end of the previous DrawPass
241     // TODO: rename
242     AtlasToken fPrevFlushToken;
243 
244     // the number of flushes since this atlas has been last used
245     // TODO: rename
246     int fFlushesSinceLastUse;
247 
248     std::vector<PlotEvictionCallback*> fEvictionCallbacks;
249 
250     struct Page {
251         // allocated array of Plots
252         std::unique_ptr<sk_sp<Plot>[]> fPlotArray;
253         // LRU list of Plots (MRU at head - LRU at tail)
254         PlotList fPlotList;
255     };
256     // proxies kept separate to make it easier to pass them up to client
257     sk_sp<TextureProxy> fProxies[PlotLocator::kMaxMultitexturePages];
258     Page fPages[PlotLocator::kMaxMultitexturePages];
259     uint32_t fMaxPages;
260 
261     uint32_t fNumActivePages;
262 
263     SkDEBUGCODE(void validate(const AtlasLocator& atlasLocator) const;)
264 };
265 
266 // For text there are three atlases (A8, 565, ARGB) that are kept in relation with one another. In
267 // general, because A8 is the most frequently used mask format its dimensions are 2x the 565 and
268 // ARGB dimensions, with the constraint that an atlas size will always contain at least one plot.
269 // Since the ARGB atlas takes the most space, its dimensions are used to size the other two atlases.
270 class DrawAtlasConfig {
271 public:
272     // The capabilities of the GPU define maxTextureSize. The client provides maxBytes, and this
273     // represents the largest they want a single atlas texture to be. Due to multitexturing, we
274     // may expand temporarily to use more space as needed.
275     DrawAtlasConfig(int maxTextureSize, size_t maxBytes);
276 
277     SkISize atlasDimensions(MaskFormat type) const;
278     SkISize plotDimensions(MaskFormat type) const;
279 
280 private:
281     // On some systems texture coordinates are represented using half-precision floating point
282     // with 11 significant bits, which limits the largest atlas dimensions to 2048x2048.
283     // For simplicity we'll use this constraint for all of our atlas textures.
284     // This can be revisited later if we need larger atlases.
285     inline static constexpr int kMaxAtlasDim = 2048;
286 
287     SkISize fARGBDimensions;
288     int     fMaxTextureSize;
289 };
290 
291 }  // namespace skgpu::graphite
292 
293 #endif
294