aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--gn/effects.gni2
-rwxr-xr-xinclude/effects/SkShadowMaskFilter.h51
-rw-r--r--samplecode/SampleAndroidShadows.cpp27
-rwxr-xr-xsrc/effects/SkShadowMaskFilter.cpp569
4 files changed, 649 insertions, 0 deletions
diff --git a/gn/effects.gni b/gn/effects.gni
index bfa391895b..97e0f98654 100644
--- a/gn/effects.gni
+++ b/gn/effects.gni
@@ -57,6 +57,7 @@ skia_effects_sources = [
"$_src/effects/SkPerlinNoiseShader.cpp",
"$_src/effects/SkPictureImageFilter.cpp",
"$_src/effects/SkRRectsGaussianEdgeMaskFilter.cpp",
+ "$_src/effects/SkShadowMaskFilter.cpp",
"$_src/effects/SkTableColorFilter.cpp",
"$_src/effects/SkTableMaskFilter.cpp",
"$_src/effects/SkTileImageFilter.cpp",
@@ -116,6 +117,7 @@ skia_effects_sources = [
"$_include/effects/SkPaintImageFilter.h",
"$_include/effects/SkPerlinNoiseShader.h",
"$_include/effects/SkRRectsGaussianEdgeMaskFilter.h",
+ "$_include/effects/SkShadowMaskFilter.h",
"$_include/effects/SkTableColorFilter.h",
"$_include/effects/SkTableMaskFilter.h",
"$_include/effects/SkTileImageFilter.h",
diff --git a/include/effects/SkShadowMaskFilter.h b/include/effects/SkShadowMaskFilter.h
new file mode 100755
index 0000000000..a85da63db9
--- /dev/null
+++ b/include/effects/SkShadowMaskFilter.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkShadowMaskFilter_DEFINED
+#define SkShadowMaskFilter_DEFINED
+
+#include "SkMaskFilter.h"
+
+
+/*
+ * This filter implements a pair of shadows for an occluding object-- one representing
+ * ambient occlusion, and one representing a displaced shadow from a point light.
+ */
+class SK_API SkShadowMaskFilter {
+public:
+ enum ShadowFlags {
+ kNone_ShadowFlag = 0x00,
+ /** The occluding object is not opaque. Knowing that the occluder is opaque allows
+ * us to cull shadow geometry behind it and improve performance. */
+ kTransparentOccluder_ShadowFlag = 0x01,
+ /** Use a larger umbra for a darker shadow */
+ kLargerUmbra_ShadowFlag = 0x02,
+ /** Use a Gaussian for the edge function rather than smoothstep */
+ kGaussianEdge_ShadowFlag = 0x04,
+ /** mask for all shadow flags */
+ kAll_ShadowFlag = 0x07
+ };
+
+ /** Create a shadow maskfilter.
+ * @param occluderHeight Height of occluding object off of ground plane.
+ * @param lightPos Position of the light applied to this object.
+ * @param lightRadius Radius of the light (light is assumed to be spherical).
+ * @param ambientAlpha Base opacity of the ambient occlusion shadow.
+ * @param spotAlpha Base opacity of the displaced spot shadow.
+ * @param flags Flags to use - defaults to none
+ * @return The new shadow maskfilter
+ */
+ static sk_sp<SkMaskFilter> Make(SkScalar occluderHeight, const SkPoint3& lightPos,
+ SkScalar lightRadius, SkScalar ambientAlpha,
+ SkScalar spotAlpha, uint32_t flags = kNone_ShadowFlag);
+
+ SK_DECLARE_FLATTENABLE_REGISTRAR_GROUP()
+
+private:
+ SkShadowMaskFilter(); // can't be instantiated
+};
+#endif
diff --git a/samplecode/SampleAndroidShadows.cpp b/samplecode/SampleAndroidShadows.cpp
index a23cac423d..0b9358aec5 100644
--- a/samplecode/SampleAndroidShadows.cpp
+++ b/samplecode/SampleAndroidShadows.cpp
@@ -12,6 +12,7 @@
#include "SkGaussianEdgeShader.h"
#include "SkPath.h"
#include "SkPoint3.h"
+#include "SkShadowMaskFilter.h"
#include "SkUtils.h"
#include "SkView.h"
#include "sk_tool_utils.h"
@@ -366,6 +367,30 @@ protected:
void drawShadowedPath(SkCanvas* canvas, const SkPath& path, SkScalar zValue,
const SkPaint& paint, SkScalar ambientAlpha,
const SkPoint3& lightPos, SkScalar lightWidth, SkScalar spotAlpha) {
+#ifdef USE_MASK_FILTER
+ if (fUseAlt) {
+ if (fShowAmbient) {
+ this->drawAmbientShadowAlt(canvas, path, zValue, ambientAlpha);
+ }
+ if (fShowSpot) {
+ this->drawSpotShadowAlt(canvas, path, zValue, lightPos, lightWidth, spotAlpha);
+ }
+ } else {
+ SkPaint newPaint;
+ newPaint.setColor(SK_ColorBLACK);
+ if (!fShowAmbient) {
+ ambientAlpha = 0;
+ }
+ if (!fShowSpot) {
+ spotAlpha = 0;
+ }
+
+ newPaint.setMaskFilter(SkShadowMaskFilter::Make(zValue, lightPos, lightWidth,
+ ambientAlpha, spotAlpha));
+
+ canvas->drawPath(path, newPaint);
+ }
+#else
if (fShowAmbient) {
if (fUseAlt) {
this->drawAmbientShadowAlt(canvas, path, zValue, ambientAlpha);
@@ -380,6 +405,8 @@ protected:
this->drawSpotShadow(canvas, path, zValue, lightPos, lightWidth, spotAlpha);
}
}
+#endif
+
if (fShowObject) {
canvas->drawPath(path, paint);
} else {
diff --git a/src/effects/SkShadowMaskFilter.cpp b/src/effects/SkShadowMaskFilter.cpp
new file mode 100755
index 0000000000..25feaf6f97
--- /dev/null
+++ b/src/effects/SkShadowMaskFilter.cpp
@@ -0,0 +1,569 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkShadowMaskFilter.h"
+#include "SkReadBuffer.h"
+#include "SkStringUtils.h"
+#include "SkWriteBuffer.h"
+
+#if SK_SUPPORT_GPU
+#include "GrContext.h"
+#include "GrRenderTargetContext.h"
+#include "GrFragmentProcessor.h"
+#include "GrInvariantOutput.h"
+#include "GrStyle.h"
+#include "GrTexture.h"
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLProgramDataManager.h"
+#include "glsl/GrGLSLSampler.h"
+#include "glsl/GrGLSLUniformHandler.h"
+#include "SkStrokeRec.h"
+#endif
+
+class SkShadowMaskFilterImpl : public SkMaskFilter {
+public:
+ SkShadowMaskFilterImpl(SkScalar occluderHeight, const SkPoint3& lightPos, SkScalar lightRadius,
+ SkScalar ambientAlpha, SkScalar spotAlpha, uint32_t flags);
+
+ // overrides from SkMaskFilter
+ SkMask::Format getFormat() const override;
+ bool filterMask(SkMask* dst, const SkMask& src, const SkMatrix&,
+ SkIPoint* margin) const override;
+
+#if SK_SUPPORT_GPU
+ bool canFilterMaskGPU(const SkRRect& devRRect,
+ const SkIRect& clipBounds,
+ const SkMatrix& ctm,
+ SkRect* maskRect) const override;
+ bool directFilterMaskGPU(GrTextureProvider* texProvider,
+ GrRenderTargetContext* drawContext,
+ GrPaint* grp,
+ const GrClip&,
+ const SkMatrix& viewMatrix,
+ const SkStrokeRec& strokeRec,
+ const SkPath& path) const override;
+ bool directFilterRRectMaskGPU(GrContext*,
+ GrRenderTargetContext* drawContext,
+ GrPaint* grp,
+ const GrClip&,
+ const SkMatrix& viewMatrix,
+ const SkStrokeRec& strokeRec,
+ const SkRRect& rrect,
+ const SkRRect& devRRect) const override;
+ bool filterMaskGPU(GrTexture* src,
+ const SkMatrix& ctm,
+ const SkIRect& maskRect,
+ GrTexture** result) const override;
+#endif
+
+ void computeFastBounds(const SkRect&, SkRect*) const override;
+
+ SK_TO_STRING_OVERRIDE()
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkShadowMaskFilterImpl)
+
+private:
+ SkScalar fOccluderHeight;
+ SkPoint3 fLightPos;
+ SkScalar fLightRadius;
+ SkScalar fAmbientAlpha;
+ SkScalar fSpotAlpha;
+ uint32_t fFlags;
+
+ SkShadowMaskFilterImpl(SkReadBuffer&);
+ void flatten(SkWriteBuffer&) const override;
+
+ friend class SkShadowMaskFilter;
+
+ typedef SkMaskFilter INHERITED;
+};
+
+sk_sp<SkMaskFilter> SkShadowMaskFilter::Make(SkScalar occluderHeight, const SkPoint3& lightPos,
+ SkScalar lightRadius, SkScalar ambientAlpha,
+ SkScalar spotAlpha, uint32_t flags) {
+ // add some param checks here for early exit
+
+ return sk_sp<SkMaskFilter>(new SkShadowMaskFilterImpl(occluderHeight, lightPos, lightRadius,
+ ambientAlpha, spotAlpha, flags));
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+SkShadowMaskFilterImpl::SkShadowMaskFilterImpl(SkScalar occluderHeight, const SkPoint3& lightPos,
+ SkScalar lightRadius, SkScalar ambientAlpha,
+ SkScalar spotAlpha, uint32_t flags)
+ : fOccluderHeight(occluderHeight)
+ , fLightPos(lightPos)
+ , fLightRadius(lightRadius)
+ , fAmbientAlpha(ambientAlpha)
+ , fSpotAlpha(spotAlpha)
+ , fFlags(flags) {
+ SkASSERT(fOccluderHeight > 0);
+ SkASSERT(fLightPos.z() > 0 && fLightPos.z() > fOccluderHeight);
+ SkASSERT(fLightRadius > 0);
+ SkASSERT(fAmbientAlpha >= 0);
+ SkASSERT(fSpotAlpha >= 0);
+}
+
+SkMask::Format SkShadowMaskFilterImpl::getFormat() const {
+ return SkMask::kA8_Format;
+}
+
+bool SkShadowMaskFilterImpl::filterMask(SkMask* dst, const SkMask& src,
+ const SkMatrix& matrix,
+ SkIPoint* margin) const {
+ // TODO something
+ return false;
+}
+
+void SkShadowMaskFilterImpl::computeFastBounds(const SkRect& src, SkRect* dst) const {
+ // TODO compute based on ambient + spot data
+ dst->set(src.fLeft, src.fTop, src.fRight, src.fBottom);
+}
+
+sk_sp<SkFlattenable> SkShadowMaskFilterImpl::CreateProc(SkReadBuffer& buffer) {
+ const SkScalar occluderHeight = buffer.readScalar();
+ const SkScalar lightX = buffer.readScalar();
+ const SkScalar lightY = buffer.readScalar();
+ const SkScalar lightZ = buffer.readScalar();
+ const SkPoint3 lightPos = SkPoint3::Make(lightX, lightY, lightZ);
+ const SkScalar lightRadius = buffer.readScalar();
+ const SkScalar ambientAlpha = buffer.readScalar();
+ const SkScalar spotAlpha = buffer.readScalar();
+ const uint32_t flags = buffer.readUInt();
+
+ return SkShadowMaskFilter::Make(occluderHeight, lightPos, lightRadius,
+ ambientAlpha, spotAlpha, flags);
+}
+
+void SkShadowMaskFilterImpl::flatten(SkWriteBuffer& buffer) const {
+ buffer.writeScalar(fOccluderHeight);
+ buffer.writeScalar(fLightPos.fX);
+ buffer.writeScalar(fLightPos.fY);
+ buffer.writeScalar(fLightPos.fZ);
+ buffer.writeScalar(fLightRadius);
+ buffer.writeScalar(fAmbientAlpha);
+ buffer.writeScalar(fSpotAlpha);
+ buffer.writeUInt(fFlags);
+}
+
+#if SK_SUPPORT_GPU
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class GrShadowEdgeEffect : public GrFragmentProcessor {
+public:
+ enum Type {
+ kGaussian_Type,
+ kSmoothStep_Type,
+ kGeometric_Type
+ };
+
+ static sk_sp<GrFragmentProcessor> Make(Type type);
+
+ ~GrShadowEdgeEffect() override {}
+ const char* name() const override { return "GrShadowEdge"; }
+
+private:
+ GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
+
+ GrShadowEdgeEffect(Type type);
+
+ void onGetGLSLProcessorKey(const GrGLSLCaps& caps,
+ GrProcessorKeyBuilder* b) const override;
+
+ bool onIsEqual(const GrFragmentProcessor& other) const override;
+
+ void onComputeInvariantOutput(GrInvariantOutput* inout) const override;
+
+ Type fType;
+
+ GR_DECLARE_FRAGMENT_PROCESSOR_TEST;
+
+ typedef GrFragmentProcessor INHERITED;
+};
+
+sk_sp<GrFragmentProcessor> GrShadowEdgeEffect::Make(Type type) {
+ return sk_sp<GrFragmentProcessor>(new GrShadowEdgeEffect(type));
+}
+
+void GrShadowEdgeEffect::onComputeInvariantOutput(GrInvariantOutput* inout) const {
+ inout->mulByUnknownSingleComponent();
+}
+
+GrShadowEdgeEffect::GrShadowEdgeEffect(Type type)
+ : fType(type) {
+ this->initClassID<GrShadowEdgeEffect>();
+ // TODO: remove this when we switch to a non-distance based approach
+ // enable output of distance information for shape
+ fUsesDistanceVectorField = true;
+}
+
+bool GrShadowEdgeEffect::onIsEqual(const GrFragmentProcessor& other) const {
+ const GrShadowEdgeEffect& see = other.cast<GrShadowEdgeEffect>();
+ return fType == see.fType;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrShadowEdgeEffect);
+
+sk_sp<GrFragmentProcessor> GrShadowEdgeEffect::TestCreate(GrProcessorTestData* d) {
+ int t = d->fRandom->nextRangeU(0, 2);
+ GrShadowEdgeEffect::Type type = kGaussian_Type;
+ if (1 == t) {
+ type = kSmoothStep_Type;
+ } else if (2 == t) {
+ type = kGeometric_Type;
+ }
+ return GrShadowEdgeEffect::Make(type);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+class GrGLShadowEdgeEffect : public GrGLSLFragmentProcessor {
+public:
+ void emitCode(EmitArgs&) override;
+
+protected:
+ void onSetData(const GrGLSLProgramDataManager&, const GrProcessor&) override;
+
+private:
+ typedef GrGLSLFragmentProcessor INHERITED;
+};
+
+void GrGLShadowEdgeEffect::emitCode(EmitArgs& args) {
+
+ GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+
+ // TODO: handle smoothstep and geometric cases
+ if (!args.fGpImplementsDistanceVector) {
+ fragBuilder->codeAppendf("// GP does not implement fsDistanceVector - "
+ " returning semi-transparent black in GrGLShadowEdgeEffect\n");
+ fragBuilder->codeAppendf("vec4 color = %s;", args.fInputColor);
+ fragBuilder->codeAppendf("%s = vec4(0.0, 0.0, 0.0, color.r);", args.fOutputColor);
+ } else {
+ fragBuilder->codeAppendf("vec4 color = %s;", args.fInputColor);
+ fragBuilder->codeAppend("float radius = color.r*256.0*64.0 + color.g*64.0;");
+ fragBuilder->codeAppend("float pad = color.b*64.0;");
+
+ fragBuilder->codeAppendf("float factor = 1.0 - clamp((%s.z - pad)/radius, 0.0, 1.0);",
+ fragBuilder->distanceVectorName());
+ fragBuilder->codeAppend("factor = exp(-factor * factor * 4.0) - 0.018;");
+ fragBuilder->codeAppendf("%s = factor*vec4(0.0, 0.0, 0.0, color.a);",
+ args.fOutputColor);
+ }
+}
+
+void GrGLShadowEdgeEffect::onSetData(const GrGLSLProgramDataManager& pdman,
+ const GrProcessor& proc) {
+}
+
+void GrShadowEdgeEffect::onGetGLSLProcessorKey(const GrGLSLCaps& caps,
+ GrProcessorKeyBuilder* b) const {
+ GrGLShadowEdgeEffect::GenKey(*this, caps, b);
+}
+
+GrGLSLFragmentProcessor* GrShadowEdgeEffect::onCreateGLSLInstance() const {
+ return new GrGLShadowEdgeEffect;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool SkShadowMaskFilterImpl::canFilterMaskGPU(const SkRRect& devRRect,
+ const SkIRect& clipBounds,
+ const SkMatrix& ctm,
+ SkRect* maskRect) const {
+ // TODO
+ *maskRect = devRRect.rect();
+ return true;
+}
+
+bool SkShadowMaskFilterImpl::directFilterMaskGPU(GrTextureProvider* texProvider,
+ GrRenderTargetContext* drawContext,
+ GrPaint* grp,
+ const GrClip& clip,
+ const SkMatrix& viewMatrix,
+ const SkStrokeRec& strokeRec,
+ const SkPath& path) const {
+ SkASSERT(drawContext);
+ // TODO: this will not handle local coordinates properly
+
+ // if circle
+ // TODO: switch to SkScalarNearlyEqual when either oval renderer is updated or we
+ // have our own GeometryProc.
+ if (path.isOval(nullptr) && path.getBounds().width() == path.getBounds().height()) {
+ SkRRect rrect = SkRRect::MakeOval(path.getBounds());
+ return this->directFilterRRectMaskGPU(nullptr, drawContext, grp, clip, SkMatrix::I(),
+ strokeRec, rrect, rrect);
+ } else if (path.isRect(nullptr)) {
+ SkRRect rrect = SkRRect::MakeRect(path.getBounds());
+ return this->directFilterRRectMaskGPU(nullptr, drawContext, grp, clip, SkMatrix::I(),
+ strokeRec, rrect, rrect);
+ }
+
+ // TODO
+ return false;
+}
+
+#define MAX_BLUR_RADIUS 16383.75f
+#define MAX_PAD 64
+
+bool SkShadowMaskFilterImpl::directFilterRRectMaskGPU(GrContext*,
+ GrRenderTargetContext* drawContext,
+ GrPaint* grp,
+ const GrClip& clip,
+ const SkMatrix& viewMatrix,
+ const SkStrokeRec& strokeRec,
+ const SkRRect& rrect,
+ const SkRRect& devRRect) const {
+ // It's likely the caller has already done these checks, but we have to be sure.
+ // TODO: support analytic blurring of general rrect
+
+ // Fast path only supports filled rrects for now.
+ // TODO: fill and stroke as well.
+ if (SkStrokeRec::kFill_Style != strokeRec.getStyle()) {
+ return false;
+ }
+ // Fast path only supports simple rrects with circular corners.
+ SkASSERT(devRRect.allCornersCircular());
+ if (!rrect.isRect() && !rrect.isOval() && !rrect.isSimple()) {
+ return false;
+ }
+ // Fast path only supports uniform scale.
+ SkScalar scaleFactors[2];
+ if (!viewMatrix.getMinMaxScales(scaleFactors)) {
+ // matrix is degenerate
+ return false;
+ }
+ if (scaleFactors[0] != scaleFactors[1]) {
+ return false;
+ }
+ SkScalar scaleFactor = scaleFactors[0];
+
+ // For all of these, we need to ensure we have a rrect with radius >= 0.5f in device space
+ const SkScalar minRadius = 0.5f / scaleFactor;
+ bool isRect = rrect.getSimpleRadii().fX <= minRadius;
+
+ // TODO: take flags into account when generating shadow data
+
+ if (fAmbientAlpha > 0.0f) {
+ static const float kHeightFactor = 1.0f / 128.0f;
+ static const float kGeomFactor = 64.0f;
+
+ SkScalar srcSpaceAmbientRadius = fOccluderHeight * kHeightFactor * kGeomFactor;
+ // the device-space radius sent to the blur shader must fit in 14.2 fixed point
+ if (srcSpaceAmbientRadius*scaleFactor > MAX_BLUR_RADIUS) {
+ srcSpaceAmbientRadius = MAX_BLUR_RADIUS / scaleFactor;
+ }
+ const float umbraAlpha = 1.0f / (1.0f + SkTMax(fOccluderHeight * kHeightFactor, 0.0f));
+ const SkScalar ambientOffset = srcSpaceAmbientRadius * umbraAlpha;
+
+ // For the ambient rrect, we inset the offset rect by half the srcSpaceAmbientRadius
+ // to get our stroke shape.
+ SkScalar ambientPathOutset = SkTMax(ambientOffset - srcSpaceAmbientRadius * 0.5f,
+ minRadius);
+
+ SkRRect ambientRRect;
+ if (isRect) {
+ const SkRect temp = rrect.rect().makeOutset(ambientPathOutset, ambientPathOutset);
+ ambientRRect = SkRRect::MakeRectXY(temp, ambientPathOutset, ambientPathOutset);
+ } else {
+ rrect.outset(ambientPathOutset, ambientPathOutset, &ambientRRect);
+ }
+
+ // we outset the stroke a little to cover up AA on the interior edge
+ float pad = 0.5f;
+ // handle scale of radius and pad due to CTM
+ pad *= scaleFactor;
+ const SkScalar devSpaceAmbientRadius = srcSpaceAmbientRadius * scaleFactor;
+ SkASSERT(devSpaceAmbientRadius <= MAX_BLUR_RADIUS);
+ SkASSERT(pad < MAX_PAD);
+ // convert devSpaceAmbientRadius to 14.2 fixed point and place in the R & G components
+ // convert pad to 6.2 fixed point and place in the B component
+ // TODO: replace this with separate vertex attributes passed by a new GeoProc.
+ // For now we can't easily pass attributes to the fragment shader, so we're overriding
+ // the paint color.
+ uint16_t iDevSpaceAmbientRadius = (uint16_t)(4.0f * devSpaceAmbientRadius);
+
+ GrPaint newPaint(*grp);
+ newPaint.setAntiAlias(true);
+ SkStrokeRec ambientStrokeRec(SkStrokeRec::kHairline_InitStyle);
+ ambientStrokeRec.setStrokeStyle(srcSpaceAmbientRadius + 2.0f * pad, false);
+ newPaint.setColor4f(GrColor4f((iDevSpaceAmbientRadius >> 8)/255.f,
+ (iDevSpaceAmbientRadius & 0xff)/255.f,
+ 4.0f * pad/255.f,
+ fAmbientAlpha));
+
+ sk_sp<GrFragmentProcessor> fp(GrShadowEdgeEffect::Make(GrShadowEdgeEffect::kGaussian_Type));
+ // TODO: switch to coverage FP
+ newPaint.addColorFragmentProcessor(std::move(fp));
+ drawContext->drawRRect(clip, newPaint, viewMatrix, ambientRRect,
+ GrStyle(ambientStrokeRec, nullptr));
+ }
+
+ if (fSpotAlpha > 0.0f) {
+ float zRatio = SkTPin(fOccluderHeight / (fLightPos.fZ - fOccluderHeight), 0.0f, 0.95f);
+
+ SkScalar srcSpaceSpotRadius = 2.0f * fLightRadius * zRatio;
+ // the device-space radius sent to the blur shader must fit in 14.2 fixed point
+ if (srcSpaceSpotRadius > MAX_BLUR_RADIUS) {
+ srcSpaceSpotRadius = MAX_BLUR_RADIUS;
+ }
+
+ SkRRect spotRRect;
+ if (isRect) {
+ spotRRect = SkRRect::MakeRectXY(rrect.rect(), minRadius, minRadius);
+ } else {
+ spotRRect = rrect;
+ }
+
+ SkRRect spotShadowRRect;
+ // Compute the scale and translation for the spot shadow.
+ const SkScalar scale = fLightPos.fZ / (fLightPos.fZ - fOccluderHeight);
+ spotRRect.transform(SkMatrix::MakeScale(scale, scale), &spotShadowRRect);
+
+ SkPoint center = SkPoint::Make(spotShadowRRect.rect().centerX(),
+ spotShadowRRect.rect().centerY());
+ SkMatrix ctmInverse;
+ if (!viewMatrix.invert(&ctmInverse)) {
+ SkDebugf("Matrix is degenerate. Will not render spot shadow!\n");
+ //**** TODO: this is not good
+ return true;
+ }
+ SkPoint lightPos2D = SkPoint::Make(fLightPos.fX, fLightPos.fY);
+ ctmInverse.mapPoints(&lightPos2D, 1);
+ const SkPoint spotOffset = SkPoint::Make(zRatio*(center.fX - lightPos2D.fX),
+ zRatio*(center.fY - lightPos2D.fY));
+
+ // We want to extend the stroked area in so that it meets up with the caster
+ // geometry. The stroked geometry will, by definition already be inset half the
+ // stroke width but we also have to account for the scaling.
+ // We also add 1/2 to cover up AA on the interior edge.
+ SkScalar scaleOffset = (scale - 1.0f) * SkTMax(SkTMax(SkTAbs(rrect.rect().fLeft),
+ SkTAbs(rrect.rect().fRight)),
+ SkTMax(SkTAbs(rrect.rect().fTop),
+ SkTAbs(rrect.rect().fBottom)));
+ SkScalar insetAmount = spotOffset.length() - (0.5f * srcSpaceSpotRadius) +
+ scaleOffset + 0.5f;
+
+ // Compute area
+ SkScalar strokeWidth = srcSpaceSpotRadius + insetAmount;
+ SkScalar strokedArea = 2.0f*strokeWidth *
+ (spotShadowRRect.width() + spotShadowRRect.height());
+ SkScalar filledArea = (spotShadowRRect.height() + srcSpaceSpotRadius) *
+ (spotShadowRRect.width() + srcSpaceSpotRadius);
+
+ GrPaint newPaint(*grp);
+ newPaint.setAntiAlias(true);
+ SkStrokeRec spotStrokeRec(SkStrokeRec::kFill_InitStyle);
+ // If the area of the stroked geometry is larger than the fill geometry,
+ // or if the caster is transparent, just fill it.
+ if (strokedArea > filledArea ||
+ fFlags & SkShadowMaskFilter::kTransparentOccluder_ShadowFlag) {
+ spotStrokeRec.setStrokeStyle(srcSpaceSpotRadius, true);
+ } else {
+ // Since we can't have unequal strokes, inset the shadow rect so the inner
+ // and outer edges of the stroke will land where we want.
+ SkRect insetRect = spotShadowRRect.rect().makeInset(insetAmount / 2.0f,
+ insetAmount / 2.0f);
+ SkScalar insetRad = SkTMax(spotShadowRRect.getSimpleRadii().fX - insetAmount / 2.0f,
+ minRadius);
+ spotShadowRRect = SkRRect::MakeRectXY(insetRect, insetRad, insetRad);
+ spotStrokeRec.setStrokeStyle(strokeWidth, false);
+ }
+
+ // handle scale of radius and pad due to CTM
+ const SkScalar devSpaceSpotRadius = srcSpaceSpotRadius * scaleFactor;
+ SkASSERT(devSpaceSpotRadius <= MAX_BLUR_RADIUS);
+
+ const SkScalar devSpaceSpotPad = 0;
+ SkASSERT(devSpaceSpotPad < MAX_PAD);
+
+ // convert devSpaceSpotRadius to 14.2 fixed point and place in the R & G
+ // components convert devSpaceSpotPad to 6.2 fixed point and place in the B component
+ // TODO: replace this with separate vertex attributes passed by a new GeoProc.
+ // For now we can't easily pass attributes to the fragment shader, so we're overriding
+ // the paint color.
+ uint16_t iDevSpaceSpotRadius = (uint16_t)(4.0f * devSpaceSpotRadius);
+ newPaint.setColor4f(GrColor4f((iDevSpaceSpotRadius >> 8) / 255.f,
+ (iDevSpaceSpotRadius & 0xff) / 255.f,
+ devSpaceSpotPad,
+ fSpotAlpha));
+ spotShadowRRect.offset(spotOffset.fX, spotOffset.fY);
+
+ sk_sp<GrFragmentProcessor> fp(GrShadowEdgeEffect::Make(GrShadowEdgeEffect::kGaussian_Type));
+ // TODO: switch to coverage FP
+ newPaint.addColorFragmentProcessor(std::move(fp));
+
+ drawContext->drawRRect(clip, newPaint, viewMatrix, spotShadowRRect,
+ GrStyle(spotStrokeRec, nullptr));
+ }
+
+ return true;
+}
+
+bool SkShadowMaskFilterImpl::filterMaskGPU(GrTexture* src,
+ const SkMatrix& ctm,
+ const SkIRect& maskRect,
+ GrTexture** result) const {
+ // TODO
+ return false;
+}
+
+#endif
+
+#ifndef SK_IGNORE_TO_STRING
+void SkShadowMaskFilterImpl::toString(SkString* str) const {
+ str->append("SkShadowMaskFilterImpl: (");
+
+ str->append("occluderHeight: ");
+ str->appendScalar(fOccluderHeight);
+ str->append(" ");
+
+ str->append("lightPos: (");
+ str->appendScalar(fLightPos.fX);
+ str->append(", ");
+ str->appendScalar(fLightPos.fY);
+ str->append(", ");
+ str->appendScalar(fLightPos.fZ);
+ str->append(") ");
+
+ str->append("lightRadius: ");
+ str->appendScalar(fLightRadius);
+ str->append(" ");
+
+ str->append("ambientAlpha: ");
+ str->appendScalar(fAmbientAlpha);
+ str->append(" ");
+
+ str->append("spotAlpha: ");
+ str->appendScalar(fSpotAlpha);
+ str->append(" ");
+
+ str->append("flags: (");
+ if (fFlags) {
+ bool needSeparator = false;
+ SkAddFlagToString(str,
+ SkToBool(fFlags & SkShadowMaskFilter::kTransparentOccluder_ShadowFlag),
+ "TransparentOccluder", &needSeparator);
+ SkAddFlagToString(str,
+ SkToBool(fFlags & SkShadowMaskFilter::kGaussianEdge_ShadowFlag),
+ "GaussianEdge", &needSeparator);
+ SkAddFlagToString(str,
+ SkToBool(fFlags & SkShadowMaskFilter::kLargerUmbra_ShadowFlag),
+ "LargerUmbra", &needSeparator);
+ } else {
+ str->append("None");
+ }
+ str->append("))");
+}
+#endif
+
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkShadowMaskFilter)
+SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkShadowMaskFilterImpl)
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END