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