diff options
Diffstat (limited to 'src/effects/SkShadowMaskFilter.cpp')
-rwxr-xr-x | src/effects/SkShadowMaskFilter.cpp | 569 |
1 files changed, 569 insertions, 0 deletions
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 |