diff options
author | Jim Van Verth <jvanverth@google.com> | 2016-11-17 15:27:09 -0500 |
---|---|---|
committer | Skia Commit-Bot <skia-commit-bot@chromium.org> | 2016-11-21 21:44:20 +0000 |
commit | c59034145862bf6dc0c503cb1e47eecd321ffa8c (patch) | |
tree | 2e9be313f7221dfe66c9777280be809d75c5a025 | |
parent | 71de072f99a0a0e83d4c06420d870af2824a9d2d (diff) |
Add shadowrrect geometry processor
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=4233
Change-Id: I637099709cfe30f7d3c1883e23840a47a7a25c10
Reviewed-on: https://skia-review.googlesource.com/4233
Commit-Queue: Jim Van Verth <jvanverth@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
-rw-r--r-- | gn/gpu.gni | 4 | ||||
-rw-r--r-- | include/gpu/GrRenderTargetContext.h | 17 | ||||
-rw-r--r-- | samplecode/SampleAndroidShadows.cpp | 2 | ||||
-rwxr-xr-x | src/effects/SkShadowMaskFilter.cpp | 196 | ||||
-rw-r--r-- | src/gpu/GrRenderTargetContext.cpp | 61 | ||||
-rwxr-xr-x | src/gpu/batches/GrShadowRRectBatch.cpp | 964 | ||||
-rwxr-xr-x | src/gpu/batches/GrShadowRRectBatch.h | 26 | ||||
-rwxr-xr-x | src/gpu/effects/GrShadowGeoProc.cpp | 104 | ||||
-rwxr-xr-x | src/gpu/effects/GrShadowGeoProc.h | 56 |
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(¢er, 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 |