TextureCache.cpp revision 38e0c32852e3b9d8ca4a9d3791577f52536419cb
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <GLES2/gl2.h>
18
19#include <utils/Mutex.h>
20
21#include "AssetAtlas.h"
22#include "Caches.h"
23#include "Texture.h"
24#include "TextureCache.h"
25#include "Properties.h"
26#include "utils/TraceUtils.h"
27
28namespace android {
29namespace uirenderer {
30
31///////////////////////////////////////////////////////////////////////////////
32// Constructors/destructor
33///////////////////////////////////////////////////////////////////////////////
34
35TextureCache::TextureCache()
36        : mCache(LruCache<uint32_t, Texture*>::kUnlimitedCapacity)
37        , mSize(0)
38        , mMaxSize(MB(DEFAULT_TEXTURE_CACHE_SIZE))
39        , mFlushRate(DEFAULT_TEXTURE_CACHE_FLUSH_RATE)
40        , mAssetAtlas(nullptr) {
41    char property[PROPERTY_VALUE_MAX];
42    if (property_get(PROPERTY_TEXTURE_CACHE_SIZE, property, nullptr) > 0) {
43        INIT_LOGD("  Setting texture cache size to %sMB", property);
44        setMaxSize(MB(atof(property)));
45    } else {
46        INIT_LOGD("  Using default texture cache size of %.2fMB", DEFAULT_TEXTURE_CACHE_SIZE);
47    }
48
49    if (property_get(PROPERTY_TEXTURE_CACHE_FLUSH_RATE, property, nullptr) > 0) {
50        float flushRate = atof(property);
51        INIT_LOGD("  Setting texture cache flush rate to %.2f%%", flushRate * 100.0f);
52        setFlushRate(flushRate);
53    } else {
54        INIT_LOGD("  Using default texture cache flush rate of %.2f%%",
55                DEFAULT_TEXTURE_CACHE_FLUSH_RATE * 100.0f);
56    }
57
58    mCache.setOnEntryRemovedListener(this);
59
60    glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize);
61    INIT_LOGD("    Maximum texture dimension is %d pixels", mMaxTextureSize);
62
63    mDebugEnabled = Properties::debugLevel & kDebugCaches;
64}
65
66TextureCache::~TextureCache() {
67    mCache.clear();
68}
69
70///////////////////////////////////////////////////////////////////////////////
71// Size management
72///////////////////////////////////////////////////////////////////////////////
73
74uint32_t TextureCache::getSize() {
75    return mSize;
76}
77
78uint32_t TextureCache::getMaxSize() {
79    return mMaxSize;
80}
81
82void TextureCache::setMaxSize(uint32_t maxSize) {
83    mMaxSize = maxSize;
84    while (mSize > mMaxSize) {
85        mCache.removeOldest();
86    }
87}
88
89void TextureCache::setFlushRate(float flushRate) {
90    mFlushRate = std::max(0.0f, std::min(1.0f, flushRate));
91}
92
93///////////////////////////////////////////////////////////////////////////////
94// Callbacks
95///////////////////////////////////////////////////////////////////////////////
96
97void TextureCache::operator()(uint32_t&, Texture*& texture) {
98    // This will be called already locked
99    if (texture) {
100        mSize -= texture->bitmapSize;
101        TEXTURE_LOGD("TextureCache::callback: name, removed size, mSize = %d, %d, %d",
102                texture->id, texture->bitmapSize, mSize);
103        if (mDebugEnabled) {
104            ALOGD("Texture deleted, size = %d", texture->bitmapSize);
105        }
106        texture->deleteTexture();
107        delete texture;
108    }
109}
110
111///////////////////////////////////////////////////////////////////////////////
112// Caching
113///////////////////////////////////////////////////////////////////////////////
114
115void TextureCache::setAssetAtlas(AssetAtlas* assetAtlas) {
116    mAssetAtlas = assetAtlas;
117}
118
119void TextureCache::resetMarkInUse(void* ownerToken) {
120    LruCache<uint32_t, Texture*>::Iterator iter(mCache);
121    while (iter.next()) {
122        if (iter.value()->isInUse == ownerToken) {
123            iter.value()->isInUse = nullptr;
124        }
125    }
126}
127
128bool TextureCache::canMakeTextureFromBitmap(const SkBitmap* bitmap) {
129    if (bitmap->width() > mMaxTextureSize || bitmap->height() > mMaxTextureSize) {
130        ALOGW("Bitmap too large to be uploaded into a texture (%dx%d, max=%dx%d)",
131                bitmap->width(), bitmap->height(), mMaxTextureSize, mMaxTextureSize);
132        return false;
133    }
134    return true;
135}
136
137// Returns a prepared Texture* that either is already in the cache or can fit
138// in the cache (and is thus added to the cache)
139Texture* TextureCache::getCachedTexture(const SkBitmap* bitmap, AtlasUsageType atlasUsageType) {
140    if (CC_LIKELY(mAssetAtlas != nullptr) && atlasUsageType == AtlasUsageType::Use) {
141        AssetAtlas::Entry* entry = mAssetAtlas->getEntry(bitmap->pixelRef());
142        if (CC_UNLIKELY(entry)) {
143            return entry->texture;
144        }
145    }
146
147    Texture* texture = mCache.get(bitmap->pixelRef()->getStableID());
148
149    if (!texture) {
150        if (!canMakeTextureFromBitmap(bitmap)) {
151            return nullptr;
152        }
153
154        const uint32_t size = bitmap->rowBytes() * bitmap->height();
155        bool canCache = size < mMaxSize;
156        // Don't even try to cache a bitmap that's bigger than the cache
157        while (canCache && mSize + size > mMaxSize) {
158            Texture* oldest = mCache.peekOldestValue();
159            if (oldest && !oldest->isInUse) {
160                mCache.removeOldest();
161            } else {
162                canCache = false;
163            }
164        }
165
166        if (canCache) {
167            texture = new Texture(Caches::getInstance());
168            texture->bitmapSize = size;
169            texture->generation = bitmap->getGenerationID();
170            texture->upload(*bitmap);
171
172            mSize += size;
173            TEXTURE_LOGD("TextureCache::get: create texture(%p): name, size, mSize = %d, %d, %d",
174                     bitmap, texture->id, size, mSize);
175            if (mDebugEnabled) {
176                ALOGD("Texture created, size = %d", size);
177            }
178            mCache.put(bitmap->pixelRef()->getStableID(), texture);
179        }
180    } else if (!texture->isInUse && bitmap->getGenerationID() != texture->generation) {
181        // Texture was in the cache but is dirty, re-upload
182        // TODO: Re-adjust the cache size if the bitmap's dimensions have changed
183        texture->upload(*bitmap);
184        texture->generation = bitmap->getGenerationID();
185    }
186
187    return texture;
188}
189
190bool TextureCache::prefetchAndMarkInUse(void* ownerToken, const SkBitmap* bitmap) {
191    Texture* texture = getCachedTexture(bitmap, AtlasUsageType::Use);
192    if (texture) {
193        texture->isInUse = ownerToken;
194    }
195    return texture;
196}
197
198Texture* TextureCache::get(const SkBitmap* bitmap, AtlasUsageType atlasUsageType) {
199    Texture* texture = getCachedTexture(bitmap, atlasUsageType);
200
201    if (!texture) {
202        if (!canMakeTextureFromBitmap(bitmap)) {
203            return nullptr;
204        }
205
206        const uint32_t size = bitmap->rowBytes() * bitmap->height();
207        texture = new Texture(Caches::getInstance());
208        texture->bitmapSize = size;
209        texture->upload(*bitmap);
210        texture->generation = bitmap->getGenerationID();
211        texture->cleanup = true;
212    }
213
214    return texture;
215}
216
217void TextureCache::releaseTexture(uint32_t pixelRefStableID) {
218    Mutex::Autolock _l(mLock);
219    mGarbage.push_back(pixelRefStableID);
220}
221
222void TextureCache::clearGarbage() {
223    Mutex::Autolock _l(mLock);
224    size_t count = mGarbage.size();
225    for (size_t i = 0; i < count; i++) {
226        uint32_t pixelRefId = mGarbage[i];
227        mCache.remove(pixelRefId);
228    }
229    mGarbage.clear();
230}
231
232void TextureCache::clear() {
233    mCache.clear();
234    TEXTURE_LOGD("TextureCache:clear(), mSize = %d", mSize);
235}
236
237void TextureCache::flush() {
238    if (mFlushRate >= 1.0f || mCache.size() == 0) return;
239    if (mFlushRate <= 0.0f) {
240        clear();
241        return;
242    }
243
244    uint32_t targetSize = uint32_t(mSize * mFlushRate);
245    TEXTURE_LOGD("TextureCache::flush: target size: %d", targetSize);
246
247    while (mSize > targetSize) {
248        mCache.removeOldest();
249    }
250}
251
252}; // namespace uirenderer
253}; // namespace android
254