blob: b048469a3fec123e98e6f098e62c957654778ac7 [file] [log] [blame]
/*
* Copyright (C) 2011 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.
*/
#ifndef ANDROID_HWUI_SHAPE_CACHE_H
#define ANDROID_HWUI_SHAPE_CACHE_H
#include <GLES2/gl2.h>
#include <SkBitmap.h>
#include <SkCanvas.h>
#include <SkPaint.h>
#include <SkPath.h>
#include <SkRect.h>
#include "Debug.h"
#include "Properties.h"
#include "Texture.h"
#include "utils/Compare.h"
#include "utils/GenerationCache.h"
namespace android {
namespace uirenderer {
///////////////////////////////////////////////////////////////////////////////
// Defines
///////////////////////////////////////////////////////////////////////////////
// Debug
#if DEBUG_SHAPES
#define SHAPE_LOGD(...) LOGD(__VA_ARGS__)
#else
#define SHAPE_LOGD(...)
#endif
///////////////////////////////////////////////////////////////////////////////
// Classes
///////////////////////////////////////////////////////////////////////////////
/**
* Alpha texture used to represent a path.
*/
struct PathTexture: public Texture {
PathTexture(): Texture() {
}
/**
* Left coordinate of the path bounds.
*/
float left;
/**
* Top coordinate of the path bounds.
*/
float top;
/**
* Offset to draw the path at the correct origin.
*/
float offset;
}; // struct PathTexture
/**
* Describe a shape in the shape cache.
*/
struct ShapeCacheEntry {
enum ShapeType {
kShapeNone,
kShapeRect,
kShapeRoundRect,
kShapeCircle,
kShapeOval,
kShapeArc,
kShapePath
};
ShapeCacheEntry() {
shapeType = kShapeNone;
join = SkPaint::kDefault_Join;
cap = SkPaint::kDefault_Cap;
style = SkPaint::kFill_Style;
float v = 4.0f;
miter = *(uint32_t*) &v;
v = 1.0f;
strokeWidth = *(uint32_t*) &v;
pathEffect = NULL;
}
ShapeCacheEntry(const ShapeCacheEntry& entry):
shapeType(entry.shapeType), join(entry.join), cap(entry.cap),
style(entry.style), miter(entry.miter),
strokeWidth(entry.strokeWidth), pathEffect(entry.pathEffect) {
}
ShapeCacheEntry(ShapeType type, SkPaint* paint) {
shapeType = type;
join = paint->getStrokeJoin();
cap = paint->getStrokeCap();
float v = paint->getStrokeMiter();
miter = *(uint32_t*) &v;
v = paint->getStrokeWidth();
strokeWidth = *(uint32_t*) &v;
style = paint->getStyle();
pathEffect = paint->getPathEffect();
}
virtual ~ShapeCacheEntry() {
}
ShapeType shapeType;
SkPaint::Join join;
SkPaint::Cap cap;
SkPaint::Style style;
uint32_t miter;
uint32_t strokeWidth;
SkPathEffect* pathEffect;
bool operator<(const ShapeCacheEntry& rhs) const {
LTE_INT(shapeType) {
LTE_INT(join) {
LTE_INT(cap) {
LTE_INT(style) {
LTE_INT(miter) {
LTE_INT(strokeWidth) {
LTE_INT(pathEffect) {
return lessThan(rhs);
}
}
}
}
}
}
}
return false;
}
protected:
virtual bool lessThan(const ShapeCacheEntry& rhs) const {
return false;
}
}; // struct ShapeCacheEntry
struct RoundRectShapeCacheEntry: public ShapeCacheEntry {
RoundRectShapeCacheEntry(float width, float height, float rx, float ry, SkPaint* paint):
ShapeCacheEntry(ShapeCacheEntry::kShapeRoundRect, paint) {
mWidth = *(uint32_t*) &width;
mHeight = *(uint32_t*) &height;
mRx = *(uint32_t*) &rx;
mRy = *(uint32_t*) &ry;
}
RoundRectShapeCacheEntry(): ShapeCacheEntry() {
mWidth = 0;
mHeight = 0;
mRx = 0;
mRy = 0;
}
RoundRectShapeCacheEntry(const RoundRectShapeCacheEntry& entry):
ShapeCacheEntry(entry) {
mWidth = entry.mWidth;
mHeight = entry.mHeight;
mRx = entry.mRx;
mRy = entry.mRy;
}
bool lessThan(const ShapeCacheEntry& r) const {
const RoundRectShapeCacheEntry& rhs = (const RoundRectShapeCacheEntry&) r;
LTE_INT(mWidth) {
LTE_INT(mHeight) {
LTE_INT(mRx) {
LTE_INT(mRy) {
return false;
}
}
}
}
return false;
}
private:
uint32_t mWidth;
uint32_t mHeight;
uint32_t mRx;
uint32_t mRy;
}; // RoundRectShapeCacheEntry
struct CircleShapeCacheEntry: public ShapeCacheEntry {
CircleShapeCacheEntry(float radius, SkPaint* paint):
ShapeCacheEntry(ShapeCacheEntry::kShapeCircle, paint) {
mRadius = *(uint32_t*) &radius;
}
CircleShapeCacheEntry(): ShapeCacheEntry() {
mRadius = 0;
}
CircleShapeCacheEntry(const CircleShapeCacheEntry& entry):
ShapeCacheEntry(entry) {
mRadius = entry.mRadius;
}
bool lessThan(const ShapeCacheEntry& r) const {
const CircleShapeCacheEntry& rhs = (const CircleShapeCacheEntry&) r;
LTE_INT(mRadius) {
return false;
}
return false;
}
private:
uint32_t mRadius;
}; // CircleShapeCacheEntry
struct OvalShapeCacheEntry: public ShapeCacheEntry {
OvalShapeCacheEntry(float width, float height, SkPaint* paint):
ShapeCacheEntry(ShapeCacheEntry::kShapeOval, paint) {
mWidth = *(uint32_t*) &width;
mHeight = *(uint32_t*) &height;
}
OvalShapeCacheEntry(): ShapeCacheEntry() {
mWidth = mHeight = 0;
}
OvalShapeCacheEntry(const OvalShapeCacheEntry& entry):
ShapeCacheEntry(entry) {
mWidth = entry.mWidth;
mHeight = entry.mHeight;
}
bool lessThan(const ShapeCacheEntry& r) const {
const OvalShapeCacheEntry& rhs = (const OvalShapeCacheEntry&) r;
LTE_INT(mWidth) {
LTE_INT(mHeight) {
return false;
}
}
return false;
}
private:
uint32_t mWidth;
uint32_t mHeight;
}; // OvalShapeCacheEntry
struct RectShapeCacheEntry: public ShapeCacheEntry {
RectShapeCacheEntry(float width, float height, SkPaint* paint):
ShapeCacheEntry(ShapeCacheEntry::kShapeRect, paint) {
mWidth = *(uint32_t*) &width;
mHeight = *(uint32_t*) &height;
}
RectShapeCacheEntry(): ShapeCacheEntry() {
mWidth = mHeight = 0;
}
RectShapeCacheEntry(const RectShapeCacheEntry& entry):
ShapeCacheEntry(entry) {
mWidth = entry.mWidth;
mHeight = entry.mHeight;
}
bool lessThan(const ShapeCacheEntry& r) const {
const RectShapeCacheEntry& rhs = (const RectShapeCacheEntry&) r;
LTE_INT(mWidth) {
LTE_INT(mHeight) {
return false;
}
}
return false;
}
private:
uint32_t mWidth;
uint32_t mHeight;
}; // RectShapeCacheEntry
struct ArcShapeCacheEntry: public ShapeCacheEntry {
ArcShapeCacheEntry(float width, float height, float startAngle, float sweepAngle,
bool useCenter, SkPaint* paint):
ShapeCacheEntry(ShapeCacheEntry::kShapeArc, paint) {
mWidth = *(uint32_t*) &width;
mHeight = *(uint32_t*) &height;
mStartAngle = *(uint32_t*) &startAngle;
mSweepAngle = *(uint32_t*) &sweepAngle;
mUseCenter = useCenter ? 1 : 0;
}
ArcShapeCacheEntry(): ShapeCacheEntry() {
mWidth = 0;
mHeight = 0;
mStartAngle = 0;
mSweepAngle = 0;
mUseCenter = 0;
}
ArcShapeCacheEntry(const ArcShapeCacheEntry& entry):
ShapeCacheEntry(entry) {
mWidth = entry.mWidth;
mHeight = entry.mHeight;
mStartAngle = entry.mStartAngle;
mSweepAngle = entry.mSweepAngle;
mUseCenter = entry.mUseCenter;
}
bool lessThan(const ShapeCacheEntry& r) const {
const ArcShapeCacheEntry& rhs = (const ArcShapeCacheEntry&) r;
LTE_INT(mWidth) {
LTE_INT(mHeight) {
LTE_INT(mStartAngle) {
LTE_INT(mSweepAngle) {
LTE_INT(mUseCenter) {
return false;
}
}
}
}
}
return false;
}
private:
uint32_t mWidth;
uint32_t mHeight;
uint32_t mStartAngle;
uint32_t mSweepAngle;
uint32_t mUseCenter;
}; // ArcShapeCacheEntry
/**
* A simple LRU shape cache. The cache has a maximum size expressed in bytes.
* Any texture added to the cache causing the cache to grow beyond the maximum
* allowed size will also cause the oldest texture to be kicked out.
*/
template<typename Entry>
class ShapeCache: public OnEntryRemoved<Entry, PathTexture*> {
public:
ShapeCache(const char* name, const char* propertyName, float defaultSize);
~ShapeCache();
/**
* Used as a callback when an entry is removed from the cache.
* Do not invoke directly.
*/
void operator()(Entry& path, PathTexture*& texture);
/**
* Clears the cache. This causes all textures to be deleted.
*/
void clear();
/**
* Sets the maximum size of the cache in bytes.
*/
void setMaxSize(uint32_t maxSize);
/**
* Returns the maximum size of the cache in bytes.
*/
uint32_t getMaxSize();
/**
* Returns the current size of the cache in bytes.
*/
uint32_t getSize();
protected:
PathTexture* addTexture(const Entry& entry, const SkPath *path, const SkPaint* paint);
PathTexture* get(Entry entry) {
return mCache.get(entry);
}
void removeTexture(PathTexture* texture);
GenerationCache<Entry, PathTexture*> mCache;
uint32_t mSize;
uint32_t mMaxSize;
GLuint mMaxTextureSize;
char* mName;
bool mDebugEnabled;
private:
/**
* Generates the texture from a bitmap into the specified texture structure.
*/
void generateTexture(SkBitmap& bitmap, Texture* texture);
void init();
}; // class ShapeCache
class RoundRectShapeCache: public ShapeCache<RoundRectShapeCacheEntry> {
public:
RoundRectShapeCache();
PathTexture* getRoundRect(float width, float height, float rx, float ry, SkPaint* paint);
}; // class RoundRectShapeCache
class CircleShapeCache: public ShapeCache<CircleShapeCacheEntry> {
public:
CircleShapeCache();
PathTexture* getCircle(float radius, SkPaint* paint);
}; // class CircleShapeCache
class OvalShapeCache: public ShapeCache<OvalShapeCacheEntry> {
public:
OvalShapeCache();
PathTexture* getOval(float width, float height, SkPaint* paint);
}; // class OvalShapeCache
class RectShapeCache: public ShapeCache<RectShapeCacheEntry> {
public:
RectShapeCache();
PathTexture* getRect(float width, float height, SkPaint* paint);
}; // class RectShapeCache
class ArcShapeCache: public ShapeCache<ArcShapeCacheEntry> {
public:
ArcShapeCache();
PathTexture* getArc(float width, float height, float startAngle, float sweepAngle,
bool useCenter, SkPaint* paint);
}; // class ArcShapeCache
///////////////////////////////////////////////////////////////////////////////
// Constructors/destructor
///////////////////////////////////////////////////////////////////////////////
template<class Entry>
ShapeCache<Entry>::ShapeCache(const char* name, const char* propertyName, float defaultSize):
mCache(GenerationCache<ShapeCacheEntry, PathTexture*>::kUnlimitedCapacity),
mSize(0), mMaxSize(MB(defaultSize)) {
char property[PROPERTY_VALUE_MAX];
if (property_get(propertyName, property, NULL) > 0) {
INIT_LOGD(" Setting %s cache size to %sMB", name, property);
setMaxSize(MB(atof(property)));
} else {
INIT_LOGD(" Using default %s cache size of %.2fMB", name, defaultSize);
}
size_t len = strlen(name);
mName = new char[len + 1];
strcpy(mName, name);
mName[len] = '\0';
init();
}
template<class Entry>
ShapeCache<Entry>::~ShapeCache() {
mCache.clear();
delete[] mName;
}
template<class Entry>
void ShapeCache<Entry>::init() {
mCache.setOnEntryRemovedListener(this);
GLint maxTextureSize;
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
mMaxTextureSize = maxTextureSize;
mDebugEnabled = readDebugLevel() & kDebugCaches;
}
///////////////////////////////////////////////////////////////////////////////
// Size management
///////////////////////////////////////////////////////////////////////////////
template<class Entry>
uint32_t ShapeCache<Entry>::getSize() {
return mSize;
}
template<class Entry>
uint32_t ShapeCache<Entry>::getMaxSize() {
return mMaxSize;
}
template<class Entry>
void ShapeCache<Entry>::setMaxSize(uint32_t maxSize) {
mMaxSize = maxSize;
while (mSize > mMaxSize) {
mCache.removeOldest();
}
}
///////////////////////////////////////////////////////////////////////////////
// Callbacks
///////////////////////////////////////////////////////////////////////////////
template<class Entry>
void ShapeCache<Entry>::operator()(Entry& path, PathTexture*& texture) {
removeTexture(texture);
}
///////////////////////////////////////////////////////////////////////////////
// Caching
///////////////////////////////////////////////////////////////////////////////
template<class Entry>
void ShapeCache<Entry>::removeTexture(PathTexture* texture) {
if (texture) {
const uint32_t size = texture->width * texture->height;
mSize -= size;
SHAPE_LOGD("ShapeCache::callback: delete %s: name, size, mSize = %d, %d, %d",
mName, texture->id, size, mSize);
if (mDebugEnabled) {
LOGD("Shape %s deleted, size = %d", mName, size);
}
glDeleteTextures(1, &texture->id);
delete texture;
}
}
template<class Entry>
PathTexture* ShapeCache<Entry>::addTexture(const Entry& entry, const SkPath *path,
const SkPaint* paint) {
const SkRect& bounds = path->getBounds();
const float pathWidth = fmax(bounds.width(), 1.0f);
const float pathHeight = fmax(bounds.height(), 1.0f);
const float offset = fmax(paint->getStrokeWidth(), 1.0f) * 1.5f;
const uint32_t width = uint32_t(pathWidth + offset * 2.0 + 0.5);
const uint32_t height = uint32_t(pathHeight + offset * 2.0 + 0.5);
if (width > mMaxTextureSize || height > mMaxTextureSize) {
LOGW("Shape %s too large to be rendered into a texture", mName);
return NULL;
}
const uint32_t size = width * height;
// Don't even try to cache a bitmap that's bigger than the cache
if (size < mMaxSize) {
while (mSize + size > mMaxSize) {
mCache.removeOldest();
}
}
PathTexture* texture = new PathTexture;
texture->left = bounds.fLeft;
texture->top = bounds.fTop;
texture->offset = offset;
texture->width = width;
texture->height = height;
texture->generation = path->getGenerationID();
SkBitmap bitmap;
bitmap.setConfig(SkBitmap::kA8_Config, width, height);
bitmap.allocPixels();
bitmap.eraseColor(0);
SkPaint pathPaint(*paint);
// Make sure the paint is opaque, color, alpha, filter, etc.
// will be applied later when compositing the alpha8 texture
pathPaint.setColor(0xff000000);
pathPaint.setAlpha(255);
pathPaint.setColorFilter(NULL);
pathPaint.setMaskFilter(NULL);
pathPaint.setShader(NULL);
SkXfermode* mode = SkXfermode::Create(SkXfermode::kSrc_Mode);
SkSafeUnref(pathPaint.setXfermode(mode));
SkCanvas canvas(bitmap);
canvas.translate(-bounds.fLeft + offset, -bounds.fTop + offset);
canvas.drawPath(*path, pathPaint);
generateTexture(bitmap, texture);
if (size < mMaxSize) {
mSize += size;
SHAPE_LOGD("ShapeCache::get: create %s: name, size, mSize = %d, %d, %d",
mName, texture->id, size, mSize);
if (mDebugEnabled) {
LOGD("Shape %s created, size = %d", mName, size);
}
mCache.put(entry, texture);
} else {
texture->cleanup = true;
}
return texture;
}
template<class Entry>
void ShapeCache<Entry>::clear() {
mCache.clear();
}
template<class Entry>
void ShapeCache<Entry>::generateTexture(SkBitmap& bitmap, Texture* texture) {
SkAutoLockPixels alp(bitmap);
if (!bitmap.readyToDraw()) {
LOGE("Cannot generate texture from bitmap");
return;
}
glGenTextures(1, &texture->id);
glBindTexture(GL_TEXTURE_2D, texture->id);
// Textures are Alpha8
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
texture->blend = true;
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, texture->width, texture->height, 0,
GL_ALPHA, GL_UNSIGNED_BYTE, bitmap.getPixels());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
}; // namespace uirenderer
}; // namespace android
#endif // ANDROID_HWUI_SHAPE_CACHE_H