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