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 "Caches.h"
22#include "Texture.h"
23#include "TextureCache.h"
24#include "Properties.h"
25#include "utils/TraceUtils.h"
26#include "hwui/Bitmap.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(Properties::textureCacheSize)
39        , mFlushRate(Properties::textureCacheFlushRate) {
40    mCache.setOnEntryRemovedListener(this);
41
42    glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize);
43    INIT_LOGD("    Maximum texture dimension is %d pixels", mMaxTextureSize);
44
45    mDebugEnabled = Properties::debugLevel & kDebugCaches;
46}
47
48TextureCache::~TextureCache() {
49    this->clear();
50}
51
52///////////////////////////////////////////////////////////////////////////////
53// Size management
54///////////////////////////////////////////////////////////////////////////////
55
56uint32_t TextureCache::getSize() {
57    return mSize;
58}
59
60uint32_t TextureCache::getMaxSize() {
61    return mMaxSize;
62}
63
64///////////////////////////////////////////////////////////////////////////////
65// Callbacks
66///////////////////////////////////////////////////////////////////////////////
67
68void TextureCache::operator()(uint32_t&, Texture*& texture) {
69    // This will be called already locked
70    if (texture) {
71        mSize -= texture->bitmapSize;
72        TEXTURE_LOGD("TextureCache::callback: name, removed size, mSize = %d, %d, %d",
73                texture->id, texture->bitmapSize, mSize);
74        if (mDebugEnabled) {
75            ALOGD("Texture deleted, size = %d", texture->bitmapSize);
76        }
77        texture->deleteTexture();
78        delete texture;
79    }
80}
81
82///////////////////////////////////////////////////////////////////////////////
83// Caching
84///////////////////////////////////////////////////////////////////////////////
85
86void TextureCache::resetMarkInUse(void* ownerToken) {
87    LruCache<uint32_t, Texture*>::Iterator iter(mCache);
88    while (iter.next()) {
89        if (iter.value()->isInUse == ownerToken) {
90            iter.value()->isInUse = nullptr;
91        }
92    }
93}
94
95bool TextureCache::canMakeTextureFromBitmap(Bitmap* bitmap) {
96    if (bitmap->width() > mMaxTextureSize || bitmap->height() > mMaxTextureSize) {
97        ALOGW("Bitmap too large to be uploaded into a texture (%dx%d, max=%dx%d)",
98                bitmap->width(), bitmap->height(), mMaxTextureSize, mMaxTextureSize);
99        return false;
100    }
101    return true;
102}
103
104Texture* TextureCache::createTexture(Bitmap* bitmap) {
105     Texture* texture = new Texture(Caches::getInstance());
106     texture->bitmapSize = bitmap->rowBytes() * bitmap->height();
107     texture->generation = bitmap->getGenerationID();
108     texture->upload(*bitmap);
109     return texture;
110}
111
112// Returns a prepared Texture* that either is already in the cache or can fit
113// in the cache (and is thus added to the cache)
114Texture* TextureCache::getCachedTexture(Bitmap* bitmap) {
115    if (bitmap->isHardware()) {
116        auto textureIterator = mHardwareTextures.find(bitmap->getStableID());
117        if (textureIterator == mHardwareTextures.end()) {
118            Texture*  texture = createTexture(bitmap);
119            mHardwareTextures.insert(std::make_pair(bitmap->getStableID(),
120                    std::unique_ptr<Texture>(texture)));
121            if (mDebugEnabled) {
122                ALOGD("Texture created for hw bitmap size = %d", texture->bitmapSize);
123            }
124            return texture;
125        }
126        return textureIterator->second.get();
127    }
128
129    Texture* texture = mCache.get(bitmap->getStableID());
130
131    if (!texture) {
132        if (!canMakeTextureFromBitmap(bitmap)) {
133            return nullptr;
134        }
135
136        const uint32_t size = bitmap->rowBytes() * bitmap->height();
137        bool canCache = size < mMaxSize;
138        // Don't even try to cache a bitmap that's bigger than the cache
139        while (canCache && mSize + size > mMaxSize) {
140            Texture* oldest = mCache.peekOldestValue();
141            if (oldest && !oldest->isInUse) {
142                mCache.removeOldest();
143            } else {
144                canCache = false;
145            }
146        }
147
148        if (canCache) {
149            texture = createTexture(bitmap);
150            mSize += size;
151            TEXTURE_LOGD("TextureCache::get: create texture(%p): name, size, mSize = %d, %d, %d",
152                     bitmap, texture->id, size, mSize);
153            if (mDebugEnabled) {
154                ALOGD("Texture created, size = %d", size);
155            }
156            mCache.put(bitmap->getStableID(), texture);
157        }
158    } else if (!texture->isInUse && bitmap->getGenerationID() != texture->generation) {
159        // Texture was in the cache but is dirty, re-upload
160        // TODO: Re-adjust the cache size if the bitmap's dimensions have changed
161        texture->upload(*bitmap);
162        texture->generation = bitmap->getGenerationID();
163    }
164
165    return texture;
166}
167
168bool TextureCache::prefetchAndMarkInUse(void* ownerToken, Bitmap* bitmap) {
169    Texture* texture = getCachedTexture(bitmap);
170    if (texture) {
171        texture->isInUse = ownerToken;
172    }
173    return texture;
174}
175
176bool TextureCache::prefetch(Bitmap* bitmap) {
177    return getCachedTexture(bitmap);
178}
179
180Texture* TextureCache::get(Bitmap* bitmap) {
181    Texture* texture = getCachedTexture(bitmap);
182
183    if (!texture) {
184        if (!canMakeTextureFromBitmap(bitmap)) {
185            return nullptr;
186        }
187        texture = createTexture(bitmap);
188        texture->cleanup = true;
189    }
190
191    return texture;
192}
193
194bool TextureCache::destroyTexture(uint32_t pixelRefStableID) {
195    auto hardwareIter = mHardwareTextures.find(pixelRefStableID);
196    if (hardwareIter != mHardwareTextures.end()) {
197        hardwareIter->second->deleteTexture();
198        mHardwareTextures.erase(hardwareIter);
199        return true;
200    }
201    return mCache.remove(pixelRefStableID);
202}
203
204void TextureCache::clear() {
205    mCache.clear();
206    for(auto& iter: mHardwareTextures) {
207        iter.second->deleteTexture();
208    }
209    mHardwareTextures.clear();
210    TEXTURE_LOGD("TextureCache:clear(), mSize = %d", mSize);
211}
212
213void TextureCache::flush() {
214    if (mFlushRate >= 1.0f || mCache.size() == 0) return;
215    if (mFlushRate <= 0.0f) {
216        clear();
217        return;
218    }
219
220    uint32_t targetSize = uint32_t(mSize * mFlushRate);
221    TEXTURE_LOGD("TextureCache::flush: target size: %d", targetSize);
222
223    while (mSize > targetSize) {
224        mCache.removeOldest();
225    }
226}
227
228}; // namespace uirenderer
229}; // namespace android
230