blob: 3462e9243b96cbfbcfa27019fd2ff7345cb576fb [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
54GrTextureStripAtlas::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
91 // Since all the rows are always stored in a contiguous array, we can save the memory
92 // required for storing row numbers and just compute it with some pointer arithmetic
93 rowNumber = static_cast<int>(row - fRows);
94 } 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);
134 rowNumber = static_cast<int>(row - fRows);
135
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,
142 fDesc.fWidth, fDesc.fRowHeight,
143 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.com1f47f4f2012-08-16 14:49:16 +0000182 fTexture = fDesc.fContext->findAndLockTexture(texDesc, cacheData, &params);
183 if (NULL == fTexture) {
184 fTexture = fDesc.fContext->createAndLockTexture(&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);
rileya@google.com2e2aedc2012-08-13 20:28:48 +0000190}
191
192void GrTextureStripAtlas::unlockTexture() {
robertphillips@google.com1f47f4f2012-08-16 14:49:16 +0000193 GrAssert(NULL != fTexture && 0 == fLockedRows);
194 fDesc.fContext->unlockTexture(fTexture);
195 fTexture = NULL;
rileya@google.com2e2aedc2012-08-13 20:28:48 +0000196}
197
198void GrTextureStripAtlas::initLRU() {
199 fLRUFront = NULL;
200 fLRUBack = NULL;
201 // Initially all the rows are in the LRU list
202 for (int i = 0; i < fNumRows; ++i) {
203 fRows[i].fKey = kEmptyAtlasRowKey;
204 fRows[i].fNext = NULL;
205 fRows[i].fPrev = NULL;
206 this->appendLRU(fRows + i);
207 }
208 GrAssert(NULL == fLRUFront->fPrev && NULL == fLRUBack->fNext);
209}
210
211void GrTextureStripAtlas::appendLRU(AtlasRow* row) {
212 GrAssert(NULL == row->fPrev && NULL == row->fNext);
213 if (NULL == fLRUFront && NULL == fLRUBack) {
214 fLRUFront = row;
215 fLRUBack = row;
216 } else {
217 row->fPrev = fLRUBack;
218 fLRUBack->fNext = row;
219 fLRUBack = row;
220 }
221}
222
223void GrTextureStripAtlas::removeFromLRU(AtlasRow* row) {
224 GrAssert(NULL != row);
225 if (NULL != row->fNext && NULL != row->fPrev) {
226 row->fPrev->fNext = row->fNext;
227 row->fNext->fPrev = row->fPrev;
228 } else {
229 if (NULL == row->fNext) {
230 GrAssert(row == fLRUBack);
231 fLRUBack = row->fPrev;
rileya@google.comb3e50f22012-08-20 17:43:08 +0000232 if (fLRUBack) {
233 fLRUBack->fNext = NULL;
234 }
rileya@google.com2e2aedc2012-08-13 20:28:48 +0000235 }
236 if (NULL == row->fPrev) {
237 GrAssert(row == fLRUFront);
238 fLRUFront = row->fNext;
rileya@google.comb3e50f22012-08-20 17:43:08 +0000239 if (fLRUFront) {
240 fLRUFront->fPrev = NULL;
241 }
rileya@google.com2e2aedc2012-08-13 20:28:48 +0000242 }
243 }
244 row->fNext = NULL;
245 row->fPrev = NULL;
246}
247
248int GrTextureStripAtlas::searchByKey(uint32_t key) {
249 AtlasRow target;
250 target.fKey = key;
251 return SkTSearch<AtlasRow, GrTextureStripAtlas::compareKeys>((const AtlasRow**)fKeyTable.begin(),
252 fKeyTable.count(),
253 &target,
254 sizeof(AtlasRow*));
255}
256
257#ifdef SK_DEBUG
258void GrTextureStripAtlas::validate() {
259
260 // Our key table should be sorted
261 uint32_t prev = 1 > fKeyTable.count() ? 0 : fKeyTable[0]->fKey;
262 for (int i = 1; i < fKeyTable.count(); ++i) {
263 GrAssert(prev < fKeyTable[i]->fKey);
264 GrAssert(fKeyTable[i]->fKey != kEmptyAtlasRowKey);
265 prev = fKeyTable[i]->fKey;
266 }
267
268 int lruCount = 0;
269 // Validate LRU pointers, and count LRU entries
270 GrAssert(NULL == fLRUFront || NULL == fLRUFront->fPrev);
271 GrAssert(NULL == fLRUBack || NULL == fLRUBack->fNext);
272 for (AtlasRow* r = fLRUFront; r != NULL; r = r->fNext) {
273 if (NULL == r->fNext) {
274 GrAssert(r == fLRUBack);
275 } else {
276 GrAssert(r->fNext->fPrev == r);
277 }
278 ++lruCount;
279 }
280
281 int rowLocks = 0;
282 int freeRows = 0;
283
284 for (int i = 0; i < fNumRows; ++i) {
285 rowLocks += fRows[i].fLocks;
286 if (0 == fRows[i].fLocks) {
287 ++freeRows;
288 bool inLRU = false;
289 // Step through the LRU and make sure it's present
290 for (AtlasRow* r = fLRUFront; r != NULL; r = r->fNext) {
291 if (r == &fRows[i]) {
292 inLRU = true;
293 break;
294 }
295 }
296 GrAssert(inLRU);
297 } else {
298 // If we are locked, we should have a key
299 GrAssert(kEmptyAtlasRowKey != fRows[i].fKey);
300 }
301
302 // If we have a key != kEmptyAtlasRowKey, it should be in the key table
303 GrAssert(fRows[i].fKey == kEmptyAtlasRowKey || this->searchByKey(fRows[i].fKey) >= 0);
304 }
305
rileya@google.comb3e50f22012-08-20 17:43:08 +0000306 // Our count of locks should equal the sum of row locks, unless we ran out of rows and flushed,
307 // in which case we'll have one more lock than recorded in the rows (to represent the pending
308 // lock of a row; which ensures we don't unlock the texture prematurely).
309 GrAssert(rowLocks == fLockedRows || rowLocks + 1 == fLockedRows);
rileya@google.com2e2aedc2012-08-13 20:28:48 +0000310
311 // We should have one lru entry for each free row
312 GrAssert(freeRows == lruCount);
313
314 // If we have locked rows, we should have a locked texture, otherwise
315 // it should be unlocked
316 if (fLockedRows == 0) {
robertphillips@google.com1f47f4f2012-08-16 14:49:16 +0000317 GrAssert(NULL == fTexture);
rileya@google.com2e2aedc2012-08-13 20:28:48 +0000318 } else {
robertphillips@google.com1f47f4f2012-08-16 14:49:16 +0000319 GrAssert(NULL != fTexture);
rileya@google.com2e2aedc2012-08-13 20:28:48 +0000320 }
321}
322#endif
323