| /* |
| * 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. |
| */ |
| |
| #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); |
| } |
| }; |
| |
| class 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 = 4.0f; |
| 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, centroid3d, |
| *drawTransform, lightCenter, lightRadius, casterBounds, *localClip, |
| spotBuffer); |
| } |
| |
| 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()); |
| } |
| if (!mShadowProcessor->add(task)) { |
| mShadowProcessor->process(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()); |
| } |
| if (!mProcessor->add(task)) { |
| mProcessor->process(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 |