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