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