aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rwxr-xr-xsrc/utils/SkShadowUtils.cpp295
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);
}
}