Index: src/effects/SkRRectsGaussianEdgeShader.cpp |
diff --git a/src/effects/SkRRectsGaussianEdgeShader.cpp b/src/effects/SkRRectsGaussianEdgeShader.cpp |
new file mode 100644 |
index 0000000000000000000000000000000000000000..d3967dbb4434b55e41cce3367b75bd4d75967f30 |
--- /dev/null |
+++ b/src/effects/SkRRectsGaussianEdgeShader.cpp |
@@ -0,0 +1,435 @@ |
+/* |
+ * 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 "SkRRectsGaussianEdgeShader.h" |
+#include "SkReadBuffer.h" |
+#include "SkWriteBuffer.h" |
+ |
+ /** \class SkRRectsGaussianEdgeShaderImpl |
+ * This shader applies a gaussian edge to the intersection of two round rects. |
+ * The round rects must have the same radii at each corner and the x&y radii |
+ * must also be equal. |
+ */ |
+class SkRRectsGaussianEdgeShaderImpl : public SkShader { |
+public: |
+ SkRRectsGaussianEdgeShaderImpl(const SkRRect& first, const SkRRect& second, |
+ SkScalar radius, SkScalar pad) |
+ : fFirst(first) |
+ , fSecond(second) |
+ , fRadius(radius) |
+ , fPad(pad) { |
+ } |
+ |
+ bool isOpaque() const override { return false; } |
+ |
+#if SK_SUPPORT_GPU |
+ sk_sp<GrFragmentProcessor> asFragmentProcessor(const AsFPArgs&) const override; |
+#endif |
+ |
+ class GaussianEdgeShaderContext : public SkShader::Context { |
+ public: |
+ GaussianEdgeShaderContext(const SkRRectsGaussianEdgeShaderImpl&, const ContextRec&); |
+ |
+ ~GaussianEdgeShaderContext() override { } |
+ |
+ void shadeSpan(int x, int y, SkPMColor[], int count) override; |
+ |
+ uint32_t getFlags() const override { return 0; } |
+ |
+ private: |
+ SkColor fPaintColor; |
+ |
+ typedef SkShader::Context INHERITED; |
+ }; |
+ |
+ SK_TO_STRING_OVERRIDE() |
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkRRectsGaussianEdgeShaderImpl) |
+ |
+protected: |
+ void flatten(SkWriteBuffer&) const override; |
+ size_t onContextSize(const ContextRec&) const override; |
+ Context* onCreateContext(const ContextRec&, void*) const override; |
+ |
+private: |
+ SkRRect fFirst; |
+ SkRRect fSecond; |
+ SkScalar fRadius; |
+ SkScalar fPad; |
+ |
+ friend class SkRRectsGaussianEdgeShader; // for serialization registration system |
+ |
+ typedef SkShader INHERITED; |
+}; |
+ |
+//////////////////////////////////////////////////////////////////////////// |
+ |
+#if SK_SUPPORT_GPU |
+ |
+#include "GrCoordTransform.h" |
+#include "GrFragmentProcessor.h" |
+#include "GrInvariantOutput.h" |
+#include "glsl/GrGLSLFragmentProcessor.h" |
+#include "glsl/GrGLSLFragmentShaderBuilder.h" |
+#include "glsl/GrGLSLProgramDataManager.h" |
+#include "glsl/GrGLSLUniformHandler.h" |
+#include "SkGr.h" |
+#include "SkGrPriv.h" |
+ |
+class RRectsGaussianEdgeFP : public GrFragmentProcessor { |
+public: |
+ enum Mode { |
+ kCircle_Mode, |
+ kRect_Mode, |
+ kSimpleCircular_Mode, |
+ }; |
+ |
+ RRectsGaussianEdgeFP(const SkRRect& first, const SkRRect& second, |
+ SkScalar radius, SkScalar pad) |
+ : fFirst(first) |
+ , fSecond(second) |
+ , fRadius(radius) |
+ , fPad(pad) { |
+ this->initClassID<RRectsGaussianEdgeFP>(); |
+ this->setWillReadFragmentPosition(); |
+ |
+ fFirstMode = ComputeMode(fFirst); |
+ fSecondMode = ComputeMode(fSecond); |
+ } |
+ |
+ class GLSLRRectsGaussianEdgeFP : public GrGLSLFragmentProcessor { |
+ public: |
+ GLSLRRectsGaussianEdgeFP() { } |
+ |
+ void emitModeCode(Mode mode, |
+ GrGLSLFPFragmentBuilder* fragBuilder, |
+ const char* posName, |
+ const char* sizesName, |
+ const char* radiiName, |
+ const char* outputName, |
+ const char indices[2]) { // how to access the params for the 2 rrects |
+ |
+ // positive distance is towards the center of the circle |
+ fragBuilder->codeAppendf("vec2 delta = %s.xy - %s.%s;", |
+ fragBuilder->fragmentPosition(), |
+ posName, indices); |
+ |
+ switch (mode) { |
+ case kCircle_Mode: |
+ fragBuilder->codeAppendf("%s = %s.%c - length(delta);", |
+ outputName, |
+ sizesName, indices[0]); |
+ break; |
+ case kRect_Mode: |
+ fragBuilder->codeAppendf("float xDist = %s.%c - abs(delta.x);", |
+ sizesName, indices[0]); |
+ fragBuilder->codeAppendf("float yDist = %s.%c - abs(delta.y);", |
+ sizesName, indices[1]); |
+ fragBuilder->codeAppendf("%s = min(xDist, yDist);", outputName); |
+ break; |
+ case kSimpleCircular_Mode: |
+ // For the circular round rect we first compute the distance |
+ // to the rect. Then we compute a multiplier that is 1 if the |
+ // point is in one of the circular corners. We then compute the |
+ // distance from the corner and then use the multiplier to mask |
+ // between the two distances. |
+ fragBuilder->codeAppendf("float xDist = %s.%c - abs(delta.x);", |
+ sizesName, indices[0]); |
+ fragBuilder->codeAppendf("float yDist = %s.%c - abs(delta.y);", |
+ sizesName, indices[1]); |
+ fragBuilder->codeAppend("float rectDist = min(xDist, yDist);"); |
+ |
+ fragBuilder->codeAppendf("vec2 cornerCenter = %s.%s - %s.%s;", |
+ sizesName, indices, |
+ radiiName, indices); |
+ fragBuilder->codeAppend("delta = vec2(abs(delta.x) - cornerCenter.x," |
+ "abs(delta.y) - cornerCenter.y);"); |
+ fragBuilder->codeAppendf("xDist = %s.%c - abs(delta.x);", |
+ radiiName, indices[0]); |
+ fragBuilder->codeAppendf("yDist = %s.%c - abs(delta.y);", |
+ radiiName, indices[1]); |
+ fragBuilder->codeAppend("float cornerDist = min(xDist, yDist);"); |
+ fragBuilder->codeAppend("float multiplier = step(0.0, cornerDist);"); |
+ |
+ fragBuilder->codeAppendf("delta += %s.%s;", radiiName, indices); |
+ |
+ fragBuilder->codeAppendf("cornerDist = 2.0 * %s.%c - length(delta);", |
+ radiiName, indices[0]); |
+ |
+ fragBuilder->codeAppendf("%s = (multiplier * cornerDist) +" |
+ "((1.0-multiplier) * rectDist);", |
+ outputName); |
+ break; |
+ } |
+ } |
+ |
+ void emitCode(EmitArgs& args) override { |
+ const RRectsGaussianEdgeFP& fp = args.fFp.cast<RRectsGaussianEdgeFP>(); |
+ GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; |
+ GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; |
+ |
+ const char* positionsUniName = nullptr; |
+ fPositionsUni = uniformHandler->addUniform(kFragment_GrShaderFlag, |
+ kVec4f_GrSLType, kDefault_GrSLPrecision, |
+ "Positions", &positionsUniName); |
+ const char* sizesUniName = nullptr; |
+ fSizesUni = uniformHandler->addUniform(kFragment_GrShaderFlag, |
+ kVec4f_GrSLType, kDefault_GrSLPrecision, |
+ "Sizes", &sizesUniName); |
+ const char* radiiUniName = nullptr; |
+ fRadiiUni = uniformHandler->addUniform(kFragment_GrShaderFlag, |
+ kVec4f_GrSLType, kDefault_GrSLPrecision, |
+ "Radii", &radiiUniName); |
+ const char* padRadUniName = nullptr; |
+ fPadRadUni = uniformHandler->addUniform(kFragment_GrShaderFlag, |
+ kVec2f_GrSLType, kDefault_GrSLPrecision, |
+ "PadRad", &padRadUniName); |
+ |
+ fragBuilder->codeAppend("float firstDist;"); |
+ fragBuilder->codeAppend("{"); |
+ this->emitModeCode(fp.firstMode(), fragBuilder, |
+ positionsUniName, sizesUniName, radiiUniName, "firstDist", "xy"); |
+ fragBuilder->codeAppend("}"); |
+ |
+ fragBuilder->codeAppend("float secondDist;"); |
+ fragBuilder->codeAppend("{"); |
+ this->emitModeCode(fp.secondMode(), fragBuilder, |
+ positionsUniName, sizesUniName, radiiUniName, "secondDist", "zw"); |
+ fragBuilder->codeAppend("}"); |
+ |
+ // Here use the sign of the distance to the two round rects to mask off the different |
+ // cases. |
+ fragBuilder->codeAppend("float in1 = step(0.0f, firstDist);"); |
+ fragBuilder->codeAppend("float in2 = step(0.0f, secondDist);"); |
+ fragBuilder->codeAppend("float dist = " |
+ "in1*in2 * min(firstDist, secondDist);" |
+ "in1*(1.0-in2) * firstDist +" |
+ "(1.0-in1)*in2 * secondDist;"); |
+ |
+ // Finally use the distance to apply the Gaussian edge |
+ fragBuilder->codeAppendf("float factor = 1.0 - clamp((dist - %s.x)/%s.y, 0.0, 1.0);", |
+ padRadUniName, padRadUniName); |
+ fragBuilder->codeAppend("factor = exp(-factor * factor * 4.0) - 0.018;"); |
+ fragBuilder->codeAppendf("%s = vec4(%s.rgb, factor);", |
+ args.fOutputColor, args.fInputColor); |
+ } |
+ |
+ static void GenKey(const GrProcessor& proc, const GrGLSLCaps&, |
+ GrProcessorKeyBuilder* b) { |
+ const RRectsGaussianEdgeFP& fp = proc.cast<RRectsGaussianEdgeFP>(); |
+ |
+ b->add32(fp.firstMode() | (fp.secondMode() << 4)); |
+ } |
+ |
+ protected: |
+ void onSetData(const GrGLSLProgramDataManager& pdman, const GrProcessor& proc) override { |
+ const RRectsGaussianEdgeFP& edgeFP = proc.cast<RRectsGaussianEdgeFP>(); |
+ |
+ const SkRRect& first = edgeFP.first(); |
+ const SkRRect& second = edgeFP.second(); |
+ |
+ pdman.set4f(fPositionsUni, |
+ first.getBounds().centerX(), |
+ first.getBounds().centerY(), |
+ second.getBounds().centerX(), |
+ second.getBounds().centerY()); |
+ |
+ pdman.set4f(fSizesUni, |
+ 0.5f * first.rect().width(), |
+ 0.5f * first.rect().height(), |
+ 0.5f * second.rect().width(), |
+ 0.5f * second.rect().height()); |
+ |
+ // This is a bit of overkill since fX should equal fY for both round rects but it |
+ // makes the shader code simpler. |
+ pdman.set4f(fRadiiUni, |
+ 0.5f * first.getSimpleRadii().fX, |
+ 0.5f * first.getSimpleRadii().fY, |
+ 0.5f * second.getSimpleRadii().fX, |
+ 0.5f * second.getSimpleRadii().fY); |
+ |
+ pdman.set2f(fPadRadUni, edgeFP.pad(), edgeFP.radius()); |
+ } |
+ |
+ private: |
+ // The centers of the two round rects (x1, y1, x2, y2) |
+ GrGLSLProgramDataManager::UniformHandle fPositionsUni; |
+ |
+ // The half widths and half heights of the two round rects (w1/2, h1/2, w2/2, h2/2) |
+ // For circles we still upload both width & height to simplify things |
+ GrGLSLProgramDataManager::UniformHandle fSizesUni; |
+ |
+ // The half corner radii of the two round rects (rx1/2, ry1/2, rx2/2, ry2/2) |
+ // We upload both the x&y radii (although they are currently always the same) to make |
+ // the indexing in the shader code simpler. In some future world we could also support |
+ // non-circular corner round rects & ellipses. |
+ GrGLSLProgramDataManager::UniformHandle fRadiiUni; |
+ |
+ // The pad and radius parameters (padding, radius) |
+ GrGLSLProgramDataManager::UniformHandle fPadRadUni; |
+ |
+ typedef GrGLSLFragmentProcessor INHERITED; |
+ }; |
+ |
+ void onGetGLSLProcessorKey(const GrGLSLCaps& caps, GrProcessorKeyBuilder* b) const override { |
+ GLSLRRectsGaussianEdgeFP::GenKey(*this, caps, b); |
+ } |
+ |
+ const char* name() const override { return "RRectsGaussianEdgeFP"; } |
+ |
+ void onComputeInvariantOutput(GrInvariantOutput* inout) const override { |
+ inout->setToUnknown(GrInvariantOutput::kWill_ReadInput); |
+ } |
+ |
+ const SkRRect& first() const { return fFirst; } |
+ Mode firstMode() const { return fFirstMode; } |
+ const SkRRect& second() const { return fSecond; } |
+ Mode secondMode() const { return fSecondMode; } |
+ SkScalar radius() const { return fRadius; } |
+ SkScalar pad() const { return fPad; } |
+ |
+private: |
+ static Mode ComputeMode(const SkRRect& rr) { |
+ if (rr.isCircle()) { |
+ return kCircle_Mode; |
+ } else if (rr.isRect()) { |
+ return kRect_Mode; |
+ } else { |
+ SkASSERT(rr.isSimpleCircular()); |
+ return kSimpleCircular_Mode; |
+ } |
+ } |
+ |
+ GrGLSLFragmentProcessor* onCreateGLSLInstance() const override { |
+ return new GLSLRRectsGaussianEdgeFP; |
+ } |
+ |
+ bool onIsEqual(const GrFragmentProcessor& proc) const override { |
+ const RRectsGaussianEdgeFP& edgeFP = proc.cast<RRectsGaussianEdgeFP>(); |
+ return fFirst == edgeFP.fFirst && fSecond == edgeFP.fSecond && |
+ fRadius == edgeFP.fRadius && fPad == edgeFP.fPad; |
+ } |
+ |
+ SkRRect fFirst; |
+ Mode fFirstMode; |
+ SkRRect fSecond; |
+ Mode fSecondMode; |
+ SkScalar fRadius; |
+ SkScalar fPad; |
+ |
+ typedef GrFragmentProcessor INHERITED; |
+}; |
+ |
+//////////////////////////////////////////////////////////////////////////// |
+ |
+sk_sp<GrFragmentProcessor> SkRRectsGaussianEdgeShaderImpl::asFragmentProcessor( |
+ const AsFPArgs& args) const { |
+ return sk_make_sp<RRectsGaussianEdgeFP>(fFirst, fSecond, fRadius, fPad); |
+} |
+ |
+#endif |
+ |
+//////////////////////////////////////////////////////////////////////////// |
+ |
+SkRRectsGaussianEdgeShaderImpl::GaussianEdgeShaderContext::GaussianEdgeShaderContext( |
+ const SkRRectsGaussianEdgeShaderImpl& shader, |
+ const ContextRec& rec) |
+ : INHERITED(shader, rec) { |
+ |
+ fPaintColor = rec.fPaint->getColor(); |
+} |
+ |
+void SkRRectsGaussianEdgeShaderImpl::GaussianEdgeShaderContext::shadeSpan(int x, int y, |
+ SkPMColor result[], |
+ int count) { |
+ // TODO: implement |
+ for (int i = 0; i < count; ++i) { |
+ result[i] = fPaintColor; |
+ } |
+} |
+ |
+//////////////////////////////////////////////////////////////////////////// |
+ |
+#ifndef SK_IGNORE_TO_STRING |
+void SkRRectsGaussianEdgeShaderImpl::toString(SkString* str) const { |
+ str->appendf("RRectsGaussianEdgeShader: ()"); |
+} |
+#endif |
+ |
+sk_sp<SkFlattenable> SkRRectsGaussianEdgeShaderImpl::CreateProc(SkReadBuffer& buf) { |
+ // Discarding SkShader flattenable params |
+ bool hasLocalMatrix = buf.readBool(); |
+ SkAssertResult(!hasLocalMatrix); |
+ |
+ SkRect rect1, rect2; |
+ |
+ buf.readRect(&rect1); |
+ SkScalar xRad1 = buf.readScalar(); |
+ SkScalar yRad1 = buf.readScalar(); |
+ |
+ buf.readRect(&rect2); |
+ SkScalar xRad2 = buf.readScalar(); |
+ SkScalar yRad2 = buf.readScalar(); |
+ |
+ SkScalar radius = buf.readScalar(); |
+ SkScalar pad = buf.readScalar(); |
+ |
+ return sk_make_sp<SkRRectsGaussianEdgeShaderImpl>(SkRRect::MakeRectXY(rect1, xRad1, yRad1), |
+ SkRRect::MakeRectXY(rect2, xRad2, yRad2), |
+ radius, pad); |
+} |
+ |
+void SkRRectsGaussianEdgeShaderImpl::flatten(SkWriteBuffer& buf) const { |
+ INHERITED::flatten(buf); |
+ |
+ SkASSERT(fFirst.isRect() || fFirst.isCircle() || fFirst.isSimpleCircular()); |
+ buf.writeRect(fFirst.rect()); |
+ const SkVector& radii1 = fFirst.getSimpleRadii(); |
+ buf.writeScalar(radii1.fX); |
+ buf.writeScalar(radii1.fY); |
+ |
+ SkASSERT(fSecond.isRect() || fSecond.isCircle() || fSecond.isSimpleCircular()); |
+ buf.writeRect(fSecond.rect()); |
+ const SkVector& radii2 = fSecond.getSimpleRadii(); |
+ buf.writeScalar(radii2.fX); |
+ buf.writeScalar(radii2.fY); |
+ |
+ buf.writeScalar(fRadius); |
+ buf.writeScalar(fPad); |
+} |
+ |
+size_t SkRRectsGaussianEdgeShaderImpl::onContextSize(const ContextRec& rec) const { |
+ return sizeof(GaussianEdgeShaderContext); |
+} |
+ |
+SkShader::Context* SkRRectsGaussianEdgeShaderImpl::onCreateContext(const ContextRec& rec, |
+ void* storage) const { |
+ return new (storage) GaussianEdgeShaderContext(*this, rec); |
+} |
+ |
+/////////////////////////////////////////////////////////////////////////////// |
+ |
+sk_sp<SkShader> SkRRectsGaussianEdgeShader::Make(const SkRRect& first, |
+ const SkRRect& second, |
+ SkScalar radius, |
+ SkScalar pad) { |
+ if ((!first.isRect() && !first.isCircle() && !first.isSimpleCircular()) || |
+ (!second.isRect() && !second.isCircle() && !second.isSimpleCircular())) { |
+ // we only deal with the shapes where the x & y radii are equal |
+ // and the same for all four corners |
+ return nullptr; |
+ } |
+ |
+ return sk_make_sp<SkRRectsGaussianEdgeShaderImpl>(first, second, radius, pad); |
+} |
+ |
+/////////////////////////////////////////////////////////////////////////////// |
+ |
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkRRectsGaussianEdgeShader) |
+SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkRRectsGaussianEdgeShaderImpl) |
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END |
+ |
+/////////////////////////////////////////////////////////////////////////////// |