GradientCache.cpp revision b48800428906ae455c2b63acacd44e390e1fee49
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
21#include "Caches.h"
22#include "Debug.h"
23#include "GradientCache.h"
24#include "Properties.h"
25
26namespace android {
27namespace uirenderer {
28
29///////////////////////////////////////////////////////////////////////////////
30// Functions
31///////////////////////////////////////////////////////////////////////////////
32
33template<typename T>
34static inline T min(T a, T b) {
35    return a < b ? a : b;
36}
37
38///////////////////////////////////////////////////////////////////////////////
39// Cache entry
40///////////////////////////////////////////////////////////////////////////////
41
42hash_t GradientCacheEntry::hash() const {
43    uint32_t hash = JenkinsHashMix(0, count);
44    for (uint32_t i = 0; i < count; i++) {
45        hash = JenkinsHashMix(hash, android::hash_type(colors[i]));
46        hash = JenkinsHashMix(hash, android::hash_type(positions[i]));
47    }
48    return JenkinsHashWhiten(hash);
49}
50
51int GradientCacheEntry::compare(const GradientCacheEntry& lhs, const GradientCacheEntry& rhs) {
52    int deltaInt = int(lhs.count) - int(rhs.count);
53    if (deltaInt != 0) return deltaInt;
54
55    deltaInt = memcmp(lhs.colors, rhs.colors, lhs.count * sizeof(uint32_t));
56    if (deltaInt != 0) return deltaInt;
57
58    return memcmp(lhs.positions, rhs.positions, lhs.count * sizeof(float));
59}
60
61///////////////////////////////////////////////////////////////////////////////
62// Constructors/destructor
63///////////////////////////////////////////////////////////////////////////////
64
65GradientCache::GradientCache():
66        mCache(LruCache<GradientCacheEntry, Texture*>::kUnlimitedCapacity),
67        mSize(0), mMaxSize(MB(DEFAULT_GRADIENT_CACHE_SIZE)) {
68    char property[PROPERTY_VALUE_MAX];
69    if (property_get(PROPERTY_GRADIENT_CACHE_SIZE, property, NULL) > 0) {
70        INIT_LOGD("  Setting gradient cache size to %sMB", property);
71        setMaxSize(MB(atof(property)));
72    } else {
73        INIT_LOGD("  Using default gradient cache size of %.2fMB", DEFAULT_GRADIENT_CACHE_SIZE);
74    }
75
76    glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize);
77
78    mCache.setOnEntryRemovedListener(this);
79
80    const Extensions& extensions = Extensions::getInstance();
81    mUseFloatTexture = extensions.getMajorGlVersion() >= 3;
82    mHasNpot = extensions.hasNPot();
83}
84
85GradientCache::GradientCache(uint32_t maxByteSize):
86        mCache(LruCache<GradientCacheEntry, Texture*>::kUnlimitedCapacity),
87        mSize(0), mMaxSize(maxByteSize) {
88    mCache.setOnEntryRemovedListener(this);
89}
90
91GradientCache::~GradientCache() {
92    mCache.clear();
93}
94
95///////////////////////////////////////////////////////////////////////////////
96// Size management
97///////////////////////////////////////////////////////////////////////////////
98
99uint32_t GradientCache::getSize() {
100    return mSize;
101}
102
103uint32_t GradientCache::getMaxSize() {
104    return mMaxSize;
105}
106
107void GradientCache::setMaxSize(uint32_t maxSize) {
108    mMaxSize = maxSize;
109    while (mSize > mMaxSize) {
110        mCache.removeOldest();
111    }
112}
113
114///////////////////////////////////////////////////////////////////////////////
115// Callbacks
116///////////////////////////////////////////////////////////////////////////////
117
118void GradientCache::operator()(GradientCacheEntry& shader, Texture*& texture) {
119    if (texture) {
120        const uint32_t size = texture->width * texture->height * bytesPerPixel();
121        mSize -= size;
122
123        glDeleteTextures(1, &texture->id);
124        delete texture;
125    }
126}
127
128///////////////////////////////////////////////////////////////////////////////
129// Caching
130///////////////////////////////////////////////////////////////////////////////
131
132Texture* GradientCache::get(uint32_t* colors, float* positions, int count) {
133    GradientCacheEntry gradient(colors, positions, count);
134    Texture* texture = mCache.get(gradient);
135
136    if (!texture) {
137        texture = addLinearGradient(gradient, colors, positions, count);
138    }
139
140    return texture;
141}
142
143void GradientCache::clear() {
144    mCache.clear();
145}
146
147void GradientCache::getGradientInfo(const uint32_t* colors, const int count,
148        GradientInfo& info) {
149    uint32_t width = 256 * (count - 1);
150
151    if (!mHasNpot) {
152        width = 1 << (31 - __builtin_clz(width));
153    }
154
155    bool hasAlpha = false;
156    for (int i = 0; i < count; i++) {
157        if (((colors[i] >> 24) & 0xff) < 255) {
158            hasAlpha = true;
159            break;
160        }
161    }
162
163    info.width = min(width, uint32_t(mMaxTextureSize));
164    info.hasAlpha = hasAlpha;
165}
166
167Texture* GradientCache::addLinearGradient(GradientCacheEntry& gradient,
168        uint32_t* colors, float* positions, int count) {
169
170    GradientInfo info;
171    getGradientInfo(colors, count, info);
172
173    Texture* texture = new Texture;
174    texture->width = info.width;
175    texture->height = 2;
176    texture->blend = info.hasAlpha;
177    texture->generation = 1;
178
179    // Asume the cache is always big enough
180    const uint32_t size = texture->width * texture->height * bytesPerPixel();
181    while (getSize() + size > mMaxSize) {
182        mCache.removeOldest();
183    }
184
185    generateTexture(colors, positions, count, texture);
186
187    mSize += size;
188    mCache.put(gradient, texture);
189
190    return texture;
191}
192
193size_t GradientCache::bytesPerPixel() const {
194    // We use 4 channels (RGBA)
195    return 4 * (mUseFloatTexture ? sizeof(float) : sizeof(uint8_t));
196}
197
198void GradientCache::splitToBytes(uint32_t inColor, GradientColor& outColor) const {
199    outColor.r = (inColor >> 16) & 0xff;
200    outColor.g = (inColor >>  8) & 0xff;
201    outColor.b = (inColor >>  0) & 0xff;
202    outColor.a = (inColor >> 24) & 0xff;
203}
204
205void GradientCache::splitToFloats(uint32_t inColor, GradientColor& outColor) const {
206    outColor.r = ((inColor >> 16) & 0xff) / 255.0f;
207    outColor.g = ((inColor >>  8) & 0xff) / 255.0f;
208    outColor.b = ((inColor >>  0) & 0xff) / 255.0f;
209    outColor.a = ((inColor >> 24) & 0xff) / 255.0f;
210}
211
212void GradientCache::mixBytes(GradientColor& start, GradientColor& end, float amount,
213        uint8_t*& dst) const {
214    float oppAmount = 1.0f - amount;
215    const float alpha = start.a * oppAmount + end.a * amount;
216    const float a = alpha / 255.0f;
217
218    *dst++ = uint8_t(a * (start.r * oppAmount + end.r * amount));
219    *dst++ = uint8_t(a * (start.g * oppAmount + end.g * amount));
220    *dst++ = uint8_t(a * (start.b * oppAmount + end.b * amount));
221    *dst++ = uint8_t(alpha);
222}
223
224void GradientCache::mixFloats(GradientColor& start, GradientColor& end, float amount,
225        uint8_t*& dst) const {
226    float oppAmount = 1.0f - amount;
227    const float a = start.a * oppAmount + end.a * amount;
228
229    float* d = (float*) dst;
230    *d++ = a * (start.r * oppAmount + end.r * amount);
231    *d++ = a * (start.g * oppAmount + end.g * amount);
232    *d++ = a * (start.b * oppAmount + end.b * amount);
233    *d++ = a;
234
235    dst += 4 * sizeof(float);
236}
237
238void GradientCache::generateTexture(uint32_t* colors, float* positions,
239        int count, Texture* texture) {
240    const uint32_t width = texture->width;
241    const GLsizei rowBytes = width * bytesPerPixel();
242    uint8_t pixels[rowBytes * texture->height];
243
244    static ChannelSplitter gSplitters[] = {
245            &android::uirenderer::GradientCache::splitToBytes,
246            &android::uirenderer::GradientCache::splitToFloats,
247    };
248    ChannelSplitter split = gSplitters[mUseFloatTexture];
249
250    static ChannelMixer gMixers[] = {
251            &android::uirenderer::GradientCache::mixBytes,
252            &android::uirenderer::GradientCache::mixFloats,
253    };
254    ChannelMixer mix = gMixers[mUseFloatTexture];
255
256    GradientColor start;
257    (this->*split)(colors[0], start);
258
259    GradientColor end;
260    (this->*split)(colors[1], end);
261
262    int currentPos = 1;
263    float startPos = positions[0];
264    float distance = positions[1] - startPos;
265
266    uint8_t* dst = pixels;
267    for (uint32_t x = 0; x < width; x++) {
268        float pos = x / float(width - 1);
269        if (pos > positions[currentPos]) {
270            start = end;
271            startPos = positions[currentPos];
272
273            currentPos++;
274
275            (this->*split)(colors[currentPos], end);
276            distance = positions[currentPos] - startPos;
277        }
278
279        float amount = (pos - startPos) / distance;
280        (this->*mix)(start, end, amount, dst);
281    }
282
283    memcpy(pixels + rowBytes, pixels, rowBytes);
284
285    glGenTextures(1, &texture->id);
286    glBindTexture(GL_TEXTURE_2D, texture->id);
287    glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
288
289    if (mUseFloatTexture) {
290        // We have to use GL_RGBA16F because GL_RGBA32F does not support filtering
291        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, texture->height, 0,
292                GL_RGBA, GL_FLOAT, pixels);
293    } else {
294        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, texture->height, 0,
295                GL_RGBA, GL_UNSIGNED_BYTE, pixels);
296    }
297
298    texture->setFilter(GL_LINEAR);
299    texture->setWrap(GL_CLAMP_TO_EDGE);
300}
301
302}; // namespace uirenderer
303}; // namespace android
304