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.hasFloatTextures();
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        texture->deleteTexture();
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 the npot extension is not supported we cannot use non-clamp
152    // wrap modes. We therefore find the nearest largest power of 2
153    // unless width is already a power of 2
154    if (!mHasNpot && (width & (width - 1)) != 0) {
155        width = 1 << (32 - __builtin_clz(width));
156    }
157
158    bool hasAlpha = false;
159    for (int i = 0; i < count; i++) {
160        if (((colors[i] >> 24) & 0xff) < 255) {
161            hasAlpha = true;
162            break;
163        }
164    }
165
166    info.width = min(width, uint32_t(mMaxTextureSize));
167    info.hasAlpha = hasAlpha;
168}
169
170Texture* GradientCache::addLinearGradient(GradientCacheEntry& gradient,
171        uint32_t* colors, float* positions, int count) {
172
173    GradientInfo info;
174    getGradientInfo(colors, count, info);
175
176    Texture* texture = new Texture();
177    texture->width = info.width;
178    texture->height = 2;
179    texture->blend = info.hasAlpha;
180    texture->generation = 1;
181
182    // Asume the cache is always big enough
183    const uint32_t size = texture->width * texture->height * bytesPerPixel();
184    while (getSize() + size > mMaxSize) {
185        mCache.removeOldest();
186    }
187
188    generateTexture(colors, positions, count, texture);
189
190    mSize += size;
191    mCache.put(gradient, texture);
192
193    return texture;
194}
195
196size_t GradientCache::bytesPerPixel() const {
197    // We use 4 channels (RGBA)
198    return 4 * (mUseFloatTexture ? sizeof(float) : sizeof(uint8_t));
199}
200
201void GradientCache::splitToBytes(uint32_t inColor, GradientColor& outColor) const {
202    outColor.r = (inColor >> 16) & 0xff;
203    outColor.g = (inColor >>  8) & 0xff;
204    outColor.b = (inColor >>  0) & 0xff;
205    outColor.a = (inColor >> 24) & 0xff;
206}
207
208void GradientCache::splitToFloats(uint32_t inColor, GradientColor& outColor) const {
209    outColor.r = ((inColor >> 16) & 0xff) / 255.0f;
210    outColor.g = ((inColor >>  8) & 0xff) / 255.0f;
211    outColor.b = ((inColor >>  0) & 0xff) / 255.0f;
212    outColor.a = ((inColor >> 24) & 0xff) / 255.0f;
213}
214
215void GradientCache::mixBytes(GradientColor& start, GradientColor& end, float amount,
216        uint8_t*& dst) const {
217    float oppAmount = 1.0f - amount;
218    const float alpha = start.a * oppAmount + end.a * amount;
219    const float a = alpha / 255.0f;
220
221    *dst++ = uint8_t(a * (start.r * oppAmount + end.r * amount));
222    *dst++ = uint8_t(a * (start.g * oppAmount + end.g * amount));
223    *dst++ = uint8_t(a * (start.b * oppAmount + end.b * amount));
224    *dst++ = uint8_t(alpha);
225}
226
227void GradientCache::mixFloats(GradientColor& start, GradientColor& end, float amount,
228        uint8_t*& dst) const {
229    float oppAmount = 1.0f - amount;
230    const float a = start.a * oppAmount + end.a * amount;
231
232    float* d = (float*) dst;
233    *d++ = a * (start.r * oppAmount + end.r * amount);
234    *d++ = a * (start.g * oppAmount + end.g * amount);
235    *d++ = a * (start.b * oppAmount + end.b * amount);
236    *d++ = a;
237
238    dst += 4 * sizeof(float);
239}
240
241void GradientCache::generateTexture(uint32_t* colors, float* positions,
242        int count, Texture* texture) {
243    const uint32_t width = texture->width;
244    const GLsizei rowBytes = width * bytesPerPixel();
245    uint8_t pixels[rowBytes * texture->height];
246
247    static ChannelSplitter gSplitters[] = {
248            &android::uirenderer::GradientCache::splitToBytes,
249            &android::uirenderer::GradientCache::splitToFloats,
250    };
251    ChannelSplitter split = gSplitters[mUseFloatTexture];
252
253    static ChannelMixer gMixers[] = {
254            &android::uirenderer::GradientCache::mixBytes,
255            &android::uirenderer::GradientCache::mixFloats,
256    };
257    ChannelMixer mix = gMixers[mUseFloatTexture];
258
259    GradientColor start;
260    (this->*split)(colors[0], start);
261
262    GradientColor end;
263    (this->*split)(colors[1], end);
264
265    int currentPos = 1;
266    float startPos = positions[0];
267    float distance = positions[1] - startPos;
268
269    uint8_t* dst = pixels;
270    for (uint32_t x = 0; x < width; x++) {
271        float pos = x / float(width - 1);
272        if (pos > positions[currentPos]) {
273            start = end;
274            startPos = positions[currentPos];
275
276            currentPos++;
277
278            (this->*split)(colors[currentPos], end);
279            distance = positions[currentPos] - startPos;
280        }
281
282        float amount = (pos - startPos) / distance;
283        (this->*mix)(start, end, amount, dst);
284    }
285
286    memcpy(pixels + rowBytes, pixels, rowBytes);
287
288    glGenTextures(1, &texture->id);
289    Caches::getInstance().bindTexture(texture->id);
290    glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
291
292    if (mUseFloatTexture) {
293        // We have to use GL_RGBA16F because GL_RGBA32F does not support filtering
294        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, texture->height, 0,
295                GL_RGBA, GL_FLOAT, pixels);
296    } else {
297        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, texture->height, 0,
298                GL_RGBA, GL_UNSIGNED_BYTE, pixels);
299    }
300
301    texture->setFilter(GL_LINEAR);
302    texture->setWrap(GL_CLAMP_TO_EDGE);
303}
304
305}; // namespace uirenderer
306}; // namespace android
307