Precache paths from a worker thread

Change-Id: I3e7b53d67e0e03e403beaf55c39350ead7f1e309
This commit is contained in:
Romain Guy
2013-03-08 17:44:20 -08:00
parent 6e20040893
commit ca89e2a687
11 changed files with 484 additions and 86 deletions

View File

@@ -1005,7 +1005,7 @@ public:
: DrawBoundedOp(paint), mPath(path) {
float left, top, offset;
uint32_t width, height;
computePathBounds(path, paint, left, top, offset, width, height);
PathCache::computePathBounds(path, paint, left, top, offset, width, height);
left -= offset;
top -= offset;
mLocalBounds.set(left, top, left + width, top + height);
@@ -1016,6 +1016,11 @@ public:
return renderer.drawPath(mPath, getPaint(renderer));
}
virtual void onDrawOpDeferred(OpenGLRenderer& renderer) {
SkPaint* paint = getPaint(renderer);
renderer.getCaches().pathCache.precache(mPath, paint);
}
virtual void output(int level, uint32_t flags) {
OP_LOG("Draw Path %p in "RECT_STRING, mPath, RECT_ARGS(mLocalBounds));
}

View File

@@ -17,7 +17,6 @@
#define LOG_TAG "OpenGLRenderer"
#include <utils/JenkinsHash.h>
#include <utils/threads.h>
#include "Caches.h"
#include "Debug.h"

View File

@@ -276,6 +276,12 @@ void OpenGLRenderer::finish() {
renderOverdraw();
endTiling();
// When finish() is invoked on FBO 0 we've reached the end
// of the current frame
if (getTargetFbo() == 0) {
mCaches.pathCache.trim();
}
if (!suppressErrorChecks()) {
#if DEBUG_OPENGL
GLenum status = GL_NO_ERROR;

View File

@@ -16,34 +16,84 @@
#define LOG_TAG "OpenGLRenderer"
#include <utils/threads.h>
#include <utils/Mutex.h>
#include <sys/sysinfo.h>
#include "Caches.h"
#include "PathCache.h"
#include "Properties.h"
namespace android {
namespace uirenderer {
// Defined in ShapeCache.h
///////////////////////////////////////////////////////////////////////////////
// Path precaching
///////////////////////////////////////////////////////////////////////////////
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);
bool PathCache::PrecacheThread::threadLoop() {
mSignal.wait();
Vector<Task> tasks;
{
Mutex::Autolock l(mLock);
tasks = mTasks;
mTasks.clear();
}
Caches& caches = Caches::getInstance();
uint32_t maxSize = caches.maxTextureSize;
ATRACE_BEGIN("pathPrecache");
for (size_t i = 0; i < tasks.size(); i++) {
const Task& task = tasks.itemAt(i);
float left, top, offset;
uint32_t width, height;
PathCache::computePathBounds(task.path, task.paint, left, top, offset, width, height);
if (width <= maxSize && height <= maxSize) {
SkBitmap* bitmap = new SkBitmap();
PathTexture* texture = task.texture;
texture->left = left;
texture->top = top;
texture->offset = offset;
texture->width = width;
texture->height = height;
PathCache::drawPath(task.path, task.paint, *bitmap, left, top, offset, width, height);
texture->future()->produce(bitmap);
} else {
task.texture->future()->produce(NULL);
}
}
ATRACE_END();
return true;
}
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);
void PathCache::PrecacheThread::addTask(PathTexture* texture, SkPath* path, SkPaint* paint) {
if (!isRunning()) {
run("libhwui:pathPrecache", PRIORITY_DEFAULT);
}
left = bounds.fLeft;
top = bounds.fTop;
Task task;
task.texture = texture;
task.path = path;
task.paint = paint;
offset = (int) floorf(fmax(paint->getStrokeWidth(), 1.0f) * 1.5f + 0.5f);
Mutex::Autolock l(mLock);
mTasks.add(task);
mSignal.signal();
}
width = uint32_t(pathWidth + offset * 2.0 + 0.5);
height = uint32_t(pathHeight + offset * 2.0 + 0.5);
void PathCache::PrecacheThread::exit() {
{
Mutex::Autolock l(mLock);
mTasks.clear();
}
requestExit();
mSignal.signal();
}
///////////////////////////////////////////////////////////////////////////////
@@ -51,7 +101,11 @@ void computeBounds(const SkRect& bounds, const SkPaint* paint,
///////////////////////////////////////////////////////////////////////////////
PathCache::PathCache(): ShapeCache<PathCacheEntry>("path",
PROPERTY_PATH_CACHE_SIZE, DEFAULT_PATH_CACHE_SIZE) {
PROPERTY_PATH_CACHE_SIZE, DEFAULT_PATH_CACHE_SIZE), mThread(new PrecacheThread()) {
}
PathCache::~PathCache() {
mThread->exit();
}
void PathCache::remove(SkPath* path) {
@@ -60,7 +114,7 @@ void PathCache::remove(SkPath* path) {
while (i.next()) {
const PathCacheEntry& key = i.key();
if (key.path == path) {
if (key.path == path || key.path == path->getSourcePath()) {
pathsToRemove.push(key);
}
}
@@ -71,12 +125,12 @@ void PathCache::remove(SkPath* path) {
}
void PathCache::removeDeferred(SkPath* path) {
Mutex::Autolock _l(mLock);
Mutex::Autolock l(mLock);
mGarbage.push(path);
}
void PathCache::clearGarbage() {
Mutex::Autolock _l(mLock);
Mutex::Autolock l(mLock);
size_t count = mGarbage.size();
for (size_t i = 0; i < count; i++) {
remove(mGarbage.itemAt(i));
@@ -84,24 +138,86 @@ void PathCache::clearGarbage() {
mGarbage.clear();
}
PathTexture* PathCache::get(SkPath* path, SkPaint* paint) {
/**
* To properly handle path mutations at draw time we always make a copy
* of paths objects when recording display lists. The source path points
* to the path we originally copied the path from. This ensures we use
* the original path as a cache key the first time a path is inserted
* in the cache. The source path is also used to reclaim garbage when a
* Dalvik Path object is collected.
*/
static SkPath* getSourcePath(SkPath* path) {
const SkPath* sourcePath = path->getSourcePath();
if (sourcePath && sourcePath->getGenerationID() == path->getGenerationID()) {
path = const_cast<SkPath*>(sourcePath);
return const_cast<SkPath*>(sourcePath);
}
return path;
}
PathTexture* PathCache::get(SkPath* path, SkPaint* paint) {
path = getSourcePath(path);
PathCacheEntry entry(path, paint);
PathTexture* texture = mCache.get(entry);
if (!texture) {
texture = addTexture(entry, path, paint);
} else if (path->getGenerationID() != texture->generation) {
mCache.remove(entry);
texture = addTexture(entry, path, paint);
} else {
// A bitmap is attached to the texture, this means we need to
// upload it as a GL texture
if (texture->future() != NULL) {
// But we must first wait for the worker thread to be done
// producing the bitmap, so let's wait
SkBitmap* bitmap = texture->future()->get();
if (bitmap) {
addTexture(entry, bitmap, texture);
texture->clearFuture();
} else {
ALOGW("Path too large to be rendered into a texture (%dx%d)",
texture->width, texture->height);
texture->clearFuture();
texture = NULL;
mCache.remove(entry);
}
} else if (path->getGenerationID() != texture->generation) {
mCache.remove(entry);
texture = addTexture(entry, path, paint);
}
}
return texture;
}
void PathCache::precache(SkPath* path, SkPaint* paint) {
path = getSourcePath(path);
PathCacheEntry entry(path, paint);
PathTexture* texture = mCache.get(entry);
bool generate = false;
if (!texture) {
generate = true;
} else if (path->getGenerationID() != texture->generation) {
mCache.remove(entry);
generate = true;
}
if (generate) {
// It is important to specify the generation ID so we do not
// attempt to precache the same path several times
texture = createTexture(0.0f, 0.0f, 0.0f, 0, 0, path->getGenerationID(), true);
// During the precaching phase we insert path texture objects into
// the cache that do not point to any GL texture. They are instead
// treated as a task for the precaching worker thread. This is why
// we do not check the cache limit when inserting these objects.
// The conversion into GL texture will happen in get(), when a client
// asks for a path texture. This is also when the cache limit will
// be enforced.
mCache.put(entry, texture);
mThread->addTask(texture, path, paint);
}
}
}; // namespace uirenderer
}; // namespace android

View File

@@ -17,14 +17,21 @@
#ifndef ANDROID_HWUI_PATH_CACHE_H
#define ANDROID_HWUI_PATH_CACHE_H
#include <utils/Thread.h>
#include <utils/Vector.h>
#include "Debug.h"
#include "ShapeCache.h"
#include "thread/Signal.h"
class SkPaint;
class SkPath;
namespace android {
namespace uirenderer {
class Caches;
///////////////////////////////////////////////////////////////////////////////
// Classes
///////////////////////////////////////////////////////////////////////////////
@@ -69,6 +76,7 @@ inline hash_t hash_type(const PathCacheEntry& entry) {
class PathCache: public ShapeCache<PathCacheEntry> {
public:
PathCache();
~PathCache();
/**
* Returns the texture associated with the specified path. If the texture
@@ -89,7 +97,35 @@ public:
*/
void clearGarbage();
void precache(SkPath* path, SkPaint* paint);
private:
class PrecacheThread: public Thread {
public:
PrecacheThread(): mSignal(Condition::WAKE_UP_ONE) { }
void addTask(PathTexture* texture, SkPath* path, SkPaint* paint);
void exit();
private:
struct Task {
PathTexture* texture;
SkPath* path;
SkPaint* paint;
};
virtual bool threadLoop();
// Lock for the list of tasks
Mutex mLock;
Vector<Task> mTasks;
// Signal used to wake up the thread when a new
// task is available in the list
mutable Signal mSignal;
};
sp<PrecacheThread> mThread;
Vector<SkPath*> mGarbage;
mutable Mutex mLock;
}; // class PathCache

View File

@@ -17,6 +17,8 @@
#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>
@@ -27,10 +29,13 @@
#include <utils/JenkinsHash.h>
#include <utils/LruCache.h>
#include <utils/Trace.h>
#include <utils/CallStack.h>
#include "Debug.h"
#include "Properties.h"
#include "Texture.h"
#include "thread/Future.h"
namespace android {
namespace uirenderer {
@@ -57,6 +62,16 @@ struct PathTexture: public Texture {
PathTexture(): Texture() {
}
PathTexture(bool hasFuture): Texture() {
if (hasFuture) {
mFuture = new Future<SkBitmap*>();
}
}
~PathTexture() {
clearFuture();
}
/**
* Left coordinate of the path bounds.
*/
@@ -69,6 +84,20 @@ struct PathTexture: public Texture {
* Offset to draw the path at the correct origin.
*/
float offset;
sp<Future<SkBitmap*> > future() const {
return mFuture;
}
void clearFuture() {
if (mFuture != NULL) {
delete mFuture->get();
mFuture.clear();
}
}
private:
sp<Future<SkBitmap*> > mFuture;
}; // struct PathTexture
/**
@@ -449,6 +478,52 @@ public:
*/
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);
@@ -460,17 +535,51 @@ protected:
*/
void purgeCache(uint32_t width, uint32_t height);
void initBitmap(SkBitmap& bitmap, uint32_t width, uint32_t height);
void initPaint(SkPaint& paint);
bool checkTextureSize(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, bool hasFuture = false) {
PathTexture* texture = new PathTexture(hasFuture);
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;
@@ -617,23 +726,6 @@ void ShapeCache<Entry>::removeTexture(PathTexture* texture) {
}
}
void computePathBounds(const SkPath* path, const SkPaint* paint,
float& left, float& top, float& offset, uint32_t& width, uint32_t& height);
void computeBounds(const SkRect& bounds, const SkPaint* paint,
float& left, float& top, float& offset, uint32_t& width, uint32_t& height);
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;
}
template<class Entry>
void ShapeCache<Entry>::purgeCache(uint32_t width, uint32_t height) {
const uint32_t size = width * height;
@@ -646,38 +738,16 @@ void ShapeCache<Entry>::purgeCache(uint32_t width, uint32_t height) {
}
template<class Entry>
void ShapeCache<Entry>::initBitmap(SkBitmap& bitmap, uint32_t width, uint32_t height) {
bitmap.setConfig(SkBitmap::kA8_Config, width, height);
bitmap.allocPixels();
bitmap.eraseColor(0);
}
template<class Entry>
void ShapeCache<Entry>::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));
}
template<class Entry>
bool ShapeCache<Entry>::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;
void ShapeCache<Entry>::trim() {
while (mSize > mMaxSize) {
mCache.removeOldest();
}
return true;
}
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;
@@ -688,16 +758,10 @@ PathTexture* ShapeCache<Entry>::addTexture(const Entry& entry, const SkPath *pat
purgeCache(width, height);
SkBitmap bitmap;
initBitmap(bitmap, width, height);
drawPath(path, paint, bitmap, left, top, offset, width, height);
SkPaint pathPaint(*paint);
initPaint(pathPaint);
SkCanvas canvas(bitmap);
canvas.translate(-left + offset, -top + offset);
canvas.drawPath(*path, pathPaint);
PathTexture* texture = createTexture(left, top, offset, width, height, path->getGenerationID());
PathTexture* texture = createTexture(left, top, offset, width, height,
path->getGenerationID());
addTexture(entry, &bitmap, texture);
return texture;

View File

@@ -20,7 +20,7 @@
#include <SkCanvas.h>
#include <utils/threads.h>
#include <utils/Mutex.h>
#include "Caches.h"
#include "TextureCache.h"

View File

@@ -0,0 +1,58 @@
/*
* 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.
* 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_BARRIER_H
#define ANDROID_HWUI_BARRIER_H
#include <utils/Condition.h>
namespace android {
namespace uirenderer {
class Barrier {
public:
Barrier(Condition::WakeUpType type = Condition::WAKE_UP_ALL) : mType(type), mOpened(false) { }
~Barrier() { }
void open() {
Mutex::Autolock l(mLock);
mOpened = true;
mCondition.signal(mType);
}
void close() {
Mutex::Autolock l(mLock);
mOpened = false;
}
void wait() const {
Mutex::Autolock l(mLock);
while (!mOpened) {
mCondition.wait(mLock);
}
}
private:
Condition::WakeUpType mType;
volatile bool mOpened;
mutable Mutex mLock;
mutable Condition mCondition;
};
}; // namespace uirenderer
}; // namespace android
#endif // ANDROID_HWUI_BARRIER_H

58
libs/hwui/thread/Future.h Normal file
View File

@@ -0,0 +1,58 @@
/*
* 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.
* 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_FUTURE_H
#define ANDROID_HWUI_FUTURE_H
#include <utils/RefBase.h>
#include "Barrier.h"
namespace android {
namespace uirenderer {
template<class T>
class Future: public LightRefBase<Future<T> > {
public:
Future(Condition::WakeUpType type = Condition::WAKE_UP_ONE): mBarrier(type), mResult() { }
~Future() { }
/**
* Returns the result of this future, blocking if
* the result is not available yet.
*/
T get() const {
mBarrier.wait();
return mResult;
}
/**
* This method must be called only once.
*/
void produce(T result) {
mResult = result;
mBarrier.open();
}
private:
Barrier mBarrier;
T mResult;
};
}; // namespace uirenderer
}; // namespace android
#endif // ANDROID_HWUI_FUTURE_H

56
libs/hwui/thread/Signal.h Normal file
View File

@@ -0,0 +1,56 @@
/*
* 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.
* 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_SIGNAL_H
#define ANDROID_HWUI_SIGNAL_H
#include <stdint.h>
#include <sys/types.h>
#include <utils/threads.h>
namespace android {
namespace uirenderer {
class Signal {
public:
Signal(Condition::WakeUpType type = Condition::WAKE_UP_ALL) : mType(type), mSignaled(false) { }
~Signal() { }
void signal() {
Mutex::Autolock l(mLock);
mSignaled = true;
mCondition.signal(mType);
}
void wait() {
Mutex::Autolock l(mLock);
while (!mSignaled) {
mCondition.wait(mLock);
}
mSignaled = false;
}
private:
Condition::WakeUpType mType;
volatile bool mSignaled;
mutable Mutex mLock;
mutable Condition mCondition;
};
}; // namespace uirenderer
}; // namespace android
#endif // ANDROID_HWUI_SIGNAL_H

View File

@@ -45,13 +45,13 @@ public class PathsCacheActivity extends Activity {
setContentView(view);
}
private Path makePath() {
private static Path makePath() {
Path path = new Path();
buildPath(path);
return path;
}
private void buildPath(Path path) {
private static void buildPath(Path path) {
path.moveTo(0.0f, 0.0f);
path.cubicTo(0.0f, 0.0f, 100.0f, 150.0f, 100.0f, 200.0f);
path.cubicTo(100.0f, 200.0f, 50.0f, 300.0f, -80.0f, 200.0f);