diff options
-rwxr-xr-x | src/utils/SkShadowUtils.cpp | 295 |
1 files 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<GrFragmentProcessor> 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<SkShadowVertices> 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<SkShadowVertices> 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 <typename FACTORY> -class TessPathRec : public SkResourceCache::Rec { +class CachedTessellations : public SkRefCnt { public: - TessPathRec(const SkResourceCache::Key& key, const SkMatrix& viewMatrix, const FACTORY& factory, - sk_sp<SkShadowVertices> vertices) - : fVertices(std::move(vertices)), fFactory(factory), fOriginalMatrix(viewMatrix) { + size_t size() const { return fAmbientSet.size() + fSpotSet.size(); } + + sk_sp<SkShadowVertices> find(const AmbientVerticesFactory& ambient, const SkMatrix& matrix, + SkVector* translate) const { + return fAmbientSet.find(ambient, matrix, translate); + } + + sk_sp<SkShadowVertices> add(const SkPath& devPath, const AmbientVerticesFactory& ambient, + const SkMatrix& matrix) { + return fAmbientSet.add(devPath, ambient, matrix); + } + + sk_sp<SkShadowVertices> find(const SpotVerticesFactory& spot, const SkMatrix& matrix, + SkVector* translate) const { + return fSpotSet.find(spot, matrix, translate); + } + + sk_sp<SkShadowVertices> add(const SkPath& devPath, const SpotVerticesFactory& spot, + const SkMatrix& matrix) { + return fSpotSet.add(devPath, spot, matrix); + } + +private: + template <typename FACTORY, int MAX_ENTRIES> + class Set { + public: + size_t size() const { return fSize; } + + sk_sp<SkShadowVertices> 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<SkShadowVertices> add(const SkPath& devPath, const FACTORY& factory, + const SkMatrix& matrix) { + sk_sp<SkShadowVertices> 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<SkShadowVertices> fVertices; + SkMatrix fMatrix; + }; + Entry fEntries[MAX_ENTRIES]; + int fCount = 0; + size_t fSize = 0; + }; + + Set<AmbientVerticesFactory, 4> fAmbientSet; + Set<SpotVerticesFactory, 4> 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<CachedTessellations> 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<SkResourceCache::Key*>(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<SkShadowVertices> refVertices() const { return fVertices; } + const char* getCategory() const override { return "tessellated shadow masks"; } - const FACTORY& factory() const { return fFactory; } + sk_sp<CachedTessellations> refTessellations() const { return fTessellations; } - const SkMatrix& originalViewMatrix() const { return fOriginalMatrix; } + template <typename FACTORY> + sk_sp<SkShadowVertices> find(const FACTORY& factory, const SkMatrix& matrix, + SkVector* translate) const { + return fTessellations->find(factory, matrix, translate); + } private: std::unique_ptr<uint8_t[]> fKey; - sk_sp<SkShadowVertices> fVertices; - FACTORY fFactory; - SkMatrix fOriginalMatrix; + sk_sp<CachedTessellations> 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 <typename FACTORY> 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<SkShadowVertices> 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<CachedTessellations> fTessellationsOnFailure; + const FACTORY* fFactory; }; @@ -176,27 +318,16 @@ struct 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; + const CachedTessellationsRec& rec = static_cast<const CachedTessellationsRec&>(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<uint32_t*>(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<SkPath> 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 <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; @@ -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<CachedTessellations> 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<FACTORY>(*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); } } |