GrTextureStripAtlas.cpp revision 6121405df5689875c7309c20632a82897fce4127
1/*
2 * Copyright 2012 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#include "GrTextureStripAtlas.h"
9#include "GrContext.h"
10#include "GrTexture.h"
11#include "SkGr.h"
12#include "SkPixelRef.h"
13#include "SkTSearch.h"
14
15#ifdef SK_DEBUG
16    #define VALIDATE this->validate()
17#else
18    #define VALIDATE
19#endif
20
21class GrTextureStripAtlas::Hash : public SkTDynamicHash<GrTextureStripAtlas::AtlasEntry,
22                                                        GrTextureStripAtlas::Desc> {};
23
24int32_t GrTextureStripAtlas::gCacheCount = 0;
25
26GrTextureStripAtlas::Hash* GrTextureStripAtlas::gAtlasCache = nullptr;
27
28GrTextureStripAtlas::Hash* GrTextureStripAtlas::GetCache() {
29
30    if (nullptr == gAtlasCache) {
31        gAtlasCache = new Hash;
32    }
33
34    return gAtlasCache;
35}
36
37// Remove the specified atlas from the cache
38void GrTextureStripAtlas::CleanUp(const GrContext*, void* info) {
39    SkASSERT(info);
40
41    AtlasEntry* entry = static_cast<AtlasEntry*>(info);
42
43    // remove the cache entry
44    GetCache()->remove(entry->fDesc);
45
46    // remove the actual entry
47    delete entry;
48
49    if (0 == GetCache()->count()) {
50        delete gAtlasCache;
51        gAtlasCache = nullptr;
52    }
53}
54
55GrTextureStripAtlas* GrTextureStripAtlas::GetAtlas(const GrTextureStripAtlas::Desc& desc) {
56    AtlasEntry* entry = GetCache()->find(desc);
57    if (nullptr == entry) {
58        entry = new AtlasEntry;
59
60        entry->fAtlas = new GrTextureStripAtlas(desc);
61        entry->fDesc = desc;
62
63        desc.fContext->addCleanUp(CleanUp, entry);
64
65        GetCache()->add(entry);
66    }
67
68    return entry->fAtlas;
69}
70
71GrTextureStripAtlas::GrTextureStripAtlas(GrTextureStripAtlas::Desc desc)
72    : fCacheKey(sk_atomic_inc(&gCacheCount))
73    , fLockedRows(0)
74    , fDesc(desc)
75    , fNumRows(desc.fHeight / desc.fRowHeight)
76    , fTexture(nullptr)
77    , fRows(new AtlasRow[fNumRows])
78    , fLRUFront(nullptr)
79    , fLRUBack(nullptr) {
80    SkASSERT(fNumRows * fDesc.fRowHeight == fDesc.fHeight);
81    this->initLRU();
82    fNormalizedYHeight = SK_Scalar1 / fDesc.fHeight;
83    VALIDATE;
84}
85
86GrTextureStripAtlas::~GrTextureStripAtlas() { delete[] fRows; }
87
88int GrTextureStripAtlas::lockRow(const SkBitmap& data) {
89    VALIDATE;
90    if (0 == fLockedRows) {
91        this->lockTexture();
92        if (!fTexture) {
93            return -1;
94        }
95    }
96
97    int key = data.getGenerationID();
98    int rowNumber = -1;
99    int index = this->searchByKey(key);
100
101    if (index >= 0) {
102        // We already have the data in a row, so we can just return that row
103        AtlasRow* row = fKeyTable[index];
104        if (0 == row->fLocks) {
105            this->removeFromLRU(row);
106        }
107        ++row->fLocks;
108        ++fLockedRows;
109
110        // Since all the rows are always stored in a contiguous array, we can save the memory
111        // required for storing row numbers and just compute it with some pointer arithmetic
112        rowNumber = static_cast<int>(row - fRows);
113    } else {
114        // ~index is the index where we will insert the new key to keep things sorted
115        index = ~index;
116
117        // We don't have this data cached, so pick the least recently used row to copy into
118        AtlasRow* row = this->getLRU();
119
120        ++fLockedRows;
121
122        if (nullptr == row) {
123            // force a flush, which should unlock all the rows; then try again
124            fDesc.fContext->flush();
125            row = this->getLRU();
126            if (nullptr == row) {
127                --fLockedRows;
128                return -1;
129            }
130        }
131
132        this->removeFromLRU(row);
133
134        uint32_t oldKey = row->fKey;
135
136        // If we are writing into a row that already held bitmap data, we need to remove the
137        // reference to that genID which is stored in our sorted table of key values.
138        if (oldKey != kEmptyAtlasRowKey) {
139
140            // Find the entry in the list; if it's before the index where we plan on adding the new
141            // entry, we decrement since it will shift elements ahead of it back by one.
142            int oldIndex = this->searchByKey(oldKey);
143            if (oldIndex < index) {
144                --index;
145            }
146
147            fKeyTable.remove(oldIndex);
148        }
149
150        row->fKey = key;
151        row->fLocks = 1;
152        fKeyTable.insert(index, 1, &row);
153        rowNumber = static_cast<int>(row - fRows);
154
155        SkAutoLockPixels lock(data);
156
157        // Pass in the kDontFlush flag, since we know we're writing to a part of this texture
158        // that is not currently in use
159        fTexture->writePixels(0,  rowNumber * fDesc.fRowHeight,
160                              fDesc.fWidth, fDesc.fRowHeight,
161                              SkImageInfo2GrPixelConfig(data.info()),
162                              data.getPixels(),
163                              data.rowBytes(),
164                              GrContext::kDontFlush_PixelOpsFlag);
165    }
166
167    SkASSERT(rowNumber >= 0);
168    VALIDATE;
169    return rowNumber;
170}
171
172void GrTextureStripAtlas::unlockRow(int row) {
173    VALIDATE;
174    --fRows[row].fLocks;
175    --fLockedRows;
176    SkASSERT(fRows[row].fLocks >= 0 && fLockedRows >= 0);
177    if (0 == fRows[row].fLocks) {
178        this->appendLRU(fRows + row);
179    }
180    if (0 == fLockedRows) {
181        this->unlockTexture();
182    }
183    VALIDATE;
184}
185
186GrTextureStripAtlas::AtlasRow* GrTextureStripAtlas::getLRU() {
187    // Front is least-recently-used
188    AtlasRow* row = fLRUFront;
189    return row;
190}
191
192void GrTextureStripAtlas::lockTexture() {
193    GrSurfaceDesc texDesc;
194    texDesc.fWidth = fDesc.fWidth;
195    texDesc.fHeight = fDesc.fHeight;
196    texDesc.fConfig = fDesc.fConfig;
197
198    static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
199    GrUniqueKey key;
200    GrUniqueKey::Builder builder(&key, kDomain, 1);
201    builder[0] = static_cast<uint32_t>(fCacheKey);
202    builder.finish();
203
204    fTexture = fDesc.fContext->textureProvider()->findAndRefTextureByUniqueKey(key);
205    if (nullptr == fTexture) {
206        fTexture = fDesc.fContext->textureProvider()->createTexture(texDesc, true, nullptr, 0);
207        if (!fTexture) {
208            return;
209        }
210        fDesc.fContext->textureProvider()->assignUniqueKeyToTexture(key, fTexture);
211        // This is a new texture, so all of our cache info is now invalid
212        this->initLRU();
213        fKeyTable.rewind();
214    }
215    SkASSERT(fTexture);
216}
217
218void GrTextureStripAtlas::unlockTexture() {
219    SkASSERT(fTexture && 0 == fLockedRows);
220    fTexture->unref();
221    fTexture = nullptr;
222}
223
224void GrTextureStripAtlas::initLRU() {
225    fLRUFront = nullptr;
226    fLRUBack = nullptr;
227    // Initially all the rows are in the LRU list
228    for (int i = 0; i < fNumRows; ++i) {
229        fRows[i].fKey = kEmptyAtlasRowKey;
230        fRows[i].fNext = nullptr;
231        fRows[i].fPrev = nullptr;
232        this->appendLRU(fRows + i);
233    }
234    SkASSERT(nullptr == fLRUFront || nullptr == fLRUFront->fPrev);
235    SkASSERT(nullptr == fLRUBack || nullptr == fLRUBack->fNext);
236}
237
238void GrTextureStripAtlas::appendLRU(AtlasRow* row) {
239    SkASSERT(nullptr == row->fPrev && nullptr == row->fNext);
240    if (nullptr == fLRUFront && nullptr == fLRUBack) {
241        fLRUFront = row;
242        fLRUBack = row;
243    } else {
244        row->fPrev = fLRUBack;
245        fLRUBack->fNext = row;
246        fLRUBack = row;
247    }
248}
249
250void GrTextureStripAtlas::removeFromLRU(AtlasRow* row) {
251    SkASSERT(row);
252    if (row->fNext && row->fPrev) {
253        row->fPrev->fNext = row->fNext;
254        row->fNext->fPrev = row->fPrev;
255    } else {
256        if (nullptr == row->fNext) {
257            SkASSERT(row == fLRUBack);
258            fLRUBack = row->fPrev;
259            if (fLRUBack) {
260                fLRUBack->fNext = nullptr;
261            }
262        }
263        if (nullptr == row->fPrev) {
264            SkASSERT(row == fLRUFront);
265            fLRUFront = row->fNext;
266            if (fLRUFront) {
267                fLRUFront->fPrev = nullptr;
268            }
269        }
270    }
271    row->fNext = nullptr;
272    row->fPrev = nullptr;
273}
274
275int GrTextureStripAtlas::searchByKey(uint32_t key) {
276    AtlasRow target;
277    target.fKey = key;
278    return SkTSearch<const AtlasRow,
279                     GrTextureStripAtlas::KeyLess>((const AtlasRow**)fKeyTable.begin(),
280                                                   fKeyTable.count(),
281                                                   &target,
282                                                   sizeof(AtlasRow*));
283}
284
285#ifdef SK_DEBUG
286void GrTextureStripAtlas::validate() {
287
288    // Our key table should be sorted
289    uint32_t prev = 1 > fKeyTable.count() ? 0 : fKeyTable[0]->fKey;
290    for (int i = 1; i < fKeyTable.count(); ++i) {
291        SkASSERT(prev < fKeyTable[i]->fKey);
292        SkASSERT(fKeyTable[i]->fKey != kEmptyAtlasRowKey);
293        prev = fKeyTable[i]->fKey;
294    }
295
296    int lruCount = 0;
297    // Validate LRU pointers, and count LRU entries
298    SkASSERT(nullptr == fLRUFront || nullptr == fLRUFront->fPrev);
299    SkASSERT(nullptr == fLRUBack  || nullptr == fLRUBack->fNext);
300    for (AtlasRow* r = fLRUFront; r != nullptr; r = r->fNext) {
301        if (nullptr == r->fNext) {
302            SkASSERT(r == fLRUBack);
303        } else {
304            SkASSERT(r->fNext->fPrev == r);
305        }
306        ++lruCount;
307    }
308
309    int rowLocks = 0;
310    int freeRows = 0;
311
312    for (int i = 0; i < fNumRows; ++i) {
313        rowLocks += fRows[i].fLocks;
314        if (0 == fRows[i].fLocks) {
315            ++freeRows;
316            bool inLRU = false;
317            // Step through the LRU and make sure it's present
318            for (AtlasRow* r = fLRUFront; r != nullptr; r = r->fNext) {
319                if (r == &fRows[i]) {
320                    inLRU = true;
321                    break;
322                }
323            }
324            SkASSERT(inLRU);
325        } else {
326            // If we are locked, we should have a key
327            SkASSERT(kEmptyAtlasRowKey != fRows[i].fKey);
328        }
329
330        // If we have a key != kEmptyAtlasRowKey, it should be in the key table
331        SkASSERT(fRows[i].fKey == kEmptyAtlasRowKey || this->searchByKey(fRows[i].fKey) >= 0);
332    }
333
334    // Our count of locks should equal the sum of row locks, unless we ran out of rows and flushed,
335    // in which case we'll have one more lock than recorded in the rows (to represent the pending
336    // lock of a row; which ensures we don't unlock the texture prematurely).
337    SkASSERT(rowLocks == fLockedRows || rowLocks + 1 == fLockedRows);
338
339    // We should have one lru entry for each free row
340    SkASSERT(freeRows == lruCount);
341
342    // If we have locked rows, we should have a locked texture, otherwise
343    // it should be unlocked
344    if (fLockedRows == 0) {
345        SkASSERT(nullptr == fTexture);
346    } else {
347        SkASSERT(fTexture);
348    }
349}
350#endif
351