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#define LOG_TAG "OpenGLRenderer"
18
19#include <utils/JenkinsHash.h>
20#include <utils/Log.h>
21
22#include "Caches.h"
23#include "PatchCache.h"
24#include "Properties.h"
25
26namespace android {
27namespace uirenderer {
28
29///////////////////////////////////////////////////////////////////////////////
30// Constructors/destructor
31///////////////////////////////////////////////////////////////////////////////
32
33PatchCache::PatchCache():
34        mSize(0), mCache(LruCache<PatchDescription, Patch*>::kUnlimitedCapacity),
35        mMeshBuffer(0), mFreeBlocks(NULL), mGenerationId(0) {
36    char property[PROPERTY_VALUE_MAX];
37    if (property_get(PROPERTY_PATCH_CACHE_SIZE, property, NULL) > 0) {
38        INIT_LOGD("  Setting patch cache size to %skB", property);
39        mMaxSize = KB(atoi(property));
40    } else {
41        INIT_LOGD("  Using default patch cache size of %.2fkB", DEFAULT_PATCH_CACHE_SIZE);
42        mMaxSize = KB(DEFAULT_PATCH_CACHE_SIZE);
43    }
44}
45
46PatchCache::~PatchCache() {
47    clear();
48}
49
50void PatchCache::init(Caches& caches) {
51    bool created = false;
52    if (!mMeshBuffer) {
53        glGenBuffers(1, &mMeshBuffer);
54        created = true;
55    }
56
57    caches.bindMeshBuffer(mMeshBuffer);
58    caches.resetVertexPointers();
59
60    if (created) {
61        createVertexBuffer();
62    }
63}
64
65///////////////////////////////////////////////////////////////////////////////
66// Caching
67///////////////////////////////////////////////////////////////////////////////
68
69hash_t PatchCache::PatchDescription::hash() const {
70    uint32_t hash = JenkinsHashMix(0, android::hash_type(mPatch));
71    hash = JenkinsHashMix(hash, mBitmapWidth);
72    hash = JenkinsHashMix(hash, mBitmapHeight);
73    hash = JenkinsHashMix(hash, mPixelWidth);
74    hash = JenkinsHashMix(hash, mPixelHeight);
75    return JenkinsHashWhiten(hash);
76}
77
78int PatchCache::PatchDescription::compare(const PatchCache::PatchDescription& lhs,
79            const PatchCache::PatchDescription& rhs) {
80    return memcmp(&lhs, &rhs, sizeof(PatchDescription));
81}
82
83void PatchCache::clear() {
84    clearCache();
85
86    if (mMeshBuffer) {
87        Caches::getInstance().unbindMeshBuffer();
88        glDeleteBuffers(1, &mMeshBuffer);
89        mMeshBuffer = 0;
90        mSize = 0;
91    }
92}
93
94void PatchCache::clearCache() {
95    LruCache<PatchDescription, Patch*>::Iterator i(mCache);
96    while (i.next()) {
97        delete i.value();
98    }
99    mCache.clear();
100
101    BufferBlock* block = mFreeBlocks;
102    while (block) {
103        BufferBlock* next = block->next;
104        delete block;
105        block = next;
106    }
107    mFreeBlocks = NULL;
108}
109
110void PatchCache::remove(Vector<patch_pair_t>& patchesToRemove, Res_png_9patch* patch) {
111    LruCache<PatchDescription, Patch*>::Iterator i(mCache);
112    while (i.next()) {
113        const PatchDescription& key = i.key();
114        if (key.getPatch() == patch) {
115            patchesToRemove.push(patch_pair_t(&key, i.value()));
116        }
117    }
118}
119
120void PatchCache::removeDeferred(Res_png_9patch* patch) {
121    Mutex::Autolock _l(mLock);
122
123    // Assert that patch is not already garbage
124    size_t count = mGarbage.size();
125    for (size_t i = 0; i < count; i++) {
126        if (patch == mGarbage[i]) {
127            patch = NULL;
128            break;
129        }
130    }
131    LOG_ALWAYS_FATAL_IF(patch == NULL);
132
133    mGarbage.push(patch);
134}
135
136void PatchCache::clearGarbage() {
137    Vector<patch_pair_t> patchesToRemove;
138
139    { // scope for the mutex
140        Mutex::Autolock _l(mLock);
141        size_t count = mGarbage.size();
142        for (size_t i = 0; i < count; i++) {
143            Res_png_9patch* patch = mGarbage[i];
144            remove(patchesToRemove, patch);
145            // A Res_png_9patch is actually an array of byte that's larger
146            // than sizeof(Res_png_9patch). It must be freed as an array.
147            delete[] (int8_t*) patch;
148        }
149        mGarbage.clear();
150    }
151
152    // TODO: We could sort patchesToRemove by offset to merge
153    // adjacent free blocks
154    for (size_t i = 0; i < patchesToRemove.size(); i++) {
155        const patch_pair_t& pair = patchesToRemove[i];
156
157        // Release the patch and mark the space in the free list
158        Patch* patch = pair.getSecond();
159        BufferBlock* block = new BufferBlock(patch->offset, patch->getSize());
160        block->next = mFreeBlocks;
161        mFreeBlocks = block;
162
163        mSize -= patch->getSize();
164
165        mCache.remove(*pair.getFirst());
166        delete patch;
167    }
168
169#if DEBUG_PATCHES
170    if (patchesToRemove.size() > 0) {
171        dumpFreeBlocks("Removed garbage");
172    }
173#endif
174}
175
176void PatchCache::createVertexBuffer() {
177    glBufferData(GL_ARRAY_BUFFER, mMaxSize, NULL, GL_DYNAMIC_DRAW);
178    mSize = 0;
179    mFreeBlocks = new BufferBlock(0, mMaxSize);
180    mGenerationId++;
181}
182
183/**
184 * Sets the mesh's offsets and copies its associated vertices into
185 * the mesh buffer (VBO).
186 */
187void PatchCache::setupMesh(Patch* newMesh, TextureVertex* vertices) {
188    // This call ensures the VBO exists and that it is bound
189    init(Caches::getInstance());
190
191    // If we're running out of space, let's clear the entire cache
192    uint32_t size = newMesh->getSize();
193    if (mSize + size > mMaxSize) {
194        clearCache();
195        createVertexBuffer();
196    }
197
198    // Find a block where we can fit the mesh
199    BufferBlock* previous = NULL;
200    BufferBlock* block = mFreeBlocks;
201    while (block) {
202        // The mesh fits
203        if (block->size >= size) {
204            break;
205        }
206        previous = block;
207        block = block->next;
208    }
209
210    // We have enough space left in the buffer, but it's
211    // too fragmented, let's clear the cache
212    if (!block) {
213        clearCache();
214        createVertexBuffer();
215        previous = NULL;
216        block = mFreeBlocks;
217    }
218
219    // Copy the 9patch mesh in the VBO
220    newMesh->offset = (GLintptr) (block->offset);
221    newMesh->textureOffset = newMesh->offset + gMeshTextureOffset;
222    glBufferSubData(GL_ARRAY_BUFFER, newMesh->offset, size, vertices);
223
224    // Remove the block since we've used it entirely
225    if (block->size == size) {
226        if (previous) {
227            previous->next = block->next;
228        } else {
229            mFreeBlocks = block->next;
230        }
231        delete block;
232    } else {
233        // Resize the block now that it's occupied
234        block->offset += size;
235        block->size -= size;
236    }
237
238    mSize += size;
239}
240
241const Patch* PatchCache::get(const AssetAtlas::Entry* entry,
242        const uint32_t bitmapWidth, const uint32_t bitmapHeight,
243        const float pixelWidth, const float pixelHeight, const Res_png_9patch* patch) {
244
245    const PatchDescription description(bitmapWidth, bitmapHeight, pixelWidth, pixelHeight, patch);
246    const Patch* mesh = mCache.get(description);
247
248    if (!mesh) {
249        Patch* newMesh = new Patch();
250        TextureVertex* vertices;
251
252        if (entry) {
253            // An atlas entry has a UV mapper
254            vertices = newMesh->createMesh(bitmapWidth, bitmapHeight,
255                    pixelWidth, pixelHeight, entry->uvMapper, patch);
256        } else {
257            vertices = newMesh->createMesh(bitmapWidth, bitmapHeight,
258                    pixelWidth, pixelHeight, patch);
259        }
260
261        if (vertices) {
262            setupMesh(newMesh, vertices);
263        }
264
265#if DEBUG_PATCHES
266        dumpFreeBlocks("Adding patch");
267#endif
268
269        mCache.put(description, newMesh);
270        return newMesh;
271    }
272
273    return mesh;
274}
275
276#if DEBUG_PATCHES
277void PatchCache::dumpFreeBlocks(const char* prefix) {
278    String8 dump;
279    BufferBlock* block = mFreeBlocks;
280    while (block) {
281        dump.appendFormat("->(%d, %d)", block->offset, block->size);
282        block = block->next;
283    }
284    ALOGD("%s: Free blocks%s", prefix, dump.string());
285}
286#endif
287
288}; // namespace uirenderer
289}; // namespace android
290