| /* | 
 |  * Copyright (C) 2010 The Android Open Source Project | 
 |  * | 
 |  * Licensed under the Apache License, Version 2.0 (the "License"); | 
 |  * you may not use this file except in compliance with the License. | 
 |  * You may obtain a copy of the License at | 
 |  * | 
 |  *      http://www.apache.org/licenses/LICENSE-2.0 | 
 |  * | 
 |  * Unless required by applicable law or agreed to in writing, software | 
 |  * distributed under the License is distributed on an "AS IS" BASIS, | 
 |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 |  * See the License for the specific language governing permissions and | 
 |  * limitations under the License. | 
 |  */ | 
 |  | 
 | #include <utils/JenkinsHash.h> | 
 |  | 
 | #include "Caches.h" | 
 | #include "Debug.h" | 
 | #include "GradientCache.h" | 
 | #include "Properties.h" | 
 | #include "DeviceInfo.h" | 
 |  | 
 | #include <cutils/properties.h> | 
 |  | 
 | namespace android { | 
 | namespace uirenderer { | 
 |  | 
 | /////////////////////////////////////////////////////////////////////////////// | 
 | // Functions | 
 | /////////////////////////////////////////////////////////////////////////////// | 
 |  | 
 | template<typename T> | 
 | static inline T min(T a, T b) { | 
 |     return a < b ? a : b; | 
 | } | 
 |  | 
 | /////////////////////////////////////////////////////////////////////////////// | 
 | // Cache entry | 
 | /////////////////////////////////////////////////////////////////////////////// | 
 |  | 
 | hash_t GradientCacheEntry::hash() const { | 
 |     uint32_t hash = JenkinsHashMix(0, count); | 
 |     for (uint32_t i = 0; i < count; i++) { | 
 |         hash = JenkinsHashMix(hash, android::hash_type(colors[i])); | 
 |         hash = JenkinsHashMix(hash, android::hash_type(positions[i])); | 
 |     } | 
 |     return JenkinsHashWhiten(hash); | 
 | } | 
 |  | 
 | int GradientCacheEntry::compare(const GradientCacheEntry& lhs, const GradientCacheEntry& rhs) { | 
 |     int deltaInt = int(lhs.count) - int(rhs.count); | 
 |     if (deltaInt != 0) return deltaInt; | 
 |  | 
 |     deltaInt = memcmp(lhs.colors.get(), rhs.colors.get(), lhs.count * sizeof(uint32_t)); | 
 |     if (deltaInt != 0) return deltaInt; | 
 |  | 
 |     return memcmp(lhs.positions.get(), rhs.positions.get(), lhs.count * sizeof(float)); | 
 | } | 
 |  | 
 | /////////////////////////////////////////////////////////////////////////////// | 
 | // Constructors/destructor | 
 | /////////////////////////////////////////////////////////////////////////////// | 
 |  | 
 | GradientCache::GradientCache(const Extensions& extensions) | 
 |         : mCache(LruCache<GradientCacheEntry, Texture*>::kUnlimitedCapacity) | 
 |         , mSize(0) | 
 |         , mMaxSize(MB(1)) | 
 |         , mUseFloatTexture(extensions.hasFloatTextures()) | 
 |         , mHasNpot(extensions.hasNPot()) | 
 |         , mHasLinearBlending(extensions.hasLinearBlending()) { | 
 |     mMaxTextureSize = DeviceInfo::get()->maxTextureSize(); | 
 |  | 
 |     mCache.setOnEntryRemovedListener(this); | 
 | } | 
 |  | 
 | GradientCache::~GradientCache() { | 
 |     mCache.clear(); | 
 | } | 
 |  | 
 | /////////////////////////////////////////////////////////////////////////////// | 
 | // Size management | 
 | /////////////////////////////////////////////////////////////////////////////// | 
 |  | 
 | uint32_t GradientCache::getSize() { | 
 |     return mSize; | 
 | } | 
 |  | 
 | uint32_t GradientCache::getMaxSize() { | 
 |     return mMaxSize; | 
 | } | 
 |  | 
 | /////////////////////////////////////////////////////////////////////////////// | 
 | // Callbacks | 
 | /////////////////////////////////////////////////////////////////////////////// | 
 |  | 
 | void GradientCache::operator()(GradientCacheEntry&, Texture*& texture) { | 
 |     if (texture) { | 
 |         mSize -= texture->objectSize(); | 
 |         texture->deleteTexture(); | 
 |         delete texture; | 
 |     } | 
 | } | 
 |  | 
 | /////////////////////////////////////////////////////////////////////////////// | 
 | // Caching | 
 | /////////////////////////////////////////////////////////////////////////////// | 
 |  | 
 | Texture* GradientCache::get(uint32_t* colors, float* positions, int count) { | 
 |     GradientCacheEntry gradient(colors, positions, count); | 
 |     Texture* texture = mCache.get(gradient); | 
 |  | 
 |     if (!texture) { | 
 |         texture = addLinearGradient(gradient, colors, positions, count); | 
 |     } | 
 |  | 
 |     return texture; | 
 | } | 
 |  | 
 | void GradientCache::clear() { | 
 |     mCache.clear(); | 
 | } | 
 |  | 
 | void GradientCache::getGradientInfo(const uint32_t* colors, const int count, | 
 |         GradientInfo& info) { | 
 |     uint32_t width = 256 * (count - 1); | 
 |  | 
 |     // If the npot extension is not supported we cannot use non-clamp | 
 |     // wrap modes. We therefore find the nearest largest power of 2 | 
 |     // unless width is already a power of 2 | 
 |     if (!mHasNpot && (width & (width - 1)) != 0) { | 
 |         width = 1 << (32 - __builtin_clz(width)); | 
 |     } | 
 |  | 
 |     bool hasAlpha = false; | 
 |     for (int i = 0; i < count; i++) { | 
 |         if (((colors[i] >> 24) & 0xff) < 255) { | 
 |             hasAlpha = true; | 
 |             break; | 
 |         } | 
 |     } | 
 |  | 
 |     info.width = min(width, uint32_t(mMaxTextureSize)); | 
 |     info.hasAlpha = hasAlpha; | 
 | } | 
 |  | 
 | Texture* GradientCache::addLinearGradient(GradientCacheEntry& gradient, | 
 |         uint32_t* colors, float* positions, int count) { | 
 |  | 
 |     GradientInfo info; | 
 |     getGradientInfo(colors, count, info); | 
 |  | 
 |     Texture* texture = new Texture(Caches::getInstance()); | 
 |     texture->blend = info.hasAlpha; | 
 |     texture->generation = 1; | 
 |  | 
 |     // Assume the cache is always big enough | 
 |     const uint32_t size = info.width * 2 * bytesPerPixel(); | 
 |     while (getSize() + size > mMaxSize) { | 
 |         LOG_ALWAYS_FATAL_IF(!mCache.removeOldest(), | 
 |                 "Ran out of things to remove from the cache? getSize() = %" PRIu32 | 
 |                 ", size = %" PRIu32 ", mMaxSize = %" PRIu32 ", width = %" PRIu32, | 
 |                 getSize(), size, mMaxSize, info.width); | 
 |     } | 
 |  | 
 |     generateTexture(colors, positions, info.width, 2, texture); | 
 |  | 
 |     mSize += size; | 
 |     LOG_ALWAYS_FATAL_IF((int)size != texture->objectSize(), | 
 |             "size != texture->objectSize(), size %" PRIu32 ", objectSize %d" | 
 |             " width = %" PRIu32 " bytesPerPixel() = %zu", | 
 |             size, texture->objectSize(), info.width, bytesPerPixel()); | 
 |     mCache.put(gradient, texture); | 
 |  | 
 |     return texture; | 
 | } | 
 |  | 
 | size_t GradientCache::bytesPerPixel() const { | 
 |     // We use 4 channels (RGBA) | 
 |     return 4 * (mUseFloatTexture ? /* fp16 */ 2 : sizeof(uint8_t)); | 
 | } | 
 |  | 
 | size_t GradientCache::sourceBytesPerPixel() const { | 
 |     // We use 4 channels (RGBA) and upload from floats (not half floats) | 
 |     return 4 * (mUseFloatTexture ? sizeof(float) : sizeof(uint8_t)); | 
 | } | 
 |  | 
 | void GradientCache::mixBytes(const FloatColor& start, const FloatColor& end, | 
 |         float amount, uint8_t*& dst) const { | 
 |     float oppAmount = 1.0f - amount; | 
 |     float a = start.a * oppAmount + end.a * amount; | 
 |     *dst++ = uint8_t(OECF(start.r * oppAmount + end.r * amount) * 255.0f); | 
 |     *dst++ = uint8_t(OECF(start.g * oppAmount + end.g * amount) * 255.0f); | 
 |     *dst++ = uint8_t(OECF(start.b * oppAmount + end.b * amount) * 255.0f); | 
 |     *dst++ = uint8_t(a * 255.0f); | 
 | } | 
 |  | 
 | void GradientCache::mixFloats(const FloatColor& start, const FloatColor& end, | 
 |         float amount, uint8_t*& dst) const { | 
 |     float oppAmount = 1.0f - amount; | 
 |     float a = start.a * oppAmount + end.a * amount; | 
 |     float* d = (float*) dst; | 
 | #ifdef ANDROID_ENABLE_LINEAR_BLENDING | 
 |     // We want to stay linear | 
 |     *d++ = (start.r * oppAmount + end.r * amount); | 
 |     *d++ = (start.g * oppAmount + end.g * amount); | 
 |     *d++ = (start.b * oppAmount + end.b * amount); | 
 | #else | 
 |     *d++ = OECF(start.r * oppAmount + end.r * amount); | 
 |     *d++ = OECF(start.g * oppAmount + end.g * amount); | 
 |     *d++ = OECF(start.b * oppAmount + end.b * amount); | 
 | #endif | 
 |     *d++ = a; | 
 |     dst += 4 * sizeof(float); | 
 | } | 
 |  | 
 | void GradientCache::generateTexture(uint32_t* colors, float* positions, | 
 |         const uint32_t width, const uint32_t height, Texture* texture) { | 
 |     const GLsizei rowBytes = width * sourceBytesPerPixel(); | 
 |     uint8_t pixels[rowBytes * height]; | 
 |  | 
 |     static ChannelMixer gMixers[] = { | 
 |             // colors are stored gamma-encoded | 
 |             &android::uirenderer::GradientCache::mixBytes, | 
 |             // colors are stored in linear (linear blending on) | 
 |             // or gamma-encoded (linear blending off) | 
 |             &android::uirenderer::GradientCache::mixFloats, | 
 |     }; | 
 |     ChannelMixer mix = gMixers[mUseFloatTexture]; | 
 |  | 
 |     FloatColor start; | 
 |     start.set(colors[0]); | 
 |  | 
 |     FloatColor end; | 
 |     end.set(colors[1]); | 
 |  | 
 |     int currentPos = 1; | 
 |     float startPos = positions[0]; | 
 |     float distance = positions[1] - startPos; | 
 |  | 
 |     uint8_t* dst = pixels; | 
 |     for (uint32_t x = 0; x < width; x++) { | 
 |         float pos = x / float(width - 1); | 
 |         if (pos > positions[currentPos]) { | 
 |             start = end; | 
 |             startPos = positions[currentPos]; | 
 |  | 
 |             currentPos++; | 
 |  | 
 |             end.set(colors[currentPos]); | 
 |             distance = positions[currentPos] - startPos; | 
 |         } | 
 |  | 
 |         float amount = (pos - startPos) / distance; | 
 |         (this->*mix)(start, end, amount, dst); | 
 |     } | 
 |  | 
 |     memcpy(pixels + rowBytes, pixels, rowBytes); | 
 |  | 
 |     if (mUseFloatTexture) { | 
 |         texture->upload(GL_RGBA16F, width, height, GL_RGBA, GL_FLOAT, pixels); | 
 |     } else { | 
 |         GLint internalFormat = mHasLinearBlending ? GL_SRGB8_ALPHA8 : GL_RGBA; | 
 |         texture->upload(internalFormat, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); | 
 |     } | 
 |  | 
 |     texture->setFilter(GL_LINEAR); | 
 |     texture->setWrap(GL_CLAMP_TO_EDGE); | 
 | } | 
 |  | 
 | }; // namespace uirenderer | 
 | }; // namespace android |