1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
|
/*
* Copyright 2015 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef GrBatchAtlas_DEFINED
#define GrBatchAtlas_DEFINED
#include "GrTexture.h"
#include "SkPoint.h"
#include "SkTDArray.h"
#include "SkTInternalLList.h"
#include "batches/GrDrawBatch.h"
class GrRectanizer;
struct GrBatchAtlasConfig {
int numPlotsX() const { return fWidth / fPlotWidth; }
int numPlotsY() const { return fHeight / fPlotWidth; }
int fWidth;
int fHeight;
int fLog2Width;
int fLog2Height;
int fPlotWidth;
int fPlotHeight;
};
class GrBatchAtlas {
public:
// An AtlasID is an opaque handle which callers can use to determine if the atlas contains
// a specific piece of data
typedef uint64_t AtlasID;
static const uint32_t kInvalidAtlasID = 0;
static const uint64_t kInvalidAtlasGeneration = 0;
// A function pointer for use as a callback during eviction. Whenever GrBatchAtlas evicts a
// specific AtlasID, it will call all of the registered listeners so they can optionally process
// the eviction
typedef void (*EvictionFunc)(GrBatchAtlas::AtlasID, void*);
GrBatchAtlas(GrTexture*, int numPlotsX, int numPlotsY);
~GrBatchAtlas();
// Adds a width x height subimage to the atlas. Upon success it returns
// the containing GrPlot and absolute location in the backing texture.
// nullptr is returned if the subimage cannot fit in the atlas.
// If provided, the image data will be written to the CPU-side backing bitmap.
// NOTE: If the client intends to refer to the atlas, they should immediately call 'setUseToken'
// with the currentToken from the batch target, otherwise the next call to addToAtlas might
// cause an eviction
bool addToAtlas(AtlasID*, GrDrawBatch::Target*, int width, int height, const void* image,
SkIPoint16* loc);
GrTexture* getTexture() const { return fTexture; }
uint64_t atlasGeneration() const { return fAtlasGeneration; }
inline bool hasID(AtlasID id) {
uint32_t index = GetIndexFromID(id);
SkASSERT(index < fNumPlots);
return fPlotArray[index]->genID() == GetGenerationFromID(id);
}
// To ensure the atlas does not evict a given entry, the client must set the last use token
inline void setLastUseToken(AtlasID id, GrBatchDrawToken batchToken) {
SkASSERT(this->hasID(id));
uint32_t index = GetIndexFromID(id);
SkASSERT(index < fNumPlots);
this->makeMRU(fPlotArray[index]);
fPlotArray[index]->setLastUseToken(batchToken);
}
inline void registerEvictionCallback(EvictionFunc func, void* userData) {
EvictionData* data = fEvictionCallbacks.append();
data->fFunc = func;
data->fData = userData;
}
/*
* A class which can be handed back to GrBatchAtlas for updating in bulk last use tokens. The
* current max number of plots the GrBatchAtlas can handle is 32, if in the future this is
* insufficient then we can move to a 64 bit int
*/
class BulkUseTokenUpdater {
public:
BulkUseTokenUpdater() : fPlotAlreadyUpdated(0) {}
BulkUseTokenUpdater(const BulkUseTokenUpdater& that)
: fPlotsToUpdate(that.fPlotsToUpdate)
, fPlotAlreadyUpdated(that.fPlotAlreadyUpdated) {
}
void add(AtlasID id) {
int index = GrBatchAtlas::GetIndexFromID(id);
if (!this->find(index)) {
this->set(index);
}
}
void reset() {
fPlotsToUpdate.reset();
fPlotAlreadyUpdated = 0;
}
private:
bool find(int index) const {
SkASSERT(index < kMaxPlots);
return (fPlotAlreadyUpdated >> index) & 1;
}
void set(int index) {
SkASSERT(!this->find(index));
fPlotAlreadyUpdated = fPlotAlreadyUpdated | (1 << index);
fPlotsToUpdate.push_back(index);
}
static const int kMinItems = 4;
static const int kMaxPlots = 32;
SkSTArray<kMinItems, int, true> fPlotsToUpdate;
uint32_t fPlotAlreadyUpdated;
friend class GrBatchAtlas;
};
void setLastUseTokenBulk(const BulkUseTokenUpdater& updater, GrBatchDrawToken batchToken) {
int count = updater.fPlotsToUpdate.count();
for (int i = 0; i < count; i++) {
BatchPlot* plot = fPlotArray[updater.fPlotsToUpdate[i]];
this->makeMRU(plot);
plot->setLastUseToken(batchToken);
}
}
static const int kGlyphMaxDim = 256;
static bool GlyphTooLargeForAtlas(int width, int height) {
return width > kGlyphMaxDim || height > kGlyphMaxDim;
}
private:
// The backing GrTexture for a GrBatchAtlas is broken into a spatial grid of BatchPlots.
// The BatchPlots keep track of subimage placement via their GrRectanizer. A BatchPlot
// manages the lifetime of its data using two tokens, a last use token and a last upload token.
// Once a BatchPlot is "full" (i.e. there is no room for the new subimage according to the
// GrRectanizer), it can no longer be used unless the last use of the GrPlot has already been
// flushed through to the gpu.
class BatchPlot : public SkRefCnt {
SK_DECLARE_INTERNAL_LLIST_INTERFACE(BatchPlot);
public:
// index() is a unique id for the plot relative to the owning GrAtlas. genID() is a
// monotonically incremented number which is bumped every time this plot is
// evicted from the cache (i.e., there is continuity in genID() across atlas spills).
uint32_t index() const { return fIndex; }
uint64_t genID() const { return fGenID; }
GrBatchAtlas::AtlasID id() const {
SkASSERT(GrBatchAtlas::kInvalidAtlasID != fID);
return fID;
}
SkDEBUGCODE(size_t bpp() const { return fBytesPerPixel; })
bool addSubImage(int width, int height, const void* image, SkIPoint16* loc);
// To manage the lifetime of a plot, we use two tokens. We use the last upload token to
// know when we can 'piggy back' uploads, ie if the last upload hasn't been flushed to gpu,
// we don't need to issue a new upload even if we update the cpu backing store. We use
// lastUse to determine when we can evict a plot from the cache, ie if the last use has
// already flushed through the gpu then we can reuse the plot.
GrBatchDrawToken lastUploadToken() const { return fLastUpload; }
GrBatchDrawToken lastUseToken() const { return fLastUse; }
void setLastUploadToken(GrBatchDrawToken batchToken) { fLastUpload = batchToken; }
void setLastUseToken(GrBatchDrawToken batchToken) { fLastUse = batchToken; }
void uploadToTexture(GrDrawBatch::WritePixelsFn&, GrTexture* texture);
void resetRects();
private:
BatchPlot(int index, uint64_t genID, int offX, int offY, int width, int height,
GrPixelConfig config);
~BatchPlot() override;
// Create a clone of this plot. The cloned plot will take the place of the
// current plot in the atlas.
BatchPlot* clone() const {
return new BatchPlot(fIndex, fGenID+1, fX, fY, fWidth, fHeight, fConfig);
}
static GrBatchAtlas::AtlasID CreateId(uint32_t index, uint64_t generation) {
SkASSERT(index < (1 << 16));
SkASSERT(generation < ((uint64_t)1 << 48));
return generation << 16 | index;
}
GrBatchDrawToken fLastUpload;
GrBatchDrawToken fLastUse;
const uint32_t fIndex;
uint64_t fGenID;
GrBatchAtlas::AtlasID fID;
unsigned char* fData;
const int fWidth;
const int fHeight;
const int fX;
const int fY;
GrRectanizer* fRects;
const SkIPoint16 fOffset; // the offset of the plot in the backing texture
const GrPixelConfig fConfig;
const size_t fBytesPerPixel;
SkIRect fDirtyRect;
SkDEBUGCODE(bool fDirty;)
friend class GrBatchAtlas;
typedef SkRefCnt INHERITED;
};
typedef SkTInternalLList<BatchPlot> GrBatchPlotList;
static uint32_t GetIndexFromID(AtlasID id) {
return id & 0xffff;
}
// top 48 bits are reserved for the generation ID
static uint64_t GetGenerationFromID(AtlasID id) {
return (id >> 16) & 0xffffffffffff;
}
inline void updatePlot(GrDrawBatch::Target*, AtlasID*, BatchPlot*);
inline void makeMRU(BatchPlot* plot) {
if (fPlotList.head() == plot) {
return;
}
fPlotList.remove(plot);
fPlotList.addToHead(plot);
}
inline void processEviction(AtlasID);
GrTexture* fTexture;
int fPlotWidth;
int fPlotHeight;
SkDEBUGCODE(uint32_t fNumPlots;)
uint64_t fAtlasGeneration;
struct EvictionData {
EvictionFunc fFunc;
void* fData;
};
SkTDArray<EvictionData> fEvictionCallbacks;
// allocated array of GrBatchPlots
SkAutoTUnref<BatchPlot>* fPlotArray;
// LRU list of GrPlots (MRU at head - LRU at tail)
GrBatchPlotList fPlotList;
};
#endif
|