blob: 3a2942b375ddf209e38b782d1af644a4f86ff714 [file] [log] [blame]
rileya@google.com2e2aedc2012-08-13 20:28:48 +00001
2/*
3 * Copyright 2012 Google Inc.
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
9#include "GrTextureStripAtlas.h"
10#include "SkPixelRef.h"
11#include "SkTSearch.h"
12#include "GrBinHashKey.h"
13#include "GrTexture.h"
14
15#ifdef SK_DEBUG
16 #define VALIDATE this->validate()
17#else
18 #define VALIDATE
19#endif
20
21GR_DEFINE_RESOURCE_CACHE_DOMAIN(GrTextureStripAtlas, GetTextureStripAtlasDomain)
22
23int32_t GrTextureStripAtlas::gCacheCount = 0;
24
25// Hash table entry for atlases
26class AtlasEntry;
27typedef GrTBinHashKey<AtlasEntry, sizeof(GrTextureStripAtlas::Desc)> AtlasHashKey;
28class AtlasEntry : public ::GrNoncopyable {
29public:
30 AtlasEntry() : fAtlas(NULL) {}
31 ~AtlasEntry() { SkDELETE(fAtlas); }
32 int compare(const AtlasHashKey& key) const { return fKey.compare(key); }
33 AtlasHashKey fKey;
34 GrTextureStripAtlas* fAtlas;
35};
36
rileya@google.com2e2aedc2012-08-13 20:28:48 +000037GrTextureStripAtlas* GrTextureStripAtlas::GetAtlas(const GrTextureStripAtlas::Desc& desc) {
rileya@google.comb3e50f22012-08-20 17:43:08 +000038 static SkTDArray<AtlasEntry> gAtlasEntries;
rileya@google.com2e2aedc2012-08-13 20:28:48 +000039 static GrTHashTable<AtlasEntry, AtlasHashKey, 8> gAtlasCache;
40 AtlasHashKey key;
41 key.setKeyData(desc.asKey());
42 AtlasEntry* entry = gAtlasCache.find(key);
43 if (NULL != entry) {
44 return entry->fAtlas;
45 } else {
rileya@google.comb3e50f22012-08-20 17:43:08 +000046 entry = gAtlasEntries.push();
rileya@google.com2e2aedc2012-08-13 20:28:48 +000047 entry->fAtlas = SkNEW_ARGS(GrTextureStripAtlas, (desc));
48 entry->fKey = key;
49 gAtlasCache.insert(key, entry);
50 return entry->fAtlas;
51 }
52}
53
rmistry@google.comfbfcd562012-08-23 18:09:54 +000054GrTextureStripAtlas::GrTextureStripAtlas(GrTextureStripAtlas::Desc desc)
rileya@google.comf61c7462012-08-13 21:03:39 +000055 : fCacheID(sk_atomic_inc(&gCacheCount))
rileya@google.com2e2aedc2012-08-13 20:28:48 +000056 , fLockedRows(0)
57 , fDesc(desc)
58 , fNumRows(desc.fHeight / desc.fRowHeight)
robertphillips@google.com1f47f4f2012-08-16 14:49:16 +000059 , fTexture(NULL)
rileya@google.com2e2aedc2012-08-13 20:28:48 +000060 , fRows(SkNEW_ARRAY(AtlasRow, fNumRows))
61 , fLRUFront(NULL)
62 , fLRUBack(NULL) {
63 GrAssert(fNumRows * fDesc.fRowHeight == fDesc.fHeight);
64 this->initLRU();
65 VALIDATE;
66}
67
68GrTextureStripAtlas::~GrTextureStripAtlas() {
69 SkDELETE_ARRAY(fRows);
70}
71
72int GrTextureStripAtlas::lockRow(const SkBitmap& data) {
73 VALIDATE;
74 if (0 == fLockedRows) {
75 this->lockTexture();
76 }
77
78 int key = data.getGenerationID();
79 int rowNumber = -1;
80 int index = this->searchByKey(key);
81
82 if (index >= 0) {
83 // We already have the data in a row, so we can just return that row
84 AtlasRow* row = fKeyTable[index];
85 if (0 == row->fLocks) {
86 this->removeFromLRU(row);
87 }
88 ++row->fLocks;
rileya@google.comb3e50f22012-08-20 17:43:08 +000089 ++fLockedRows;
rileya@google.com2e2aedc2012-08-13 20:28:48 +000090
rmistry@google.comfbfcd562012-08-23 18:09:54 +000091 // Since all the rows are always stored in a contiguous array, we can save the memory
rileya@google.com2e2aedc2012-08-13 20:28:48 +000092 // required for storing row numbers and just compute it with some pointer arithmetic
rmistry@google.comfbfcd562012-08-23 18:09:54 +000093 rowNumber = static_cast<int>(row - fRows);
rileya@google.com2e2aedc2012-08-13 20:28:48 +000094 } else {
95 // ~index is the index where we will insert the new key to keep things sorted
96 index = ~index;
97
98 // We don't have this data cached, so pick the least recently used row to copy into
99 AtlasRow* row = this->getLRU();
100
rileya@google.comb3e50f22012-08-20 17:43:08 +0000101 ++fLockedRows;
102
rileya@google.com2e2aedc2012-08-13 20:28:48 +0000103 if (NULL == row) {
104 // force a flush, which should unlock all the rows; then try again
105 fDesc.fContext->flush();
106 row = this->getLRU();
107 if (NULL == row) {
rileya@google.comb3e50f22012-08-20 17:43:08 +0000108 --fLockedRows;
rileya@google.com2e2aedc2012-08-13 20:28:48 +0000109 return -1;
110 }
111 }
112
113 this->removeFromLRU(row);
114
115 uint32_t oldKey = row->fKey;
rileya@google.com2e2aedc2012-08-13 20:28:48 +0000116
117 // If we are writing into a row that already held bitmap data, we need to remove the
118 // reference to that genID which is stored in our sorted table of key values.
119 if (oldKey != kEmptyAtlasRowKey) {
120
121 // Find the entry in the list; if it's before the index where we plan on adding the new
122 // entry, we decrement since it will shift elements ahead of it back by one.
123 int oldIndex = this->searchByKey(oldKey);
rileya@google.comb3e50f22012-08-20 17:43:08 +0000124 if (oldIndex < index) {
rileya@google.com2e2aedc2012-08-13 20:28:48 +0000125 --index;
126 }
127
128 fKeyTable.remove(oldIndex);
129 }
130
rileya@google.comb3e50f22012-08-20 17:43:08 +0000131 row->fKey = key;
132 row->fLocks = 1;
rileya@google.com2e2aedc2012-08-13 20:28:48 +0000133 fKeyTable.insert(index, 1, &row);
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000134 rowNumber = static_cast<int>(row - fRows);
rileya@google.com2e2aedc2012-08-13 20:28:48 +0000135
136 SkAutoLockPixels lock(data);
137
138 // Pass in the kDontFlush flag, since we know we're writing to a part of this texture
139 // that is not currently in use
bsalomon@google.com0342a852012-08-20 19:22:38 +0000140 fDesc.fContext->writeTexturePixels(fTexture,
141 0, rowNumber * fDesc.fRowHeight,
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000142 fDesc.fWidth, fDesc.fRowHeight,
bsalomon@google.com0342a852012-08-20 19:22:38 +0000143 SkBitmapConfig2GrPixelConfig(data.config()),
144 data.getPixels(),
145 data.rowBytes(),
146 GrContext::kDontFlush_PixelOpsFlag);
rileya@google.com2e2aedc2012-08-13 20:28:48 +0000147 }
148
rileya@google.com2e2aedc2012-08-13 20:28:48 +0000149 GrAssert(rowNumber >= 0);
150 VALIDATE;
151 return rowNumber;
152}
153
154void GrTextureStripAtlas::unlockRow(int row) {
155 VALIDATE;
156 --fRows[row].fLocks;
157 --fLockedRows;
158 GrAssert(fRows[row].fLocks >= 0 && fLockedRows >= 0);
159 if (0 == fRows[row].fLocks) {
160 this->appendLRU(fRows + row);
161 }
162 if (0 == fLockedRows) {
163 this->unlockTexture();
164 }
165 VALIDATE;
166}
167
168GrTextureStripAtlas::AtlasRow* GrTextureStripAtlas::getLRU() {
169 // Front is least-recently-used
170 AtlasRow* row = fLRUFront;
171 return row;
172}
173
174void GrTextureStripAtlas::lockTexture() {
175 GrTextureParams params;
176 GrTextureDesc texDesc;
177 texDesc.fWidth = fDesc.fWidth;
178 texDesc.fHeight = fDesc.fHeight;
179 texDesc.fConfig = fDesc.fConfig;
180 GrCacheData cacheData(fCacheID);
181 cacheData.fResourceDomain = GetTextureStripAtlasDomain();
robertphillips@google.com9fbcad02012-09-09 14:44:15 +0000182 fTexture = fDesc.fContext->findTexture(texDesc, cacheData, &params);
robertphillips@google.com1f47f4f2012-08-16 14:49:16 +0000183 if (NULL == fTexture) {
robertphillips@google.com9fbcad02012-09-09 14:44:15 +0000184 fTexture = fDesc.fContext->createTexture(&params, texDesc, cacheData, NULL, 0);
rileya@google.com2e2aedc2012-08-13 20:28:48 +0000185 // This is a new texture, so all of our cache info is now invalid
186 this->initLRU();
187 fKeyTable.rewind();
188 }
robertphillips@google.com1f47f4f2012-08-16 14:49:16 +0000189 GrAssert(NULL != fTexture);
robertphillips@google.com50a035d2012-09-07 19:44:33 +0000190 fTexture->ref();
rileya@google.com2e2aedc2012-08-13 20:28:48 +0000191}
192
193void GrTextureStripAtlas::unlockTexture() {
robertphillips@google.com1f47f4f2012-08-16 14:49:16 +0000194 GrAssert(NULL != fTexture && 0 == fLockedRows);
robertphillips@google.com50a035d2012-09-07 19:44:33 +0000195 fTexture->unref();
robertphillips@google.com1f47f4f2012-08-16 14:49:16 +0000196 fTexture = NULL;
robertphillips@google.com50a035d2012-09-07 19:44:33 +0000197 fDesc.fContext->purgeCache();
rileya@google.com2e2aedc2012-08-13 20:28:48 +0000198}
199
200void GrTextureStripAtlas::initLRU() {
201 fLRUFront = NULL;
202 fLRUBack = NULL;
203 // Initially all the rows are in the LRU list
204 for (int i = 0; i < fNumRows; ++i) {
205 fRows[i].fKey = kEmptyAtlasRowKey;
206 fRows[i].fNext = NULL;
207 fRows[i].fPrev = NULL;
208 this->appendLRU(fRows + i);
209 }
210 GrAssert(NULL == fLRUFront->fPrev && NULL == fLRUBack->fNext);
211}
212
213void GrTextureStripAtlas::appendLRU(AtlasRow* row) {
214 GrAssert(NULL == row->fPrev && NULL == row->fNext);
215 if (NULL == fLRUFront && NULL == fLRUBack) {
216 fLRUFront = row;
217 fLRUBack = row;
218 } else {
219 row->fPrev = fLRUBack;
220 fLRUBack->fNext = row;
221 fLRUBack = row;
222 }
223}
224
225void GrTextureStripAtlas::removeFromLRU(AtlasRow* row) {
226 GrAssert(NULL != row);
227 if (NULL != row->fNext && NULL != row->fPrev) {
228 row->fPrev->fNext = row->fNext;
229 row->fNext->fPrev = row->fPrev;
230 } else {
231 if (NULL == row->fNext) {
232 GrAssert(row == fLRUBack);
233 fLRUBack = row->fPrev;
rileya@google.comb3e50f22012-08-20 17:43:08 +0000234 if (fLRUBack) {
235 fLRUBack->fNext = NULL;
236 }
rileya@google.com2e2aedc2012-08-13 20:28:48 +0000237 }
238 if (NULL == row->fPrev) {
239 GrAssert(row == fLRUFront);
240 fLRUFront = row->fNext;
rileya@google.comb3e50f22012-08-20 17:43:08 +0000241 if (fLRUFront) {
242 fLRUFront->fPrev = NULL;
243 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000244 }
rileya@google.com2e2aedc2012-08-13 20:28:48 +0000245 }
246 row->fNext = NULL;
247 row->fPrev = NULL;
248}
249
250int GrTextureStripAtlas::searchByKey(uint32_t key) {
251 AtlasRow target;
252 target.fKey = key;
253 return SkTSearch<AtlasRow, GrTextureStripAtlas::compareKeys>((const AtlasRow**)fKeyTable.begin(),
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000254 fKeyTable.count(),
255 &target,
rileya@google.com2e2aedc2012-08-13 20:28:48 +0000256 sizeof(AtlasRow*));
257}
258
259#ifdef SK_DEBUG
260void GrTextureStripAtlas::validate() {
261
262 // Our key table should be sorted
263 uint32_t prev = 1 > fKeyTable.count() ? 0 : fKeyTable[0]->fKey;
264 for (int i = 1; i < fKeyTable.count(); ++i) {
265 GrAssert(prev < fKeyTable[i]->fKey);
266 GrAssert(fKeyTable[i]->fKey != kEmptyAtlasRowKey);
267 prev = fKeyTable[i]->fKey;
268 }
269
270 int lruCount = 0;
271 // Validate LRU pointers, and count LRU entries
272 GrAssert(NULL == fLRUFront || NULL == fLRUFront->fPrev);
273 GrAssert(NULL == fLRUBack || NULL == fLRUBack->fNext);
274 for (AtlasRow* r = fLRUFront; r != NULL; r = r->fNext) {
275 if (NULL == r->fNext) {
276 GrAssert(r == fLRUBack);
277 } else {
278 GrAssert(r->fNext->fPrev == r);
279 }
280 ++lruCount;
281 }
282
283 int rowLocks = 0;
284 int freeRows = 0;
285
286 for (int i = 0; i < fNumRows; ++i) {
287 rowLocks += fRows[i].fLocks;
288 if (0 == fRows[i].fLocks) {
289 ++freeRows;
290 bool inLRU = false;
291 // Step through the LRU and make sure it's present
292 for (AtlasRow* r = fLRUFront; r != NULL; r = r->fNext) {
293 if (r == &fRows[i]) {
294 inLRU = true;
295 break;
296 }
297 }
298 GrAssert(inLRU);
299 } else {
300 // If we are locked, we should have a key
301 GrAssert(kEmptyAtlasRowKey != fRows[i].fKey);
302 }
303
304 // If we have a key != kEmptyAtlasRowKey, it should be in the key table
305 GrAssert(fRows[i].fKey == kEmptyAtlasRowKey || this->searchByKey(fRows[i].fKey) >= 0);
306 }
307
rileya@google.comb3e50f22012-08-20 17:43:08 +0000308 // Our count of locks should equal the sum of row locks, unless we ran out of rows and flushed,
309 // in which case we'll have one more lock than recorded in the rows (to represent the pending
310 // lock of a row; which ensures we don't unlock the texture prematurely).
311 GrAssert(rowLocks == fLockedRows || rowLocks + 1 == fLockedRows);
rileya@google.com2e2aedc2012-08-13 20:28:48 +0000312
313 // We should have one lru entry for each free row
314 GrAssert(freeRows == lruCount);
315
316 // If we have locked rows, we should have a locked texture, otherwise
317 // it should be unlocked
318 if (fLockedRows == 0) {
robertphillips@google.com1f47f4f2012-08-16 14:49:16 +0000319 GrAssert(NULL == fTexture);
rileya@google.com2e2aedc2012-08-13 20:28:48 +0000320 } else {
robertphillips@google.com1f47f4f2012-08-16 14:49:16 +0000321 GrAssert(NULL != fTexture);
rileya@google.com2e2aedc2012-08-13 20:28:48 +0000322 }
323}
324#endif
325