| /* | 
 |  * 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(nullptr) { | 
 |     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) override { | 
 |         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(nullptr) { | 
 |     } | 
 |  | 
 |     ~Buffer() { | 
 |         mTask.clear(); | 
 |         delete mBuffer; | 
 |     } | 
 |  | 
 |     unsigned int getSize() { | 
 |         blockOnPrecache(); | 
 |         return mBuffer->getSize(); | 
 |     } | 
 |  | 
 |     const VertexBuffer* getVertexBuffer() { | 
 |         blockOnPrecache(); | 
 |         return mBuffer; | 
 |     } | 
 |  | 
 | private: | 
 |     void blockOnPrecache() { | 
 |         if (mTask != nullptr) { | 
 |             mBuffer = mTask->getResult(); | 
 |             LOG_ALWAYS_FATAL_IF(mBuffer == nullptr, "Failed to precache"); | 
 |             mTask.clear(); | 
 |         } | 
 |     } | 
 |     sp<Task<VertexBuffer*> > mTask; | 
 |     VertexBuffer* mBuffer; | 
 | }; | 
 |  | 
 | /////////////////////////////////////////////////////////////////////////////// | 
 | // Shadow tessellation task processing | 
 | /////////////////////////////////////////////////////////////////////////////// | 
 |  | 
 | 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 reverseVertexArray(Vertex* polygon, int len) { | 
 |     int n = len / 2; | 
 |     for (int i = 0; i < n; i++) { | 
 |         Vertex tmp = polygon[i]; | 
 |         int k = len - 1 - i; | 
 |         polygon[i] = polygon[k]; | 
 |         polygon[k] = tmp; | 
 |     } | 
 | } | 
 |  | 
 | 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 | 
 |     std::vector<Vertex> casterVertices2d; | 
 |     const float casterRefinementThreshold = 2.0f; | 
 |     PathTessellator::approximatePathOutlineVertices(*casterPerimeter, | 
 |             casterRefinementThreshold, casterVertices2d); | 
 |  | 
 |     // Shadow requires CCW for now. TODO: remove potential double-reverse | 
 |     reverseVertexArray(&casterVertices2d.front(), 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 = std::min(minZ, casterPolygon[i].z); | 
 |         maxZ = std::max(maxZ, casterPolygon[i].z); | 
 |     } | 
 |  | 
 |     // map the centroid of the caster into 3d | 
 |     Vector2 centroid =  ShadowTessellator::centroid2d( | 
 |             reinterpret_cast<const Vector2*>(&casterVertices2d.front()), | 
 |             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) override { | 
 |         TessellationCache::ShadowTask* t = static_cast<TessellationCache::ShadowTask*>(task.get()); | 
 |         ATRACE_NAME("shadow tessellation"); | 
 |  | 
 |         tessellateShadows(&t->drawTransform, &t->localClip, t->opaque, &t->casterPerimeter, | 
 |                 &t->transformXY, &t->transformZ, t->lightCenter, t->lightRadius, | 
 |                 t->ambientBuffer, t->spotBuffer); | 
 |  | 
 |         t->setResult(TessellationCache::vertexBuffer_pair_t(&t->ambientBuffer, &t->spotBuffer)); | 
 |     } | 
 | }; | 
 |  | 
 | /////////////////////////////////////////////////////////////////////////////// | 
 | // Cache constructor/destructor | 
 | /////////////////////////////////////////////////////////////////////////////// | 
 |  | 
 | TessellationCache::TessellationCache() | 
 |         : mMaxSize(Properties::tessellationCacheSize) | 
 |         , mCache(LruCache<Description, Buffer*>::kUnlimitedCapacity) | 
 |         , mShadowCache(LruCache<ShadowDescription, Task<vertexBuffer_pair_t*>*>::kUnlimitedCapacity) { | 
 |     mCache.setOnEntryRemovedListener(&mBufferRemovedListener); | 
 |     mShadowCache.setOnEntryRemovedListener(&mBufferPairRemovedListener); | 
 |     mDebugEnabled = Properties::debugLevel & 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; | 
 | } | 
 |  | 
 | /////////////////////////////////////////////////////////////////////////////// | 
 | // 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); | 
 |  | 
 |     if (mShadowCache.get(key)) return; | 
 |     sp<ShadowTask> task = new ShadowTask(drawTransform, localClip, opaque, | 
 |             casterPerimeter, transformXY, transformZ, lightCenter, lightRadius); | 
 |     if (mShadowProcessor == nullptr) { | 
 |         mShadowProcessor = new ShadowProcessor(Caches::getInstance()); | 
 |     } | 
 |     mShadowProcessor->add(task); | 
 |     task->incStrong(nullptr); // 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 == nullptr, "shadow not precached"); | 
 |     outBuffers = task->getResult(); | 
 | } | 
 |  | 
 | sp<TessellationCache::ShadowTask> TessellationCache::getShadowTask( | 
 |         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); | 
 |     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 == nullptr, "shadow not precached"); | 
 |     return task; | 
 | } | 
 |  | 
 | /////////////////////////////////////////////////////////////////////////////// | 
 | // 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 == nullptr) { | 
 |             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 |