From b6069dfba7b7ab563a3fccb2f38307e47035300c Mon Sep 17 00:00:00 2001 From: Jim Van Verth Date: Fri, 28 Apr 2017 11:00:35 -0400 Subject: [PATCH] Fix fast blur for shadow rrects. * Handles case where blur is greater than corner radius. * Speeds up shader by avoiding a divide. * Tweaks shadow sample to be more MD-ish. Bug: skia: Change-Id: I2990357b39784c22f02a8e51872391fd68e8226a Reviewed-on: https://skia-review.googlesource.com/14141 Commit-Queue: Jim Van Verth Reviewed-by: Brian Salomon --- samplecode/SampleAndroidShadows.cpp | 30 +- .../shadows/SkAmbientShadowMaskFilter.cpp | 13 +- src/gpu/effects/GrShadowGeoProc.cpp | 8 +- src/gpu/ops/GrShadowRRectOp.cpp | 382 +++++++++--------- src/utils/SkShadowTessellator.cpp | 2 +- src/utils/SkShadowUtils.cpp | 108 +++-- 6 files changed, 271 insertions(+), 272 deletions(-) diff --git a/samplecode/SampleAndroidShadows.cpp b/samplecode/SampleAndroidShadows.cpp index 55acd57f09..02b494ff20 100644 --- a/samplecode/SampleAndroidShadows.cpp +++ b/samplecode/SampleAndroidShadows.cpp @@ -52,7 +52,7 @@ public: , fAnimAngle(0) , fShowAmbient(true) , fShowSpot(true) - , fUseAlt(true) + , fUseAlt(false) , fShowObject(true) , fIgnoreShadowAlpha(false) {} @@ -72,7 +72,7 @@ protected: fWideRectPath.addRect(SkRect::MakeXYWH(0, 0, 630, 70)); fWideOvalPath.addOval(SkRect::MakeXYWH(0, 0, 630, 70)); - fLightPos = SkPoint3::Make(-700, -700, 2800); + fLightPos = SkPoint3::Make(350, 0, 600); } // overrides from SkEventSink @@ -465,38 +465,32 @@ protected: void onDrawContent(SkCanvas* canvas) override { this->drawBG(canvas); - const SkScalar kLightWidth = 2800; - const SkScalar kAmbientAlpha = 0.25f; + const SkScalar kLightWidth = 800; + const SkScalar kAmbientAlpha = 0.1f; const SkScalar kSpotAlpha = 0.25f; SkPaint paint; paint.setAntiAlias(true); SkPoint3 lightPos = fLightPos; - lightPos.fX = canvas->getBaseLayerSize().fWidth * 0.5f; paint.setColor(SK_ColorWHITE); canvas->translate(200, 90); - lightPos.fX += 200; - lightPos.fY += 90; SkScalar zValue = SkTMax(1.0f, 2 + fZDelta); - std::function zFunc = + std::function zFunc = [zValue](SkScalar, SkScalar) { return zValue; }; this->drawShadowedPath(canvas, fRRPath, zFunc, paint, kAmbientAlpha, lightPos, kLightWidth, kSpotAlpha); paint.setColor(SK_ColorRED); canvas->translate(250, 0); - lightPos.fX += 250; - zValue = SkTMax(1.0f, 4 + fZDelta); + zValue = SkTMax(1.0f, 8 + fZDelta); zFunc = [zValue](SkScalar, SkScalar) { return zValue; }; this->drawShadowedPath(canvas, fRectPath, zFunc, paint, kAmbientAlpha, lightPos, kLightWidth, kSpotAlpha); paint.setColor(SK_ColorBLUE); canvas->translate(-250, 110); - lightPos.fX -= 250; - lightPos.fY += 110; zValue = SkTMax(1.0f, 12 + fZDelta); zFunc = [zValue](SkScalar, SkScalar) { return zValue; }; this->drawShadowedPath(canvas, fCirclePath, zFunc, paint, kAmbientAlpha, @@ -504,7 +498,6 @@ protected: paint.setColor(SK_ColorGREEN); canvas->translate(250, 0); - lightPos.fX += 250; zValue = SkTMax(1.0f, 64 + fZDelta); zFunc = [zValue](SkScalar, SkScalar) { return zValue; }; this->drawShadowedPath(canvas, fRRPath, zFunc, paint, kAmbientAlpha, @@ -512,8 +505,6 @@ protected: paint.setColor(SK_ColorYELLOW); canvas->translate(-250, 110); - lightPos.fX -= 250; - lightPos.fY += 110; zValue = SkTMax(1.0f, 8 + fZDelta); zFunc = [zValue](SkScalar, SkScalar) { return zValue; }; this->drawShadowedPath(canvas, fFunkyRRPath, zFunc, paint, kAmbientAlpha, @@ -521,7 +512,6 @@ protected: paint.setColor(SK_ColorCYAN); canvas->translate(250, 0); - lightPos.fX += 250; zValue = SkTMax(1.0f, 16 + fZDelta); zFunc = [zValue](SkScalar, SkScalar) { return zValue; }; this->drawShadowedPath(canvas, fCubicPath, zFunc, paint, @@ -535,8 +525,6 @@ protected: paint.setColor(SK_ColorMAGENTA); canvas->translate(-125, 60); - lightPos.fX -= 125; - lightPos.fY += 60; zValue = SkTMax(1.0f, 32 + fZDelta); zFunc = [zValue](SkScalar, SkScalar) { return zValue; }; this->drawShadowedPath(canvas, tmpPath, zFunc, paint, .1f, @@ -555,9 +543,6 @@ protected: persp.preTranslate(-pivot.fX, -pivot.fY); persp.postTranslate(pivot.fX + translate.fX, pivot.fY + translate.fY); canvas->setMatrix(persp); - lightPos = fLightPos; - lightPos.fX += pivot.fX + translate.fX; - lightPos.fY += pivot.fY + translate.fY; zValue = SkTMax(1.0f, 16 + fZDelta); SkScalar radians = SkDegreesToRadians(fAnimAngle); zFunc = [zValue, pivot, radians](SkScalar x, SkScalar y) { @@ -576,9 +561,6 @@ protected: persp.preTranslate(-pivot.fX, -pivot.fY); persp.postTranslate(pivot.fX + translate.fX, pivot.fY + translate.fY); canvas->setMatrix(persp); - lightPos = fLightPos; - lightPos.fX += pivot.fX + translate.fX; - lightPos.fY += pivot.fY + translate.fY; zValue = SkTMax(1.0f, 32 + fZDelta); zFunc = [zValue, pivot, radians](SkScalar x, SkScalar y) { return -SkScalarSin(radians)*x + diff --git a/src/effects/shadows/SkAmbientShadowMaskFilter.cpp b/src/effects/shadows/SkAmbientShadowMaskFilter.cpp index 84f9836b52..4f4bba2598 100644 --- a/src/effects/shadows/SkAmbientShadowMaskFilter.cpp +++ b/src/effects/shadows/SkAmbientShadowMaskFilter.cpp @@ -217,13 +217,13 @@ bool SkAmbientShadowMaskFilterImpl::directFilterRRectMaskGPU(GrContext*, // TODO: take flags into account when generating shadow data if (fAmbientAlpha > 0.0f) { - SkScalar srcSpaceAmbientRadius = fOccluderHeight * kHeightFactor * kGeomFactor; + SkScalar srcSpaceStrokeWidth = fOccluderHeight * kHeightFactor * kGeomFactor; const float umbraAlpha = (1.0f + SkTMax(fOccluderHeight * kHeightFactor, 0.0f)); - const SkScalar strokeWidth = srcSpaceAmbientRadius * umbraAlpha; + const SkScalar blurWidth = srcSpaceStrokeWidth * umbraAlpha; // For the ambient rrect, we outset the offset rect by srcSpaceAmbientRadius // minus half the strokeWidth to get our stroke shape. - SkScalar ambientPathOutset = SkTMax(srcSpaceAmbientRadius - strokeWidth * 0.5f, + SkScalar ambientPathOutset = SkTMax(srcSpaceStrokeWidth * 0.5f, minRadius); SkRRect ambientRRect; @@ -234,17 +234,18 @@ bool SkAmbientShadowMaskFilterImpl::directFilterRRectMaskGPU(GrContext*, rrect.outset(ambientPathOutset, ambientPathOutset, &ambientRRect); } - const SkScalar devSpaceAmbientRadius = strokeWidth * scaleFactor; + const SkScalar devSpaceAmbientBlur = blurWidth * scaleFactor; GrPaint newPaint(paint); GrColor4f color = newPaint.getColor4f(); color.fRGBA[3] *= fAmbientAlpha; newPaint.setColor4f(color); SkStrokeRec ambientStrokeRec(SkStrokeRec::kHairline_InitStyle); - ambientStrokeRec.setStrokeStyle(strokeWidth, false); + bool transparent = SkToBool(fFlags & SkShadowFlags::kTransparentOccluder_ShadowFlag); + ambientStrokeRec.setStrokeStyle(srcSpaceStrokeWidth, transparent); rtContext->drawShadowRRect(clip, std::move(newPaint), viewMatrix, ambientRRect, - devSpaceAmbientRadius, + devSpaceAmbientBlur, GrStyle(ambientStrokeRec, nullptr)); } diff --git a/src/gpu/effects/GrShadowGeoProc.cpp b/src/gpu/effects/GrShadowGeoProc.cpp index e11a672dba..4e33e4ba39 100644 --- a/src/gpu/effects/GrShadowGeoProc.cpp +++ b/src/gpu/effects/GrShadowGeoProc.cpp @@ -26,7 +26,7 @@ public: // emit attributes varyingHandler->emitAttributes(rsgp); - fragBuilder->codeAppend("vec4 shadowParams;"); + fragBuilder->codeAppend("vec3 shadowParams;"); varyingHandler->addPassThroughAttribute(rsgp.inShadowParams(), "shadowParams"); // setup pass through color @@ -47,9 +47,7 @@ public: 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("float factor = 1.0 - clamp(distance, 0.0, 1.0);"); fragBuilder->codeAppend("factor = exp(-factor * factor * 4.0) - 0.018;"); fragBuilder->codeAppendf("%s = vec4(factor);", args.fOutputCoverage); @@ -83,7 +81,7 @@ GrRRectShadowGeoProc::GrRRectShadowGeoProc(const SkMatrix& localMatrix) fInPosition = &this->addVertexAttrib("inPosition", kVec2f_GrVertexAttribType, kHigh_GrSLPrecision); fInColor = &this->addVertexAttrib("inColor", kVec4ub_GrVertexAttribType); - fInShadowParams = &this->addVertexAttrib("inShadowParams", kVec4f_GrVertexAttribType); + fInShadowParams = &this->addVertexAttrib("inShadowParams", kVec3f_GrVertexAttribType); } void GrRRectShadowGeoProc::getGLSLProcessorKey(const GrShaderCaps& caps, diff --git a/src/gpu/ops/GrShadowRRectOp.cpp b/src/gpu/ops/GrShadowRRectOp.cpp index 00ec35fba8..5b172f78f6 100644 --- a/src/gpu/ops/GrShadowRRectOp.cpp +++ b/src/gpu/ops/GrShadowRRectOp.cpp @@ -168,8 +168,7 @@ private: SkPoint fPos; GrColor fColor; SkPoint fOffset; - SkScalar fOuterRadius; - SkScalar fBlurRadius; + SkScalar fDistanceCorrection; }; int instanceCount = fCircles.count(); @@ -201,6 +200,7 @@ private: SkScalar outerRadius = circle.fOuterRadius; SkScalar innerRadius = circle.fInnerRadius; SkScalar blurRadius = circle.fBlurRadius; + SkScalar distanceCorrection = outerRadius / blurRadius; const SkRect& bounds = circle.fDevBounds; CircleVertex* ov0 = reinterpret_cast(vertices + 0 * vertexStride); @@ -222,50 +222,42 @@ private: ov0->fPos = center + SkPoint::Make(-octOffset * halfWidth, -halfWidth); ov0->fColor = color; ov0->fOffset = SkPoint::Make(-octOffset, -1); - ov0->fOuterRadius = outerRadius; - ov0->fBlurRadius = blurRadius; + ov0->fDistanceCorrection = distanceCorrection; ov1->fPos = center + SkPoint::Make(octOffset * halfWidth, -halfWidth); ov1->fColor = color; ov1->fOffset = SkPoint::Make(octOffset, -1); - ov1->fOuterRadius = outerRadius; - ov1->fBlurRadius = blurRadius; + ov1->fDistanceCorrection = distanceCorrection; ov2->fPos = center + SkPoint::Make(halfWidth, -octOffset * halfWidth); ov2->fColor = color; ov2->fOffset = SkPoint::Make(1, -octOffset); - ov2->fOuterRadius = outerRadius; - ov2->fBlurRadius = blurRadius; + ov2->fDistanceCorrection = distanceCorrection; ov3->fPos = center + SkPoint::Make(halfWidth, octOffset * halfWidth); ov3->fColor = color; ov3->fOffset = SkPoint::Make(1, octOffset); - ov3->fOuterRadius = outerRadius; - ov3->fBlurRadius = blurRadius; + ov3->fDistanceCorrection = distanceCorrection; ov4->fPos = center + SkPoint::Make(octOffset * halfWidth, halfWidth); ov4->fColor = color; ov4->fOffset = SkPoint::Make(octOffset, 1); - ov4->fOuterRadius = outerRadius; - ov4->fBlurRadius = blurRadius; + ov4->fDistanceCorrection = distanceCorrection; ov5->fPos = center + SkPoint::Make(-octOffset * halfWidth, halfWidth); ov5->fColor = color; ov5->fOffset = SkPoint::Make(-octOffset, 1); - ov5->fOuterRadius = outerRadius; - ov5->fBlurRadius = blurRadius; + ov5->fDistanceCorrection = distanceCorrection; ov6->fPos = center + SkPoint::Make(-halfWidth, octOffset * halfWidth); ov6->fColor = color; ov6->fOffset = SkPoint::Make(-1, octOffset); - ov6->fOuterRadius = outerRadius; - ov6->fBlurRadius = blurRadius; + ov6->fDistanceCorrection = distanceCorrection; ov7->fPos = center + SkPoint::Make(-halfWidth, -octOffset * halfWidth); ov7->fColor = color; ov7->fOffset = SkPoint::Make(-1, -octOffset); - ov7->fOuterRadius = outerRadius; - ov7->fBlurRadius = blurRadius; + ov7->fDistanceCorrection = distanceCorrection; if (circle.fStroked) { // compute the inner ring @@ -286,58 +278,49 @@ private: 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; + iv0->fDistanceCorrection = distanceCorrection; 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; + iv1->fDistanceCorrection = distanceCorrection; 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; + iv2->fDistanceCorrection = distanceCorrection; 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; + iv3->fDistanceCorrection = distanceCorrection; 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; + iv4->fDistanceCorrection = distanceCorrection; 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; + iv5->fDistanceCorrection = distanceCorrection; 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; + iv6->fDistanceCorrection = distanceCorrection; 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; + iv7->fDistanceCorrection = distanceCorrection; } else { // filled CircleVertex* iv = reinterpret_cast(vertices + 8 * vertexStride); iv->fPos = center; iv->fColor = color; iv->fOffset = SkPoint::Make(0, 0); - iv->fOuterRadius = outerRadius; - iv->fBlurRadius = blurRadius; + iv->fDistanceCorrection = distanceCorrection; } const uint16_t* primIndices = circle_type_to_indices(circle.fStroked); @@ -392,10 +375,7 @@ private: }; /////////////////////////////////////////////////////////////////////////////////////////////////// - -// 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. +// The geometry for a shadow roundrect is similar to a 9-patch: // ____________ // |_|________|_| // | | | | @@ -404,9 +384,23 @@ private: // |_|________|_| // |_|________|_| // -// 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. +// However, each corner is rendered as a fan rather than a simple quad, as below. (The diagram +// shows the upper part of the upper left corner. The bottom triangle would similarly be split +// into two triangles.) +// ________ +// |\ \ | +// | \ \ | +// | \\ | +// | \| +// -------- +// +// The center of the fan handles the curve of the corner. For roundrects where the stroke width +// is greater than the corner radius, the outer triangles blend from the curve to the straight +// sides. Otherwise these triangles will be degenerate. +// +// In the case where the stroke width is greater than the corner radius and the +// blur radius (overstroke), we add additional geometry to mark out the rectangle in the center. +// This rectangle extends the coverage values of the center edges of the 9-patch. // ____________ // |_|________|_| // | |\ ____ /| | @@ -415,40 +409,44 @@ private: // |_|/______\|_| // |_|________|_| // -// For filled rrects we reuse the overstroke geometry but make the inner rect degenerate -// (either a point or a horizontal or vertical line). +// For filled rrects we reuse the stroke geometry but add an additional quad to the center. -static const uint16_t gOverstrokeRRectIndices[] = { - // clang-format off - // 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, +static const uint16_t gRRectIndices[] = { + // clang-format off + // overstroke quads + // we place this at the beginning so that we can skip these indices when rendering as filled + 0, 6, 25, 0, 25, 24, + 6, 18, 27, 6, 27, 25, + 18, 12, 26, 18, 26, 27, + 12, 0, 24, 12, 24, 26, - // 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, + // corners + 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, + 6, 11, 10, 6, 10, 9, 6, 9, 8, 6, 8, 7, + 12, 17, 16, 12, 16, 15, 12, 15, 14, 12, 14, 13, + 18, 19, 20, 18, 20, 21, 18, 21, 22, 18, 22, 23, - // 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, - // clang-format on + // edges + 0, 5, 11, 0, 11, 6, + 6, 7, 19, 6, 19, 18, + 18, 23, 17, 18, 17, 12, + 12, 13, 1, 12, 1, 0, + + // fill quad + // we place this at the end so that we can skip these indices when rendering as stroked + 0, 6, 18, 0, 18, 12, + // clang-format on }; -// 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); +static const int kIndicesPerOverstrokeRRect = SK_ARRAY_COUNT(gRRectIndices) - 6; // simple stroke count skips overstroke indices -static const int kIndicesPerStrokeRRect = kIndicesPerOverstrokeRRect - 6 * 4 + 6; -static const int kVertsPerStrokeRRect = 16; -static const int kVertsPerOverstrokeRRect = 24; +static const int kIndicesPerStrokeRRect = kIndicesPerOverstrokeRRect - 6*4; +// fill count adds final quad to stroke count +static const int kIndicesPerFillRRect = kIndicesPerStrokeRRect + 6; +static const int kVertsPerStrokeRRect = 24; +static const int kVertsPerOverstrokeRRect = 28; +static const int kVertsPerFillRRect = 24; enum RRectType { kFill_RRectType, @@ -459,7 +457,7 @@ enum RRectType { static int rrect_type_to_vert_count(RRectType type) { switch (type) { case kFill_RRectType: - return kVertsPerOverstrokeRRect; + return kVertsPerFillRRect; case kStroke_RRectType: return kVertsPerStrokeRRect; case kOverstroke_RRectType: @@ -472,7 +470,7 @@ static int rrect_type_to_vert_count(RRectType type) { static int rrect_type_to_index_count(RRectType type) { switch (type) { case kFill_RRectType: - return kIndicesPerOverstrokeRRect; + return kIndicesPerFillRRect; case kStroke_RRectType: return kIndicesPerStrokeRRect; case kOverstroke_RRectType: @@ -485,25 +483,15 @@ static int rrect_type_to_index_count(RRectType type) { static const uint16_t* rrect_type_to_indices(RRectType type) { switch (type) { case kFill_RRectType: - return gOverstrokeRRectIndices; case kStroke_RRectType: - return gStrokeRRectIndices; + return gRRectIndices + 6*4; case kOverstroke_RRectType: - return gOverstrokeRRectIndices; + return gRRectIndices; } SkFAIL("Invalid type"); return nullptr; } -// 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 ShadowCircularRRectOp final : public GrLegacyMeshDrawOp { public: DEFINE_OP_CLASS_ID @@ -515,8 +503,9 @@ public: : INHERITED(ClassID()), fViewMatrixIfUsingLocalCoords(viewMatrix) { SkRect bounds = devRect; SkASSERT(!(devStrokeWidth <= 0 && strokeOnly)); - SkScalar innerRadius = 0.0f; + SkScalar overStrokeRadius = 0.0f; SkScalar outerRadius = devRadius; + SkScalar umbraInset = SkTMax(outerRadius, blurRadius); SkScalar halfWidth = 0; RRectType type = kFill_RRectType; if (devStrokeWidth > 0) { @@ -525,22 +514,32 @@ public: } else { halfWidth = SkScalarHalf(devStrokeWidth); } - - if (strokeOnly) { - // 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); + + // If the client has requested a stroke smaller than the outer radius, + // we will assume they want no special umbra inset (this is for ambient shadows). + if (devStrokeWidth <= outerRadius) { + umbraInset = outerRadius; + } + + if (strokeOnly) { + // 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()) { + // We don't worry about an inner radius, we just need to know if we + // need to create overstroke vertices. + overStrokeRadius = SkTMax(devStrokeWidth - umbraInset, + 0.0f); + type = overStrokeRadius > 0 ? kOverstroke_RRectType : kStroke_RRectType; + } + } } this->setBounds(bounds, HasAABloat::kNo, IsZeroArea::kNo); - fGeoData.emplace_back(Geometry{color, outerRadius, innerRadius, blurRadius, bounds, type}); + fGeoData.emplace_back(Geometry{color, outerRadius, umbraInset, overStrokeRadius, + blurRadius, bounds, type}); fVertCount = rrect_type_to_vert_count(type); fIndexCount = rrect_type_to_index_count(type); } @@ -552,10 +551,11 @@ public: 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", + "OuterRad: %.2f, Umbra: %.2f, Overstroke: %.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); + fGeoData[i].fOuterRadius, fGeoData[i].fUmbraInset, + fGeoData[i].fOverstroke, fGeoData[i].fBlurRadius); } string.append(DumpPipelineInfo(*this->pipeline())); string.append(INHERITED::dumpInfo()); @@ -580,73 +580,37 @@ private: SkPoint fPos; GrColor fColor; SkPoint fOffset; - SkScalar fOuterRadius; - SkScalar fBlurRadius; + SkScalar fDistanceCorrection; }; - static void FillInOverstrokeVerts(CircleVertex** verts, const SkRect& bounds, SkScalar smInset, - SkScalar bigInset, SkScalar xOffset, SkScalar outerRadius, - GrColor color, SkScalar blurRadius) { - SkASSERT(smInset < bigInset); - + static void FillInOverstrokeVerts(CircleVertex** verts, const SkRect& bounds, SkScalar inset, + GrColor color, SkScalar distanceCorrection) { // TL - (*verts)->fPos = SkPoint::Make(bounds.fLeft + smInset, bounds.fTop + smInset); + (*verts)->fPos = SkPoint::Make(bounds.fLeft + inset, bounds.fTop + inset); (*verts)->fColor = color; - (*verts)->fOffset = SkPoint::Make(xOffset, 0); - (*verts)->fOuterRadius = outerRadius; - (*verts)->fBlurRadius = blurRadius; + (*verts)->fOffset = SkPoint::Make(0, 0); + (*verts)->fDistanceCorrection = distanceCorrection; (*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)->fPos = SkPoint::Make(bounds.fRight - inset, bounds.fTop + inset); (*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)->fDistanceCorrection = distanceCorrection; (*verts)++; // BL - (*verts)->fPos = SkPoint::Make(bounds.fLeft + smInset, bounds.fBottom - smInset); + (*verts)->fPos = SkPoint::Make(bounds.fLeft + inset, bounds.fBottom - inset); (*verts)->fColor = color; - (*verts)->fOffset = SkPoint::Make(xOffset, 0); - (*verts)->fOuterRadius = outerRadius; - (*verts)->fBlurRadius = blurRadius; + (*verts)->fOffset = SkPoint::Make(0, 0); + (*verts)->fDistanceCorrection = distanceCorrection; (*verts)++; // BR - (*verts)->fPos = SkPoint::Make(bounds.fRight - smInset, bounds.fBottom - smInset); + (*verts)->fPos = SkPoint::Make(bounds.fRight - inset, bounds.fBottom - inset); (*verts)->fColor = color; - (*verts)->fOffset = SkPoint::Make(xOffset, 0); - (*verts)->fOuterRadius = outerRadius; - (*verts)->fBlurRadius = blurRadius; + (*verts)->fOffset = SkPoint::Make(0, 0); + (*verts)->fDistanceCorrection = distanceCorrection; (*verts)++; } @@ -691,69 +655,90 @@ private: const SkRect& bounds = args.fDevBounds; - SkScalar yCoords[4] = {bounds.fTop, bounds.fTop + outerRadius, - bounds.fBottom - outerRadius, bounds.fBottom}; + SkScalar umbraInset = args.fUmbraInset; + + SkScalar minDim = 0.5f*SkTMin(bounds.width(), bounds.height()); + if (umbraInset > minDim) { + umbraInset = minDim; + } + + SkScalar xInner[4] = { bounds.fLeft + umbraInset, bounds.fRight - umbraInset, + bounds.fLeft + umbraInset, bounds.fRight - umbraInset }; + SkScalar xMid[4] = { bounds.fLeft + outerRadius, bounds.fRight - outerRadius, + bounds.fLeft + outerRadius, bounds.fRight - outerRadius}; + SkScalar xOuter[4] = { bounds.fLeft, bounds.fRight, + bounds.fLeft, bounds.fRight }; + SkScalar yInner[4] = { bounds.fTop + umbraInset, bounds.fTop + umbraInset, + bounds.fBottom - umbraInset, bounds.fBottom - umbraInset }; + SkScalar yMid[4] = { bounds.fTop + outerRadius, bounds.fTop + outerRadius, + bounds.fBottom - outerRadius, bounds.fBottom - outerRadius }; + SkScalar yOuter[4] = { bounds.fTop, bounds.fTop, + bounds.fBottom, 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; + + // In the case where we have to inset more for the umbra, our two triangles in the + // corner get skewed to a diamond rather than a square. To correct for that, + // we also skew the vectors we send to the shader that help define the circle. + // By doing so, we end up with a quarter circle in the corner rather than the + // elliptical curve. + SkVector outerVec = SkVector::Make(0.5f*(outerRadius - umbraInset), -umbraInset); + outerVec.normalize(); + SkVector diagVec = SkVector::Make(outerVec.fX + outerVec.fY, + outerVec.fX + outerVec.fY); + diagVec *= umbraInset / (2 * umbraInset - outerRadius); + SkScalar distanceCorrection = umbraInset / blurRadius; + + // build corner by corner for (int i = 0; i < 4; ++i) { - verts->fPos = SkPoint::Make(bounds.fLeft, yCoords[i]); + // inner point + verts->fPos = SkPoint::Make(xInner[i], yInner[i]); verts->fColor = color; - verts->fOffset = SkPoint::Make(-1, yOuterRadii[i]); - verts->fOuterRadius = outerRadius; - verts->fBlurRadius = blurRadius; + verts->fOffset = SkVector::Make(0, 0); + verts->fDistanceCorrection = distanceCorrection; verts++; - verts->fPos = SkPoint::Make(bounds.fLeft + outerRadius, yCoords[i]); + // outer points + verts->fPos = SkPoint::Make(xOuter[i], yInner[i]); verts->fColor = color; - verts->fOffset = SkPoint::Make(0, yOuterRadii[i]); - verts->fOuterRadius = outerRadius; - verts->fBlurRadius = blurRadius; + verts->fOffset = SkVector::Make(0, -1); + verts->fDistanceCorrection = distanceCorrection; verts++; - verts->fPos = SkPoint::Make(bounds.fRight - outerRadius, yCoords[i]); + verts->fPos = SkPoint::Make(xOuter[i], yMid[i]); verts->fColor = color; - verts->fOffset = SkPoint::Make(0, yOuterRadii[i]); - verts->fOuterRadius = outerRadius; - verts->fBlurRadius = blurRadius; + verts->fOffset = outerVec; + verts->fDistanceCorrection = distanceCorrection; verts++; - verts->fPos = SkPoint::Make(bounds.fRight, yCoords[i]); + verts->fPos = SkPoint::Make(xOuter[i], yOuter[i]); verts->fColor = color; - verts->fOffset = SkPoint::Make(1, yOuterRadii[i]); - verts->fOuterRadius = outerRadius; - verts->fBlurRadius = blurRadius; + verts->fOffset = diagVec; + verts->fDistanceCorrection = distanceCorrection; + verts++; + + verts->fPos = SkPoint::Make(xMid[i], yOuter[i]); + verts->fColor = color; + verts->fOffset = outerVec; + verts->fDistanceCorrection = distanceCorrection; + verts++; + + verts->fPos = SkPoint::Make(xInner[i], yOuter[i]); + verts->fColor = color; + verts->fOffset = SkVector::Make(0, -1); + verts->fDistanceCorrection = distanceCorrection; 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. + // parameters equal to those in the center of the 9-patch. This will + // give constant values across this inner ring. if (kOverstroke_RRectType == args.fType) { - SkASSERT(args.fInnerRadius <= 0.0f); + SkASSERT(args.fOverstroke > 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); + FillInOverstrokeVerts(&verts, bounds, umbraInset + args.fOverstroke, + color, distanceCorrection); } const uint16_t* primIndices = rrect_type_to_indices(args.fType); @@ -792,7 +777,8 @@ private: struct Geometry { GrColor fColor; SkScalar fOuterRadius; - SkScalar fInnerRadius; + SkScalar fUmbraInset; + SkScalar fOverstroke; SkScalar fBlurRadius; SkRect fDevBounds; RRectType fType; @@ -839,9 +825,9 @@ static std::unique_ptr make_shadow_rrect_op(GrColor color, 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); + viewMatrix[SkMatrix::kMSkewX] * radii.fY); + SkDEBUGCODE(SkScalar yRadius = SkScalarAbs(viewMatrix[SkMatrix::kMSkewY] * radii.fX + + viewMatrix[SkMatrix::kMScaleY] * radii.fY)); SkASSERT(SkScalarNearlyEqual(xRadius, yRadius)); SkStrokeRec::Style style = stroke.getStyle(); @@ -859,9 +845,9 @@ static std::unique_ptr make_shadow_rrect_op(GrColor color, scaledStroke.set(1, 1); } else { scaledStroke.fX = SkScalarAbs( - strokeWidth * (viewMatrix[SkMatrix::kMScaleX] + viewMatrix[SkMatrix::kMSkewY])); + strokeWidth * (viewMatrix[SkMatrix::kMScaleX] + viewMatrix[SkMatrix::kMSkewY])); scaledStroke.fY = SkScalarAbs( - strokeWidth * (viewMatrix[SkMatrix::kMSkewX] + viewMatrix[SkMatrix::kMScaleY])); + strokeWidth * (viewMatrix[SkMatrix::kMSkewX] + viewMatrix[SkMatrix::kMScaleY])); } // we don't handle anisotropic strokes @@ -875,7 +861,7 @@ static std::unique_ptr make_shadow_rrect_op(GrColor color, // 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)) { + if (!isStrokeOnly && SK_ScalarHalf > xRadius) { return nullptr; } diff --git a/src/utils/SkShadowTessellator.cpp b/src/utils/SkShadowTessellator.cpp index 7d29b063a0..5dee5df3e0 100755 --- a/src/utils/SkShadowTessellator.cpp +++ b/src/utils/SkShadowTessellator.cpp @@ -778,7 +778,7 @@ SkSpotShadowTessellator::SkSpotShadowTessellator(const SkPath& path, const SkMat // if the umbra would collapse, we back off a bit on inner blur and adjust the alpha SkScalar newRadius = SkScalarSqrt(minDistSq) - kTolerance; fOffsetAdjust = newRadius - radius; - SkScalar ratio = 256 * newRadius / radius; + SkScalar ratio = 128 * (newRadius + radius) / radius; // they aren't PMColors, but the interpolation algorithm is the same fUmbraColor = SkPMLerp(fUmbraColor, fPenumbraColor, (unsigned)ratio); radius = newRadius; diff --git a/src/utils/SkShadowUtils.cpp b/src/utils/SkShadowUtils.cpp index 8206bd3f54..fc0d44a277 100644 --- a/src/utils/SkShadowUtils.cpp +++ b/src/utils/SkShadowUtils.cpp @@ -454,33 +454,79 @@ void draw_shadow(const FACTORY& factory, SkCanvas* canvas, ShadowedPath& path, S } } +static bool draw_analytic_shadows(SkCanvas* canvas, const SkPath& path, SkScalar occluderZ, + const SkPoint3& devLightPos, SkScalar lightRadius, + SkScalar ambientAlpha, SkScalar spotAlpha, SkColor color, + uint32_t flags) { + SkRect rect; + SkRRect rrect; + if (canvas->getTotalMatrix().isSimilarity()) { + if (path.isRect(&rect)) { + SkPaint newPaint; + newPaint.setColor(color); + if (ambientAlpha > 0) { + newPaint.setMaskFilter(SkAmbientShadowMaskFilter::Make(occluderZ, + ambientAlpha, flags)); + canvas->drawRect(rect, newPaint); + } + if (spotAlpha > 0) { + newPaint.setMaskFilter(SkSpotShadowMaskFilter::Make(occluderZ, devLightPos, + lightRadius, spotAlpha, + flags)); + canvas->drawRect(rect, newPaint); + } + return true; + } else if (path.isRRect(&rrect) && rrect.isSimpleCircular() && + rrect.radii(SkRRect::kUpperLeft_Corner).fX > SK_ScalarNearlyZero) { + SkPaint newPaint; + newPaint.setColor(color); + if (ambientAlpha > 0) { + newPaint.setMaskFilter(SkAmbientShadowMaskFilter::Make(occluderZ, + ambientAlpha, flags)); + canvas->drawRRect(rrect, newPaint); + } + if (spotAlpha > 0) { + newPaint.setMaskFilter(SkSpotShadowMaskFilter::Make(occluderZ, devLightPos, + lightRadius, spotAlpha, + flags)); + canvas->drawRRect(rrect, newPaint); + } + return true; + } else if (path.isOval(&rect) && SkScalarNearlyEqual(rect.width(), rect.height()) && + rect.width() > SK_ScalarNearlyZero) { + SkPaint newPaint; + newPaint.setColor(color); + if (ambientAlpha > 0) { + newPaint.setMaskFilter(SkAmbientShadowMaskFilter::Make(occluderZ, + ambientAlpha, flags)); + canvas->drawOval(rect, newPaint); + } + if (spotAlpha > 0) { + newPaint.setMaskFilter(SkSpotShadowMaskFilter::Make(occluderZ, devLightPos, + lightRadius, spotAlpha, + flags)); + canvas->drawOval(rect, newPaint); + } + return true; + } + } + + return false; +} + // Draw an offset spot shadow and outlining ambient shadow for the given path. void SkShadowUtils::DrawShadow(SkCanvas* canvas, const SkPath& path, SkScalar occluderHeight, const SkPoint3& devLightPos, SkScalar lightRadius, SkScalar ambientAlpha, SkScalar spotAlpha, SkColor color, uint32_t flags, SkResourceCache* cache) { - SkAutoCanvasRestore acr(canvas, true); - SkMatrix viewMatrix = canvas->getTotalMatrix(); - - // try circular fast path - SkRect rect; - if (viewMatrix.isSimilarity() && - path.isOval(&rect) && rect.width() == rect.height()) { - SkPaint newPaint; - newPaint.setColor(color); - if (ambientAlpha > 0) { - newPaint.setMaskFilter(SkAmbientShadowMaskFilter::Make(occluderHeight, ambientAlpha, - flags)); - canvas->drawOval(rect, newPaint); - } - if (spotAlpha > 0) { - newPaint.setMaskFilter(SkSpotShadowMaskFilter::Make(occluderHeight, devLightPos, - lightRadius, spotAlpha, flags)); - canvas->drawOval(rect, newPaint); - } + // try fast paths + if (draw_analytic_shadows(canvas, path, occluderHeight, devLightPos, lightRadius, + ambientAlpha, spotAlpha, color, flags)) { return; } + SkAutoCanvasRestore acr(canvas, true); + SkMatrix viewMatrix = canvas->getTotalMatrix(); canvas->resetMatrix(); ShadowedPath shadowedPath(&path, &viewMatrix); @@ -563,28 +609,14 @@ void SkShadowUtils::DrawUncachedShadow(SkCanvas* canvas, const SkPath& path, const SkPoint3& lightPos, SkScalar lightRadius, SkScalar ambientAlpha, SkScalar spotAlpha, SkColor color, uint32_t flags) { - SkAutoCanvasRestore acr(canvas, true); - SkMatrix viewMatrix = canvas->getTotalMatrix(); - - // try circular fast path - SkRect rect; - if (viewMatrix.isSimilarity() && - path.isOval(&rect) && rect.width() == rect.height()) { - SkPaint newPaint; - newPaint.setColor(color); - if (ambientAlpha > 0) { - newPaint.setMaskFilter(SkAmbientShadowMaskFilter::Make(heightFunc(0,0), ambientAlpha, - flags)); - canvas->drawOval(rect, newPaint); - } - if (spotAlpha > 0) { - newPaint.setMaskFilter(SkSpotShadowMaskFilter::Make(heightFunc(0,0), lightPos, - lightRadius, spotAlpha, flags)); - canvas->drawOval(rect, newPaint); - } + // try fast paths + if (draw_analytic_shadows(canvas, path, heightFunc(0, 0), lightPos, lightRadius, + ambientAlpha, spotAlpha, color, flags)) { return; } + SkAutoCanvasRestore acr(canvas, true); + SkMatrix viewMatrix = canvas->getTotalMatrix(); canvas->resetMatrix(); bool transparent = SkToBool(flags & SkShadowFlags::kTransparentOccluder_ShadowFlag);