Add caching of ambient and spot shadow meshes.
authorBrian Salomon <bsalomon@google.com>
Wed, 1 Feb 2017 17:07:17 +0000 (12:07 -0500)
committerSkia Commit-Bot <skia-commit-bot@chromium.org>
Wed, 1 Feb 2017 18:01:17 +0000 (18:01 +0000)
Change-Id: If882186225621af4af4b4ddae0c786ec33ff40f3
Reviewed-on: https://skia-review.googlesource.com/7643
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Jim Van Verth <jvanverth@google.com>
src/utils/SkShadowTessellator.h
src/utils/SkShadowUtils.cpp

index 63e7bdc..13924f2 100755 (executable)
@@ -45,6 +45,11 @@ public:
     int indexCount() const { return fIndexCnt; }
     const uint16_t* indices() const { return fIndices.get(); }
 
+    size_t size() const {
+        return sizeof(*this) + fVertexCnt * (sizeof(SkPoint) + sizeof(SkColor)) +
+               fIndexCnt * sizeof(uint16_t);
+    }
+
 private:
     template<typename T> using Deleter = SkTDArray<SkPoint>::Deleter;
     template<typename T> using UniqueArray = std::unique_ptr<const T[], Deleter<T>>;
index 6a08965..6ad87e5 100755 (executable)
@@ -9,7 +9,13 @@
 #include "SkCanvas.h"
 #include "SkColorFilter.h"
 #include "SkPath.h"
+#include "SkResourceCache.h"
 #include "SkShadowTessellator.h"
+#include "SkTLazy.h"
+#if SK_SUPPORT_GPU
+#include "GrShape.h"
+#include "effects/GrBlurredEdgeFragmentProcessor.h"
+#endif
 
 /**
 *  Gaussian color filter -- produces a Gaussian ramp based on the color's B value,
@@ -63,7 +69,6 @@ void SkGaussianColorFilter::toString(SkString* str) const {
 #endif
 
 #if SK_SUPPORT_GPU
-#include "effects/GrBlurredEdgeFragmentProcessor.h"
 
 sk_sp<GrFragmentProcessor> SkGaussianColorFilter::asFragmentProcessor(GrContext*,
                                                                       SkColorSpace*) const {
@@ -72,6 +77,218 @@ sk_sp<GrFragmentProcessor> SkGaussianColorFilter::asFragmentProcessor(GrContext*
 #endif
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+struct AmbientVerticesFactory {
+    SkScalar fRadius;
+    SkColor fUmbraColor;
+    SkColor fPenumbraColor;
+    bool fTransparent;
+
+    bool operator==(const AmbientVerticesFactory& that) const {
+        return fRadius == that.fRadius && fUmbraColor == that.fUmbraColor &&
+               fPenumbraColor == that.fPenumbraColor && fTransparent == that.fTransparent;
+    }
+    bool operator!=(const AmbientVerticesFactory& that) const { return !(*this == that); }
+
+    sk_sp<SkShadowVertices> makeVertices(const SkPath& devPath) const {
+        return SkShadowVertices::MakeAmbient(devPath, fRadius, fUmbraColor, fPenumbraColor,
+                                             fTransparent);
+    }
+};
+
+struct SpotVerticesFactory {
+    SkScalar fRadius;
+    SkColor fUmbraColor;
+    SkColor fPenumbraColor;
+    SkScalar fScale;
+    SkVector fOffset;
+    bool fTransparent;
+
+    bool operator==(const SpotVerticesFactory& that) const {
+        return fRadius == that.fRadius && fUmbraColor == that.fUmbraColor &&
+               fPenumbraColor == that.fPenumbraColor && fTransparent == that.fTransparent &&
+               fScale == that.fScale && fOffset == that.fOffset;
+    }
+    bool operator!=(const SpotVerticesFactory& that) const { return !(*this == that); }
+
+    sk_sp<SkShadowVertices> makeVertices(const SkPath& devPath) const {
+        return SkShadowVertices::MakeSpot(devPath, fScale, fOffset, fRadius, fUmbraColor,
+                                          fPenumbraColor, fTransparent);
+    }
+};
+
+/**
+ * A record of shadow vertices stored in SkResourceCache. Each shape may have one record for a given
+ * FACTORY type.
+ */
+template <typename FACTORY>
+class TessPathRec : public SkResourceCache::Rec {
+public:
+    TessPathRec(const SkResourceCache::Key& key, const SkMatrix& viewMatrix, const FACTORY& factory,
+                sk_sp<SkShadowVertices> vertices)
+            : fVertices(std::move(vertices)), fFactory(factory), fOriginalMatrix(viewMatrix) {
+        fKey.reset(new uint8_t[key.size()]);
+        memcpy(fKey.get(), &key, key.size());
+    }
+
+    const Key& getKey() const override {
+        return *reinterpret_cast<SkResourceCache::Key*>(fKey.get());
+    }
+    size_t bytesUsed() const override { return fVertices->size(); }
+
+    const char* getCategory() const override { return "tessellated shadow mask"; }
+
+    sk_sp<SkShadowVertices> refVertices() const { return fVertices; }
+
+    const FACTORY& factory() const { return fFactory; }
+
+    const SkMatrix& originalViewMatrix() const { return fOriginalMatrix; }
+
+private:
+    std::unique_ptr<uint8_t[]> fKey;
+    sk_sp<SkShadowVertices> fVertices;
+    FACTORY fFactory;
+    SkMatrix fOriginalMatrix;
+};
+
+/**
+ * Used by FindVisitor to determine whether a cache entry can be reused and if so returns the
+ * vertices and translation vector.
+ */
+template <typename FACTORY>
+struct FindContext {
+    FindContext(const SkMatrix* viewMatrix, const FACTORY* factory)
+            : fViewMatrix(viewMatrix), fFactory(factory) {}
+    const SkMatrix* fViewMatrix;
+    SkVector fTranslate = {0, 0};
+    sk_sp<SkShadowVertices> fVertices;
+    const FACTORY* fFactory;
+};
+
+/**
+ * Function called by SkResourceCache when a matching cache key is found. The FACTORY and matrix of
+ * the FindContext are used to determine if the vertices are reusable. If so the vertices and
+ * necessary translation vector are set on the FindContext.
+ */
+template <typename FACTORY>
+bool FindVisitor(const SkResourceCache::Rec& baseRec, void* ctx) {
+    FindContext<FACTORY>* findContext = (FindContext<FACTORY>*)ctx;
+    const TessPathRec<FACTORY>& rec = static_cast<const TessPathRec<FACTORY>&>(baseRec);
+
+    const SkMatrix& viewMatrix = *findContext->fViewMatrix;
+    const SkMatrix& recMatrix = rec.originalViewMatrix();
+    if (findContext->fViewMatrix->hasPerspective() || recMatrix.hasPerspective()) {
+        if (recMatrix != viewMatrix) {
+            return false;
+        }
+    } else if (recMatrix.getScaleX() != viewMatrix.getScaleX() ||
+               recMatrix.getSkewX() != viewMatrix.getSkewX() ||
+               recMatrix.getScaleY() != viewMatrix.getScaleY() ||
+               recMatrix.getSkewY() != viewMatrix.getSkewY()) {
+        return false;
+    }
+    if (*findContext->fFactory != rec.factory()) {
+        return false;
+    }
+    findContext->fTranslate.fX = viewMatrix.getTranslateX() - recMatrix.getTranslateX();
+    findContext->fTranslate.fY = viewMatrix.getTranslateY() - recMatrix.getTranslateY();
+    findContext->fVertices = rec.refVertices();
+    return true;
+}
+
+class ShadowedPath {
+public:
+    ShadowedPath(const SkPath* path, const SkMatrix* viewMatrix)
+            : fOriginalPath(path)
+            , fViewMatrix(viewMatrix)
+#if SK_SUPPORT_GPU
+            , fShapeForKey(*path, GrStyle::SimpleFill())
+#endif
+    {}
+
+    const SkPath& transformedPath() {
+        if (!fTransformedPath.isValid()) {
+            fOriginalPath->transform(*fViewMatrix, fTransformedPath.init());
+        }
+        return *fTransformedPath.get();
+    }
+
+    const SkMatrix& viewMatrix() const { return *fViewMatrix; }
+#if SK_SUPPORT_GPU
+    /** Negative means the vertices should not be cached for this path. */
+    int keyBytes() const { return fShapeForKey.unstyledKeySize() * sizeof(uint32_t); }
+    void writeKey(void* key) const {
+        fShapeForKey.writeUnstyledKey(reinterpret_cast<uint32_t*>(key));
+    }
+#else
+    int keyBytes() const { return -1; }
+    void writeKey(void* key) const { SkFAIL("Should never be called"); }
+#endif
+
+private:
+    const SkPath* fOriginalPath;
+    const SkMatrix* fViewMatrix;
+#if SK_SUPPORT_GPU
+    GrShape fShapeForKey;
+#endif
+    SkTLazy<SkPath> fTransformedPath;
+};
+
+/**
+ * Draws a shadow to 'canvas'. The vertices used to draw the shadow are created by 'factory' unless
+ * they are first found in SkResourceCache.
+ */
+template <typename FACTORY>
+void draw_shadow(const FACTORY& factory, SkCanvas* canvas, ShadowedPath& path, SkColor color) {
+    FindContext<FACTORY> context(&path.viewMatrix(), &factory);
+    static void* kNamespace;
+
+    SkResourceCache::Key* key = nullptr;
+    SkAutoSTArray<32 * 4, uint8_t> keyStorage;
+    int keyDataBytes = path.keyBytes();
+    if (keyDataBytes >= 0) {
+        keyStorage.reset(keyDataBytes + sizeof(SkResourceCache::Key));
+        key = new (keyStorage.begin()) SkResourceCache::Key();
+        path.writeKey((uint32_t*)(keyStorage.begin() + sizeof(*key)));
+        key->init(&kNamespace, 0, keyDataBytes);
+        SkResourceCache::Find(*key, FindVisitor<FACTORY>, &context);
+    }
+
+    sk_sp<SkShadowVertices> vertices;
+    const SkVector* translate;
+    static constexpr SkVector kZeroTranslate = {0, 0};
+    bool foundInCache = SkToBool(context.fVertices);
+    if (foundInCache) {
+        vertices = std::move(context.fVertices);
+        translate = &context.fTranslate;
+    } else {
+        // TODO: handle transforming the path as part of the tessellator
+        vertices = factory.makeVertices(path.transformedPath());
+        translate = &kZeroTranslate;
+    }
+
+    SkPaint paint;
+    paint.setColor(color);
+    paint.setColorFilter(SkGaussianColorFilter::Make());
+    if (translate->fX || translate->fY) {
+        canvas->save();
+        canvas->translate(translate->fX, translate->fY);
+    }
+    canvas->drawVertices(SkCanvas::kTriangles_VertexMode, vertices->vertexCount(),
+                         vertices->positions(), nullptr, vertices->colors(), vertices->indices(),
+                         vertices->indexCount(), paint);
+    if (translate->fX || translate->fY) {
+        canvas->restore();
+    }
+    if (!foundInCache && key) {
+        SkResourceCache::Add(
+                new TessPathRec<FACTORY>(*key, path.viewMatrix(), factory, std::move(vertices)));
+    }
+}
+}
+
 static const float kHeightFactor = 1.0f / 128.0f;
 static const float kGeomFactor = 64.0f;
 
@@ -80,58 +297,47 @@ void SkShadowUtils::DrawShadow(SkCanvas* canvas, const SkPath& path, SkScalar oc
                                const SkPoint3& lightPos, SkScalar lightRadius,
                                SkScalar ambientAlpha, SkScalar spotAlpha, SkColor color,
                                uint32_t flags) {
+    SkMatrix viewMatrix = canvas->getTotalMatrix();
 
-    SkPath xformedPath;
-    // TODO: handle transforming the path as part of the tessellator
-    path.transform(canvas->getTotalMatrix(), &xformedPath);
     canvas->save();
     canvas->resetMatrix();
 
+    ShadowedPath shadowedPath(&path, &viewMatrix);
+
     bool transparent = SkToBool(flags & SkShadowFlags::kTransparentOccluder_ShadowFlag);
 
     if (ambientAlpha > 0) {
-        SkScalar radius = occluderHeight * kHeightFactor * kGeomFactor;
+        AmbientVerticesFactory factory;
+        factory.fRadius = occluderHeight * kHeightFactor * kGeomFactor;
         SkScalar umbraAlpha = SkScalarInvert((1.0f + SkTMax(occluderHeight*kHeightFactor, 0.0f)));
         // umbraColor is the interior value, penumbraColor the exterior value.
         // umbraAlpha is the factor that is linearly interpolated from outside to inside, and
         // then "blurred" by the GrBlurredEdgeFP. It is then multiplied by fAmbientAlpha to get
         // the final alpha.
-        SkColor  umbraColor = SkColorSetARGB(255, 0, ambientAlpha*255.9999f, umbraAlpha*255.9999f);
-        SkColor  penumbraColor = SkColorSetARGB(255, 0, ambientAlpha*255.9999f, 0);
+        factory.fUmbraColor =
+                SkColorSetARGB(255, 0, ambientAlpha * 255.9999f, umbraAlpha * 255.9999f);
+        factory.fPenumbraColor = SkColorSetARGB(255, 0, ambientAlpha * 255.9999f, 0);
+        factory.fTransparent = transparent;
 
-        sk_sp<SkShadowVertices> vertices =
-                SkShadowVertices::MakeAmbient(xformedPath, radius, umbraColor, penumbraColor,
-                                              transparent);
-        SkPaint paint;
-        paint.setColor(color);
-        paint.setColorFilter(SkGaussianColorFilter::Make());
-        canvas->drawVertices(SkCanvas::kTriangles_VertexMode, vertices->vertexCount(),
-                             vertices->positions(), nullptr, vertices->colors(),
-                             vertices->indices(), vertices->indexCount(), paint);
+        draw_shadow(factory, canvas, shadowedPath, color);
     }
 
     if (spotAlpha > 0) {
+        SpotVerticesFactory factory;
         float zRatio = SkTPin(occluderHeight / (lightPos.fZ - occluderHeight), 0.0f, 0.95f);
-        SkScalar radius = lightRadius * zRatio;
+        factory.fRadius = lightRadius * zRatio;
 
         // Compute the scale and translation for the spot shadow.
-        const SkScalar scale = lightPos.fZ / (lightPos.fZ - occluderHeight);
+        factory.fScale = lightPos.fZ / (lightPos.fZ - occluderHeight);
 
         SkPoint center = SkPoint::Make(path.getBounds().centerX(), path.getBounds().centerY());
-        const SkVector spotOffset = SkVector::Make(zRatio*(center.fX - lightPos.fX),
-                                                   zRatio*(center.fY - lightPos.fY));
-
-        SkColor  umbraColor = SkColorSetARGB(255, 0, spotAlpha*255.9999f, 255);
-        SkColor  penumbraColor = SkColorSetARGB(255, 0, spotAlpha*255.9999f, 0);
-        sk_sp<SkShadowVertices> vertices =
-                SkShadowVertices::MakeSpot(xformedPath, scale, spotOffset, radius, umbraColor,
-                                           penumbraColor, transparent);
-        SkPaint paint;
-        paint.setColor(color);
-        paint.setColorFilter(SkGaussianColorFilter::Make());
-        canvas->drawVertices(SkCanvas::kTriangles_VertexMode, vertices->vertexCount(),
-                             vertices->positions(), nullptr, vertices->colors(),
-                             vertices->indices(), vertices->indexCount(), paint);
+        factory.fOffset = SkVector::Make(zRatio * (center.fX - lightPos.fX),
+                                         zRatio * (center.fY - lightPos.fY));
+        factory.fUmbraColor = SkColorSetARGB(255, 0, spotAlpha * 255.9999f, 255);
+        factory.fPenumbraColor = SkColorSetARGB(255, 0, spotAlpha * 255.9999f, 0);
+        factory.fTransparent = transparent;
+
+        draw_shadow(factory, canvas, shadowedPath, color);
     }
 
     canvas->restore();