android_frameworks_base/libs/hwui/GradientCache.cpp
Romain Guy 6183c97e5f Bowing my head in shame, going back to gamma interpolated gradients
Frankengradients (linearly interpolated RGB, gamma interpolated alpha) look
fantastic but unfortunately create sligh compatibility issues. For instance,
a gradient from 0xffea1030 to 0x00ea1030 (opaque to alpha, with a single
color) blended on top of 0xff101010 would not look the same as a single
opaque gradient from 0xffea1030 to 0xff101010. The difference is hardly
noticeable on simple gradients but it could cause confusion amongst app
developers. Their life is hard enough as it is, let's be good to them.

My crusade against the gamma world is not over and one day I shall
be the victor. I am patience.

Bug: 35485208
Test: UiRendering.ShaderTests, UiRendering.GradientTests, manual testing
Change-Id: I8204e60cdf0a6b12dfe22638d30ca9622687000e
2017-03-16 12:29:03 -07:00

273 lines
8.9 KiB
C++

/*
* 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 <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(Extensions& extensions)
: mCache(LruCache<GradientCacheEntry, Texture*>::kUnlimitedCapacity)
, mSize(0)
, mMaxSize(Properties::gradientCacheSize)
, mUseFloatTexture(extensions.hasFloatTextures())
, mHasNpot(extensions.hasNPot())
, mHasLinearBlending(extensions.hasLinearBlending()) {
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;
}
///////////////////////////////////////////////////////////////////////////////
// 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(a * OECF(start.r * oppAmount + end.r * amount) * 255.0f);
*dst++ = uint8_t(a * OECF(start.g * oppAmount + end.g * amount) * 255.0f);
*dst++ = uint8_t(a * 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++ = a * (start.r * oppAmount + end.r * amount);
*d++ = a * (start.g * oppAmount + end.g * amount);
*d++ = a * (start.b * oppAmount + end.b * amount);
#else
*d++ = a * OECF(start.r * oppAmount + end.r * amount);
*d++ = a * OECF(start.g * oppAmount + end.g * amount);
*d++ = a * 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.setUnPreMultiplied(colors[0]);
FloatColor end;
end.setUnPreMultiplied(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.setUnPreMultiplied(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