blob: a1a9d0301c0296de4713528cb28eba4407c6f34a [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.com9a927142012-08-14 21:33:15 +000037// Ugly way of ensuring that we clean up the atlases on exit
38struct AtlasEntries {
39 ~AtlasEntries() { fEntries.deleteAll(); }
40 SkTDArray<AtlasEntry*> fEntries;
41};
42
rileya@google.com2e2aedc2012-08-13 20:28:48 +000043GrTextureStripAtlas* GrTextureStripAtlas::GetAtlas(const GrTextureStripAtlas::Desc& desc) {
rileya@google.com9a927142012-08-14 21:33:15 +000044 static AtlasEntries gAtlasEntries;
rileya@google.com2e2aedc2012-08-13 20:28:48 +000045 static GrTHashTable<AtlasEntry, AtlasHashKey, 8> gAtlasCache;
46 AtlasHashKey key;
47 key.setKeyData(desc.asKey());
48 AtlasEntry* entry = gAtlasCache.find(key);
49 if (NULL != entry) {
50 return entry->fAtlas;
51 } else {
rileya@google.com9a927142012-08-14 21:33:15 +000052 entry = SkNEW(AtlasEntry);
53 gAtlasEntries.fEntries.push(entry);
rileya@google.com2e2aedc2012-08-13 20:28:48 +000054 entry->fAtlas = SkNEW_ARGS(GrTextureStripAtlas, (desc));
55 entry->fKey = key;
56 gAtlasCache.insert(key, entry);
57 return entry->fAtlas;
58 }
59}
60
61GrTextureStripAtlas::GrTextureStripAtlas(GrTextureStripAtlas::Desc desc)
rileya@google.comf61c7462012-08-13 21:03:39 +000062 : fCacheID(sk_atomic_inc(&gCacheCount))
rileya@google.com2e2aedc2012-08-13 20:28:48 +000063 , fLockedRows(0)
64 , fDesc(desc)
65 , fNumRows(desc.fHeight / desc.fRowHeight)
robertphillips@google.com1f47f4f2012-08-16 14:49:16 +000066 , fTexture(NULL)
rileya@google.com2e2aedc2012-08-13 20:28:48 +000067 , fRows(SkNEW_ARRAY(AtlasRow, fNumRows))
68 , fLRUFront(NULL)
69 , fLRUBack(NULL) {
70 GrAssert(fNumRows * fDesc.fRowHeight == fDesc.fHeight);
71 this->initLRU();
72 VALIDATE;
73}
74
75GrTextureStripAtlas::~GrTextureStripAtlas() {
76 SkDELETE_ARRAY(fRows);
77}
78
79int GrTextureStripAtlas::lockRow(const SkBitmap& data) {
80 VALIDATE;
81 if (0 == fLockedRows) {
82 this->lockTexture();
83 }
84
85 int key = data.getGenerationID();
86 int rowNumber = -1;
87 int index = this->searchByKey(key);
88
89 if (index >= 0) {
90 // We already have the data in a row, so we can just return that row
91 AtlasRow* row = fKeyTable[index];
92 if (0 == row->fLocks) {
93 this->removeFromLRU(row);
94 }
95 ++row->fLocks;
96
97 // Since all the rows are always stored in a contiguous array, we can save the memory
98 // required for storing row numbers and just compute it with some pointer arithmetic
99 rowNumber = static_cast<int>(row - fRows);
100 } else {
101 // ~index is the index where we will insert the new key to keep things sorted
102 index = ~index;
103
104 // We don't have this data cached, so pick the least recently used row to copy into
105 AtlasRow* row = this->getLRU();
106
107 if (NULL == row) {
108 // force a flush, which should unlock all the rows; then try again
109 fDesc.fContext->flush();
110 row = this->getLRU();
111 if (NULL == row) {
112 return -1;
113 }
114 }
115
116 this->removeFromLRU(row);
117
118 uint32_t oldKey = row->fKey;
rileya@google.com9a927142012-08-14 21:33:15 +0000119 row->fKey = key;
120 row->fLocks = 1;
rileya@google.com2e2aedc2012-08-13 20:28:48 +0000121
122 // If we are writing into a row that already held bitmap data, we need to remove the
123 // reference to that genID which is stored in our sorted table of key values.
124 if (oldKey != kEmptyAtlasRowKey) {
125
126 // Find the entry in the list; if it's before the index where we plan on adding the new
127 // entry, we decrement since it will shift elements ahead of it back by one.
128 int oldIndex = this->searchByKey(oldKey);
rileya@google.com9a927142012-08-14 21:33:15 +0000129 if (oldIndex <= index) {
rileya@google.com2e2aedc2012-08-13 20:28:48 +0000130 --index;
131 }
132
133 fKeyTable.remove(oldIndex);
134 }
135
rileya@google.com2e2aedc2012-08-13 20:28:48 +0000136 fKeyTable.insert(index, 1, &row);
137 rowNumber = static_cast<int>(row - fRows);
138
139 SkAutoLockPixels lock(data);
140
141 // Pass in the kDontFlush flag, since we know we're writing to a part of this texture
142 // that is not currently in use
robertphillips@google.com1f47f4f2012-08-16 14:49:16 +0000143 fDesc.fContext->internalWriteTexturePixels(fTexture, 0,
rileya@google.com2e2aedc2012-08-13 20:28:48 +0000144 rowNumber * fDesc.fRowHeight,
145 fDesc.fWidth,
146 fDesc.fRowHeight,
147 SkBitmapConfig2GrPixelConfig(data.config()),
148 data.getPixels(), data.rowBytes(),
149 GrContext::kDontFlush_PixelOpsFlag);
150 }
151
152 ++fLockedRows;
153 GrAssert(rowNumber >= 0);
154 VALIDATE;
155 return rowNumber;
156}
157
158void GrTextureStripAtlas::unlockRow(int row) {
159 VALIDATE;
160 --fRows[row].fLocks;
161 --fLockedRows;
162 GrAssert(fRows[row].fLocks >= 0 && fLockedRows >= 0);
163 if (0 == fRows[row].fLocks) {
164 this->appendLRU(fRows + row);
165 }
166 if (0 == fLockedRows) {
167 this->unlockTexture();
168 }
169 VALIDATE;
170}
171
172GrTextureStripAtlas::AtlasRow* GrTextureStripAtlas::getLRU() {
173 // Front is least-recently-used
174 AtlasRow* row = fLRUFront;
175 return row;
176}
177
178void GrTextureStripAtlas::lockTexture() {
179 GrTextureParams params;
180 GrTextureDesc texDesc;
181 texDesc.fWidth = fDesc.fWidth;
182 texDesc.fHeight = fDesc.fHeight;
183 texDesc.fConfig = fDesc.fConfig;
184 GrCacheData cacheData(fCacheID);
185 cacheData.fResourceDomain = GetTextureStripAtlasDomain();
robertphillips@google.com1f47f4f2012-08-16 14:49:16 +0000186 fTexture = fDesc.fContext->findAndLockTexture(texDesc, cacheData, &params);
187 if (NULL == fTexture) {
188 fTexture = fDesc.fContext->createAndLockTexture(&params, texDesc, cacheData, NULL, 0);
rileya@google.com2e2aedc2012-08-13 20:28:48 +0000189 // This is a new texture, so all of our cache info is now invalid
190 this->initLRU();
191 fKeyTable.rewind();
192 }
robertphillips@google.com1f47f4f2012-08-16 14:49:16 +0000193 GrAssert(NULL != fTexture);
rileya@google.com2e2aedc2012-08-13 20:28:48 +0000194}
195
196void GrTextureStripAtlas::unlockTexture() {
robertphillips@google.com1f47f4f2012-08-16 14:49:16 +0000197 GrAssert(NULL != fTexture && 0 == fLockedRows);
198 fDesc.fContext->unlockTexture(fTexture);
199 fTexture = NULL;
rileya@google.com2e2aedc2012-08-13 20:28:48 +0000200}
201
202void GrTextureStripAtlas::initLRU() {
203 fLRUFront = NULL;
204 fLRUBack = NULL;
205 // Initially all the rows are in the LRU list
206 for (int i = 0; i < fNumRows; ++i) {
207 fRows[i].fKey = kEmptyAtlasRowKey;
208 fRows[i].fNext = NULL;
209 fRows[i].fPrev = NULL;
210 this->appendLRU(fRows + i);
211 }
212 GrAssert(NULL == fLRUFront->fPrev && NULL == fLRUBack->fNext);
213}
214
215void GrTextureStripAtlas::appendLRU(AtlasRow* row) {
216 GrAssert(NULL == row->fPrev && NULL == row->fNext);
217 if (NULL == fLRUFront && NULL == fLRUBack) {
218 fLRUFront = row;
219 fLRUBack = row;
220 } else {
221 row->fPrev = fLRUBack;
222 fLRUBack->fNext = row;
223 fLRUBack = row;
224 }
225}
226
227void GrTextureStripAtlas::removeFromLRU(AtlasRow* row) {
228 GrAssert(NULL != row);
229 if (NULL != row->fNext && NULL != row->fPrev) {
230 row->fPrev->fNext = row->fNext;
231 row->fNext->fPrev = row->fPrev;
232 } else {
233 if (NULL == row->fNext) {
234 GrAssert(row == fLRUBack);
235 fLRUBack = row->fPrev;
236 fLRUBack->fNext = NULL;
237 }
238 if (NULL == row->fPrev) {
239 GrAssert(row == fLRUFront);
240 fLRUFront = row->fNext;
241 fLRUFront->fPrev = NULL;
242 }
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
306 // Our count of locks should equal the sum of row locks
307 GrAssert(rowLocks == fLockedRows);
308
309 // We should have one lru entry for each free row
310 GrAssert(freeRows == lruCount);
311
312 // If we have locked rows, we should have a locked texture, otherwise
313 // it should be unlocked
314 if (fLockedRows == 0) {
robertphillips@google.com1f47f4f2012-08-16 14:49:16 +0000315 GrAssert(NULL == fTexture);
rileya@google.com2e2aedc2012-08-13 20:28:48 +0000316 } else {
robertphillips@google.com1f47f4f2012-08-16 14:49:16 +0000317 GrAssert(NULL != fTexture);
rileya@google.com2e2aedc2012-08-13 20:28:48 +0000318 }
319}
320#endif
321