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 <jvanverth@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
This commit is contained in:
Brian Salomon 2017-02-03 14:25:02 -05:00 committed by Skia Commit-Bot
parent ea518f0a7e
commit d1ac9823fd

View File

@ -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,28 +318,17 @@ 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;
}
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();
const CachedTessellationsRec& rec = static_cast<const CachedTessellationsRec&>(baseRec);
findContext->fVertices =
rec.find(*findContext->fFactory, *findContext->fViewMatrix, &findContext->fTranslate);
if (findContext->fVertices) {
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 {
public:
@ -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,10 +401,25 @@ 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
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);
}
}