Line data Source code
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 0 : int numPlotsX() const { return fWidth / fPlotWidth; }
21 0 : 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 0 : 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 :
93 0 : GrContext* context() const { return fContext; }
94 0 : sk_sp<GrTextureProxy> getProxy() const { return fProxy; }
95 :
96 0 : uint64_t atlasGeneration() const { return fAtlasGeneration; }
97 :
98 0 : inline bool hasID(AtlasID id) {
99 0 : uint32_t index = GetIndexFromID(id);
100 0 : SkASSERT(index < fNumPlots);
101 0 : 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. */
105 0 : inline void setLastUseToken(AtlasID id, GrDrawOpUploadToken token) {
106 0 : SkASSERT(this->hasID(id));
107 0 : uint32_t index = GetIndexFromID(id);
108 0 : SkASSERT(index < fNumPlots);
109 0 : this->makeMRU(fPlotArray[index].get());
110 0 : fPlotArray[index]->setLastUseToken(token);
111 0 : }
112 :
113 0 : inline void registerEvictionCallback(EvictionFunc func, void* userData) {
114 0 : EvictionData* data = fEvictionCallbacks.append();
115 0 : data->fFunc = func;
116 0 : data->fData = userData;
117 0 : }
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 0 : class BulkUseTokenUpdater {
125 : public:
126 0 : BulkUseTokenUpdater() : fPlotAlreadyUpdated(0) {}
127 0 : BulkUseTokenUpdater(const BulkUseTokenUpdater& that)
128 0 : : fPlotsToUpdate(that.fPlotsToUpdate)
129 0 : , fPlotAlreadyUpdated(that.fPlotAlreadyUpdated) {
130 0 : }
131 :
132 0 : void add(AtlasID id) {
133 0 : int index = GrDrawOpAtlas::GetIndexFromID(id);
134 0 : if (!this->find(index)) {
135 0 : this->set(index);
136 : }
137 0 : }
138 :
139 0 : void reset() {
140 0 : fPlotsToUpdate.reset();
141 0 : fPlotAlreadyUpdated = 0;
142 0 : }
143 :
144 : private:
145 0 : bool find(int index) const {
146 0 : SkASSERT(index < kMaxPlots);
147 0 : return (fPlotAlreadyUpdated >> index) & 1;
148 : }
149 :
150 0 : void set(int index) {
151 0 : SkASSERT(!this->find(index));
152 0 : fPlotAlreadyUpdated = fPlotAlreadyUpdated | (1 << index);
153 0 : fPlotsToUpdate.push_back(index);
154 0 : }
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 :
164 0 : void setLastUseTokenBulk(const BulkUseTokenUpdater& updater, GrDrawOpUploadToken token) {
165 0 : int count = updater.fPlotsToUpdate.count();
166 0 : for (int i = 0; i < count; i++) {
167 0 : Plot* plot = fPlotArray[updater.fPlotsToUpdate[i]].get();
168 0 : this->makeMRU(plot);
169 0 : plot->setLastUseToken(token);
170 : }
171 0 : }
172 :
173 : static const int kGlyphMaxDim = 256;
174 0 : static bool GlyphTooLargeForAtlas(int width, int height) {
175 0 : 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. */
193 0 : 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 : */
198 0 : uint64_t genID() const { return fGenID; }
199 0 : GrDrawOpAtlas::AtlasID id() const {
200 0 : SkASSERT(GrDrawOpAtlas::kInvalidAtlasID != fID);
201 0 : return fID;
202 : }
203 0 : 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 : */
214 0 : GrDrawOpUploadToken lastUploadToken() const { return fLastUpload; }
215 0 : GrDrawOpUploadToken lastUseToken() const { return fLastUse; }
216 0 : void setLastUploadToken(GrDrawOpUploadToken token) { fLastUpload = token; }
217 0 : 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 : */
232 0 : Plot* clone() const {
233 0 : return new Plot(fIndex, fGenID + 1, fX, fY, fWidth, fHeight, fConfig);
234 : }
235 :
236 0 : static GrDrawOpAtlas::AtlasID CreateId(uint32_t index, uint64_t generation) {
237 0 : SkASSERT(index < (1 << 16));
238 0 : SkASSERT(generation < ((uint64_t)1 << 48));
239 0 : 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 :
267 0 : static uint32_t GetIndexFromID(AtlasID id) {
268 0 : return id & 0xffff;
269 : }
270 :
271 : // top 48 bits are reserved for the generation ID
272 0 : static uint64_t GetGenerationFromID(AtlasID id) {
273 0 : return (id >> 16) & 0xffffffffffff;
274 : }
275 :
276 : inline bool updatePlot(GrDrawOp::Target*, AtlasID*, Plot*);
277 :
278 0 : inline void makeMRU(Plot* plot) {
279 0 : if (fPlotList.head() == plot) {
280 0 : return;
281 : }
282 :
283 0 : fPlotList.remove(plot);
284 0 : 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
|