| /* |
| * 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" |
| |
| 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(Extensions& extensions) |
| : mCache(LruCache<GradientCacheEntry, Texture*>::kUnlimitedCapacity) |
| , mSize(0) |
| , mMaxSize(MB(DEFAULT_GRADIENT_CACHE_SIZE)) |
| , mUseFloatTexture(extensions.hasFloatTextures()) |
| , mHasNpot(extensions.hasNPot()){ |
| char property[PROPERTY_VALUE_MAX]; |
| if (property_get(PROPERTY_GRADIENT_CACHE_SIZE, property, nullptr) > 0) { |
| INIT_LOGD(" Setting gradient cache size to %sMB", property); |
| setMaxSize(MB(atof(property))); |
| } else { |
| INIT_LOGD(" Using default gradient cache size of %.2fMB", DEFAULT_GRADIENT_CACHE_SIZE); |
| } |
| |
| glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize); |
| |
| mCache.setOnEntryRemovedListener(this); |
| } |
| |
| GradientCache::~GradientCache() { |
| mCache.clear(); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Size management |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| uint32_t GradientCache::getSize() { |
| return mSize; |
| } |
| |
| uint32_t GradientCache::getMaxSize() { |
| return mMaxSize; |
| } |
| |
| void GradientCache::setMaxSize(uint32_t maxSize) { |
| mMaxSize = maxSize; |
| while (mSize > mMaxSize) { |
| mCache.removeOldest(); |
| } |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Callbacks |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| void GradientCache::operator()(GradientCacheEntry&, Texture*& texture) { |
| if (texture) { |
| const uint32_t size = texture->width * texture->height * bytesPerPixel(); |
| mSize -= size; |
| |
| 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->width = info.width; |
| texture->height = 2; |
| texture->blend = info.hasAlpha; |
| texture->generation = 1; |
| |
| // Asume the cache is always big enough |
| const uint32_t size = texture->width * texture->height * bytesPerPixel(); |
| while (getSize() + size > mMaxSize) { |
| mCache.removeOldest(); |
| } |
| |
| generateTexture(colors, positions, texture); |
| |
| mSize += size; |
| mCache.put(gradient, texture); |
| |
| return texture; |
| } |
| |
| size_t GradientCache::bytesPerPixel() const { |
| // We use 4 channels (RGBA) |
| return 4 * (mUseFloatTexture ? sizeof(float) : sizeof(uint8_t)); |
| } |
| |
| void GradientCache::splitToBytes(uint32_t inColor, GradientColor& outColor) const { |
| outColor.r = (inColor >> 16) & 0xff; |
| outColor.g = (inColor >> 8) & 0xff; |
| outColor.b = (inColor >> 0) & 0xff; |
| outColor.a = (inColor >> 24) & 0xff; |
| } |
| |
| void GradientCache::splitToFloats(uint32_t inColor, GradientColor& outColor) const { |
| outColor.r = ((inColor >> 16) & 0xff) / 255.0f; |
| outColor.g = ((inColor >> 8) & 0xff) / 255.0f; |
| outColor.b = ((inColor >> 0) & 0xff) / 255.0f; |
| outColor.a = ((inColor >> 24) & 0xff) / 255.0f; |
| } |
| |
| void GradientCache::mixBytes(GradientColor& start, GradientColor& end, float amount, |
| uint8_t*& dst) const { |
| float oppAmount = 1.0f - amount; |
| const float alpha = start.a * oppAmount + end.a * amount; |
| const float a = alpha / 255.0f; |
| |
| *dst++ = uint8_t(a * (start.r * oppAmount + end.r * amount)); |
| *dst++ = uint8_t(a * (start.g * oppAmount + end.g * amount)); |
| *dst++ = uint8_t(a * (start.b * oppAmount + end.b * amount)); |
| *dst++ = uint8_t(alpha); |
| } |
| |
| void GradientCache::mixFloats(GradientColor& start, GradientColor& end, float amount, |
| uint8_t*& dst) const { |
| float oppAmount = 1.0f - amount; |
| const float a = start.a * oppAmount + end.a * amount; |
| |
| float* d = (float*) dst; |
| *d++ = a * (start.r * oppAmount + end.r * amount); |
| *d++ = a * (start.g * oppAmount + end.g * amount); |
| *d++ = a * (start.b * oppAmount + end.b * amount); |
| *d++ = a; |
| |
| dst += 4 * sizeof(float); |
| } |
| |
| void GradientCache::generateTexture(uint32_t* colors, float* positions, Texture* texture) { |
| const uint32_t width = texture->width; |
| const GLsizei rowBytes = width * bytesPerPixel(); |
| uint8_t pixels[rowBytes * texture->height]; |
| |
| static ChannelSplitter gSplitters[] = { |
| &android::uirenderer::GradientCache::splitToBytes, |
| &android::uirenderer::GradientCache::splitToFloats, |
| }; |
| ChannelSplitter split = gSplitters[mUseFloatTexture]; |
| |
| static ChannelMixer gMixers[] = { |
| &android::uirenderer::GradientCache::mixBytes, |
| &android::uirenderer::GradientCache::mixFloats, |
| }; |
| ChannelMixer mix = gMixers[mUseFloatTexture]; |
| |
| GradientColor start; |
| (this->*split)(colors[0], start); |
| |
| GradientColor end; |
| (this->*split)(colors[1], end); |
| |
| 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++; |
| |
| (this->*split)(colors[currentPos], end); |
| distance = positions[currentPos] - startPos; |
| } |
| |
| float amount = (pos - startPos) / distance; |
| (this->*mix)(start, end, amount, dst); |
| } |
| |
| memcpy(pixels + rowBytes, pixels, rowBytes); |
| |
| glGenTextures(1, &texture->id); |
| Caches::getInstance().textureState().bindTexture(texture->id); |
| glPixelStorei(GL_UNPACK_ALIGNMENT, 4); |
| |
| if (mUseFloatTexture) { |
| // We have to use GL_RGBA16F because GL_RGBA32F does not support filtering |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, texture->height, 0, |
| GL_RGBA, GL_FLOAT, pixels); |
| } else { |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, texture->height, 0, |
| GL_RGBA, GL_UNSIGNED_BYTE, pixels); |
| } |
| |
| texture->setFilter(GL_LINEAR); |
| texture->setWrap(GL_CLAMP_TO_EDGE); |
| } |
| |
| }; // namespace uirenderer |
| }; // namespace android |