diff options
Diffstat (limited to 'libs/hwui/TessellationCache.cpp')
-rw-r--r-- | libs/hwui/TessellationCache.cpp | 481 |
1 files changed, 481 insertions, 0 deletions
diff --git a/libs/hwui/TessellationCache.cpp b/libs/hwui/TessellationCache.cpp new file mode 100644 index 000000000000..08b54ff0f619 --- /dev/null +++ b/libs/hwui/TessellationCache.cpp @@ -0,0 +1,481 @@ +/* + * Copyright (C) 2014 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" +#define ATRACE_TAG ATRACE_TAG_VIEW + +#include <utils/JenkinsHash.h> +#include <utils/Trace.h> + +#include "Caches.h" +#include "OpenGLRenderer.h" +#include "PathTessellator.h" +#include "ShadowTessellator.h" +#include "TessellationCache.h" + +#include "thread/Signal.h" +#include "thread/Task.h" +#include "thread/TaskProcessor.h" + +namespace android { +namespace uirenderer { + +/////////////////////////////////////////////////////////////////////////////// +// Cache entries +/////////////////////////////////////////////////////////////////////////////// + +TessellationCache::Description::Description() + : type(kNone) + , scaleX(1.0f) + , scaleY(1.0f) + , aa(false) + , cap(SkPaint::kDefault_Cap) + , style(SkPaint::kFill_Style) + , strokeWidth(1.0f) { + memset(&shape, 0, sizeof(Shape)); +} + +TessellationCache::Description::Description(Type type, const Matrix4& transform, const SkPaint& paint) + : type(type) + , aa(paint.isAntiAlias()) + , cap(paint.getStrokeCap()) + , style(paint.getStyle()) + , strokeWidth(paint.getStrokeWidth()) { + PathTessellator::extractTessellationScales(transform, &scaleX, &scaleY); + memset(&shape, 0, sizeof(Shape)); +} + +hash_t TessellationCache::Description::hash() const { + uint32_t hash = JenkinsHashMix(0, type); + hash = JenkinsHashMix(hash, aa); + hash = JenkinsHashMix(hash, cap); + hash = JenkinsHashMix(hash, style); + hash = JenkinsHashMix(hash, android::hash_type(strokeWidth)); + hash = JenkinsHashMix(hash, android::hash_type(scaleX)); + hash = JenkinsHashMix(hash, android::hash_type(scaleY)); + hash = JenkinsHashMixBytes(hash, (uint8_t*) &shape, sizeof(Shape)); + return JenkinsHashWhiten(hash); +} + +void TessellationCache::Description::setupMatrixAndPaint(Matrix4* matrix, SkPaint* paint) const { + matrix->loadScale(scaleX, scaleY, 1.0f); + paint->setAntiAlias(aa); + paint->setStrokeCap(cap); + paint->setStyle(style); + paint->setStrokeWidth(strokeWidth); +} + +TessellationCache::ShadowDescription::ShadowDescription() + : nodeKey(NULL) { + memset(&matrixData, 0, 16 * sizeof(float)); +} + +TessellationCache::ShadowDescription::ShadowDescription(const void* nodeKey, const Matrix4* drawTransform) + : nodeKey(nodeKey) { + memcpy(&matrixData, drawTransform->data, 16 * sizeof(float)); +} + +hash_t TessellationCache::ShadowDescription::hash() const { + uint32_t hash = JenkinsHashMixBytes(0, (uint8_t*) &nodeKey, sizeof(const void*)); + hash = JenkinsHashMixBytes(hash, (uint8_t*) &matrixData, 16 * sizeof(float)); + return JenkinsHashWhiten(hash); +} + +/////////////////////////////////////////////////////////////////////////////// +// General purpose tessellation task processing +/////////////////////////////////////////////////////////////////////////////// + +class TessellationCache::TessellationTask : public Task<VertexBuffer*> { +public: + TessellationTask(Tessellator tessellator, const Description& description) + : tessellator(tessellator) + , description(description) { + } + + ~TessellationTask() {} + + Tessellator tessellator; + Description description; +}; + +class TessellationCache::TessellationProcessor : public TaskProcessor<VertexBuffer*> { +public: + TessellationProcessor(Caches& caches) + : TaskProcessor<VertexBuffer*>(&caches.tasks) {} + ~TessellationProcessor() {} + + virtual void onProcess(const sp<Task<VertexBuffer*> >& task) { + TessellationTask* t = static_cast<TessellationTask*>(task.get()); + ATRACE_NAME("shape tessellation"); + VertexBuffer* buffer = t->tessellator(t->description); + t->setResult(buffer); + } +}; + +struct TessellationCache::Buffer { +public: + Buffer(const sp<Task<VertexBuffer*> >& task) + : mTask(task) + , mBuffer(NULL) { + } + + ~Buffer() { + mTask.clear(); + delete mBuffer; + } + + unsigned int getSize() { + blockOnPrecache(); + return mBuffer->getSize(); + } + + const VertexBuffer* getVertexBuffer() { + blockOnPrecache(); + return mBuffer; + } + +private: + void blockOnPrecache() { + if (mTask != NULL) { + mBuffer = mTask->getResult(); + LOG_ALWAYS_FATAL_IF(mBuffer == NULL, "Failed to precache"); + mTask.clear(); + } + } + sp<Task<VertexBuffer*> > mTask; + VertexBuffer* mBuffer; +}; + +/////////////////////////////////////////////////////////////////////////////// +// Shadow tessellation task processing +/////////////////////////////////////////////////////////////////////////////// + +class ShadowTask : public Task<TessellationCache::vertexBuffer_pair_t*> { +public: + ShadowTask(const Matrix4* drawTransform, const Rect& localClip, bool opaque, + const SkPath* casterPerimeter, const Matrix4* transformXY, const Matrix4* transformZ, + const Vector3& lightCenter, float lightRadius) + : drawTransform(*drawTransform) + , localClip(localClip) + , opaque(opaque) + , casterPerimeter(*casterPerimeter) + , transformXY(*transformXY) + , transformZ(*transformZ) + , lightCenter(lightCenter) + , lightRadius(lightRadius) { + } + + ~ShadowTask() { + TessellationCache::vertexBuffer_pair_t* bufferPair = getResult(); + delete bufferPair->getFirst(); + delete bufferPair->getSecond(); + delete bufferPair; + } + + /* Note - we deep copy all task parameters, because *even though* pointers into Allocator + * controlled objects (like the SkPath and Matrix4s) should be safe for the entire frame, + * certain Allocators are destroyed before trim() is called to flush incomplete tasks. + * + * These deep copies could be avoided, long term, by cancelling or flushing outstanding tasks + * before tearning down single-frame LinearAllocators. + */ + const Matrix4 drawTransform; + const Rect localClip; + bool opaque; + const SkPath casterPerimeter; + const Matrix4 transformXY; + const Matrix4 transformZ; + const Vector3 lightCenter; + const float lightRadius; +}; + +static void mapPointFakeZ(Vector3& point, const mat4* transformXY, const mat4* transformZ) { + // map z coordinate with true 3d matrix + point.z = transformZ->mapZ(point); + + // map x,y coordinates with draw/Skia matrix + transformXY->mapPoint(point.x, point.y); +} + +static void tessellateShadows( + const Matrix4* drawTransform, const Rect* localClip, + bool isCasterOpaque, const SkPath* casterPerimeter, + const Matrix4* casterTransformXY, const Matrix4* casterTransformZ, + const Vector3& lightCenter, float lightRadius, + VertexBuffer& ambientBuffer, VertexBuffer& spotBuffer) { + + // tessellate caster outline into a 2d polygon + Vector<Vertex> casterVertices2d; + const float casterRefinementThresholdSquared = 20.0f; // TODO: experiment with this value + PathTessellator::approximatePathOutlineVertices(*casterPerimeter, + casterRefinementThresholdSquared, casterVertices2d); + if (!ShadowTessellator::isClockwisePath(*casterPerimeter)) { + ShadowTessellator::reverseVertexArray(casterVertices2d.editArray(), + casterVertices2d.size()); + } + + if (casterVertices2d.size() == 0) return; + + // map 2d caster poly into 3d + const int casterVertexCount = casterVertices2d.size(); + Vector3 casterPolygon[casterVertexCount]; + float minZ = FLT_MAX; + float maxZ = -FLT_MAX; + for (int i = 0; i < casterVertexCount; i++) { + const Vertex& point2d = casterVertices2d[i]; + casterPolygon[i] = Vector3(point2d.x, point2d.y, 0); + mapPointFakeZ(casterPolygon[i], casterTransformXY, casterTransformZ); + minZ = fmin(minZ, casterPolygon[i].z); + maxZ = fmax(maxZ, casterPolygon[i].z); + } + + // map the centroid of the caster into 3d + Vector2 centroid = ShadowTessellator::centroid2d( + reinterpret_cast<const Vector2*>(casterVertices2d.array()), + casterVertexCount); + Vector3 centroid3d(centroid.x, centroid.y, 0); + mapPointFakeZ(centroid3d, casterTransformXY, casterTransformZ); + + // if the caster intersects the z=0 plane, lift it in Z so it doesn't + if (minZ < SHADOW_MIN_CASTER_Z) { + float casterLift = SHADOW_MIN_CASTER_Z - minZ; + for (int i = 0; i < casterVertexCount; i++) { + casterPolygon[i].z += casterLift; + } + centroid3d.z += casterLift; + } + + // Check whether we want to draw the shadow at all by checking the caster's bounds against clip. + // We only have ortho projection, so we can just ignore the Z in caster for + // simple rejection calculation. + Rect casterBounds(casterPerimeter->getBounds()); + casterTransformXY->mapRect(casterBounds); + + // actual tessellation of both shadows + ShadowTessellator::tessellateAmbientShadow( + isCasterOpaque, casterPolygon, casterVertexCount, centroid3d, + casterBounds, *localClip, maxZ, ambientBuffer); + + ShadowTessellator::tessellateSpotShadow( + isCasterOpaque, casterPolygon, casterVertexCount, + *drawTransform, lightCenter, lightRadius, casterBounds, *localClip, + spotBuffer); + + // TODO: set ambientBuffer & spotBuffer's bounds for correct layer damage +} + +class ShadowProcessor : public TaskProcessor<TessellationCache::vertexBuffer_pair_t*> { +public: + ShadowProcessor(Caches& caches) + : TaskProcessor<TessellationCache::vertexBuffer_pair_t*>(&caches.tasks) {} + ~ShadowProcessor() {} + + virtual void onProcess(const sp<Task<TessellationCache::vertexBuffer_pair_t*> >& task) { + ShadowTask* t = static_cast<ShadowTask*>(task.get()); + ATRACE_NAME("shadow tessellation"); + + VertexBuffer* ambientBuffer = new VertexBuffer; + VertexBuffer* spotBuffer = new VertexBuffer; + tessellateShadows(&t->drawTransform, &t->localClip, t->opaque, &t->casterPerimeter, + &t->transformXY, &t->transformZ, t->lightCenter, t->lightRadius, + *ambientBuffer, *spotBuffer); + + t->setResult(new TessellationCache::vertexBuffer_pair_t(ambientBuffer, spotBuffer)); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// Cache constructor/destructor +/////////////////////////////////////////////////////////////////////////////// + +TessellationCache::TessellationCache() + : mSize(0) + , mMaxSize(MB(DEFAULT_VERTEX_CACHE_SIZE)) + , mCache(LruCache<Description, Buffer*>::kUnlimitedCapacity) + , mShadowCache(LruCache<ShadowDescription, Task<vertexBuffer_pair_t*>*>::kUnlimitedCapacity) { + char property[PROPERTY_VALUE_MAX]; + if (property_get(PROPERTY_VERTEX_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_VERTEX_CACHE_SIZE); + } + + mCache.setOnEntryRemovedListener(&mBufferRemovedListener); + mShadowCache.setOnEntryRemovedListener(&mBufferPairRemovedListener); + mDebugEnabled = readDebugLevel() & kDebugCaches; +} + +TessellationCache::~TessellationCache() { + mCache.clear(); +} + +/////////////////////////////////////////////////////////////////////////////// +// Size management +/////////////////////////////////////////////////////////////////////////////// + +uint32_t TessellationCache::getSize() { + LruCache<Description, Buffer*>::Iterator iter(mCache); + uint32_t size = 0; + while (iter.next()) { + size += iter.value()->getSize(); + } + return size; +} + +uint32_t TessellationCache::getMaxSize() { + return mMaxSize; +} + +void TessellationCache::setMaxSize(uint32_t maxSize) { + mMaxSize = maxSize; + while (mSize > mMaxSize) { + mCache.removeOldest(); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// Caching +/////////////////////////////////////////////////////////////////////////////// + + +void TessellationCache::trim() { + uint32_t size = getSize(); + while (size > mMaxSize) { + size -= mCache.peekOldestValue()->getSize(); + mCache.removeOldest(); + } + mShadowCache.clear(); +} + +void TessellationCache::clear() { + mCache.clear(); + mShadowCache.clear(); +} + +/////////////////////////////////////////////////////////////////////////////// +// Callbacks +/////////////////////////////////////////////////////////////////////////////// + +void TessellationCache::BufferRemovedListener::operator()(Description& description, + Buffer*& buffer) { + delete buffer; +} + +/////////////////////////////////////////////////////////////////////////////// +// Shadows +/////////////////////////////////////////////////////////////////////////////// + +void TessellationCache::precacheShadows(const Matrix4* drawTransform, const Rect& localClip, + bool opaque, const SkPath* casterPerimeter, + const Matrix4* transformXY, const Matrix4* transformZ, + const Vector3& lightCenter, float lightRadius) { + ShadowDescription key(casterPerimeter, drawTransform); + + sp<ShadowTask> task = new ShadowTask(drawTransform, localClip, opaque, + casterPerimeter, transformXY, transformZ, lightCenter, lightRadius); + if (mShadowProcessor == NULL) { + mShadowProcessor = new ShadowProcessor(Caches::getInstance()); + } + mShadowProcessor->add(task); + + task->incStrong(NULL); // not using sp<>s, so manually ref while in the cache + mShadowCache.put(key, task.get()); +} + +void TessellationCache::getShadowBuffers(const Matrix4* drawTransform, const Rect& localClip, + bool opaque, const SkPath* casterPerimeter, + const Matrix4* transformXY, const Matrix4* transformZ, + const Vector3& lightCenter, float lightRadius, vertexBuffer_pair_t& outBuffers) { + ShadowDescription key(casterPerimeter, drawTransform); + ShadowTask* task = static_cast<ShadowTask*>(mShadowCache.get(key)); + if (!task) { + precacheShadows(drawTransform, localClip, opaque, casterPerimeter, + transformXY, transformZ, lightCenter, lightRadius); + task = static_cast<ShadowTask*>(mShadowCache.get(key)); + } + LOG_ALWAYS_FATAL_IF(task == NULL, "shadow not precached"); + outBuffers = *(task->getResult()); +} + +/////////////////////////////////////////////////////////////////////////////// +// Tessellation precaching +/////////////////////////////////////////////////////////////////////////////// + +TessellationCache::Buffer* TessellationCache::getOrCreateBuffer( + const Description& entry, Tessellator tessellator) { + Buffer* buffer = mCache.get(entry); + if (!buffer) { + // not cached, enqueue a task to fill the buffer + sp<TessellationTask> task = new TessellationTask(tessellator, entry); + buffer = new Buffer(task); + + if (mProcessor == NULL) { + mProcessor = new TessellationProcessor(Caches::getInstance()); + } + mProcessor->add(task); + mCache.put(entry, buffer); + } + return buffer; +} + +static VertexBuffer* tessellatePath(const TessellationCache::Description& description, + const SkPath& path) { + Matrix4 matrix; + SkPaint paint; + description.setupMatrixAndPaint(&matrix, &paint); + VertexBuffer* buffer = new VertexBuffer(); + PathTessellator::tessellatePath(path, &paint, matrix, *buffer); + return buffer; +} + +/////////////////////////////////////////////////////////////////////////////// +// RoundRect +/////////////////////////////////////////////////////////////////////////////// + +static VertexBuffer* tessellateRoundRect(const TessellationCache::Description& description) { + SkRect rect = SkRect::MakeWH(description.shape.roundRect.width, + description.shape.roundRect.height); + float rx = description.shape.roundRect.rx; + float ry = description.shape.roundRect.ry; + if (description.style == SkPaint::kStrokeAndFill_Style) { + float outset = description.strokeWidth / 2; + rect.outset(outset, outset); + rx += outset; + ry += outset; + } + SkPath path; + path.addRoundRect(rect, rx, ry); + return tessellatePath(description, path); +} + +TessellationCache::Buffer* TessellationCache::getRoundRectBuffer( + const Matrix4& transform, const SkPaint& paint, + float width, float height, float rx, float ry) { + Description entry(Description::kRoundRect, transform, paint); + entry.shape.roundRect.width = width; + entry.shape.roundRect.height = height; + entry.shape.roundRect.rx = rx; + entry.shape.roundRect.ry = ry; + return getOrCreateBuffer(entry, &tessellateRoundRect); +} +const VertexBuffer* TessellationCache::getRoundRect(const Matrix4& transform, const SkPaint& paint, + float width, float height, float rx, float ry) { + return getRoundRectBuffer(transform, paint, width, height, rx, ry)->getVertexBuffer(); +} + +}; // namespace uirenderer +}; // namespace android |