summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRomain Guy <romainguy@google.com>2013-03-15 19:06:39 -0700
committerRomain Guy <romainguy@google.com>2013-03-18 18:32:17 -0700
commitc46d07a29e94807e768f8b162ce9f77a88ba6f46 (patch)
tree9b6aec5f5dc1d73b6cd9d583d5c5c78285480070
parent338b18844434379de54050ff582d36ff6da3ba11 (diff)
downloadframeworks_base-c46d07a29e94807e768f8b162ce9f77a88ba6f46.zip
frameworks_base-c46d07a29e94807e768f8b162ce9f77a88ba6f46.tar.gz
frameworks_base-c46d07a29e94807e768f8b162ce9f77a88ba6f46.tar.bz2
Merge all shapes/paths caches to PathCache
This change will greatly simplify the multi-threading of all shape types. This change also uses PathTessellator to render convex paths. Change-Id: I4e65bc95c9d24ecae2183b72204de5c2dfb6ada4
-rw-r--r--libs/hwui/Android.mk1
-rw-r--r--libs/hwui/Caches.cpp20
-rw-r--r--libs/hwui/Caches.h6
-rw-r--r--libs/hwui/Debug.h2
-rw-r--r--libs/hwui/DeferredDisplayList.cpp2
-rw-r--r--libs/hwui/DisplayList.cpp6
-rw-r--r--libs/hwui/DisplayListRenderer.cpp1
-rw-r--r--libs/hwui/OpenGLRenderer.cpp18
-rw-r--r--libs/hwui/PathCache.cpp443
-rw-r--r--libs/hwui/PathCache.h249
-rw-r--r--libs/hwui/Properties.h4
-rw-r--r--libs/hwui/ShapeCache.cpp168
-rw-r--r--libs/hwui/ShapeCache.h816
-rw-r--r--tests/HwAccelerationTest/src/com/android/test/hwui/ShapesActivity.java31
14 files changed, 686 insertions, 1081 deletions
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 1618110..06e658d 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -34,7 +34,6 @@ ifeq ($(USE_OPENGL_RENDERER),true)
ProgramCache.cpp \
RenderBufferCache.cpp \
ResourceCache.cpp \
- ShapeCache.cpp \
SkiaColorFilter.cpp \
SkiaShader.cpp \
Snapshot.cpp \
diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp
index 4642a4f..dc3a4e2 100644
--- a/libs/hwui/Caches.cpp
+++ b/libs/hwui/Caches.cpp
@@ -224,16 +224,6 @@ void Caches::dumpMemoryUsage(String8 &log) {
gradientCache.getSize(), gradientCache.getMaxSize());
log.appendFormat(" PathCache %8d / %8d\n",
pathCache.getSize(), pathCache.getMaxSize());
- log.appendFormat(" CircleShapeCache %8d / %8d\n",
- circleShapeCache.getSize(), circleShapeCache.getMaxSize());
- log.appendFormat(" OvalShapeCache %8d / %8d\n",
- ovalShapeCache.getSize(), ovalShapeCache.getMaxSize());
- log.appendFormat(" RoundRectShapeCache %8d / %8d\n",
- roundRectShapeCache.getSize(), roundRectShapeCache.getMaxSize());
- log.appendFormat(" RectShapeCache %8d / %8d\n",
- rectShapeCache.getSize(), rectShapeCache.getMaxSize());
- log.appendFormat(" ArcShapeCache %8d / %8d\n",
- arcShapeCache.getSize(), arcShapeCache.getMaxSize());
log.appendFormat(" TextDropShadowCache %8d / %8d\n", dropShadowCache.getSize(),
dropShadowCache.getMaxSize());
for (uint32_t i = 0; i < fontRenderer->getFontRendererCount(); i++) {
@@ -253,11 +243,6 @@ void Caches::dumpMemoryUsage(String8 &log) {
total += gradientCache.getSize();
total += pathCache.getSize();
total += dropShadowCache.getSize();
- total += roundRectShapeCache.getSize();
- total += circleShapeCache.getSize();
- total += ovalShapeCache.getSize();
- total += rectShapeCache.getSize();
- total += arcShapeCache.getSize();
for (uint32_t i = 0; i < fontRenderer->getFontRendererCount(); i++) {
total += fontRenderer->getFontRendererSize(i);
}
@@ -325,11 +310,6 @@ void Caches::flush(FlushMode mode) {
fontRenderer->flush();
textureCache.flush();
pathCache.clear();
- roundRectShapeCache.clear();
- circleShapeCache.clear();
- ovalShapeCache.clear();
- rectShapeCache.clear();
- arcShapeCache.clear();
// fall through
case kFlushMode_Layers:
layerCache.clear();
diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h
index dc32a7e..63836c1 100644
--- a/libs/hwui/Caches.h
+++ b/libs/hwui/Caches.h
@@ -36,7 +36,6 @@
#include "GradientCache.h"
#include "PatchCache.h"
#include "ProgramCache.h"
-#include "ShapeCache.h"
#include "PathCache.h"
#include "TextDropShadowCache.h"
#include "FboCache.h"
@@ -269,11 +268,6 @@ public:
GradientCache gradientCache;
ProgramCache programCache;
PathCache pathCache;
- RoundRectShapeCache roundRectShapeCache;
- CircleShapeCache circleShapeCache;
- OvalShapeCache ovalShapeCache;
- RectShapeCache rectShapeCache;
- ArcShapeCache arcShapeCache;
PatchCache patchCache;
TextDropShadowCache dropShadowCache;
FboCache fboCache;
diff --git a/libs/hwui/Debug.h b/libs/hwui/Debug.h
index 773fe82..46beb94 100644
--- a/libs/hwui/Debug.h
+++ b/libs/hwui/Debug.h
@@ -64,7 +64,7 @@
#define DEBUG_PATCHES_EMPTY_VERTICES 0
// Turn on to display debug info about shapes
-#define DEBUG_SHAPES 0
+#define DEBUG_PATHS 0
// Turn on to display debug info about textures
#define DEBUG_TEXTURES 0
diff --git a/libs/hwui/DeferredDisplayList.cpp b/libs/hwui/DeferredDisplayList.cpp
index 8455545..5c5bf45 100644
--- a/libs/hwui/DeferredDisplayList.cpp
+++ b/libs/hwui/DeferredDisplayList.cpp
@@ -17,6 +17,8 @@
#define LOG_TAG "OpenGLRenderer"
#define ATRACE_TAG ATRACE_TAG_VIEW
+#include <SkCanvas.h>
+
#include <utils/Trace.h>
#include "Debug.h"
diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp
index 4743f58..4944fe8 100644
--- a/libs/hwui/DisplayList.cpp
+++ b/libs/hwui/DisplayList.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#include <SkCanvas.h>
+
#include "Debug.h"
#include "DisplayList.h"
#include "DisplayListOp.h"
@@ -121,9 +123,7 @@ void DisplayList::clearResources() {
}
for (size_t i = 0; i < mPaths.size(); i++) {
- SkPath* path = mPaths.itemAt(i);
- caches.pathCache.remove(path);
- delete path;
+ delete mPaths.itemAt(i);
}
for (size_t i = 0; i < mMatrices.size(); i++) {
diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp
index 11a655e..8b5b54c 100644
--- a/libs/hwui/DisplayListRenderer.cpp
+++ b/libs/hwui/DisplayListRenderer.cpp
@@ -17,6 +17,7 @@
#define LOG_TAG "OpenGLRenderer"
#include <SkCamera.h>
+#include <SkCanvas.h>
#include <private/hwui/DrawGlInfo.h>
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index e576f76..c310c4c 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -2257,9 +2257,11 @@ status_t OpenGLRenderer::drawConvexPath(const SkPath& path, SkPaint* paint) {
// TODO: try clipping large paths to viewport
PathTessellator::tessellatePath(path, paint, mSnapshot->transform, vertexBuffer);
- SkRect bounds = path.getBounds();
- PathTessellator::expandBoundsForStroke(bounds, paint, false);
- dirtyLayer(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, currentTransform());
+ if (hasLayer()) {
+ SkRect bounds = path.getBounds();
+ PathTessellator::expandBoundsForStroke(bounds, paint, false);
+ dirtyLayer(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, currentTransform());
+ }
return drawVertexBuffer(vertexBuffer, paint);
}
@@ -2389,7 +2391,7 @@ status_t OpenGLRenderer::drawRoundRect(float left, float top, float right, float
if (p->getPathEffect() != 0) {
mCaches.activeTexture(0);
- const PathTexture* texture = mCaches.roundRectShapeCache.getRoundRect(
+ const PathTexture* texture = mCaches.pathCache.getRoundRect(
right - left, bottom - top, rx, ry, p);
return drawShape(left, top, texture, p);
}
@@ -2413,7 +2415,7 @@ status_t OpenGLRenderer::drawCircle(float x, float y, float radius, SkPaint* p)
}
if (p->getPathEffect() != 0) {
mCaches.activeTexture(0);
- const PathTexture* texture = mCaches.circleShapeCache.getCircle(radius, p);
+ const PathTexture* texture = mCaches.pathCache.getCircle(radius, p);
return drawShape(x - radius, y - radius, texture, p);
}
@@ -2434,7 +2436,7 @@ status_t OpenGLRenderer::drawOval(float left, float top, float right, float bott
if (p->getPathEffect() != 0) {
mCaches.activeTexture(0);
- const PathTexture* texture = mCaches.ovalShapeCache.getOval(right - left, bottom - top, p);
+ const PathTexture* texture = mCaches.pathCache.getOval(right - left, bottom - top, p);
return drawShape(left, top, texture, p);
}
@@ -2460,7 +2462,7 @@ status_t OpenGLRenderer::drawArc(float left, float top, float right, float botto
// TODO: support fills (accounting for concavity if useCenter && sweepAngle > 180)
if (p->getStyle() != SkPaint::kStroke_Style || p->getPathEffect() != 0 || useCenter) {
mCaches.activeTexture(0);
- const PathTexture* texture = mCaches.arcShapeCache.getArc(right - left, bottom - top,
+ const PathTexture* texture = mCaches.pathCache.getArc(right - left, bottom - top,
startAngle, sweepAngle, useCenter, p);
return drawShape(left, top, texture, p);
}
@@ -2495,7 +2497,7 @@ status_t OpenGLRenderer::drawRect(float left, float top, float right, float bott
p->getStrokeMiter() != SkPaintDefaults_MiterLimit) {
mCaches.activeTexture(0);
const PathTexture* texture =
- mCaches.rectShapeCache.getRect(right - left, bottom - top, p);
+ mCaches.pathCache.getRect(right - left, bottom - top, p);
return drawShape(left, top, texture, p);
}
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index fb687cd..490c22a 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2013 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.
@@ -15,19 +15,301 @@
*/
#define LOG_TAG "OpenGLRenderer"
+#define ATRACE_TAG ATRACE_TAG_VIEW
-#include <utils/Mutex.h>
+#include <SkBitmap.h>
+#include <SkCanvas.h>
+#include <SkPaint.h>
+#include <SkPath.h>
+#include <SkRect.h>
-#include <sys/sysinfo.h>
+#include <utils/JenkinsHash.h>
+#include <utils/Trace.h>
#include "Caches.h"
#include "PathCache.h"
-#include "Properties.h"
+
+#include "thread/Signal.h"
+#include "thread/Task.h"
+#include "thread/TaskProcessor.h"
namespace android {
namespace uirenderer {
///////////////////////////////////////////////////////////////////////////////
+// Cache entries
+///////////////////////////////////////////////////////////////////////////////
+
+PathDescription::PathDescription():
+ type(kShapeNone),
+ join(SkPaint::kDefault_Join),
+ cap(SkPaint::kDefault_Cap),
+ style(SkPaint::kFill_Style),
+ miter(4.0f),
+ strokeWidth(1.0f),
+ pathEffect(NULL) {
+ memset(&shape, 0, sizeof(Shape));
+}
+
+PathDescription::PathDescription(ShapeType type, SkPaint* paint):
+ type(type),
+ join(paint->getStrokeJoin()),
+ cap(paint->getStrokeCap()),
+ style(paint->getStyle()),
+ miter(paint->getStrokeMiter()),
+ strokeWidth(paint->getStrokeWidth()),
+ pathEffect(paint->getPathEffect()) {
+ memset(&shape, 0, sizeof(Shape));
+}
+
+hash_t PathDescription::hash() const {
+ uint32_t hash = JenkinsHashMix(0, type);
+ hash = JenkinsHashMix(hash, join);
+ hash = JenkinsHashMix(hash, cap);
+ hash = JenkinsHashMix(hash, style);
+ hash = JenkinsHashMix(hash, android::hash_type(miter));
+ hash = JenkinsHashMix(hash, android::hash_type(strokeWidth));
+ hash = JenkinsHashMix(hash, android::hash_type(pathEffect));
+ hash = JenkinsHashMixBytes(hash, (uint8_t*) &shape, sizeof(Shape));
+ return JenkinsHashWhiten(hash);
+}
+
+int PathDescription::compare(const PathDescription& rhs) const {
+ return memcmp(this, &rhs, sizeof(PathDescription));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Utilities
+///////////////////////////////////////////////////////////////////////////////
+
+bool PathCache::canDrawAsConvexPath(SkPath* path, SkPaint* paint) {
+ // NOTE: This should only be used after PathTessellator handles joins properly
+ return paint->getPathEffect() == NULL && path->getConvexity() == SkPath::kConvex_Convexity;
+}
+
+void PathCache::computePathBounds(const SkPath* path, const SkPaint* paint,
+ float& left, float& top, float& offset, uint32_t& width, uint32_t& height) {
+ const SkRect& bounds = path->getBounds();
+ PathCache::computeBounds(bounds, paint, left, top, offset, width, height);
+}
+
+void PathCache::computeBounds(const SkRect& bounds, const SkPaint* paint,
+ float& left, float& top, float& offset, uint32_t& width, uint32_t& height) {
+ const float pathWidth = fmax(bounds.width(), 1.0f);
+ const float pathHeight = fmax(bounds.height(), 1.0f);
+
+ left = bounds.fLeft;
+ top = bounds.fTop;
+
+ offset = (int) floorf(fmax(paint->getStrokeWidth(), 1.0f) * 1.5f + 0.5f);
+
+ width = uint32_t(pathWidth + offset * 2.0 + 0.5);
+ height = uint32_t(pathHeight + offset * 2.0 + 0.5);
+}
+
+static void initBitmap(SkBitmap& bitmap, uint32_t width, uint32_t height) {
+ bitmap.setConfig(SkBitmap::kA8_Config, width, height);
+ bitmap.allocPixels();
+ bitmap.eraseColor(0);
+}
+
+static void initPaint(SkPaint& paint) {
+ // Make sure the paint is opaque, color, alpha, filter, etc.
+ // will be applied later when compositing the alpha8 texture
+ paint.setColor(0xff000000);
+ paint.setAlpha(255);
+ paint.setColorFilter(NULL);
+ paint.setMaskFilter(NULL);
+ paint.setShader(NULL);
+ SkXfermode* mode = SkXfermode::Create(SkXfermode::kSrc_Mode);
+ SkSafeUnref(paint.setXfermode(mode));
+}
+
+static void drawPath(const SkPath *path, const SkPaint* paint, SkBitmap& bitmap,
+ float left, float top, float offset, uint32_t width, uint32_t height) {
+ initBitmap(bitmap, width, height);
+
+ SkPaint pathPaint(*paint);
+ initPaint(pathPaint);
+
+ SkCanvas canvas(bitmap);
+ canvas.translate(-left + offset, -top + offset);
+ canvas.drawPath(*path, pathPaint);
+}
+
+static PathTexture* createTexture(float left, float top, float offset,
+ uint32_t width, uint32_t height, uint32_t id) {
+ PathTexture* texture = new PathTexture();
+ texture->left = left;
+ texture->top = top;
+ texture->offset = offset;
+ texture->width = width;
+ texture->height = height;
+ texture->generation = id;
+ return texture;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Cache constructor/destructor
+///////////////////////////////////////////////////////////////////////////////
+
+PathCache::PathCache():
+ mCache(LruCache<PathDescription, PathTexture*>::kUnlimitedCapacity),
+ mSize(0), mMaxSize(MB(DEFAULT_PATH_CACHE_SIZE)) {
+ char property[PROPERTY_VALUE_MAX];
+ if (property_get(PROPERTY_PATH_CACHE_SIZE, 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, DEFAULT_PATH_CACHE_SIZE);
+ }
+ init();
+}
+
+PathCache::~PathCache() {
+ mCache.clear();
+}
+
+void PathCache::init() {
+ mCache.setOnEntryRemovedListener(this);
+
+ GLint maxTextureSize;
+ glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
+ mMaxTextureSize = maxTextureSize;
+
+ mDebugEnabled = readDebugLevel() & kDebugCaches;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Size management
+///////////////////////////////////////////////////////////////////////////////
+
+uint32_t PathCache::getSize() {
+ return mSize;
+}
+
+uint32_t PathCache::getMaxSize() {
+ return mMaxSize;
+}
+
+void PathCache::setMaxSize(uint32_t maxSize) {
+ mMaxSize = maxSize;
+ while (mSize > mMaxSize) {
+ mCache.removeOldest();
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Callbacks
+///////////////////////////////////////////////////////////////////////////////
+
+void PathCache::operator()(PathDescription& entry, PathTexture*& texture) {
+ removeTexture(texture);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Caching
+///////////////////////////////////////////////////////////////////////////////
+
+void PathCache::removeTexture(PathTexture* texture) {
+ if (texture) {
+ const uint32_t size = texture->width * texture->height;
+ mSize -= size;
+
+ PATH_LOGD("PathCache::delete name, size, mSize = %d, %d, %d",
+ texture->id, size, mSize);
+ if (mDebugEnabled) {
+ ALOGD("Shape deleted, size = %d", size);
+ }
+
+ if (texture->id) {
+ glDeleteTextures(1, &texture->id);
+ }
+ delete texture;
+ }
+}
+
+void PathCache::purgeCache(uint32_t width, uint32_t height) {
+ 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();
+ }
+ }
+}
+
+void PathCache::trim() {
+ while (mSize > mMaxSize) {
+ mCache.removeOldest();
+ }
+}
+
+PathTexture* PathCache::addTexture(const PathDescription& entry, const SkPath *path,
+ const SkPaint* paint) {
+ ATRACE_CALL();
+
+ float left, top, offset;
+ uint32_t width, height;
+ computePathBounds(path, paint, left, top, offset, width, height);
+
+ if (!checkTextureSize(width, height)) return NULL;
+
+ purgeCache(width, height);
+
+ SkBitmap bitmap;
+ drawPath(path, paint, bitmap, left, top, offset, width, height);
+
+ PathTexture* texture = createTexture(left, top, offset, width, height,
+ path->getGenerationID());
+ addTexture(entry, &bitmap, texture);
+
+ return texture;
+}
+
+void PathCache::addTexture(const PathDescription& entry, SkBitmap* bitmap, PathTexture* texture) {
+ generateTexture(*bitmap, texture);
+
+ uint32_t size = texture->width * texture->height;
+ if (size < mMaxSize) {
+ mSize += size;
+ PATH_LOGD("PathCache::get/create: name, size, mSize = %d, %d, %d",
+ texture->id, size, mSize);
+ if (mDebugEnabled) {
+ ALOGD("Shape created, size = %d", size);
+ }
+ mCache.put(entry, texture);
+ } else {
+ texture->cleanup = true;
+ }
+}
+
+void PathCache::clear() {
+ mCache.clear();
+}
+
+void PathCache::generateTexture(SkBitmap& bitmap, Texture* texture) {
+ SkAutoLockPixels alp(bitmap);
+ if (!bitmap.readyToDraw()) {
+ ALOGE("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());
+
+ texture->setFilter(GL_LINEAR);
+ texture->setWrap(GL_CLAMP_TO_EDGE);
+}
+
+///////////////////////////////////////////////////////////////////////////////
// Path precaching
///////////////////////////////////////////////////////////////////////////////
@@ -52,7 +334,7 @@ void PathCache::PathProcessor::onProcess(const sp<Task<SkBitmap*> >& task) {
if (width <= mMaxTextureSize && height <= mMaxTextureSize) {
SkBitmap* bitmap = new SkBitmap();
- PathCache::drawPath(t->path, t->paint, *bitmap, left, top, offset, width, height);
+ drawPath(t->path, t->paint, *bitmap, left, top, offset, width, height);
t->setResult(bitmap);
} else {
texture->width = 0;
@@ -62,23 +344,17 @@ void PathCache::PathProcessor::onProcess(const sp<Task<SkBitmap*> >& task) {
}
///////////////////////////////////////////////////////////////////////////////
-// Path cache
+// Paths
///////////////////////////////////////////////////////////////////////////////
-PathCache::PathCache(): ShapeCache<PathCacheEntry>("path",
- PROPERTY_PATH_CACHE_SIZE, DEFAULT_PATH_CACHE_SIZE) {
-}
-
-PathCache::~PathCache() {
-}
-
void PathCache::remove(SkPath* path) {
- Vector<PathCacheEntry> pathsToRemove;
- LruCache<PathCacheEntry, PathTexture*>::Iterator i(mCache);
+ Vector<PathDescription> pathsToRemove;
+ LruCache<PathDescription, PathTexture*>::Iterator i(mCache);
while (i.next()) {
- const PathCacheEntry& key = i.key();
- if (key.path == path || key.path == path->getSourcePath()) {
+ const PathDescription& key = i.key();
+ if (key.type == kShapePath &&
+ (key.shape.path.mPath == path || key.shape.path.mPath == path->getSourcePath())) {
pathsToRemove.push(key);
}
}
@@ -121,7 +397,9 @@ static SkPath* getSourcePath(SkPath* path) {
PathTexture* PathCache::get(SkPath* path, SkPaint* paint) {
path = getSourcePath(path);
- PathCacheEntry entry(path, paint);
+ PathDescription entry(kShapePath, paint);
+ entry.shape.path.mPath = path;
+
PathTexture* texture = mCache.get(entry);
if (!texture) {
@@ -159,7 +437,9 @@ void PathCache::precache(SkPath* path, SkPaint* paint) {
path = getSourcePath(path);
- PathCacheEntry entry(path, paint);
+ PathDescription entry(kShapePath, paint);
+ entry.shape.path.mPath = path;
+
PathTexture* texture = mCache.get(entry);
bool generate = false;
@@ -193,5 +473,130 @@ void PathCache::precache(SkPath* path, SkPaint* paint) {
}
}
+///////////////////////////////////////////////////////////////////////////////
+// Rounded rects
+///////////////////////////////////////////////////////////////////////////////
+
+PathTexture* PathCache::getRoundRect(float width, float height,
+ float rx, float ry, SkPaint* paint) {
+ PathDescription entry(kShapeRoundRect, paint);
+ entry.shape.roundRect.mWidth = width;
+ entry.shape.roundRect.mHeight = height;
+ entry.shape.roundRect.mRx = rx;
+ entry.shape.roundRect.mRy = ry;
+
+ PathTexture* texture = get(entry);
+
+ if (!texture) {
+ SkPath path;
+ SkRect r;
+ r.set(0.0f, 0.0f, width, height);
+ path.addRoundRect(r, rx, ry, SkPath::kCW_Direction);
+
+ texture = addTexture(entry, &path, paint);
+ }
+
+ return texture;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Circles
+///////////////////////////////////////////////////////////////////////////////
+
+PathTexture* PathCache::getCircle(float radius, SkPaint* paint) {
+ PathDescription entry(kShapeCircle, paint);
+ entry.shape.circle.mRadius = radius;
+
+ PathTexture* texture = get(entry);
+
+ if (!texture) {
+ SkPath path;
+ path.addCircle(radius, radius, radius, SkPath::kCW_Direction);
+
+ texture = addTexture(entry, &path, paint);
+ }
+
+ return texture;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Ovals
+///////////////////////////////////////////////////////////////////////////////
+
+PathTexture* PathCache::getOval(float width, float height, SkPaint* paint) {
+ PathDescription entry(kShapeOval, paint);
+ entry.shape.oval.mWidth = width;
+ entry.shape.oval.mHeight = height;
+
+ PathTexture* texture = get(entry);
+
+ if (!texture) {
+ SkPath path;
+ SkRect r;
+ r.set(0.0f, 0.0f, width, height);
+ path.addOval(r, SkPath::kCW_Direction);
+
+ texture = addTexture(entry, &path, paint);
+ }
+
+ return texture;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Rects
+///////////////////////////////////////////////////////////////////////////////
+
+PathTexture* PathCache::getRect(float width, float height, SkPaint* paint) {
+ PathDescription entry(kShapeRect, paint);
+ entry.shape.rect.mWidth = width;
+ entry.shape.rect.mHeight = height;
+
+ PathTexture* texture = get(entry);
+
+ if (!texture) {
+ SkPath path;
+ SkRect r;
+ r.set(0.0f, 0.0f, width, height);
+ path.addRect(r, SkPath::kCW_Direction);
+
+ texture = addTexture(entry, &path, paint);
+ }
+
+ return texture;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Arcs
+///////////////////////////////////////////////////////////////////////////////
+
+PathTexture* PathCache::getArc(float width, float height,
+ float startAngle, float sweepAngle, bool useCenter, SkPaint* paint) {
+ PathDescription entry(kShapeArc, paint);
+ entry.shape.arc.mWidth = width;
+ entry.shape.arc.mHeight = height;
+ entry.shape.arc.mStartAngle = startAngle;
+ entry.shape.arc.mSweepAngle = sweepAngle;
+ entry.shape.arc.mUseCenter = useCenter;
+
+ PathTexture* texture = get(entry);
+
+ if (!texture) {
+ SkPath path;
+ SkRect r;
+ r.set(0.0f, 0.0f, width, height);
+ if (useCenter) {
+ path.moveTo(r.centerX(), r.centerY());
+ }
+ path.arcTo(r, startAngle, sweepAngle, !useCenter);
+ if (useCenter) {
+ path.close();
+ }
+
+ texture = addTexture(entry, &path, paint);
+ }
+
+ return texture;
+}
+
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/PathCache.h b/libs/hwui/PathCache.h
index 27031a5..e6d92df 100644
--- a/libs/hwui/PathCache.h
+++ b/libs/hwui/PathCache.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2013 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.
@@ -17,17 +17,21 @@
#ifndef ANDROID_HWUI_PATH_CACHE_H
#define ANDROID_HWUI_PATH_CACHE_H
-#include <utils/Thread.h>
+#include <GLES2/gl2.h>
+
+#include <utils/LruCache.h>
+#include <utils/Mutex.h>
#include <utils/Vector.h>
#include "Debug.h"
-#include "ShapeCache.h"
-#include "thread/Signal.h"
-#include "thread/Task.h"
-#include "thread/TaskProcessor.h"
+#include "Properties.h"
+#include "Texture.h"
+class SkBitmap;
+class SkCanvas;
class SkPaint;
class SkPath;
+class SkRect;
namespace android {
namespace uirenderer {
@@ -35,56 +39,181 @@ namespace uirenderer {
class Caches;
///////////////////////////////////////////////////////////////////////////////
+// Defines
+///////////////////////////////////////////////////////////////////////////////
+
+// Debug
+#if DEBUG_PATHS
+ #define PATH_LOGD(...) ALOGD(__VA_ARGS__)
+#else
+ #define PATH_LOGD(...)
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
// Classes
///////////////////////////////////////////////////////////////////////////////
-struct PathCacheEntry: public ShapeCacheEntry {
- PathCacheEntry(SkPath* path, SkPaint* paint):
- ShapeCacheEntry(ShapeCacheEntry::kShapePath, paint) {
- this->path = path;
+/**
+ * Alpha texture used to represent a path.
+ */
+struct PathTexture: public Texture {
+ PathTexture(): Texture() {
+ }
+
+ ~PathTexture() {
+ clearTask();
+ }
+
+ /**
+ * 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;
+
+ sp<Task<SkBitmap*> > task() const {
+ return mTask;
}
- PathCacheEntry(): ShapeCacheEntry() {
- path = NULL;
+ void setTask(const sp<Task<SkBitmap*> >& task) {
+ mTask = task;
}
- hash_t hash() const {
- uint32_t hash = ShapeCacheEntry::hash();
- hash = JenkinsHashMix(hash, android::hash_type(path));
- return JenkinsHashWhiten(hash);
+ void clearTask() {
+ if (mTask != NULL) {
+ mTask.clear();
+ }
}
- int compare(const ShapeCacheEntry& r) const {
- int deltaInt = ShapeCacheEntry::compare(r);
- if (deltaInt != 0) return deltaInt;
+private:
+ sp<Task<SkBitmap*> > mTask;
+}; // struct PathTexture
+
+enum ShapeType {
+ kShapeNone,
+ kShapeRect,
+ kShapeRoundRect,
+ kShapeCircle,
+ kShapeOval,
+ kShapeArc,
+ kShapePath
+};
+
+struct PathDescription {
+ ShapeType type;
+ SkPaint::Join join;
+ SkPaint::Cap cap;
+ SkPaint::Style style;
+ float miter;
+ float strokeWidth;
+ SkPathEffect* pathEffect;
+ union Shape {
+ struct Path {
+ SkPath* mPath;
+ } path;
+ struct RoundRect {
+ float mWidth;
+ float mHeight;
+ float mRx;
+ float mRy;
+ } roundRect;
+ struct Circle {
+ float mRadius;
+ } circle;
+ struct Oval {
+ float mWidth;
+ float mHeight;
+ } oval;
+ struct Rect {
+ float mWidth;
+ float mHeight;
+ } rect;
+ struct Arc {
+ float mWidth;
+ float mHeight;
+ float mStartAngle;
+ float mSweepAngle;
+ bool mUseCenter;
+ } arc;
+ } shape;
+
+ PathDescription();
+ PathDescription(ShapeType shapeType, SkPaint* paint);
+
+ hash_t hash() const;
- const PathCacheEntry& rhs = (const PathCacheEntry&) r;
- return path - rhs.path;
+ int compare(const PathDescription& rhs) const;
+
+ bool operator==(const PathDescription& other) const {
+ return compare(other) == 0;
+ }
+
+ bool operator!=(const PathDescription& other) const {
+ return compare(other) != 0;
}
- SkPath* path;
+ friend inline int strictly_order_type(
+ const PathDescription& lhs, const PathDescription& rhs) {
+ return lhs.compare(rhs) < 0;
+ }
-}; // PathCacheEntry
+ friend inline int compare_type(const PathDescription& lhs, const PathDescription& rhs) {
+ return lhs.compare(rhs);
+ }
-inline hash_t hash_type(const PathCacheEntry& entry) {
- return entry.hash();
-}
+ friend inline hash_t hash_type(const PathDescription& entry) {
+ return entry.hash();
+ }
+};
/**
- * A simple LRU path cache. The cache has a maximum size expressed in bytes.
+ * 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.
*/
-class PathCache: public ShapeCache<PathCacheEntry> {
+class PathCache: public OnEntryRemoved<PathDescription, PathTexture*> {
public:
PathCache();
~PathCache();
/**
- * Returns the texture associated with the specified path. If the texture
- * cannot be found in the cache, a new texture is generated.
+ * Used as a callback when an entry is removed from the cache.
+ * Do not invoke directly.
*/
+ void operator()(PathDescription& 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();
+
+ PathTexture* getRoundRect(float width, float height, float rx, float ry, SkPaint* paint);
+ PathTexture* getCircle(float radius, SkPaint* paint);
+ PathTexture* getOval(float width, float height, SkPaint* paint);
+ PathTexture* getRect(float width, float height, SkPaint* paint);
+ PathTexture* getArc(float width, float height, float startAngle, float sweepAngle,
+ bool useCenter, SkPaint* paint);
PathTexture* get(SkPath* path, SkPaint* paint);
+
/**
* Removes an entry.
*/
@@ -98,10 +227,63 @@ public:
* Process deferred removals.
*/
void clearGarbage();
+ /**
+ * Trims the contents of the cache, removing items until it's under its
+ * specified limit.
+ *
+ * Trimming is used for caches that support pre-caching from a worker
+ * thread. During pre-caching the maximum limit of the cache can be
+ * exceeded for the duration of the frame. It is therefore required to
+ * trim the cache at the end of the frame to keep the total amount of
+ * memory used under control.
+ */
+ void trim();
+ /**
+ * Precaches the specified path using background threads.
+ */
void precache(SkPath* path, SkPaint* paint);
+ static bool canDrawAsConvexPath(SkPath* path, SkPaint* paint);
+ static void computePathBounds(const SkPath* path, const SkPaint* paint,
+ float& left, float& top, float& offset, uint32_t& width, uint32_t& height);
+ static void computeBounds(const SkRect& bounds, const SkPaint* paint,
+ float& left, float& top, float& offset, uint32_t& width, uint32_t& height);
+
private:
+ PathTexture* addTexture(const PathDescription& entry,
+ const SkPath *path, const SkPaint* paint);
+ PathTexture* addTexture(const PathDescription& entry, SkBitmap* bitmap);
+ void addTexture(const PathDescription& entry, SkBitmap* bitmap, PathTexture* texture);
+
+ PathTexture* get(const PathDescription& entry) {
+ return mCache.get(entry);
+ }
+
+ /**
+ * Ensures there is enough space in the cache for a texture of the specified
+ * dimensions.
+ */
+ void purgeCache(uint32_t width, uint32_t height);
+
+ void removeTexture(PathTexture* texture);
+
+ bool checkTextureSize(uint32_t width, uint32_t height) {
+ if (width > mMaxTextureSize || height > mMaxTextureSize) {
+ ALOGW("Shape too large to be rendered into a texture (%dx%d, max=%dx%d)",
+ width, height, mMaxTextureSize, mMaxTextureSize);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Generates the texture from a bitmap into the specified texture structure.
+ */
+ void generateTexture(SkBitmap& bitmap, Texture* texture);
+
+ void init();
+
class PathTask: public Task<SkBitmap*> {
public:
PathTask(SkPath* path, SkPaint* paint, PathTexture* texture):
@@ -128,6 +310,13 @@ private:
uint32_t mMaxTextureSize;
};
+ LruCache<PathDescription, PathTexture*> mCache;
+ uint32_t mSize;
+ uint32_t mMaxSize;
+ GLuint mMaxTextureSize;
+
+ bool mDebugEnabled;
+
sp<PathProcessor> mProcessor;
Vector<SkPath*> mGarbage;
mutable Mutex mLock;
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index b8559bc..5f39abf 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -115,7 +115,6 @@ enum DebugLevel {
#define PROPERTY_RENDER_BUFFER_CACHE_SIZE "ro.hwui.r_buffer_cache_size"
#define PROPERTY_GRADIENT_CACHE_SIZE "ro.hwui.gradient_cache_size"
#define PROPERTY_PATH_CACHE_SIZE "ro.hwui.path_cache_size"
-#define PROPERTY_SHAPE_CACHE_SIZE "ro.hwui.shape_cache_size"
#define PROPERTY_DROP_SHADOW_CACHE_SIZE "ro.hwui.drop_shadow_cache_size"
#define PROPERTY_FBO_CACHE_SIZE "ro.hwui.fbo_cache_size"
@@ -159,8 +158,7 @@ enum DebugLevel {
#define DEFAULT_TEXTURE_CACHE_SIZE 24.0f
#define DEFAULT_LAYER_CACHE_SIZE 16.0f
#define DEFAULT_RENDER_BUFFER_CACHE_SIZE 2.0f
-#define DEFAULT_PATH_CACHE_SIZE 4.0f
-#define DEFAULT_SHAPE_CACHE_SIZE 1.0f
+#define DEFAULT_PATH_CACHE_SIZE 10.0f
#define DEFAULT_PATCH_CACHE_SIZE 512
#define DEFAULT_GRADIENT_CACHE_SIZE 0.5f
#define DEFAULT_DROP_SHADOW_CACHE_SIZE 2.0f
diff --git a/libs/hwui/ShapeCache.cpp b/libs/hwui/ShapeCache.cpp
deleted file mode 100644
index 5a23235..0000000
--- a/libs/hwui/ShapeCache.cpp
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * 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.
- */
-
-#define LOG_TAG "OpenGLRenderer"
-
-#include "ShapeCache.h"
-
-namespace android {
-namespace uirenderer {
-
-///////////////////////////////////////////////////////////////////////////////
-// Rounded rects
-///////////////////////////////////////////////////////////////////////////////
-
-RoundRectShapeCache::RoundRectShapeCache(): ShapeCache<RoundRectShapeCacheEntry>(
- "round rect", PROPERTY_SHAPE_CACHE_SIZE, DEFAULT_SHAPE_CACHE_SIZE) {
-}
-
-PathTexture* RoundRectShapeCache::getRoundRect(float width, float height,
- float rx, float ry, SkPaint* paint) {
- RoundRectShapeCacheEntry entry(width, height, rx, ry, paint);
- PathTexture* texture = get(entry);
-
- if (!texture) {
- SkPath path;
- SkRect r;
- r.set(0.0f, 0.0f, width, height);
- path.addRoundRect(r, rx, ry, SkPath::kCW_Direction);
-
- texture = addTexture(entry, &path, paint);
- }
-
- return texture;
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Circles
-///////////////////////////////////////////////////////////////////////////////
-
-CircleShapeCache::CircleShapeCache(): ShapeCache<CircleShapeCacheEntry>(
- "circle", PROPERTY_SHAPE_CACHE_SIZE, DEFAULT_SHAPE_CACHE_SIZE) {
-}
-
-PathTexture* CircleShapeCache::getCircle(float radius, SkPaint* paint) {
- CircleShapeCacheEntry entry(radius, paint);
- PathTexture* texture = get(entry);
-
- if (!texture) {
- SkPath path;
- path.addCircle(radius, radius, radius, SkPath::kCW_Direction);
-
- texture = addTexture(entry, &path, paint);
- }
-
- return texture;
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Ovals
-///////////////////////////////////////////////////////////////////////////////
-
-OvalShapeCache::OvalShapeCache(): ShapeCache<OvalShapeCacheEntry>(
- "oval", PROPERTY_SHAPE_CACHE_SIZE, DEFAULT_SHAPE_CACHE_SIZE) {
-}
-
-PathTexture* OvalShapeCache::getOval(float width, float height, SkPaint* paint) {
- OvalShapeCacheEntry entry(width, height, paint);
- PathTexture* texture = get(entry);
-
- if (!texture) {
- SkPath path;
- SkRect r;
- r.set(0.0f, 0.0f, width, height);
- path.addOval(r, SkPath::kCW_Direction);
-
- texture = addTexture(entry, &path, paint);
- }
-
- return texture;
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Rects
-///////////////////////////////////////////////////////////////////////////////
-
-RectShapeCache::RectShapeCache(): ShapeCache<RectShapeCacheEntry>(
- "rect", PROPERTY_SHAPE_CACHE_SIZE, DEFAULT_SHAPE_CACHE_SIZE) {
-}
-
-PathTexture* RectShapeCache::getRect(float width, float height, SkPaint* paint) {
- RectShapeCacheEntry entry(width, height, paint);
- PathTexture* texture = get(entry);
-
- if (!texture) {
- SkRect bounds;
- bounds.set(0.0f, 0.0f, width, height);
-
- float left, top, offset;
- uint32_t rectWidth, rectHeight;
- computeBounds(bounds, paint, left, top, offset, rectWidth, rectHeight);
-
- if (!checkTextureSize(rectWidth, rectHeight)) return NULL;
-
- purgeCache(rectWidth, rectHeight);
-
- SkBitmap bitmap;
- initBitmap(bitmap, rectWidth, rectHeight);
-
- SkPaint pathPaint(*paint);
- initPaint(pathPaint);
-
- SkCanvas canvas(bitmap);
- canvas.translate(-left + offset, -top + offset);
- canvas.drawRect(bounds, pathPaint);
-
- texture = createTexture(0, 0, offset, rectWidth, rectHeight, 0);
- addTexture(entry, &bitmap, texture);
- }
-
- return texture;
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Arcs
-///////////////////////////////////////////////////////////////////////////////
-
-ArcShapeCache::ArcShapeCache(): ShapeCache<ArcShapeCacheEntry>(
- "arc", PROPERTY_SHAPE_CACHE_SIZE, DEFAULT_SHAPE_CACHE_SIZE) {
-}
-
-PathTexture* ArcShapeCache::getArc(float width, float height,
- float startAngle, float sweepAngle, bool useCenter, SkPaint* paint) {
- ArcShapeCacheEntry entry(width, height, startAngle, sweepAngle, useCenter, paint);
- PathTexture* texture = get(entry);
-
- if (!texture) {
- SkPath path;
- SkRect r;
- r.set(0.0f, 0.0f, width, height);
- if (useCenter) {
- path.moveTo(r.centerX(), r.centerY());
- }
- path.arcTo(r, startAngle, sweepAngle, !useCenter);
- if (useCenter) {
- path.close();
- }
-
- texture = addTexture(entry, &path, paint);
- }
-
- return texture;
-}
-
-}; // namespace uirenderer
-}; // namespace android
diff --git a/libs/hwui/ShapeCache.h b/libs/hwui/ShapeCache.h
deleted file mode 100644
index 92314b0..0000000
--- a/libs/hwui/ShapeCache.h
+++ /dev/null
@@ -1,816 +0,0 @@
-/*
- * 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
-
-#define ATRACE_TAG ATRACE_TAG_VIEW
-
-#include <GLES2/gl2.h>
-
-#include <SkBitmap.h>
-#include <SkCanvas.h>
-#include <SkPaint.h>
-#include <SkPath.h>
-#include <SkRect.h>
-
-#include <utils/JenkinsHash.h>
-#include <utils/LruCache.h>
-#include <utils/Trace.h>
-
-#include "Debug.h"
-#include "Properties.h"
-#include "Texture.h"
-#include "thread/Task.h"
-
-namespace android {
-namespace uirenderer {
-
-///////////////////////////////////////////////////////////////////////////////
-// Defines
-///////////////////////////////////////////////////////////////////////////////
-
-// Debug
-#if DEBUG_SHAPES
- #define SHAPE_LOGD(...) ALOGD(__VA_ARGS__)
-#else
- #define SHAPE_LOGD(...)
-#endif
-
-///////////////////////////////////////////////////////////////////////////////
-// Classes
-///////////////////////////////////////////////////////////////////////////////
-
-/**
- * Alpha texture used to represent a path.
- */
-struct PathTexture: public Texture {
- PathTexture(): Texture() {
- }
-
- ~PathTexture() {
- clearTask();
- }
-
- /**
- * 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;
-
- sp<Task<SkBitmap*> > task() const {
- return mTask;
- }
-
- void setTask(const sp<Task<SkBitmap*> >& task) {
- mTask = task;
- }
-
- void clearTask() {
- if (mTask != NULL) {
- mTask.clear();
- }
- }
-
-private:
- sp<Task<SkBitmap*> > mTask;
-}; // 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;
- miter = 4.0f;
- strokeWidth = 1.0f;
- pathEffect = NULL;
- }
-
- ShapeCacheEntry(ShapeType type, SkPaint* paint) {
- shapeType = type;
- join = paint->getStrokeJoin();
- cap = paint->getStrokeCap();
- miter = paint->getStrokeMiter();
- strokeWidth = paint->getStrokeWidth();
- style = paint->getStyle();
- pathEffect = paint->getPathEffect();
- }
-
- virtual ~ShapeCacheEntry() {
- }
-
- virtual hash_t hash() const {
- uint32_t hash = JenkinsHashMix(0, shapeType);
- hash = JenkinsHashMix(hash, join);
- hash = JenkinsHashMix(hash, cap);
- hash = JenkinsHashMix(hash, style);
- hash = JenkinsHashMix(hash, android::hash_type(miter));
- hash = JenkinsHashMix(hash, android::hash_type(strokeWidth));
- hash = JenkinsHashMix(hash, android::hash_type(pathEffect));
- return JenkinsHashWhiten(hash);
- }
-
- virtual int compare(const ShapeCacheEntry& rhs) const {
- int deltaInt = shapeType - rhs.shapeType;
- if (deltaInt != 0) return deltaInt;
-
- deltaInt = join - rhs.join;
- if (deltaInt != 0) return deltaInt;
-
- deltaInt = cap - rhs.cap;
- if (deltaInt != 0) return deltaInt;
-
- deltaInt = style - rhs.style;
- if (deltaInt != 0) return deltaInt;
-
- if (miter < rhs.miter) return -1;
- if (miter > rhs.miter) return +1;
-
- if (strokeWidth < rhs.strokeWidth) return -1;
- if (strokeWidth > rhs.strokeWidth) return +1;
-
- if (pathEffect < rhs.pathEffect) return -1;
- if (pathEffect > rhs.pathEffect) return +1;
-
- return 0;
- }
-
- bool operator==(const ShapeCacheEntry& other) const {
- return compare(other) == 0;
- }
-
- bool operator!=(const ShapeCacheEntry& other) const {
- return compare(other) != 0;
- }
-
- ShapeType shapeType;
- SkPaint::Join join;
- SkPaint::Cap cap;
- SkPaint::Style style;
- float miter;
- float strokeWidth;
- SkPathEffect* pathEffect;
-}; // struct ShapeCacheEntry
-
-// Cache support
-
-inline int strictly_order_type(const ShapeCacheEntry& lhs, const ShapeCacheEntry& rhs) {
- return lhs.compare(rhs) < 0;
-}
-
-inline int compare_type(const ShapeCacheEntry& lhs, const ShapeCacheEntry& rhs) {
- return lhs.compare(rhs);
-}
-
-inline hash_t hash_type(const ShapeCacheEntry& entry) {
- return entry.hash();
-}
-
-struct RoundRectShapeCacheEntry: public ShapeCacheEntry {
- RoundRectShapeCacheEntry(float width, float height, float rx, float ry, SkPaint* paint):
- ShapeCacheEntry(ShapeCacheEntry::kShapeRoundRect, paint) {
- mWidth = width;
- mHeight = height;
- mRx = rx;
- mRy = ry;
- }
-
- RoundRectShapeCacheEntry(): ShapeCacheEntry() {
- mWidth = 0;
- mHeight = 0;
- mRx = 0;
- mRy = 0;
- }
-
- hash_t hash() const {
- uint32_t hash = ShapeCacheEntry::hash();
- hash = JenkinsHashMix(hash, android::hash_type(mWidth));
- hash = JenkinsHashMix(hash, android::hash_type(mHeight));
- hash = JenkinsHashMix(hash, android::hash_type(mRx));
- hash = JenkinsHashMix(hash, android::hash_type(mRy));
- return JenkinsHashWhiten(hash);
- }
-
- int compare(const ShapeCacheEntry& r) const {
- int deltaInt = ShapeCacheEntry::compare(r);
- if (deltaInt != 0) return deltaInt;
-
- const RoundRectShapeCacheEntry& rhs = (const RoundRectShapeCacheEntry&) r;
-
- if (mWidth < rhs.mWidth) return -1;
- if (mWidth > rhs.mWidth) return +1;
-
- if (mHeight < rhs.mHeight) return -1;
- if (mHeight > rhs.mHeight) return +1;
-
- if (mRx < rhs.mRx) return -1;
- if (mRx > rhs.mRx) return +1;
-
- if (mRy < rhs.mRy) return -1;
- if (mRy > rhs.mRy) return +1;
-
- return 0;
- }
-
-private:
- float mWidth;
- float mHeight;
- float mRx;
- float mRy;
-}; // RoundRectShapeCacheEntry
-
-inline hash_t hash_type(const RoundRectShapeCacheEntry& entry) {
- return entry.hash();
-}
-
-struct CircleShapeCacheEntry: public ShapeCacheEntry {
- CircleShapeCacheEntry(float radius, SkPaint* paint):
- ShapeCacheEntry(ShapeCacheEntry::kShapeCircle, paint) {
- mRadius = radius;
- }
-
- CircleShapeCacheEntry(): ShapeCacheEntry() {
- mRadius = 0;
- }
-
- hash_t hash() const {
- uint32_t hash = ShapeCacheEntry::hash();
- hash = JenkinsHashMix(hash, android::hash_type(mRadius));
- return JenkinsHashWhiten(hash);
- }
-
- int compare(const ShapeCacheEntry& r) const {
- int deltaInt = ShapeCacheEntry::compare(r);
- if (deltaInt != 0) return deltaInt;
-
- const CircleShapeCacheEntry& rhs = (const CircleShapeCacheEntry&) r;
-
- if (mRadius < rhs.mRadius) return -1;
- if (mRadius > rhs.mRadius) return +1;
-
- return 0;
- }
-
-private:
- float mRadius;
-}; // CircleShapeCacheEntry
-
-inline hash_t hash_type(const CircleShapeCacheEntry& entry) {
- return entry.hash();
-}
-
-struct OvalShapeCacheEntry: public ShapeCacheEntry {
- OvalShapeCacheEntry(float width, float height, SkPaint* paint):
- ShapeCacheEntry(ShapeCacheEntry::kShapeOval, paint) {
- mWidth = width;
- mHeight = height;
- }
-
- OvalShapeCacheEntry(): ShapeCacheEntry() {
- mWidth = mHeight = 0;
- }
-
- hash_t hash() const {
- uint32_t hash = ShapeCacheEntry::hash();
- hash = JenkinsHashMix(hash, android::hash_type(mWidth));
- hash = JenkinsHashMix(hash, android::hash_type(mHeight));
- return JenkinsHashWhiten(hash);
- }
-
- int compare(const ShapeCacheEntry& r) const {
- int deltaInt = ShapeCacheEntry::compare(r);
- if (deltaInt != 0) return deltaInt;
-
- const OvalShapeCacheEntry& rhs = (const OvalShapeCacheEntry&) r;
-
- if (mWidth < rhs.mWidth) return -1;
- if (mWidth > rhs.mWidth) return +1;
-
- if (mHeight < rhs.mHeight) return -1;
- if (mHeight > rhs.mHeight) return +1;
-
- return 0;
- }
-
-private:
- float mWidth;
- float mHeight;
-}; // OvalShapeCacheEntry
-
-inline hash_t hash_type(const OvalShapeCacheEntry& entry) {
- return entry.hash();
-}
-
-struct RectShapeCacheEntry: public ShapeCacheEntry {
- RectShapeCacheEntry(float width, float height, SkPaint* paint):
- ShapeCacheEntry(ShapeCacheEntry::kShapeRect, paint) {
- mWidth = width;
- mHeight = height;
- }
-
- RectShapeCacheEntry(): ShapeCacheEntry() {
- mWidth = mHeight = 0;
- }
-
- hash_t hash() const {
- uint32_t hash = ShapeCacheEntry::hash();
- hash = JenkinsHashMix(hash, android::hash_type(mWidth));
- hash = JenkinsHashMix(hash, android::hash_type(mHeight));
- return JenkinsHashWhiten(hash);
- }
-
- int compare(const ShapeCacheEntry& r) const {
- int deltaInt = ShapeCacheEntry::compare(r);
- if (deltaInt != 0) return deltaInt;
-
- const RectShapeCacheEntry& rhs = (const RectShapeCacheEntry&) r;
-
- if (mWidth < rhs.mWidth) return -1;
- if (mWidth > rhs.mWidth) return +1;
-
- if (mHeight < rhs.mHeight) return -1;
- if (mHeight > rhs.mHeight) return +1;
-
- return 0;
- }
-
-private:
- float mWidth;
- float mHeight;
-}; // RectShapeCacheEntry
-
-inline hash_t hash_type(const RectShapeCacheEntry& entry) {
- return entry.hash();
-}
-
-struct ArcShapeCacheEntry: public ShapeCacheEntry {
- ArcShapeCacheEntry(float width, float height, float startAngle, float sweepAngle,
- bool useCenter, SkPaint* paint):
- ShapeCacheEntry(ShapeCacheEntry::kShapeArc, paint) {
- mWidth = width;
- mHeight = height;
- mStartAngle = startAngle;
- mSweepAngle = sweepAngle;
- mUseCenter = useCenter ? 1 : 0;
- }
-
- ArcShapeCacheEntry(): ShapeCacheEntry() {
- mWidth = 0;
- mHeight = 0;
- mStartAngle = 0;
- mSweepAngle = 0;
- mUseCenter = 0;
- }
-
- hash_t hash() const {
- uint32_t hash = ShapeCacheEntry::hash();
- hash = JenkinsHashMix(hash, android::hash_type(mWidth));
- hash = JenkinsHashMix(hash, android::hash_type(mHeight));
- hash = JenkinsHashMix(hash, android::hash_type(mStartAngle));
- hash = JenkinsHashMix(hash, android::hash_type(mSweepAngle));
- hash = JenkinsHashMix(hash, mUseCenter);
- return JenkinsHashWhiten(hash);
- }
-
- int compare(const ShapeCacheEntry& r) const {
- int deltaInt = ShapeCacheEntry::compare(r);
- if (deltaInt != 0) return deltaInt;
-
- const ArcShapeCacheEntry& rhs = (const ArcShapeCacheEntry&) r;
-
- if (mWidth < rhs.mWidth) return -1;
- if (mWidth > rhs.mWidth) return +1;
-
- if (mHeight < rhs.mHeight) return -1;
- if (mHeight > rhs.mHeight) return +1;
-
- if (mStartAngle < rhs.mStartAngle) return -1;
- if (mStartAngle > rhs.mStartAngle) return +1;
-
- if (mSweepAngle < rhs.mSweepAngle) return -1;
- if (mSweepAngle > rhs.mSweepAngle) return +1;
-
- return mUseCenter - rhs.mUseCenter;
- }
-
-private:
- float mWidth;
- float mHeight;
- float mStartAngle;
- float mSweepAngle;
- uint32_t mUseCenter;
-}; // ArcShapeCacheEntry
-
-inline hash_t hash_type(const ArcShapeCacheEntry& entry) {
- return entry.hash();
-}
-
-/**
- * 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();
-
- /**
- * Trims the contents of the cache, removing items until it's under its
- * specified limit.
- *
- * Trimming is used for caches that support pre-caching from a worker
- * thread. During pre-caching the maximum limit of the cache can be
- * exceeded for the duration of the frame. It is therefore required to
- * trim the cache at the end of the frame to keep the total amount of
- * memory used under control.
- *
- * Only the PathCache currently supports pre-caching.
- */
- void trim();
-
- static void computePathBounds(const SkPath* path, const SkPaint* paint,
- float& left, float& top, float& offset, uint32_t& width, uint32_t& height) {
- const SkRect& bounds = path->getBounds();
- computeBounds(bounds, paint, left, top, offset, width, height);
- }
-
- static void computeBounds(const SkRect& bounds, const SkPaint* paint,
- float& left, float& top, float& offset, uint32_t& width, uint32_t& height) {
- const float pathWidth = fmax(bounds.width(), 1.0f);
- const float pathHeight = fmax(bounds.height(), 1.0f);
-
- left = bounds.fLeft;
- top = bounds.fTop;
-
- offset = (int) floorf(fmax(paint->getStrokeWidth(), 1.0f) * 1.5f + 0.5f);
-
- width = uint32_t(pathWidth + offset * 2.0 + 0.5);
- height = uint32_t(pathHeight + offset * 2.0 + 0.5);
- }
-
- static void drawPath(const SkPath *path, const SkPaint* paint, SkBitmap& bitmap,
- float left, float top, float offset, uint32_t width, uint32_t height) {
- initBitmap(bitmap, width, height);
-
- SkPaint pathPaint(*paint);
- initPaint(pathPaint);
-
- SkCanvas canvas(bitmap);
- canvas.translate(-left + offset, -top + offset);
- canvas.drawPath(*path, pathPaint);
- }
-
-protected:
- PathTexture* addTexture(const Entry& entry, const SkPath *path, const SkPaint* paint);
- PathTexture* addTexture(const Entry& entry, SkBitmap* bitmap);
- void addTexture(const Entry& entry, SkBitmap* bitmap, PathTexture* texture);
-
- /**
- * Ensures there is enough space in the cache for a texture of the specified
- * dimensions.
- */
- void purgeCache(uint32_t width, uint32_t height);
-
- PathTexture* get(Entry entry) {
- return mCache.get(entry);
- }
-
- void removeTexture(PathTexture* texture);
-
- bool checkTextureSize(uint32_t width, uint32_t height) {
- if (width > mMaxTextureSize || height > mMaxTextureSize) {
- ALOGW("Shape %s too large to be rendered into a texture (%dx%d, max=%dx%d)",
- mName, width, height, mMaxTextureSize, mMaxTextureSize);
- return false;
- }
- return true;
- }
-
- static PathTexture* createTexture(float left, float top, float offset,
- uint32_t width, uint32_t height, uint32_t id) {
- PathTexture* texture = new PathTexture();
- texture->left = left;
- texture->top = top;
- texture->offset = offset;
- texture->width = width;
- texture->height = height;
- texture->generation = id;
- return texture;
- }
-
- static void initBitmap(SkBitmap& bitmap, uint32_t width, uint32_t height) {
- bitmap.setConfig(SkBitmap::kA8_Config, width, height);
- bitmap.allocPixels();
- bitmap.eraseColor(0);
- }
-
- static void initPaint(SkPaint& paint) {
- // Make sure the paint is opaque, color, alpha, filter, etc.
- // will be applied later when compositing the alpha8 texture
- paint.setColor(0xff000000);
- paint.setAlpha(255);
- paint.setColorFilter(NULL);
- paint.setMaskFilter(NULL);
- paint.setShader(NULL);
- SkXfermode* mode = SkXfermode::Create(SkXfermode::kSrc_Mode);
- SkSafeUnref(paint.setXfermode(mode));
- }
-
- LruCache<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(LruCache<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) {
- ALOGD("Shape %s deleted, size = %d", mName, size);
- }
-
- if (texture->id) {
- glDeleteTextures(1, &texture->id);
- }
- delete texture;
- }
-}
-
-template<class Entry>
-void ShapeCache<Entry>::purgeCache(uint32_t width, uint32_t height) {
- 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();
- }
- }
-}
-
-template<class Entry>
-void ShapeCache<Entry>::trim() {
- while (mSize > mMaxSize) {
- mCache.removeOldest();
- }
-}
-
-template<class Entry>
-PathTexture* ShapeCache<Entry>::addTexture(const Entry& entry, const SkPath *path,
- const SkPaint* paint) {
- ATRACE_CALL();
-
- float left, top, offset;
- uint32_t width, height;
- computePathBounds(path, paint, left, top, offset, width, height);
-
- if (!checkTextureSize(width, height)) return NULL;
-
- purgeCache(width, height);
-
- SkBitmap bitmap;
- drawPath(path, paint, bitmap, left, top, offset, width, height);
-
- PathTexture* texture = createTexture(left, top, offset, width, height,
- path->getGenerationID());
- addTexture(entry, &bitmap, texture);
-
- return texture;
-}
-
-template<class Entry>
-void ShapeCache<Entry>::addTexture(const Entry& entry, SkBitmap* bitmap, PathTexture* texture) {
- generateTexture(*bitmap, texture);
-
- uint32_t size = texture->width * texture->height;
- if (size < mMaxSize) {
- mSize += size;
- SHAPE_LOGD("ShapeCache::get: create %s: name, size, mSize = %d, %d, %d",
- mName, texture->id, size, mSize);
- if (mDebugEnabled) {
- ALOGD("Shape %s created, size = %d", mName, size);
- }
- mCache.put(entry, texture);
- } else {
- texture->cleanup = true;
- }
-}
-
-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()) {
- ALOGE("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());
-
- texture->setFilter(GL_LINEAR);
- texture->setWrap(GL_CLAMP_TO_EDGE);
-}
-
-}; // namespace uirenderer
-}; // namespace android
-
-#endif // ANDROID_HWUI_SHAPE_CACHE_H
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ShapesActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ShapesActivity.java
index 97e5526..61dca78 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/ShapesActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ShapesActivity.java
@@ -20,6 +20,7 @@ import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
+import android.graphics.Path;
import android.graphics.RectF;
import android.os.Bundle;
import android.view.View;
@@ -34,12 +35,13 @@ public class ShapesActivity extends Activity {
}
static class ShapesView extends View {
- private Paint mNormalPaint;
- private Paint mStrokePaint;
- private Paint mFillPaint;
- private RectF mRect;
- private RectF mOval;
- private RectF mArc;
+ private final Paint mNormalPaint;
+ private final Paint mStrokePaint;
+ private final Paint mFillPaint;
+ private final RectF mRect;
+ private final RectF mOval;
+ private final RectF mArc;
+ private final Path mTriangle;
ShapesView(Context c) {
super(c);
@@ -65,6 +67,12 @@ public class ShapesActivity extends Activity {
mOval = new RectF(0.0f, 0.0f, 80.0f, 45.0f);
mArc = new RectF(0.0f, 0.0f, 100.0f, 120.0f);
+
+ mTriangle = new Path();
+ mTriangle.moveTo(0.0f, 90.0f);
+ mTriangle.lineTo(45.0f, 0.0f);
+ mTriangle.lineTo(90.0f, 90.0f);
+ mTriangle.close();
}
@Override
@@ -136,6 +144,17 @@ public class ShapesActivity extends Activity {
canvas.translate(0.0f, 110.0f);
canvas.drawArc(mArc, 30.0f, 100.0f, false, mFillPaint);
canvas.restore();
+
+ canvas.save();
+ canvas.translate(50.0f, 400.0f);
+ canvas.drawPath(mTriangle, mNormalPaint);
+
+ canvas.translate(110.0f, 0.0f);
+ canvas.drawPath(mTriangle, mStrokePaint);
+
+ canvas.translate(110.0f, 0.0f);
+ canvas.drawPath(mTriangle, mFillPaint);
+ canvas.restore();
}
}
}