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