Tessellate on worker threads

Tessellate and cache (where possible) shadow and round rect
tessellation tasks.

Change-Id: I2cfda8e11d83d51ea74af871235cf26e8f831d40
diff --git a/libs/hwui/TessellationCache.cpp b/libs/hwui/TessellationCache.cpp
new file mode 100644
index 0000000..41cc9d2
--- /dev/null
+++ b/libs/hwui/TessellationCache.cpp
@@ -0,0 +1,476 @@
+/*
+ * 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)
+        , cap(SkPaint::kDefault_Cap)
+        , style(SkPaint::kFill_Style)
+        , strokeWidth(1.0f) {
+    memset(&shape, 0, sizeof(Shape));
+}
+
+TessellationCache::Description::Description(Type type)
+        : type(type)
+        , cap(SkPaint::kDefault_Cap)
+        , style(SkPaint::kFill_Style)
+        , strokeWidth(1.0f) {
+    memset(&shape, 0, sizeof(Shape));
+}
+
+TessellationCache::Description::Description(Type type, const SkPaint* paint)
+        : type(type)
+        , cap(paint->getStrokeCap())
+        , style(paint->getStyle())
+        , strokeWidth(paint->getStrokeWidth()) {
+    memset(&shape, 0, sizeof(Shape));
+}
+
+hash_t TessellationCache::Description::hash() const {
+    uint32_t hash = JenkinsHashMix(0, type);
+    hash = JenkinsHashMix(hash, cap);
+    hash = JenkinsHashMix(hash, style);
+    hash = JenkinsHashMix(hash, android::hash_type(strokeWidth));
+    hash = JenkinsHashMixBytes(hash, (uint8_t*) &shape, sizeof(Shape));
+    return JenkinsHashWhiten(hash);
+}
+
+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,
+                const SkPaint* paint)
+        : tessellator(tessellator)
+        , description(description)
+        , paint(*paint) {
+    }
+
+    ~TessellationTask() {}
+
+    Tessellator tessellator;
+    Description description;
+
+    //copied, since input paint may not be immutable
+    const SkPaint paint;
+};
+
+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->paint);
+        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 - only the localClip is deep copied, since other pointers point at Allocator controlled
+    // objects, which are safe for the entire frame
+    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
+///////////////////////////////////////////////////////////////////////////////
+
+static VertexBuffer* tessellatePath(const SkPath& path, const SkPaint* paint,
+        float scaleX, float scaleY) {
+    VertexBuffer* buffer = new VertexBuffer();
+    Matrix4 matrix;
+    matrix.loadScale(scaleX, scaleY, 1);
+    PathTessellator::tessellatePath(path, paint, matrix, *buffer);
+    return buffer;
+}
+
+TessellationCache::Buffer* TessellationCache::getOrCreateBuffer(
+        const Description& entry, Tessellator tessellator, const SkPaint* paint) {
+    Buffer* buffer = mCache.get(entry);
+    if (!buffer) {
+        // not cached, enqueue a task to fill the buffer
+        sp<TessellationTask> task = new TessellationTask(tessellator, entry, paint);
+        buffer = new Buffer(task);
+
+        if (mProcessor == NULL) {
+            mProcessor = new TessellationProcessor(Caches::getInstance());
+        }
+        mProcessor->add(task);
+        mCache.put(entry, buffer);
+    }
+    return buffer;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Rounded rects
+///////////////////////////////////////////////////////////////////////////////
+
+static VertexBuffer* tessellateRoundRect(const TessellationCache::Description& description,
+        const SkPaint& paint) {
+    SkRect rect = SkRect::MakeWH(description.shape.roundRect.mWidth,
+            description.shape.roundRect.mHeight);
+    float rx = description.shape.roundRect.mRx;
+    float ry = description.shape.roundRect.mRy;
+    if (paint.getStyle() == SkPaint::kStrokeAndFill_Style) {
+        float outset = paint.getStrokeWidth() / 2;
+        rect.outset(outset, outset);
+        rx += outset;
+        ry += outset;
+    }
+    SkPath path;
+    path.addRoundRect(rect, rx, ry);
+    return tessellatePath(path, &paint,
+            description.shape.roundRect.mScaleX, description.shape.roundRect.mScaleY);
+}
+
+TessellationCache::Buffer* TessellationCache::getRoundRectBuffer(const Matrix4& transform,
+        float width, float height, float rx, float ry, const SkPaint* paint) {
+    Description entry(Description::kRoundRect, paint);
+    entry.shape.roundRect.mWidth = width;
+    entry.shape.roundRect.mHeight = height;
+    entry.shape.roundRect.mRx = rx;
+    entry.shape.roundRect.mRy = ry;
+    PathTessellator::extractTessellationScales(transform,
+            &entry.shape.roundRect.mScaleX, &entry.shape.roundRect.mScaleY);
+
+    return getOrCreateBuffer(entry, &tessellateRoundRect, paint);
+}
+const VertexBuffer* TessellationCache::getRoundRect(const Matrix4& transform,
+        float width, float height, float rx, float ry, const SkPaint* paint) {
+    return getRoundRectBuffer(transform, width, height, rx, ry, paint)->getVertexBuffer();
+}
+
+}; // namespace uirenderer
+}; // namespace android