From d1ac9823fd351ccb54f463273e5c34b6d719ea46 Mon Sep 17 00:00:00 2001 From: Brian Salomon Date: Fri, 3 Feb 2017 14:25:02 -0500 Subject: [PATCH] Improved SkShadowUtils caching Multiple ambient and spot shadows can be cached for each geometry. Spot shadows can be reused when x,y light offset is different. We categorize spot shadows for rrects as either transparent, opaque with partial umbra occlusion, or opaque with full umbra occlusion and use that to improve cache performance. Change-Id: Id530bdaa5092edb46c8f584979090fbb766307fc Reviewed-on: https://skia-review.googlesource.com/7987 Reviewed-by: Jim Van Verth Commit-Queue: Brian Salomon --- src/utils/SkShadowUtils.cpp | 295 ++++++++++++++++++++++++++++-------- 1 file changed, 235 insertions(+), 60 deletions(-) diff --git a/src/utils/SkShadowUtils.cpp b/src/utils/SkShadowUtils.cpp index 83699c5625..c79f991071 100755 --- a/src/utils/SkShadowUtils.cpp +++ b/src/utils/SkShadowUtils.cpp @@ -9,6 +9,7 @@ #include "SkCanvas.h" #include "SkColorFilter.h" #include "SkPath.h" +#include "SkRandom.h" #include "SkResourceCache.h" #include "SkShadowTessellator.h" #include "SkTLazy.h" @@ -81,17 +82,21 @@ sk_sp SkGaussianColorFilter::asFragmentProcessor(GrContext* namespace { +/** Factory for an ambient shadow mesh with particular shadow properties. */ struct AmbientVerticesFactory { - SkScalar fRadius; + SkScalar fRadius = SK_ScalarNaN; // NaN so that isCompatible will always fail until init'ed. 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 isCompatible(const AmbientVerticesFactory& that, SkVector* translate) const { + if (fRadius != that.fRadius || fUmbraColor != that.fUmbraColor || + fPenumbraColor != that.fPenumbraColor || fTransparent != that.fTransparent) { + return false; + } + translate->set(0, 0); + return true; } - bool operator!=(const AmbientVerticesFactory& that) const { return !(*this == that); } sk_sp makeVertices(const SkPath& devPath) const { return SkShadowVertices::MakeAmbient(devPath, fRadius, fUmbraColor, fPenumbraColor, @@ -99,37 +104,164 @@ struct AmbientVerticesFactory { } }; +/** Factory for an spot shadow mesh with particular shadow properties. */ struct SpotVerticesFactory { - SkScalar fRadius; + enum class OccluderType { + // The umbra cannot be dropped out because the occluder is not opaque. + kTransparent, + // The umbra can be dropped where it is occluded. + kOpaque, + // It is known that the entire umbra is occluded. + kOpaqueCoversUmbra + }; + + SkScalar fRadius = SK_ScalarNaN; // NaN so that isCompatible will always fail until init'ed. SkColor fUmbraColor; SkColor fPenumbraColor; SkScalar fScale; SkVector fOffset; - bool fTransparent; + OccluderType fOccluderType; - 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 isCompatible(const SpotVerticesFactory& that, SkVector* translate) const { + if (fRadius != that.fRadius || fUmbraColor != that.fUmbraColor || + fPenumbraColor != that.fPenumbraColor || fOccluderType != that.fOccluderType || + fScale != that.fScale) { + return false; + } + switch (fOccluderType) { + case OccluderType::kTransparent: + case OccluderType::kOpaqueCoversUmbra: + // 'this' and 'that' will either both have no umbra removed or both have all the + // umbra removed. + *translate = that.fOffset - fOffset; + return true; + case OccluderType::kOpaque: + // In this case we partially remove the umbra differently for 'this' and 'that' + // if the offsets don't match. + if (fOffset == that.fOffset) { + translate->set(0, 0); + return true; + } + return false; + } + SkFAIL("Uninitialized occluder type?"); + return false; } - bool operator!=(const SpotVerticesFactory& that) const { return !(*this == that); } sk_sp makeVertices(const SkPath& devPath) const { + bool transparent = OccluderType::kTransparent == fOccluderType; return SkShadowVertices::MakeSpot(devPath, fScale, fOffset, fRadius, fUmbraColor, - fPenumbraColor, fTransparent); + fPenumbraColor, transparent); } }; /** - * A record of shadow vertices stored in SkResourceCache. Each shape may have one record for a given - * FACTORY type. + * This manages a set of tessellations for a given shape in the cache. Because SkResourceCache + * records are immutable this is not itself a Rec. When we need to update it we return this on + * the FindVisitor and let the cache destory the Rec. We'll update the tessellations and then add + * a new Rec with an adjusted size for any deletions/additions. */ -template -class TessPathRec : public SkResourceCache::Rec { +class CachedTessellations : public SkRefCnt { public: - TessPathRec(const SkResourceCache::Key& key, const SkMatrix& viewMatrix, const FACTORY& factory, - sk_sp vertices) - : fVertices(std::move(vertices)), fFactory(factory), fOriginalMatrix(viewMatrix) { + size_t size() const { return fAmbientSet.size() + fSpotSet.size(); } + + sk_sp find(const AmbientVerticesFactory& ambient, const SkMatrix& matrix, + SkVector* translate) const { + return fAmbientSet.find(ambient, matrix, translate); + } + + sk_sp add(const SkPath& devPath, const AmbientVerticesFactory& ambient, + const SkMatrix& matrix) { + return fAmbientSet.add(devPath, ambient, matrix); + } + + sk_sp find(const SpotVerticesFactory& spot, const SkMatrix& matrix, + SkVector* translate) const { + return fSpotSet.find(spot, matrix, translate); + } + + sk_sp add(const SkPath& devPath, const SpotVerticesFactory& spot, + const SkMatrix& matrix) { + return fSpotSet.add(devPath, spot, matrix); + } + +private: + template + class Set { + public: + size_t size() const { return fSize; } + + sk_sp find(const FACTORY& factory, const SkMatrix& matrix, + SkVector* translate) const { + for (int i = 0; i < MAX_ENTRIES; ++i) { + if (fEntries[i].fFactory.isCompatible(factory, translate)) { + const SkMatrix& m = fEntries[i].fMatrix; + if (matrix.hasPerspective() || m.hasPerspective()) { + if (matrix != fEntries[i].fMatrix) { + continue; + } + } else if (matrix.getScaleX() != m.getScaleX() || + matrix.getSkewX() != m.getSkewX() || + matrix.getScaleY() != m.getScaleY() || + matrix.getSkewY() != m.getSkewY()) { + continue; + } + *translate += SkVector{matrix.getTranslateX() - m.getTranslateX(), + matrix.getTranslateY() - m.getTranslateY()}; + return fEntries[i].fVertices; + } + } + return nullptr; + } + + sk_sp add(const SkPath& devPath, const FACTORY& factory, + const SkMatrix& matrix) { + sk_sp vertices = factory.makeVertices(devPath); + if (!vertices) { + return nullptr; + } + int i; + if (fCount < MAX_ENTRIES) { + i = fCount++; + } else { + i = gRandom.nextULessThan(MAX_ENTRIES); + fSize -= fEntries[i].fVertices->size(); + } + fEntries[i].fFactory = factory; + fEntries[i].fVertices = vertices; + fEntries[i].fMatrix = matrix; + fSize += vertices->size(); + return vertices; + } + + private: + struct Entry { + FACTORY fFactory; + sk_sp fVertices; + SkMatrix fMatrix; + }; + Entry fEntries[MAX_ENTRIES]; + int fCount = 0; + size_t fSize = 0; + }; + + Set fAmbientSet; + Set fSpotSet; + + static SkRandom gRandom; +}; + +SkRandom CachedTessellations::gRandom; + +/** + * A record of shadow vertices stored in SkResourceCache of CachedTessellations for a particular + * path. The key represents the path's geometry and not any shadow params. + */ +class CachedTessellationsRec : public SkResourceCache::Rec { +public: + CachedTessellationsRec(const SkResourceCache::Key& key, + sk_sp tessellations) + : fTessellations(std::move(tessellations)) { fKey.reset(new uint8_t[key.size()]); memcpy(fKey.get(), &key, key.size()); } @@ -137,34 +269,44 @@ public: const Key& getKey() const override { return *reinterpret_cast(fKey.get()); } - size_t bytesUsed() const override { return fVertices->size(); } - const char* getCategory() const override { return "tessellated shadow mask"; } + size_t bytesUsed() const override { return fTessellations->size(); } - sk_sp refVertices() const { return fVertices; } + const char* getCategory() const override { return "tessellated shadow masks"; } - const FACTORY& factory() const { return fFactory; } + sk_sp refTessellations() const { return fTessellations; } - const SkMatrix& originalViewMatrix() const { return fOriginalMatrix; } + template + sk_sp find(const FACTORY& factory, const SkMatrix& matrix, + SkVector* translate) const { + return fTessellations->find(factory, matrix, translate); + } private: std::unique_ptr fKey; - sk_sp fVertices; - FACTORY fFactory; - SkMatrix fOriginalMatrix; + sk_sp fTessellations; }; /** * Used by FindVisitor to determine whether a cache entry can be reused and if so returns the - * vertices and translation vector. + * vertices and a translation vector. If the CachedTessellations does not contain a suitable + * mesh then we inform SkResourceCache to destroy the Rec and we return the CachedTessellations + * to the caller. The caller will update it and reinsert it back into the cache. */ template struct FindContext { FindContext(const SkMatrix* viewMatrix, const FACTORY* factory) : fViewMatrix(viewMatrix), fFactory(factory) {} - const SkMatrix* fViewMatrix; - SkVector fTranslate = {0, 0}; + const SkMatrix* const fViewMatrix; + // If this is valid after Find is called then we found the vertices and they should be drawn + // with fTranslate applied. sk_sp fVertices; + SkVector fTranslate = {0, 0}; + + // If this is valid after Find then the caller should add the vertices to the tessellation set + // and create a new CachedTessellationsRec and insert it into SkResourceCache. + sk_sp fTessellationsOnFailure; + const FACTORY* fFactory; }; @@ -176,27 +318,16 @@ struct FindContext { template bool FindVisitor(const SkResourceCache::Rec& baseRec, void* ctx) { FindContext* findContext = (FindContext*)ctx; - const TessPathRec& rec = static_cast&>(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; + const CachedTessellationsRec& rec = static_cast(baseRec); + findContext->fVertices = + rec.find(*findContext->fFactory, *findContext->fViewMatrix, &findContext->fTranslate); + if (findContext->fVertices) { + return true; } - 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; + // We ref the tessellations and let the cache destroy the Rec. Once the tessellations have been + // manipulated we will add a new Rec. + findContext->fTessellationsOnFailure = rec.refTessellations(); + return false; } class ShadowedPath { @@ -223,9 +354,11 @@ public: void writeKey(void* key) const { fShapeForKey.writeUnstyledKey(reinterpret_cast(key)); } + bool isRRect(SkRRect* rrect) { return fShapeForKey.asRRect(rrect, nullptr, nullptr, nullptr); } #else int keyBytes() const { return -1; } void writeKey(void* key) const { SkFAIL("Should never be called"); } + bool isRRect(SkRRect* rrect) { return false; } #endif private: @@ -237,6 +370,9 @@ private: SkTLazy fTransformedPath; }; +// This creates a domain of keys in SkResourceCache used by this file. +static void* kNamespace; + /** * Draws a shadow to 'canvas'. The vertices used to draw the shadow are created by 'factory' unless * they are first found in SkResourceCache. @@ -244,7 +380,6 @@ private: template void draw_shadow(const FACTORY& factory, SkCanvas* canvas, ShadowedPath& path, SkColor color) { FindContext context(&path.viewMatrix(), &factory); - static void* kNamespace; SkResourceCache::Key* key = nullptr; SkAutoSTArray<32 * 4, uint8_t> keyStorage; @@ -266,9 +401,24 @@ void draw_shadow(const FACTORY& factory, SkCanvas* canvas, ShadowedPath& path, S translate = &context.fTranslate; } else { // TODO: handle transforming the path as part of the tessellator - vertices = factory.makeVertices(path.transformedPath()); - if (!vertices) { - return; + if (key) { + // Update or initialize a tessellation set and add it to the cache. + sk_sp tessellations; + if (context.fTessellationsOnFailure) { + tessellations = std::move(context.fTessellationsOnFailure); + } else { + tessellations.reset(new CachedTessellations()); + } + vertices = tessellations->add(path.transformedPath(), factory, path.viewMatrix()); + if (!vertices) { + return; + } + SkResourceCache::Add(new CachedTessellationsRec(*key, std::move(tessellations))); + } else { + vertices = factory.makeVertices(path.transformedPath()); + if (!vertices) { + return; + } } translate = &kZeroTranslate; } @@ -289,10 +439,6 @@ void draw_shadow(const FACTORY& factory, SkCanvas* canvas, ShadowedPath& path, S if (translate->fX || translate->fY) { canvas->restore(); } - if (!foundInCache && key) { - SkResourceCache::Add( - new TessPathRec(*key, path.viewMatrix(), factory, std::move(vertices))); - } } } @@ -344,8 +490,37 @@ void SkShadowUtils::DrawShadow(SkCanvas* canvas, const SkPath& path, SkScalar oc zRatio * (center.fY - devLightPos.fY)); factory.fUmbraColor = SkColorSetARGB(255, 0, spotAlpha * 255.9999f, 255); factory.fPenumbraColor = SkColorSetARGB(255, 0, spotAlpha * 255.9999f, 0); - factory.fTransparent = transparent; + SkRRect rrect; + if (transparent) { + factory.fOccluderType = SpotVerticesFactory::OccluderType::kTransparent; + } else { + factory.fOccluderType = SpotVerticesFactory::OccluderType::kOpaque; + if (shadowedPath.isRRect(&rrect)) { + SkRRect devRRect; + if (rrect.transform(viewMatrix, &devRRect)) { + SkScalar s = 1.f - factory.fScale; + SkScalar w = devRRect.width(); + SkScalar h = devRRect.height(); + SkScalar hw = w / 2.f; + SkScalar hh = h / 2.f; + SkScalar umbraInsetX = s * hw + factory.fRadius; + SkScalar umbraInsetY = s * hh + factory.fRadius; + if (umbraInsetX > hw || umbraInsetY > hh) { + // There is no umbra to occlude. + factory.fOccluderType = SpotVerticesFactory::OccluderType::kTransparent; + } else if (fabsf(factory.fOffset.fX) < umbraInsetX && + fabsf(factory.fOffset.fY) < umbraInsetY) { + factory.fOccluderType = + SpotVerticesFactory::OccluderType::kOpaqueCoversUmbra; + } else if (factory.fOffset.fX > w - umbraInsetX || + factory.fOffset.fY > h - umbraInsetY) { + // There umbra is fully exposed, there is nothing to omit. + factory.fOccluderType = SpotVerticesFactory::OccluderType::kTransparent; + } + } + } + } draw_shadow(factory, canvas, shadowedPath, color); } }