| /* |
| * 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 "DeviceInfo.h" |
| #include "GradientCache.h" |
| #include "Properties.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 |