aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--gn/gpu.gni4
-rw-r--r--include/gpu/GrRenderTargetContext.h17
-rw-r--r--samplecode/SampleAndroidShadows.cpp2
-rwxr-xr-xsrc/effects/SkShadowMaskFilter.cpp196
-rw-r--r--src/gpu/GrRenderTargetContext.cpp61
-rwxr-xr-xsrc/gpu/batches/GrShadowRRectBatch.cpp964
-rwxr-xr-xsrc/gpu/batches/GrShadowRRectBatch.h26
-rwxr-xr-xsrc/gpu/effects/GrShadowGeoProc.cpp104
-rwxr-xr-xsrc/gpu/effects/GrShadowGeoProc.h56
9 files changed, 1251 insertions, 179 deletions
diff --git a/gn/gpu.gni b/gn/gpu.gni
index fca4939302..a705ed6af2 100644
--- a/gn/gpu.gni
+++ b/gn/gpu.gni
@@ -264,6 +264,8 @@ skia_gpu_sources = [
"$_src/gpu/batches/GrRectBatchFactory.cpp",
"$_src/gpu/batches/GrRegionBatch.cpp",
"$_src/gpu/batches/GrRegionBatch.h",
+ "$_src/gpu/batches/GrShadowRRectBatch.cpp",
+ "$_src/gpu/batches/GrShadowRRectBatch.h",
"$_src/gpu/batches/GrStencilAndCoverPathRenderer.cpp",
"$_src/gpu/batches/GrStencilAndCoverPathRenderer.h",
"$_src/gpu/batches/GrStencilPathBatch.h",
@@ -305,6 +307,8 @@ skia_gpu_sources = [
"$_src/gpu/effects/GrPorterDuffXferProcessor.cpp",
"$_src/gpu/effects/GrRRectEffect.cpp",
"$_src/gpu/effects/GrRRectEffect.h",
+ "$_src/gpu/effects/GrShadowGeoProc.cpp",
+ "$_src/gpu/effects/GrShadowGeoProc.h",
"$_src/gpu/effects/GrSimpleTextureEffect.cpp",
"$_src/gpu/effects/GrSimpleTextureEffect.h",
"$_src/gpu/effects/GrSingleTextureEffect.cpp",
diff --git a/include/gpu/GrRenderTargetContext.h b/include/gpu/GrRenderTargetContext.h
index d1896db0cb..6ed321c0f7 100644
--- a/include/gpu/GrRenderTargetContext.h
+++ b/include/gpu/GrRenderTargetContext.h
@@ -144,6 +144,23 @@ public:
const GrStyle& style);
/**
+ * Draw a roundrect using a paint and a shadow shader. This is separate from drawRRect
+ * because it uses different underlying geometry and GeometryProcessor
+ *
+ * @param paint describes how to color pixels.
+ * @param viewMatrix transformation matrix
+ * @param rrect the roundrect to draw
+ * @param blurRadius amount of shadow blur to apply (in device space)
+ * @param style style to apply to the rrect. Currently path effects are not allowed.
+ */
+ void drawShadowRRect(const GrClip&,
+ const GrPaint&,
+ const SkMatrix& viewMatrix,
+ const SkRRect& rrect,
+ SkScalar blurRadius,
+ const GrStyle& style);
+
+ /**
* Shortcut for drawing an SkPath consisting of nested rrects using a paint.
* Does not support stroking. The result is undefined if outer does not contain
* inner.
diff --git a/samplecode/SampleAndroidShadows.cpp b/samplecode/SampleAndroidShadows.cpp
index 0b9358aec5..87fa0510fe 100644
--- a/samplecode/SampleAndroidShadows.cpp
+++ b/samplecode/SampleAndroidShadows.cpp
@@ -434,7 +434,7 @@ protected:
canvas->translate(200, 90);
lightPos.fX += 200;
lightPos.fY += 90;
- this->drawShadowedPath(canvas, fRectPath, 2, paint, kAmbientAlpha,
+ this->drawShadowedPath(canvas, fRectPath, 2, paint, kAmbientAlpha,
lightPos, kLightWidth, kSpotAlpha);
paint.setColor(SK_ColorRED);
diff --git a/src/effects/SkShadowMaskFilter.cpp b/src/effects/SkShadowMaskFilter.cpp
index 3894d749f1..958ab170a7 100755
--- a/src/effects/SkShadowMaskFilter.cpp
+++ b/src/effects/SkShadowMaskFilter.cpp
@@ -154,125 +154,6 @@ void SkShadowMaskFilterImpl::flatten(SkWriteBuffer& buffer) const {
///////////////////////////////////////////////////////////////////////////////////////////////////
-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,
@@ -309,11 +190,8 @@ bool SkShadowMaskFilterImpl::directFilterMaskGPU(GrTextureProvider* texProvider,
return false;
}
-#define MAX_BLUR_RADIUS 16383.75f
-#define MAX_PAD 64
-
bool SkShadowMaskFilterImpl::directFilterRRectMaskGPU(GrContext*,
- GrRenderTargetContext* drawContext,
+ GrRenderTargetContext* renderTargetContext,
GrPaint* grp,
const GrClip& clip,
const SkMatrix& viewMatrix,
@@ -355,10 +233,6 @@ bool SkShadowMaskFilterImpl::directFilterRRectMaskGPU(GrContext*,
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;
@@ -375,44 +249,25 @@ bool SkShadowMaskFilterImpl::directFilterRRectMaskGPU(GrContext*,
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);
+ GrColor4f color = newPaint.getColor4f();
+ color.fRGBA[3] *= fAmbientAlpha;
+ newPaint.setColor4f(color);
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));
+ ambientStrokeRec.setStrokeStyle(srcSpaceAmbientRadius, false);
+
+ renderTargetContext->drawShadowRRect(clip, newPaint, viewMatrix, ambientRRect,
+ devSpaceAmbientRadius,
+ 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) {
@@ -442,13 +297,11 @@ bool SkShadowMaskFilterImpl::directFilterRRectMaskGPU(GrContext*,
// 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;
+ SkScalar insetAmount = spotOffset.length() - (0.5f * srcSpaceSpotRadius) + scaleOffset;
// Compute area
SkScalar strokeWidth = srcSpaceSpotRadius + insetAmount;
@@ -459,6 +312,10 @@ bool SkShadowMaskFilterImpl::directFilterRRectMaskGPU(GrContext*,
GrPaint newPaint(*grp);
newPaint.setAntiAlias(true);
+ GrColor4f color = newPaint.getColor4f();
+ color.fRGBA[3] *= fSpotAlpha;
+ newPaint.setColor4f(color);
+
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.
@@ -478,29 +335,12 @@ bool SkShadowMaskFilterImpl::directFilterRRectMaskGPU(GrContext*,
// 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));
+ spotShadowRRect.offset(spotOffset.fX, spotOffset.fY);
- drawContext->drawRRect(clip, newPaint, viewMatrix, spotShadowRRect,
- GrStyle(spotStrokeRec, nullptr));
+ renderTargetContext->drawShadowRRect(clip, newPaint, viewMatrix, spotShadowRRect,
+ devSpaceSpotRadius,
+ GrStyle(spotStrokeRec, nullptr));
}
return true;
diff --git a/src/gpu/GrRenderTargetContext.cpp b/src/gpu/GrRenderTargetContext.cpp
index e311c3d0ef..6238fa8025 100644
--- a/src/gpu/GrRenderTargetContext.cpp
+++ b/src/gpu/GrRenderTargetContext.cpp
@@ -27,6 +27,7 @@
#include "batches/GrRectBatchFactory.h"
#include "batches/GrNinePatch.h" // TODO Factory
#include "batches/GrRegionBatch.h"
+#include "batches/GrShadowRRectBatch.h"
#include "effects/GrRRectEffect.h"
@@ -913,6 +914,66 @@ void GrRenderTargetContext::drawRRect(const GrClip& origClip,
this->internalDrawPath(*clip, paint, viewMatrix, path, style);
}
+///////////////////////////////////////////////////////////////////////////////
+
+void GrRenderTargetContext::drawShadowRRect(const GrClip& clip,
+ const GrPaint& paint,
+ const SkMatrix& viewMatrix,
+ const SkRRect& rrect,
+ SkScalar blurRadius,
+ const GrStyle& style) {
+ ASSERT_SINGLE_OWNER
+ RETURN_IF_ABANDONED
+ SkDEBUGCODE(this->validate();)
+ GR_AUDIT_TRAIL_AUTO_FRAME(fAuditTrail, "GrRenderTargetContext::drawShadowRRect");
+ if (rrect.isEmpty()) {
+ return;
+ }
+
+ SkASSERT(!style.pathEffect()); // this should've been devolved to a path in SkGpuDevice
+
+ AutoCheckFlush acf(fDrawingManager);
+ const SkStrokeRec stroke = style.strokeRec();
+ bool useHWAA;
+
+ // TODO: add instancing support
+ //if (GrCaps::InstancedSupport::kNone != fContext->caps()->instancedSupport() &&
+ // stroke.isFillStyle()) {
+ // InstancedRendering* ir = this->getOpList()->instancedRendering();
+ // SkAutoTUnref<GrDrawBatch> batch(ir->recordRRect(rrect, viewMatrix, paint.getColor(),
+ // paint.isAntiAlias(), fInstancedPipelineInfo,
+ // &useHWAA));
+ // if (batch) {
+ // GrPipelineBuilder pipelineBuilder(paint, useHWAA);
+ // this->getOpList()->drawBatch(pipelineBuilder, this, *clip, batch);
+ // return;
+ // }
+ //}
+
+ if (should_apply_coverage_aa(paint, fRenderTargetProxy.get(), &useHWAA)) {
+ GrShaderCaps* shaderCaps = fContext->caps()->shaderCaps();
+ sk_sp<GrDrawBatch> batch(CreateShadowRRectBatch(
+ paint.getColor(),
+ viewMatrix,
+ rrect,
+ blurRadius,
+ stroke,
+ shaderCaps));
+ if (batch) {
+ GrPipelineBuilder pipelineBuilder(paint, useHWAA);
+ this->getOpList()->drawBatch(pipelineBuilder, this, clip, batch.get());
+ return;
+ }
+ }
+
+ SkPath path;
+ path.setIsVolatile(true);
+ path.addRRect(rrect);
+ this->internalDrawPath(clip, paint, viewMatrix, path, style);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
bool GrRenderTargetContext::drawFilledDRRect(const GrClip& clip,
const GrPaint& paintIn,
const SkMatrix& viewMatrix,
diff --git a/src/gpu/batches/GrShadowRRectBatch.cpp b/src/gpu/batches/GrShadowRRectBatch.cpp
new file mode 100755
index 0000000000..08f7e86952
--- /dev/null
+++ b/src/gpu/batches/GrShadowRRectBatch.cpp
@@ -0,0 +1,964 @@
+/*
+ * 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 "GrShadowRRectBatch.h"
+
+#include "GrBatchFlushState.h"
+#include "GrBatchTest.h"
+#include "GrResourceProvider.h"
+#include "GrStyle.h"
+
+#include "effects/GrShadowGeoProc.h"
+
+///////////////////////////////////////////////////////////////////////////////
+
+// We have two possible cases for geometry for a circle:
+
+// In the case of a normal fill, we draw geometry for the circle as an octagon.
+static const uint16_t gFillCircleIndices[] = {
+ // enter the octagon
+ 0, 1, 8, 1, 2, 8,
+ 2, 3, 8, 3, 4, 8,
+ 4, 5, 8, 5, 6, 8,
+ 6, 7, 8, 7, 0, 8,
+};
+
+// For stroked circles, we use two nested octagons.
+static const uint16_t gStrokeCircleIndices[] = {
+ // enter the octagon
+ 0, 1, 9, 0, 9, 8,
+ 1, 2, 10, 1, 10, 9,
+ 2, 3, 11, 2, 11, 10,
+ 3, 4, 12, 3, 12, 11,
+ 4, 5, 13, 4, 13, 12,
+ 5, 6, 14, 5, 14, 13,
+ 6, 7, 15, 6, 15, 14,
+ 7, 0, 8, 7, 8, 15,
+};
+
+static const int kIndicesPerFillCircle = SK_ARRAY_COUNT(gFillCircleIndices);
+static const int kIndicesPerStrokeCircle = SK_ARRAY_COUNT(gStrokeCircleIndices);
+static const int kVertsPerStrokeCircle = 16;
+static const int kVertsPerFillCircle = 9;
+
+static int circle_type_to_vert_count(bool stroked) {
+ return stroked ? kVertsPerStrokeCircle : kVertsPerFillCircle;
+}
+
+static int circle_type_to_index_count(bool stroked) {
+ return stroked ? kIndicesPerStrokeCircle : kIndicesPerFillCircle;
+}
+
+static const uint16_t* circle_type_to_indices(bool stroked) {
+ return stroked ? gStrokeCircleIndices : gFillCircleIndices;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+class ShadowCircleBatch : public GrVertexBatch {
+public:
+ DEFINE_BATCH_CLASS_ID
+
+ static GrDrawBatch* Create(GrColor color, const SkMatrix& viewMatrix, SkPoint center,
+ SkScalar radius, SkScalar blurRadius, const GrStyle& style) {
+ SkASSERT(viewMatrix.isSimilarity());
+ const SkStrokeRec& stroke = style.strokeRec();
+ if (style.hasPathEffect()) {
+ return nullptr;
+ }
+ SkStrokeRec::Style recStyle = stroke.getStyle();
+
+ viewMatrix.mapPoints(&center, 1);
+ radius = viewMatrix.mapRadius(radius);
+ SkScalar strokeWidth = viewMatrix.mapRadius(stroke.getWidth());
+
+ bool isStrokeOnly = SkStrokeRec::kStroke_Style == recStyle ||
+ SkStrokeRec::kHairline_Style == recStyle;
+ bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == recStyle;
+
+ SkScalar innerRadius = -SK_ScalarHalf;
+ SkScalar outerRadius = radius;
+ SkScalar halfWidth = 0;
+ if (hasStroke) {
+ if (SkScalarNearlyZero(strokeWidth)) {
+ halfWidth = SK_ScalarHalf;
+ } else {
+ halfWidth = SkScalarHalf(strokeWidth);
+ }
+
+ outerRadius += halfWidth;
+ if (isStrokeOnly) {
+ innerRadius = radius - halfWidth;
+ }
+ }
+
+ // TODO: still needed?
+ // The radii are outset for two reasons. First, it allows the shader to simply perform
+ // simpler computation because the computed alpha is zero, rather than 50%, at the radius.
+ // Second, the outer radius is used to compute the verts of the bounding box that is
+ // rendered and the outset ensures the box will cover all partially covered by the circle.
+ outerRadius += SK_ScalarHalf;
+ innerRadius -= SK_ScalarHalf;
+ bool stroked = isStrokeOnly && innerRadius > 0.0f;
+ ShadowCircleBatch* batch = new ShadowCircleBatch();
+ batch->fViewMatrixIfUsingLocalCoords = viewMatrix;
+
+ SkRect devBounds = SkRect::MakeLTRB(center.fX - outerRadius, center.fY - outerRadius,
+ center.fX + outerRadius, center.fY + outerRadius);
+
+ batch->fGeoData.emplace_back(Geometry{
+ color,
+ outerRadius,
+ innerRadius,
+ blurRadius,
+ devBounds,
+ stroked
+ });
+
+ // Use the original radius and stroke radius for the bounds so that it does not include the
+ // AA bloat.
+ radius += halfWidth;
+ batch->setBounds({ center.fX - radius, center.fY - radius,
+ center.fX + radius, center.fY + radius },
+ HasAABloat::kNo, IsZeroArea::kNo);
+ batch->fVertCount = circle_type_to_vert_count(stroked);
+ batch->fIndexCount = circle_type_to_index_count(stroked);
+ return batch;
+ }
+
+ const char* name() const override { return "ShadowCircleBatch"; }
+
+ SkString dumpInfo() const override {
+ SkString string;
+ for (int i = 0; i < fGeoData.count(); ++i) {
+ string.appendf("Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], "
+ "OuterRad: %.2f, InnerRad: %.2f, BlurRad: %.2f\n",
+ fGeoData[i].fColor,
+ fGeoData[i].fDevBounds.fLeft, fGeoData[i].fDevBounds.fTop,
+ fGeoData[i].fDevBounds.fRight, fGeoData[i].fDevBounds.fBottom,
+ fGeoData[i].fOuterRadius, fGeoData[i].fInnerRadius,
+ fGeoData[i].fBlurRadius);
+ }
+ string.append(INHERITED::dumpInfo());
+ return string;
+ }
+
+ void computePipelineOptimizations(GrInitInvariantOutput* color,
+ GrInitInvariantOutput* coverage,
+ GrBatchToXPOverrides* overrides) const override {
+ // When this is called on a batch, there is only one geometry bundle
+ color->setKnownFourComponents(fGeoData[0].fColor);
+ coverage->setUnknownSingleComponent();
+ }
+
+private:
+ ShadowCircleBatch() : INHERITED(ClassID()) {}
+ void initBatchTracker(const GrXPOverridesForBatch& overrides) override {
+ // Handle any overrides that affect our GP.
+ overrides.getOverrideColorIfSet(&fGeoData[0].fColor);
+ if (!overrides.readsLocalCoords()) {
+ fViewMatrixIfUsingLocalCoords.reset();
+ }
+ }
+
+ void onPrepareDraws(Target* target) const override {
+ SkMatrix localMatrix;
+ if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) {
+ return;
+ }
+
+ // Setup geometry processor
+ sk_sp<GrGeometryProcessor> gp(GrRRectShadowGeoProc::Make(localMatrix));
+
+ struct CircleVertex {
+ SkPoint fPos;
+ GrColor fColor;
+ SkPoint fOffset;
+ SkScalar fOuterRadius;
+ SkScalar fBlurRadius;
+ };
+
+ int instanceCount = fGeoData.count();
+ size_t vertexStride = gp->getVertexStride();
+ SkASSERT(vertexStride == sizeof(CircleVertex));
+
+ const GrBuffer* vertexBuffer;
+ int firstVertex;
+ char* vertices = (char*)target->makeVertexSpace(vertexStride, fVertCount,
+ &vertexBuffer, &firstVertex);
+ if (!vertices) {
+ SkDebugf("Could not allocate vertices\n");
+ return;
+ }
+
+ const GrBuffer* indexBuffer = nullptr;
+ int firstIndex = 0;
+ uint16_t* indices = target->makeIndexSpace(fIndexCount, &indexBuffer, &firstIndex);
+ if (!indices) {
+ SkDebugf("Could not allocate indices\n");
+ return;
+ }
+
+ int currStartVertex = 0;
+ for (int i = 0; i < instanceCount; i++) {
+ const Geometry& geom = fGeoData[i];
+
+ GrColor color = geom.fColor;
+ SkScalar outerRadius = geom.fOuterRadius;
+ SkScalar innerRadius = geom.fInnerRadius;
+ SkScalar blurRadius = geom.fBlurRadius;
+
+ const SkRect& bounds = geom.fDevBounds;
+ CircleVertex* ov0 = reinterpret_cast<CircleVertex*>(vertices + 0 * vertexStride);
+ CircleVertex* ov1 = reinterpret_cast<CircleVertex*>(vertices + 1 * vertexStride);
+ CircleVertex* ov2 = reinterpret_cast<CircleVertex*>(vertices + 2 * vertexStride);
+ CircleVertex* ov3 = reinterpret_cast<CircleVertex*>(vertices + 3 * vertexStride);
+ CircleVertex* ov4 = reinterpret_cast<CircleVertex*>(vertices + 4 * vertexStride);
+ CircleVertex* ov5 = reinterpret_cast<CircleVertex*>(vertices + 5 * vertexStride);
+ CircleVertex* ov6 = reinterpret_cast<CircleVertex*>(vertices + 6 * vertexStride);
+ CircleVertex* ov7 = reinterpret_cast<CircleVertex*>(vertices + 7 * vertexStride);
+
+ // The inner radius in the vertex data must be specified in normalized space.
+ innerRadius = innerRadius / outerRadius;
+
+ SkPoint center = SkPoint::Make(bounds.centerX(), bounds.centerY());
+ SkScalar halfWidth = 0.5f*bounds.width();
+ SkScalar octOffset = 0.41421356237f; // sqrt(2) - 1
+
+ ov0->fPos = center + SkPoint::Make(-octOffset*halfWidth, -halfWidth);
+ ov0->fColor = color;
+ ov0->fOffset = SkPoint::Make(-octOffset, -1);
+ ov0->fOuterRadius = outerRadius;
+ ov0->fBlurRadius = blurRadius;
+
+ ov1->fPos = center + SkPoint::Make(octOffset*halfWidth, -halfWidth);
+ ov1->fColor = color;
+ ov1->fOffset = SkPoint::Make(octOffset, -1);
+ ov1->fOuterRadius = outerRadius;
+ ov1->fBlurRadius = blurRadius;
+
+ ov2->fPos = center + SkPoint::Make(halfWidth, -octOffset*halfWidth);
+ ov2->fColor = color;
+ ov2->fOffset = SkPoint::Make(1, -octOffset);
+ ov2->fOuterRadius = outerRadius;
+ ov2->fBlurRadius = blurRadius;
+
+ ov3->fPos = center + SkPoint::Make(halfWidth, octOffset*halfWidth);
+ ov3->fColor = color;
+ ov3->fOffset = SkPoint::Make(1, octOffset);
+ ov3->fOuterRadius = outerRadius;
+ ov3->fBlurRadius = blurRadius;
+
+ ov4->fPos = center + SkPoint::Make(octOffset*halfWidth, halfWidth);
+ ov4->fColor = color;
+ ov4->fOffset = SkPoint::Make(octOffset, 1);
+ ov4->fOuterRadius = outerRadius;
+ ov4->fBlurRadius = blurRadius;
+
+ ov5->fPos = center + SkPoint::Make(-octOffset*halfWidth, halfWidth);
+ ov5->fColor = color;
+ ov5->fOffset = SkPoint::Make(-octOffset, 1);
+ ov5->fOuterRadius = outerRadius;
+ ov5->fBlurRadius = blurRadius;
+
+ ov6->fPos = center + SkPoint::Make(-halfWidth, octOffset*halfWidth);
+ ov6->fColor = color;
+ ov6->fOffset = SkPoint::Make(-1, octOffset);
+ ov6->fOuterRadius = outerRadius;
+ ov6->fBlurRadius = blurRadius;
+
+ ov7->fPos = center + SkPoint::Make(-halfWidth, -octOffset*halfWidth);
+ ov7->fColor = color;
+ ov7->fOffset = SkPoint::Make(-1, -octOffset);
+ ov7->fOuterRadius = outerRadius;
+ ov7->fBlurRadius = blurRadius;
+
+ if (geom.fStroked) {
+ // compute the inner ring
+ CircleVertex* iv0 = reinterpret_cast<CircleVertex*>(vertices + 8 * vertexStride);
+ CircleVertex* iv1 = reinterpret_cast<CircleVertex*>(vertices + 9 * vertexStride);
+ CircleVertex* iv2 = reinterpret_cast<CircleVertex*>(vertices + 10 * vertexStride);
+ CircleVertex* iv3 = reinterpret_cast<CircleVertex*>(vertices + 11 * vertexStride);
+ CircleVertex* iv4 = reinterpret_cast<CircleVertex*>(vertices + 12 * vertexStride);
+ CircleVertex* iv5 = reinterpret_cast<CircleVertex*>(vertices + 13 * vertexStride);
+ CircleVertex* iv6 = reinterpret_cast<CircleVertex*>(vertices + 14 * vertexStride);
+ CircleVertex* iv7 = reinterpret_cast<CircleVertex*>(vertices + 15 * vertexStride);
+
+ // cosine and sine of pi/8
+ SkScalar c = 0.923579533f;
+ SkScalar s = 0.382683432f;
+ SkScalar r = geom.fInnerRadius;
+
+ iv0->fPos = center + SkPoint::Make(-s*r, -c*r);
+ iv0->fColor = color;
+ iv0->fOffset = SkPoint::Make(-s*innerRadius, -c*innerRadius);
+ iv0->fOuterRadius = outerRadius;
+ iv0->fBlurRadius = blurRadius;
+
+ iv1->fPos = center + SkPoint::Make(s*r, -c*r);
+ iv1->fColor = color;
+ iv1->fOffset = SkPoint::Make(s*innerRadius, -c*innerRadius);
+ iv1->fOuterRadius = outerRadius;
+ iv1->fBlurRadius = blurRadius;
+
+ iv2->fPos = center + SkPoint::Make(c*r, -s*r);
+ iv2->fColor = color;
+ iv2->fOffset = SkPoint::Make(c*innerRadius, -s*innerRadius);
+ iv2->fOuterRadius = outerRadius;
+ iv2->fBlurRadius = blurRadius;
+
+ iv3->fPos = center + SkPoint::Make(c*r, s*r);
+ iv3->fColor = color;
+ iv3->fOffset = SkPoint::Make(c*innerRadius, s*innerRadius);
+ iv3->fOuterRadius = outerRadius;
+ iv3->fBlurRadius = blurRadius;
+
+ iv4->fPos = center + SkPoint::Make(s*r, c*r);
+ iv4->fColor = color;
+ iv4->fOffset = SkPoint::Make(s*innerRadius, c*innerRadius);
+ iv4->fOuterRadius = outerRadius;
+ iv4->fBlurRadius = blurRadius;
+
+ iv5->fPos = center + SkPoint::Make(-s*r, c*r);
+ iv5->fColor = color;
+ iv5->fOffset = SkPoint::Make(-s*innerRadius, c*innerRadius);
+ iv5->fOuterRadius = outerRadius;
+ iv5->fBlurRadius = blurRadius;
+
+ iv6->fPos = center + SkPoint::Make(-c*r, s*r);
+ iv6->fColor = color;
+ iv6->fOffset = SkPoint::Make(-c*innerRadius, s*innerRadius);
+ iv6->fOuterRadius = outerRadius;
+ iv6->fBlurRadius = blurRadius;
+
+ iv7->fPos = center + SkPoint::Make(-c*r, -s*r);
+ iv7->fColor = color;
+ iv7->fOffset = SkPoint::Make(-c*innerRadius, -s*innerRadius);
+ iv7->fOuterRadius = outerRadius;
+ iv7->fBlurRadius = blurRadius;
+ } else {
+ // filled
+ CircleVertex* iv = reinterpret_cast<CircleVertex*>(vertices + 8 * vertexStride);
+ iv->fPos = center;
+ iv->fColor = color;
+ iv->fOffset = SkPoint::Make(0, 0);
+ iv->fOuterRadius = outerRadius;
+ iv->fBlurRadius = blurRadius;
+ }
+
+ const uint16_t* primIndices = circle_type_to_indices(geom.fStroked);
+ const int primIndexCount = circle_type_to_index_count(geom.fStroked);
+ for (int i = 0; i < primIndexCount; ++i) {
+ *indices++ = primIndices[i] + currStartVertex;
+ }
+
+ currStartVertex += circle_type_to_vert_count(geom.fStroked);
+ vertices += circle_type_to_vert_count(geom.fStroked)*vertexStride;
+ }
+
+ GrMesh mesh;
+ mesh.initIndexed(kTriangles_GrPrimitiveType, vertexBuffer, indexBuffer, firstVertex,
+ firstIndex, fVertCount, fIndexCount);
+ target->draw(gp.get(), mesh);
+ }
+
+ bool onCombineIfPossible(GrBatch* t, const GrCaps& caps) override {
+ ShadowCircleBatch* that = t->cast<ShadowCircleBatch>();
+ if (!GrPipeline::CanCombine(*this->pipeline(), this->bounds(), *that->pipeline(),
+ that->bounds(), caps)) {
+ return false;
+ }
+
+ if (!fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) {
+ return false;
+ }
+
+ fGeoData.push_back_n(that->fGeoData.count(), that->fGeoData.begin());
+ this->joinBounds(*that);
+ fVertCount += that->fVertCount;
+ fIndexCount += that->fIndexCount;
+ return true;
+ }
+
+ struct Geometry {
+ GrColor fColor;
+ SkScalar fOuterRadius;
+ SkScalar fInnerRadius;
+ SkScalar fBlurRadius;
+ SkRect fDevBounds;
+ bool fStroked;
+ };
+
+ SkSTArray<1, Geometry, true> fGeoData;
+ SkMatrix fViewMatrixIfUsingLocalCoords;
+ int fVertCount;
+ int fIndexCount;
+
+ typedef GrVertexBatch INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+// We have two possible cases for geometry for a shadow roundrect.
+//
+// In the case of a normal stroke, we draw the roundrect as a 9-patch without the center quad.
+// ____________
+// |_|________|_|
+// | | | |
+// | | | |
+// | | | |
+// |_|________|_|
+// |_|________|_|
+//
+// In the case where the stroke width is greater than twice the corner radius (overstroke),
+// we add additional geometry to mark out the rectangle in the center. The shared vertices
+// are duplicated so we can set a different outer radius for the fill calculation.
+// ____________
+// |_|________|_|
+// | |\ ____ /| |
+// | | | | | |
+// | | |____| | |
+// |_|/______\|_|
+// |_|________|_|
+//
+// For filled rrects we reuse the overstroke geometry but make the inner rect degenerate
+// (either a point or a horizontal or vertical line).
+
+static const uint16_t gOverstrokeRRectIndices[] = {
+ // corners
+ 0, 1, 5, 0, 5, 4,
+ 2, 3, 7, 2, 7, 6,
+ 8, 9, 13, 8, 13, 12,
+ 10, 11, 15, 10, 15, 14,
+
+ // edges
+ 1, 2, 6, 1, 6, 5,
+ 4, 5, 9, 4, 9, 8,
+ 6, 7, 11, 6, 11, 10,
+ 9, 10, 14, 9, 14, 13,
+
+ // overstroke quads
+ // we place this at the end so that we can skip these indices when rendering as stroked
+ 16, 17, 19, 16, 19, 18,
+ 19, 17, 23, 19, 23, 21,
+ 21, 23, 22, 21, 22, 20,
+ 22, 16, 18, 22, 18, 20,
+};
+// standard stroke indices start at the same place, but will skip the overstroke "ring"
+static const uint16_t* gStrokeRRectIndices = gOverstrokeRRectIndices;
+
+// overstroke count
+static const int kIndicesPerOverstrokeRRect = SK_ARRAY_COUNT(gOverstrokeRRectIndices);
+// simple stroke count skips overstroke indices
+static const int kIndicesPerStrokeRRect = kIndicesPerOverstrokeRRect - 6 * 4 + 6;
+static const int kVertsPerStrokeRRect = 16;
+static const int kVertsPerOverstrokeRRect = 24;
+
+enum RRectType {
+ kFill_RRectType,
+ kStroke_RRectType,
+ kOverstroke_RRectType,
+};
+
+static int rrect_type_to_vert_count(RRectType type) {
+ static const int kTypeToVertCount[] = {
+ kVertsPerOverstrokeRRect,
+ kVertsPerStrokeRRect,
+ kVertsPerOverstrokeRRect,
+ };
+
+ return kTypeToVertCount[type];
+}
+
+static int rrect_type_to_index_count(RRectType type) {
+ static const int kTypeToIndexCount[] = {
+ kIndicesPerOverstrokeRRect,
+ kIndicesPerStrokeRRect,
+ kIndicesPerOverstrokeRRect,
+ };
+
+ return kTypeToIndexCount[type];
+}
+
+static const uint16_t* rrect_type_to_indices(RRectType type) {
+ static const uint16_t* kTypeToIndices[] = {
+ gOverstrokeRRectIndices,
+ gStrokeRRectIndices,
+ gOverstrokeRRectIndices,
+ };
+
+ return kTypeToIndices[type];
+}
+
+// For distance computations in the interior of filled rrects we:
+//
+// add a interior degenerate (point or line) rect
+// each vertex of that rect gets -outerRad as its radius
+// this makes the computation of the distance to the outer edge be negative
+// negative values are caught and then handled differently in the GP's onEmitCode
+// each vertex is also given the normalized x & y distance from the interior rect's edge
+// the GP takes the min of those depths +1 to get the normalized distance to the outer edge
+
+class ShadowCircularRRectBatch : public GrVertexBatch {
+public:
+ DEFINE_BATCH_CLASS_ID
+
+ // A devStrokeWidth <= 0 indicates a fill only. If devStrokeWidth > 0 then strokeOnly indicates
+ // whether the rrect is only stroked or stroked and filled.
+ ShadowCircularRRectBatch(GrColor color, const SkMatrix& viewMatrix,
+ const SkRect& devRect, float devRadius, float blurRadius,
+ float devStrokeWidth, bool strokeOnly)
+ : INHERITED(ClassID())
+ , fViewMatrixIfUsingLocalCoords(viewMatrix) {
+ SkRect bounds = devRect;
+ SkASSERT(!(devStrokeWidth <= 0 && strokeOnly));
+ SkScalar innerRadius = 0.0f;
+ SkScalar outerRadius = devRadius;
+ SkScalar halfWidth = 0;
+ RRectType type = kFill_RRectType;
+ if (devStrokeWidth > 0) {
+ if (SkScalarNearlyZero(devStrokeWidth)) {
+ halfWidth = SK_ScalarHalf;
+ } else {
+ halfWidth = SkScalarHalf(devStrokeWidth);
+ }
+
+ if (strokeOnly) {
+ // Outset stroke by 1/4 pixel
+ devStrokeWidth += 0.25f;
+ // If stroke is greater than width or height, this is still a fill
+ // Otherwise we compute stroke params
+ if (devStrokeWidth <= devRect.width() &&
+ devStrokeWidth <= devRect.height()) {
+ innerRadius = devRadius - halfWidth;
+ type = (innerRadius >= 0) ? kStroke_RRectType : kOverstroke_RRectType;
+ }
+ }
+ outerRadius += halfWidth;
+ bounds.outset(halfWidth, halfWidth);
+ }
+
+ // TODO: still needed?
+ // The radii are outset for two reasons. First, it allows the shader to simply perform
+ // simpler computation because the computed alpha is zero, rather than 50%, at the radius.
+ // Second, the outer radius is used to compute the verts of the bounding box that is
+ // rendered and the outset ensures the box will cover all partially covered by the rrect
+ // corners.
+ outerRadius += SK_ScalarHalf;
+ innerRadius -= SK_ScalarHalf;
+
+ this->setBounds(bounds, HasAABloat::kYes, IsZeroArea::kNo);
+
+ // Expand the rect for aa to generate correct vertices.
+ bounds.outset(SK_ScalarHalf, SK_ScalarHalf);
+
+ fGeoData.emplace_back(Geometry{color, outerRadius, innerRadius, blurRadius, bounds, type});
+ fVertCount = rrect_type_to_vert_count(type);
+ fIndexCount = rrect_type_to_index_count(type);
+ }
+
+ const char* name() const override { return "ShadowCircularRRectBatch"; }
+
+ SkString dumpInfo() const override {
+ SkString string;
+ for (int i = 0; i < fGeoData.count(); ++i) {
+ string.appendf("Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f],"
+ "OuterRad: %.2f, InnerRad: %.2f, BlurRad: %.2f\n",
+ fGeoData[i].fColor,
+ fGeoData[i].fDevBounds.fLeft, fGeoData[i].fDevBounds.fTop,
+ fGeoData[i].fDevBounds.fRight, fGeoData[i].fDevBounds.fBottom,
+ fGeoData[i].fOuterRadius, fGeoData[i].fInnerRadius,
+ fGeoData[i].fBlurRadius);
+ }
+ string.append(INHERITED::dumpInfo());
+ return string;
+ }
+
+ void computePipelineOptimizations(GrInitInvariantOutput* color,
+ GrInitInvariantOutput* coverage,
+ GrBatchToXPOverrides* overrides) const override {
+ // When this is called on a batch, there is only one geometry bundle
+ color->setKnownFourComponents(fGeoData[0].fColor);
+ coverage->setUnknownSingleComponent();
+ }
+
+private:
+ void initBatchTracker(const GrXPOverridesForBatch& overrides) override {
+ // Handle any overrides that affect our GP.
+ overrides.getOverrideColorIfSet(&fGeoData[0].fColor);
+ if (!overrides.readsLocalCoords()) {
+ fViewMatrixIfUsingLocalCoords.reset();
+ }
+ }
+
+ struct CircleVertex {
+ SkPoint fPos;
+ GrColor fColor;
+ SkPoint fOffset;
+ SkScalar fOuterRadius;
+ SkScalar fBlurRadius;
+ };
+
+ static void FillInOverstrokeVerts(CircleVertex** verts, const SkRect& bounds,
+ SkScalar smInset, SkScalar bigInset, SkScalar xOffset,
+ SkScalar outerRadius, GrColor color, SkScalar blurRadius) {
+ SkASSERT(smInset < bigInset);
+
+ // TL
+ (*verts)->fPos = SkPoint::Make(bounds.fLeft + smInset, bounds.fTop + smInset);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(xOffset, 0);
+ (*verts)->fOuterRadius = outerRadius;
+ (*verts)->fBlurRadius = blurRadius;
+ (*verts)++;
+
+ // TR
+ (*verts)->fPos = SkPoint::Make(bounds.fRight - smInset, bounds.fTop + smInset);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(xOffset, 0);
+ (*verts)->fOuterRadius = outerRadius;
+ (*verts)->fBlurRadius = blurRadius;
+ (*verts)++;
+
+ (*verts)->fPos = SkPoint::Make(bounds.fLeft + bigInset, bounds.fTop + bigInset);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(0, 0);
+ (*verts)->fOuterRadius = outerRadius;
+ (*verts)->fBlurRadius = blurRadius;
+ (*verts)++;
+
+ (*verts)->fPos = SkPoint::Make(bounds.fRight - bigInset, bounds.fTop + bigInset);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(0, 0);
+ (*verts)->fOuterRadius = outerRadius;
+ (*verts)->fBlurRadius = blurRadius;
+ (*verts)++;
+
+ (*verts)->fPos = SkPoint::Make(bounds.fLeft + bigInset, bounds.fBottom - bigInset);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(0, 0);
+ (*verts)->fOuterRadius = outerRadius;
+ (*verts)->fBlurRadius = blurRadius;
+ (*verts)++;
+
+ (*verts)->fPos = SkPoint::Make(bounds.fRight - bigInset, bounds.fBottom - bigInset);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(0, 0);
+ (*verts)->fOuterRadius = outerRadius;
+ (*verts)->fBlurRadius = blurRadius;
+ (*verts)++;
+
+ // BL
+ (*verts)->fPos = SkPoint::Make(bounds.fLeft + smInset, bounds.fBottom - smInset);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(xOffset, 0);
+ (*verts)->fOuterRadius = outerRadius;
+ (*verts)->fBlurRadius = blurRadius;
+ (*verts)++;
+
+ // BR
+ (*verts)->fPos = SkPoint::Make(bounds.fRight - smInset, bounds.fBottom - smInset);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(xOffset, 0);
+ (*verts)->fOuterRadius = outerRadius;
+ (*verts)->fBlurRadius = blurRadius;
+ (*verts)++;
+ }
+
+ void onPrepareDraws(Target* target) const override {
+ // Invert the view matrix as a local matrix (if any other processors require coords).
+ SkMatrix localMatrix;
+ if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) {
+ return;
+ }
+
+ // Setup geometry processor
+ sk_sp<GrGeometryProcessor> gp(GrRRectShadowGeoProc::Make(localMatrix));
+
+ int instanceCount = fGeoData.count();
+ size_t vertexStride = gp->getVertexStride();
+ SkASSERT(sizeof(CircleVertex) == vertexStride);
+
+ const GrBuffer* vertexBuffer;
+ int firstVertex;
+
+ CircleVertex* verts = (CircleVertex*)target->makeVertexSpace(vertexStride, fVertCount,
+ &vertexBuffer, &firstVertex);
+ if (!verts) {
+ SkDebugf("Could not allocate vertices\n");
+ return;
+ }
+
+ const GrBuffer* indexBuffer = nullptr;
+ int firstIndex = 0;
+ uint16_t* indices = target->makeIndexSpace(fIndexCount, &indexBuffer, &firstIndex);
+ if (!indices) {
+ SkDebugf("Could not allocate indices\n");
+ return;
+ }
+
+ int currStartVertex = 0;
+ for (int i = 0; i < instanceCount; i++) {
+ const Geometry& args = fGeoData[i];
+
+ GrColor color = args.fColor;
+ SkScalar outerRadius = args.fOuterRadius;
+
+ const SkRect& bounds = args.fDevBounds;
+
+ SkScalar yCoords[4] = {
+ bounds.fTop,
+ bounds.fTop + outerRadius,
+ bounds.fBottom - outerRadius,
+ bounds.fBottom
+ };
+
+ SkScalar yOuterRadii[4] = { -1, 0, 0, 1 };
+ // The inner radius in the vertex data must be specified in normalized space.
+ // For fills, specifying -1/outerRadius guarantees an alpha of 1.0 at the inner radius.
+ SkScalar blurRadius = args.fBlurRadius;
+ for (int i = 0; i < 4; ++i) {
+ verts->fPos = SkPoint::Make(bounds.fLeft, yCoords[i]);
+ verts->fColor = color;
+ verts->fOffset = SkPoint::Make(-1, yOuterRadii[i]);
+ verts->fOuterRadius = outerRadius;
+ verts->fBlurRadius = blurRadius;
+ verts++;
+
+ verts->fPos = SkPoint::Make(bounds.fLeft + outerRadius, yCoords[i]);
+ verts->fColor = color;
+ verts->fOffset = SkPoint::Make(0, yOuterRadii[i]);
+ verts->fOuterRadius = outerRadius;
+ verts->fBlurRadius = blurRadius;
+ verts++;
+
+ verts->fPos = SkPoint::Make(bounds.fRight - outerRadius, yCoords[i]);
+ verts->fColor = color;
+ verts->fOffset = SkPoint::Make(0, yOuterRadii[i]);
+ verts->fOuterRadius = outerRadius;
+ verts->fBlurRadius = blurRadius;
+ verts++;
+
+ verts->fPos = SkPoint::Make(bounds.fRight, yCoords[i]);
+ verts->fColor = color;
+ verts->fOffset = SkPoint::Make(1, yOuterRadii[i]);
+ verts->fOuterRadius = outerRadius;
+ verts->fBlurRadius = blurRadius;
+ verts++;
+ }
+ // Add the additional vertices for overstroked rrects.
+ // Effectively this is an additional stroked rrect, with its
+ // outer radius = outerRadius - innerRadius, and inner radius = 0.
+ // This will give us correct AA in the center and the correct
+ // distance to the outer edge.
+ //
+ // Also, the outer offset is a constant vector pointing to the right, which
+ // guarantees that the distance value along the outer rectangle is constant.
+ if (kOverstroke_RRectType == args.fType) {
+ SkASSERT(args.fInnerRadius <= 0.0f);
+
+ SkScalar overstrokeOuterRadius = outerRadius - args.fInnerRadius;
+ // this is the normalized distance from the outer rectangle of this
+ // geometry to the outer edge
+ SkScalar maxOffset = -args.fInnerRadius / overstrokeOuterRadius;
+
+ FillInOverstrokeVerts(&verts, bounds, outerRadius, overstrokeOuterRadius,
+ maxOffset, overstrokeOuterRadius, color, blurRadius);
+ }
+
+ if (kFill_RRectType == args.fType) {
+ SkScalar halfMinDim = 0.5f * SkTMin(bounds.width(), bounds.height());
+
+ SkScalar xOffset = 1.0f - outerRadius / halfMinDim;
+
+ FillInOverstrokeVerts(&verts, bounds, outerRadius, halfMinDim,
+ xOffset, halfMinDim, color, blurRadius);
+ }
+
+ const uint16_t* primIndices = rrect_type_to_indices(args.fType);
+ const int primIndexCount = rrect_type_to_index_count(args.fType);
+ for (int i = 0; i < primIndexCount; ++i) {
+ *indices++ = primIndices[i] + currStartVertex;
+ }
+
+ currStartVertex += rrect_type_to_vert_count(args.fType);
+ }
+
+ GrMesh mesh;
+ mesh.initIndexed(kTriangles_GrPrimitiveType, vertexBuffer, indexBuffer, firstVertex,
+ firstIndex, fVertCount, fIndexCount);
+ target->draw(gp.get(), mesh);
+ }
+
+ bool onCombineIfPossible(GrBatch* t, const GrCaps& caps) override {
+ ShadowCircularRRectBatch* that = t->cast<ShadowCircularRRectBatch>();
+ if (!GrPipeline::CanCombine(*this->pipeline(), this->bounds(), *that->pipeline(),
+ that->bounds(), caps)) {
+ return false;
+ }
+
+ if (!fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) {
+ return false;
+ }
+
+ fGeoData.push_back_n(that->fGeoData.count(), that->fGeoData.begin());
+ this->joinBounds(*that);
+ fVertCount += that->fVertCount;
+ fIndexCount += that->fIndexCount;
+ return true;
+ }
+
+ struct Geometry {
+ GrColor fColor;
+ SkScalar fOuterRadius;
+ SkScalar fInnerRadius;
+ SkScalar fBlurRadius;
+ SkRect fDevBounds;
+ RRectType fType;
+ };
+
+ SkSTArray<1, Geometry, true> fGeoData;
+ SkMatrix fViewMatrixIfUsingLocalCoords;
+ int fVertCount;
+ int fIndexCount;
+
+ typedef GrVertexBatch INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+static GrDrawBatch* create_shadow_circle_batch(GrColor color,
+ const SkMatrix& viewMatrix,
+ const SkRect& oval,
+ SkScalar blurRadius,
+ const SkStrokeRec& stroke,
+ const GrShaderCaps* shaderCaps) {
+ // we can only draw circles
+ SkScalar width = oval.width();
+ SkASSERT(SkScalarNearlyEqual(width, oval.height()) && viewMatrix.isSimilarity());
+ SkPoint center = { oval.centerX(), oval.centerY() };
+ return ShadowCircleBatch::Create(color, viewMatrix, center, width / 2.f,
+ blurRadius, GrStyle(stroke, nullptr));
+}
+
+static GrDrawBatch* create_shadow_rrect_batch(GrColor color,
+ const SkMatrix& viewMatrix,
+ const SkRRect& rrect,
+ SkScalar blurRadius,
+ const SkStrokeRec& stroke) {
+ SkASSERT(viewMatrix.rectStaysRect());
+ SkASSERT(rrect.isSimple());
+ SkASSERT(!rrect.isOval());
+
+ // Shadow rrect batchs only handle simple circular rrects
+ // do any matrix crunching before we reset the draw state for device coords
+ const SkRect& rrectBounds = rrect.getBounds();
+ SkRect bounds;
+ viewMatrix.mapRect(&bounds, rrectBounds);
+
+ SkVector radii = rrect.getSimpleRadii();
+ SkScalar xRadius = SkScalarAbs(viewMatrix[SkMatrix::kMScaleX] * radii.fX +
+ viewMatrix[SkMatrix::kMSkewY] * radii.fY);
+ SkScalar yRadius = SkScalarAbs(viewMatrix[SkMatrix::kMSkewX] * radii.fX +
+ viewMatrix[SkMatrix::kMScaleY] * radii.fY);
+ SkASSERT(SkScalarNearlyEqual(xRadius, yRadius));
+
+ SkStrokeRec::Style style = stroke.getStyle();
+
+ // Do (potentially) anisotropic mapping of stroke. Use -1s to indicate fill-only draws.
+ SkVector scaledStroke = { -1, -1 };
+ SkScalar strokeWidth = stroke.getWidth();
+
+ bool isStrokeOnly = SkStrokeRec::kStroke_Style == style ||
+ SkStrokeRec::kHairline_Style == style;
+ bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == style;
+
+ if (hasStroke) {
+ if (SkStrokeRec::kHairline_Style == style) {
+ scaledStroke.set(1, 1);
+ } else {
+ scaledStroke.fX = SkScalarAbs(strokeWidth*(viewMatrix[SkMatrix::kMScaleX] +
+ viewMatrix[SkMatrix::kMSkewY]));
+ scaledStroke.fY = SkScalarAbs(strokeWidth*(viewMatrix[SkMatrix::kMSkewX] +
+ viewMatrix[SkMatrix::kMScaleY]));
+ }
+
+ // we don't handle anisotropic strokes
+ if (!SkScalarNearlyEqual(scaledStroke.fX, scaledStroke.fY)) {
+ return nullptr;
+ }
+ }
+
+ // The way the effect interpolates the offset-to-ellipse/circle-center attribute only works on
+ // the interior of the rrect if the radii are >= 0.5. Otherwise, the inner rect of the nine-
+ // patch will have fractional coverage. This only matters when the interior is actually filled.
+ // We could consider falling back to rect rendering here, since a tiny radius is
+ // indistinguishable from a square corner.
+ if (!isStrokeOnly && (SK_ScalarHalf > xRadius || SK_ScalarHalf > yRadius)) {
+ return nullptr;
+ }
+
+ return new ShadowCircularRRectBatch(color, viewMatrix, bounds, xRadius,
+ blurRadius, scaledStroke.fX, isStrokeOnly);
+}
+
+GrDrawBatch* CreateShadowRRectBatch(GrColor color,
+ const SkMatrix& viewMatrix,
+ const SkRRect& rrect,
+ const SkScalar blurRadius,
+ const SkStrokeRec& stroke,
+ const GrShaderCaps* shaderCaps) {
+ if (rrect.isOval()) {
+ return create_shadow_circle_batch(color, viewMatrix, rrect.getBounds(),
+ blurRadius, stroke, shaderCaps);
+ }
+
+ if (!viewMatrix.rectStaysRect() || !rrect.isSimple()) {
+ return nullptr;
+ }
+
+ return create_shadow_rrect_batch(color, viewMatrix, rrect, blurRadius, stroke);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef GR_TEST_UTILS
+
+DRAW_BATCH_TEST_DEFINE(ShadowCircleBatch) {
+ do {
+ SkScalar rotate = random->nextSScalar1() * 360.f;
+ SkScalar translateX = random->nextSScalar1() * 1000.f;
+ SkScalar translateY = random->nextSScalar1() * 1000.f;
+ SkScalar scale = random->nextSScalar1() * 100.f;
+ SkMatrix viewMatrix;
+ viewMatrix.setRotate(rotate);
+ viewMatrix.postTranslate(translateX, translateY);
+ viewMatrix.postScale(scale, scale);
+ GrColor color = GrRandomColor(random);
+ SkRect circle = GrTest::TestSquare(random);
+ SkPoint center = { circle.centerX(), circle.centerY() };
+ SkScalar radius = circle.width() / 2.f;
+ SkStrokeRec stroke = GrTest::TestStrokeRec(random);
+ SkScalar blurRadius = random->nextSScalar1() * 72.f;
+ GrDrawBatch* batch = ShadowCircleBatch::Create(color, viewMatrix, center, radius,
+ blurRadius, GrStyle(stroke, nullptr));
+ if (batch) {
+ return batch;
+ }
+ } while (true);
+}
+
+DRAW_BATCH_TEST_DEFINE(ShadowRRectBatch) {
+ SkMatrix viewMatrix = GrTest::TestMatrixRectStaysRect(random);
+ GrColor color = GrRandomColor(random);
+ const SkRRect& rrect = GrTest::TestRRectSimple(random);
+ SkScalar blurRadius = random->nextSScalar1() * 72.f;
+ return create_shadow_rrect_batch(color, viewMatrix, rrect,
+ blurRadius, GrTest::TestStrokeRec(random));
+}
+
+#endif
diff --git a/src/gpu/batches/GrShadowRRectBatch.h b/src/gpu/batches/GrShadowRRectBatch.h
new file mode 100755
index 0000000000..617af3840a
--- /dev/null
+++ b/src/gpu/batches/GrShadowRRectBatch.h
@@ -0,0 +1,26 @@
+/*
+ * 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 GrShadowRRectBatch_DEFINED
+#define GrShadowRRectBatch_DEFINED
+
+#include "GrColor.h"
+
+class GrDrawBatch;
+class GrShaderCaps;
+class SkMatrix;
+class SkRRect;
+class SkStrokeRec;
+
+GrDrawBatch* CreateShadowRRectBatch(GrColor,
+ const SkMatrix& viewMatrix,
+ const SkRRect& rrect,
+ const SkScalar blurRadius,
+ const SkStrokeRec& stroke,
+ const GrShaderCaps* shaderCaps);
+
+#endif
diff --git a/src/gpu/effects/GrShadowGeoProc.cpp b/src/gpu/effects/GrShadowGeoProc.cpp
new file mode 100755
index 0000000000..8544a05e35
--- /dev/null
+++ b/src/gpu/effects/GrShadowGeoProc.cpp
@@ -0,0 +1,104 @@
+/*
+ * 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 "GrShadowGeoProc.h"
+
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLGeometryProcessor.h"
+#include "glsl/GrGLSLUniformHandler.h"
+#include "glsl/GrGLSLVarying.h"
+#include "glsl/GrGLSLVertexShaderBuilder.h"
+
+class GrGLSLRRectShadowGeoProc : public GrGLSLGeometryProcessor {
+public:
+ GrGLSLRRectShadowGeoProc() {}
+
+ void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
+ const GrRRectShadowGeoProc& rsgp = args.fGP.cast<GrRRectShadowGeoProc>();
+ GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
+ GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
+ GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
+ GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder;
+
+ // emit attributes
+ varyingHandler->emitAttributes(rsgp);
+ fragBuilder->codeAppend("vec4 shadowParams;");
+ varyingHandler->addPassThroughAttribute(rsgp.inShadowParams(), "shadowParams");
+
+ // setup pass through color
+ varyingHandler->addPassThroughAttribute(rsgp.inColor(), args.fOutputColor);
+
+ // Setup position
+ this->setupPosition(vertBuilder, gpArgs, rsgp.inPosition()->fName);
+
+ // emit transforms
+ this->emitTransforms(vertBuilder,
+ varyingHandler,
+ uniformHandler,
+ gpArgs->fPositionVar,
+ rsgp.inPosition()->fName,
+ rsgp.localMatrix(),
+ args.fFPCoordTransformHandler);
+
+ fragBuilder->codeAppend("float d = length(shadowParams.xy);");
+ fragBuilder->codeAppend("float distance = shadowParams.z * (1.0 - d);");
+
+ fragBuilder->codeAppend("float radius = shadowParams.w;");
+
+ fragBuilder->codeAppend("float factor = 1.0 - clamp(distance/radius, 0.0, 1.0);");
+ fragBuilder->codeAppend("factor = exp(-factor * factor * 4.0) - 0.018;");
+ fragBuilder->codeAppendf("%s = vec4(factor);",
+ args.fOutputCoverage);
+ }
+
+ void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& proc,
+ FPCoordTransformIter&& transformIter) override {
+ this->setTransformDataHelper(proc.cast<GrRRectShadowGeoProc>().localMatrix(),
+ pdman, &transformIter);
+ }
+
+ static inline void GenKey(const GrGeometryProcessor& gp,
+ const GrGLSLCaps&,
+ GrProcessorKeyBuilder* b) {
+ const GrRRectShadowGeoProc& rsgp = gp.cast<GrRRectShadowGeoProc>();
+ uint16_t key;
+ key = rsgp.localMatrix().hasPerspective() ? 0x1 : 0x0;
+ b->add32(key);
+ }
+
+private:
+ typedef GrGLSLGeometryProcessor INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrRRectShadowGeoProc::GrRRectShadowGeoProc(const SkMatrix& localMatrix)
+ : fLocalMatrix(localMatrix) {
+
+ this->initClassID<GrRRectShadowGeoProc>();
+ fInPosition = &this->addVertexAttrib("inPosition", kVec2f_GrVertexAttribType,
+ kHigh_GrSLPrecision);
+ fInColor = &this->addVertexAttrib("inColor", kVec4ub_GrVertexAttribType);
+ fInShadowParams = &this->addVertexAttrib("inShadowParams", kVec4f_GrVertexAttribType);
+}
+
+void GrRRectShadowGeoProc::getGLSLProcessorKey(const GrGLSLCaps& caps,
+ GrProcessorKeyBuilder* b) const {
+ GrGLSLRRectShadowGeoProc::GenKey(*this, caps, b);
+}
+
+GrGLSLPrimitiveProcessor* GrRRectShadowGeoProc::createGLSLInstance(const GrGLSLCaps&) const {
+ return new GrGLSLRRectShadowGeoProc();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrRRectShadowGeoProc);
+
+sk_sp<GrGeometryProcessor> GrRRectShadowGeoProc::TestCreate(GrProcessorTestData* d) {
+ return GrRRectShadowGeoProc::Make(GrTest::TestMatrix(d->fRandom));
+}
diff --git a/src/gpu/effects/GrShadowGeoProc.h b/src/gpu/effects/GrShadowGeoProc.h
new file mode 100755
index 0000000000..29e2bde84d
--- /dev/null
+++ b/src/gpu/effects/GrShadowGeoProc.h
@@ -0,0 +1,56 @@
+/*
+ * 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 GrShadowGeoProc_DEFINED
+#define GrShadowGeoProc_DEFINED
+
+#include "GrProcessor.h"
+#include "GrGeometryProcessor.h"
+
+class GrGLRRectShadowGeoProc;
+
+/**
+ * The output color of this effect is a coverage mask for a rrect shadow,
+ * assuming circular corner geometry.
+ */
+class GrRRectShadowGeoProc : public GrGeometryProcessor {
+public:
+ static sk_sp<GrGeometryProcessor> Make(const SkMatrix& localMatrix) {
+ return sk_sp<GrGeometryProcessor>(new GrRRectShadowGeoProc(localMatrix));
+ }
+
+ ~GrRRectShadowGeoProc() override {}
+
+ const char* name() const override { return "RRectShadow"; }
+
+ const Attribute* inPosition() const { return fInPosition; }
+ const Attribute* inColor() const { return fInColor; }
+ const Attribute* inShadowParams() const { return fInShadowParams; }
+ GrColor color() const { return fColor; }
+ bool colorIgnored() const { return GrColor_ILLEGAL == fColor; }
+ const SkMatrix& localMatrix() const { return fLocalMatrix; }
+
+ void getGLSLProcessorKey(const GrGLSLCaps& caps, GrProcessorKeyBuilder* b) const override;
+
+ GrGLSLPrimitiveProcessor* createGLSLInstance(const GrGLSLCaps&) const override;
+
+private:
+ GrRRectShadowGeoProc(const SkMatrix& localMatrix);
+
+ GrColor fColor;
+ SkMatrix fLocalMatrix;
+ const Attribute* fInPosition;
+ const Attribute* fInColor;
+ const Attribute* fInShadowParams;
+
+ GR_DECLARE_GEOMETRY_PROCESSOR_TEST;
+
+ typedef GrGeometryProcessor INHERITED;
+};
+
+
+#endif