From 30c4cae7d3a26252e7e45adf6e5722b34adf6848 Mon Sep 17 00:00:00 2001 From: robertphillips Date: Tue, 15 Sep 2015 10:20:55 -0700 Subject: Add special case circle blur for Ganesh This makes the blurcircles bench go from ~33us to ~8us on Windows desktop. It will require layout test suppressions Review URL: https://codereview.chromium.org/1311583005 --- gm/blurcircles.cpp | 32 ++-- gyp/effects.gypi | 3 + include/core/SkColorPriv.h | 10 + include/core/SkMaskFilter.h | 30 ++- include/core/SkRRect.h | 13 +- include/core/SkScalar.h | 2 +- src/core/SkColor.cpp | 14 +- src/core/SkMaskFilter.cpp | 2 +- src/effects/GrCircleBlurFragmentProcessor.cpp | 259 ++++++++++++++++++++++++++ src/effects/GrCircleBlurFragmentProcessor.h | 78 ++++++++ src/effects/SkBlurMask.cpp | 12 +- src/effects/SkBlurMask.h | 5 +- src/effects/SkBlurMaskFilter.cpp | 149 ++++++++------- src/gpu/GrBlurUtils.cpp | 2 +- src/gpu/GrProcessor.cpp | 2 +- src/gpu/SkGpuDevice.cpp | 6 +- 16 files changed, 494 insertions(+), 125 deletions(-) create mode 100644 src/effects/GrCircleBlurFragmentProcessor.cpp create mode 100644 src/effects/GrCircleBlurFragmentProcessor.h diff --git a/gm/blurcircles.cpp b/gm/blurcircles.cpp index e4f956fc43..74490ce424 100644 --- a/gm/blurcircles.cpp +++ b/gm/blurcircles.cpp @@ -14,37 +14,43 @@ class BlurCirclesGM : public skiagm::GM { public: - BlurCirclesGM() - : fName("blurcircles") { - } + BlurCirclesGM() { } protected: + bool runAsBench() const override { return true; } SkString onShortName() override { - return fName; + return SkString("blurcircles"); } SkISize onISize() override { return SkISize::Make(950, 950); } + void onOnceBeforeDraw() override { + const float blurRadii[kNumBlurs] = { 1,5,10,20 }; + + for (int i = 0; i < kNumBlurs; ++i) { + fBlurFilters[i].reset(SkBlurMaskFilter::Create( + kNormal_SkBlurStyle, + SkBlurMask::ConvertRadiusToSigma(SkIntToScalar(blurRadii[i])), + SkBlurMaskFilter::kHighQuality_BlurFlag)); + } + } + void onDraw(SkCanvas* canvas) override { canvas->scale(1.5f, 1.5f); canvas->translate(50,50); - const float blurRadii[] = { 1,5,10,20 }; const int circleRadii[] = { 5,10,25,50 }; - for (size_t i = 0; i < SK_ARRAY_COUNT(blurRadii); ++i) { + + for (size_t i = 0; i < kNumBlurs; ++i) { SkAutoCanvasRestore autoRestore(canvas, true); canvas->translate(0, SkIntToScalar(150*i)); for (size_t j = 0; j < SK_ARRAY_COUNT(circleRadii); ++j) { - SkMaskFilter* filter = SkBlurMaskFilter::Create( - kNormal_SkBlurStyle, - SkBlurMask::ConvertRadiusToSigma(SkIntToScalar(blurRadii[i])), - SkBlurMaskFilter::kHighQuality_BlurFlag); SkPaint paint; paint.setColor(SK_ColorBLACK); - paint.setMaskFilter(filter)->unref(); + paint.setMaskFilter(fBlurFilters[i]); canvas->drawCircle(SkIntToScalar(50),SkIntToScalar(50),SkIntToScalar(circleRadii[j]),paint); canvas->translate(SkIntToScalar(150), 0); @@ -52,7 +58,9 @@ protected: } } private: - const SkString fName; + static const int kNumBlurs = 4; + + SkAutoTUnref fBlurFilters[kNumBlurs]; typedef skiagm::GM INHERITED; }; diff --git a/gyp/effects.gypi b/gyp/effects.gypi index 188be80d0e..5be4370af8 100644 --- a/gyp/effects.gypi +++ b/gyp/effects.gypi @@ -11,6 +11,9 @@ # { 'sources': [ + '<(skia_src_path)/effects/GrCircleBlurFragmentProcessor.cpp', + '<(skia_src_path)/effects/GrCircleBlurFragmentProcessor.h', + '<(skia_src_path)/effects/Sk1DPathEffect.cpp', '<(skia_src_path)/effects/Sk2DPathEffect.cpp', '<(skia_src_path)/effects/SkAlphaThresholdFilter.cpp', diff --git a/include/core/SkColorPriv.h b/include/core/SkColorPriv.h index 3dec49b73e..6347660dbc 100644 --- a/include/core/SkColorPriv.h +++ b/include/core/SkColorPriv.h @@ -218,6 +218,16 @@ static inline int SkAlphaBlend255(S16CPU src, S16CPU dst, U8CPU alpha) { return dst + prod; } +static inline U8CPU SkUnitScalarClampToByte(SkScalar x) { + if (x < 0) { + return 0; + } + if (x >= SK_Scalar1) { + return 255; + } + return SkScalarToFixed(x) >> 8; +} + #define SK_R16_BITS 5 #define SK_G16_BITS 6 #define SK_B16_BITS 5 diff --git a/include/core/SkMaskFilter.h b/include/core/SkMaskFilter.h index df9c4ac3fe..50a1e87e00 100644 --- a/include/core/SkMaskFilter.h +++ b/include/core/SkMaskFilter.h @@ -78,17 +78,31 @@ public: /** * If asFragmentProcessor() fails the filter may be implemented on the GPU by a subclass - * overriding filterMaskGPU (declared below). That code path requires constructing a src mask - * as input. Since that is a potentially expensive operation, the subclass must also override - * this function to indicate whether filterTextureMaskGPU would succeeed if the mask were to be - * created. + * overriding filterMaskGPU (declared below). That code path requires constructing a + * src mask as input. Since that is a potentially expensive operation, the subclass must also + * override this function to indicate whether filterTextureMaskGPU would succeeed if the mask + * were to be created. * * 'maskRect' returns the device space portion of the mask that the filter needs. The mask - * passed into 'filterMaskGPU' should have the same extent as 'maskRect' but be translated - * to the upper-left corner of the mask (i.e., (maskRect.fLeft, maskRect.fTop) appears at - * (0, 0) in the mask). + * passed into 'filterMaskGPU' should have the same extent as 'maskRect' but be + * translated to the upper-left corner of the mask (i.e., (maskRect.fLeft, maskRect.fTop) + * appears at (0, 0) in the mask). + * + * Logically, how this works is: + * canFilterMaskGPU is called + * if (it returns true) + * the returned mask rect is used for quick rejecting + * either directFilterMaskGPU or directFilterRRectMaskGPU is then called + * if (neither of them handle the blur) + * the mask rect is used to generate the mask + * filterMaskGPU is called to filter the mask + * + * TODO: this should work as: + * if (canFilterMaskGPU(devShape, ...)) // rect, rrect, drrect, path + * filterMaskGPU(devShape, ...) + * this would hide the RRect special case and the mask generation */ - virtual bool canFilterMaskGPU(const SkRect& devBounds, + virtual bool canFilterMaskGPU(const SkRRect& devRRect, const SkIRect& clipBounds, const SkMatrix& ctm, SkRect* maskRect) const; diff --git a/include/core/SkRRect.h b/include/core/SkRRect.h index 37766219df..064e7be8e4 100644 --- a/include/core/SkRRect.h +++ b/include/core/SkRRect.h @@ -96,8 +96,13 @@ public: inline bool isRect() const { return kRect_Type == this->getType(); } inline bool isOval() const { return kOval_Type == this->getType(); } inline bool isSimple() const { return kSimple_Type == this->getType(); } + // TODO: should isSimpleCircular & isCircle take a tolerance? This could help + // instances where the mapping to device space is noisy. inline bool isSimpleCircular() const { - return this->isSimple() && fRadii[0].fX == fRadii[0].fY; + return this->isSimple() && SkScalarNearlyEqual(fRadii[0].fX, fRadii[0].fY); + } + inline bool isCircle() const { + return this->isOval() && SkScalarNearlyEqual(fRadii[0].fX, fRadii[0].fY); } inline bool isNinePatch() const { return kNinePatch_Type == this->getType(); } inline bool isComplex() const { return kComplex_Type == this->getType(); } @@ -140,6 +145,12 @@ public: return rr; } + static SkRRect MakeOval(const SkRect& oval) { + SkRRect rr; + rr.setOval(oval); + return rr; + } + /** * Set this RR to match the supplied oval. All x radii will equal half the * width and all y radii will equal half the height. diff --git a/include/core/SkScalar.h b/include/core/SkScalar.h index 71575a4920..4efa8417af 100644 --- a/include/core/SkScalar.h +++ b/include/core/SkScalar.h @@ -224,7 +224,7 @@ static inline bool SkScalarNearlyZero(SkScalar x, } static inline bool SkScalarNearlyEqual(SkScalar x, SkScalar y, - SkScalar tolerance = SK_ScalarNearlyZero) { + SkScalar tolerance = SK_ScalarNearlyZero) { SkASSERT(tolerance >= 0); return SkScalarAbs(x-y) <= tolerance; } diff --git a/src/core/SkColor.cpp b/src/core/SkColor.cpp index 73e6ddd874..a21f019239 100644 --- a/src/core/SkColor.cpp +++ b/src/core/SkColor.cpp @@ -70,21 +70,11 @@ void SkRGBToHSV(U8CPU r, U8CPU g, U8CPU b, SkScalar hsv[3]) { hsv[2] = v; } -static inline U8CPU UnitScalarToByte(SkScalar x) { - if (x < 0) { - return 0; - } - if (x >= SK_Scalar1) { - return 255; - } - return SkScalarToFixed(x) >> 8; -} - SkColor SkHSVToColor(U8CPU a, const SkScalar hsv[3]) { SkASSERT(hsv); - U8CPU s = UnitScalarToByte(hsv[1]); - U8CPU v = UnitScalarToByte(hsv[2]); + U8CPU s = SkUnitScalarClampToByte(hsv[1]); + U8CPU v = SkUnitScalarClampToByte(hsv[2]); if (0 == s) { // shade of gray return SkColorSetARGB(a, v, v, v); diff --git a/src/core/SkMaskFilter.cpp b/src/core/SkMaskFilter.cpp index 7c48bcacb9..6a9c2df5ed 100644 --- a/src/core/SkMaskFilter.cpp +++ b/src/core/SkMaskFilter.cpp @@ -309,7 +309,7 @@ bool SkMaskFilter::asFragmentProcessor(GrFragmentProcessor**, GrTexture*, const return false; } -bool SkMaskFilter::canFilterMaskGPU(const SkRect& devBounds, +bool SkMaskFilter::canFilterMaskGPU(const SkRRect& devRRect, const SkIRect& clipBounds, const SkMatrix& ctm, SkRect* maskRect) const { diff --git a/src/effects/GrCircleBlurFragmentProcessor.cpp b/src/effects/GrCircleBlurFragmentProcessor.cpp new file mode 100644 index 0000000000..c5aafdce0e --- /dev/null +++ b/src/effects/GrCircleBlurFragmentProcessor.cpp @@ -0,0 +1,259 @@ + +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "GrCircleBlurFragmentProcessor.h" + +#if SK_SUPPORT_GPU + +#include "GrContext.h" +#include "GrTextureProvider.h" + +#include "gl/GrGLFragmentProcessor.h" +#include "gl/builders/GrGLProgramBuilder.h" + +class GrGLCircleBlurFragmentProcessor : public GrGLFragmentProcessor { +public: + GrGLCircleBlurFragmentProcessor(const GrProcessor&) {} + void emitCode(EmitArgs&) override; + +protected: + void onSetData(const GrGLProgramDataManager&, const GrProcessor&) override; + +private: + GrGLProgramDataManager::UniformHandle fDataUniform; + + typedef GrGLFragmentProcessor INHERITED; +}; + +void GrGLCircleBlurFragmentProcessor::emitCode(EmitArgs& args) { + + const char *dataName; + + // The data is formatted as: + // x,y - the center of the circle + // z - the distance at which the intensity starts falling off (e.g., the start of the table) + // w - the size of the profile texture + fDataUniform = args.fBuilder->addUniform(GrGLProgramBuilder::kFragment_Visibility, + kVec4f_GrSLType, + kDefault_GrSLPrecision, + "data", + &dataName); + + GrGLFragmentBuilder* fsBuilder = args.fBuilder->getFragmentShaderBuilder(); + const char *fragmentPos = fsBuilder->fragmentPosition(); + + if (args.fInputColor) { + fsBuilder->codeAppendf("vec4 src=%s;", args.fInputColor); + } else { + fsBuilder->codeAppendf("vec4 src=vec4(1);"); + } + + fsBuilder->codeAppendf("vec2 vec = %s.xy - %s.xy;", fragmentPos, dataName); + fsBuilder->codeAppendf("float dist = (length(vec) - %s.z + 0.5) / %s.w;", dataName, dataName); + + fsBuilder->codeAppendf("float intensity = "); + fsBuilder->appendTextureLookup(args.fSamplers[0], "vec2(dist, 0.5)"); + fsBuilder->codeAppend(".a;"); + + fsBuilder->codeAppendf("%s = src * intensity;\n", args.fOutputColor ); +} + +void GrGLCircleBlurFragmentProcessor::onSetData(const GrGLProgramDataManager& pdman, + const GrProcessor& proc) { + const GrCircleBlurFragmentProcessor& cbfp = proc.cast(); + const SkRect& circle = cbfp.circle(); + + // The data is formatted as: + // x,y - the center of the circle + // z - the distance at which the intensity starts falling off (e.g., the start of the table) + // w - the size of the profile texture + pdman.set4f(fDataUniform, circle.centerX(), circle.centerY(), cbfp.offset(), + SkIntToScalar(cbfp.profileSize())); +} + +/////////////////////////////////////////////////////////////////////////////// + +GrCircleBlurFragmentProcessor::GrCircleBlurFragmentProcessor(const SkRect& circle, + float sigma, + float offset, + GrTexture* blurProfile) + : fCircle(circle) + , fSigma(sigma) + , fOffset(offset) + , fBlurProfileAccess(blurProfile, GrTextureParams::kBilerp_FilterMode) { + this->initClassID(); + this->addTextureAccess(&fBlurProfileAccess); + this->setWillReadFragmentPosition(); +} + +GrGLFragmentProcessor* GrCircleBlurFragmentProcessor::onCreateGLInstance() const { + return new GrGLCircleBlurFragmentProcessor(*this); +} + +void GrCircleBlurFragmentProcessor::onGetGLProcessorKey(const GrGLSLCaps& caps, + GrProcessorKeyBuilder* b) const { + GrGLCircleBlurFragmentProcessor::GenKey(*this, caps, b); +} + +void GrCircleBlurFragmentProcessor::onComputeInvariantOutput(GrInvariantOutput* inout) const { + inout->mulByUnknownSingleComponent(); +} + +// Evaluate an AA circle function centered at the origin with 'radius' at (x,y) +static inline float disk(float x, float y, float radius) { + float distSq = x*x + y*y; + if (distSq <= (radius-0.5f)*(radius-0.5f)) { + return 1.0f; + } else if (distSq >= (radius+0.5f)*(radius+0.5f)) { + return 0.0f; + } else { + float ramp = radius + 0.5f - sqrt(distSq); + SkASSERT(ramp >= 0.0f && ramp <= 1.0f); + return ramp; + } +} + +// Create the top half of an even-sized Gaussian kernel +static void make_half_kernel(float* kernel, int kernelWH, float sigma) { + SkASSERT(!(kernelWH & 1)); + + const float kernelOff = (kernelWH-1)/2.0f; + + float b = 1.0f / (2.0f * sigma * sigma); + // omit the scale term since we're just going to renormalize + + float tot = 0.0f; + for (int y = 0; y < kernelWH/2; ++y) { + for (int x = 0; x < kernelWH/2; ++x) { + // TODO: use a cheap approximation of the 2D Guassian? + float x2 = (x-kernelOff) * (x-kernelOff); + float y2 = (y-kernelOff) * (y-kernelOff); + // The kernel is symmetric so only compute it once for both sides + kernel[y*kernelWH+(kernelWH-x-1)] = kernel[y*kernelWH+x] = exp(-(x2 + y2) * b); + tot += 2.0f * kernel[y*kernelWH+x]; + } + } + // Still normalize the half kernel to 1.0 (rather than 0.5) so we don't + // have to scale by 2.0 after convolution. + for (int y = 0; y < kernelWH/2; ++y) { + for (int x = 0; x < kernelWH; ++x) { + kernel[y*kernelWH+x] /= tot; + } + } +} + +// Apply the half-kernel at 't' away from the center of the circle +static uint8_t eval_at(float t, float halfWidth, float* halfKernel, int kernelWH) { + SkASSERT(!(kernelWH & 1)); + + const float kernelOff = (kernelWH-1)/2.0f; + + float acc = 0; + + for (int y = 0; y < kernelWH/2; ++y) { + if (kernelOff-y > halfWidth+0.5f) { + // All disk() samples in this row will be 0.0f + continue; + } + + for (int x = 0; x < kernelWH; ++x) { + float image = disk(t - kernelOff + x, -kernelOff + y, halfWidth); + float kernel = halfKernel[y*kernelWH+x]; + acc += kernel * image; + } + } + + return SkUnitScalarClampToByte(acc); +} + +static inline void compute_profile_offset_and_size(float halfWH, float sigma, + float* offset, int* size) { + + if (3*sigma <= halfWH) { + // The circle is bigger than the Gaussian. In this case we know the interior of the + // blurred circle is solid. + *offset = halfWH - 3 * sigma; // This location maps to 0.5f in the weights texture. + // It should always be 255. + *size = SkScalarCeilToInt(6*sigma); + } else { + // The Gaussian is bigger than the circle. + *offset = 0.0f; + *size = SkScalarCeilToInt(halfWH + 3*sigma); + } +} + +static uint8_t* create_profile(float halfWH, float sigma) { + + int kernelWH = SkScalarCeilToInt(6.0f*sigma); + kernelWH = (kernelWH + 1) & ~1; // make it the next even number up + + SkAutoTArray halfKernel(kernelWH*kernelWH/2); + + make_half_kernel(halfKernel.get(), kernelWH, sigma); + + float offset; + int numSteps; + + compute_profile_offset_and_size(halfWH, sigma, &offset, &numSteps); + + uint8_t* weights = new uint8_t[numSteps]; + for (int i = 0; i < numSteps; ++i) { + weights[i] = eval_at(offset+i, halfWH, halfKernel.get(), kernelWH); + } + + return weights; +} + +GrTexture* GrCircleBlurFragmentProcessor::CreateCircleBlurProfileTexture( + GrTextureProvider* textureProvider, + const SkRect& circle, + float sigma, + float* offset) { + float halfWH = circle.width() / 2.0f; + + int size; + compute_profile_offset_and_size(halfWH, sigma, offset, &size); + + GrSurfaceDesc texDesc; + texDesc.fWidth = size; + texDesc.fHeight = 1; + texDesc.fConfig = kAlpha_8_GrPixelConfig; + + static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); + GrUniqueKey key; + GrUniqueKey::Builder builder(&key, kDomain, 2); + // The profile curve varies with both the sigma of the Gaussian and the size of the + // disk. Quantizing to 16.16 should be close enough though. + builder[0] = SkScalarToFixed(sigma); + builder[1] = SkScalarToFixed(halfWH); + builder.finish(); + + GrTexture *blurProfile = textureProvider->findAndRefTextureByUniqueKey(key); + + if (!blurProfile) { + SkAutoTDeleteArray profile(create_profile(halfWH, sigma)); + + blurProfile = textureProvider->createTexture(texDesc, true, profile.get(), 0); + if (blurProfile) { + textureProvider->assignUniqueKeyToTexture(key, blurProfile); + } + } + + return blurProfile; +} + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrCircleBlurFragmentProcessor); + +const GrFragmentProcessor* GrCircleBlurFragmentProcessor::TestCreate(GrProcessorTestData* d) { + SkScalar wh = d->fRandom->nextRangeScalar(100.f, 1000.f); + SkScalar sigma = d->fRandom->nextRangeF(1.f,10.f); + SkRect circle = SkRect::MakeWH(wh, wh); + return GrCircleBlurFragmentProcessor::Create(d->fContext->textureProvider(), circle, sigma); +} + +#endif diff --git a/src/effects/GrCircleBlurFragmentProcessor.h b/src/effects/GrCircleBlurFragmentProcessor.h new file mode 100644 index 0000000000..4d39ec8e11 --- /dev/null +++ b/src/effects/GrCircleBlurFragmentProcessor.h @@ -0,0 +1,78 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrCircleBlurFragmentProcessor_DEFINED +#define GrCircleBlurFragmentProcessor_DEFINED + +#include "SkTypes.h" + +#if SK_SUPPORT_GPU + +#include "GrFragmentProcessor.h" +#include "GrProcessorUnitTest.h" + +class GrTextureProvider; + +// This FP handles the special case of a blurred circle. It uses a 1D +// profile that is just rotated about the origin of the circle. +class GrCircleBlurFragmentProcessor : public GrFragmentProcessor { +public: + ~GrCircleBlurFragmentProcessor() override {}; + + const char* name() const override { return "CircleBlur"; } + + static const GrFragmentProcessor* Create(GrTextureProvider*textureProvider, + const SkRect& circle, float sigma) { + float offset; + + SkAutoTUnref blurProfile(CreateCircleBlurProfileTexture(textureProvider, + circle, + sigma, + &offset)); + if (!blurProfile) { + return nullptr; + } + return new GrCircleBlurFragmentProcessor(circle, sigma, offset, blurProfile); + } + + const SkRect& circle() const { return fCircle; } + float sigma() const { return fSigma; } + float offset() const { return fOffset; } + int profileSize() const { return fBlurProfileAccess.getTexture()->width(); } + +private: + GrCircleBlurFragmentProcessor(const SkRect& circle, float sigma, + float offset, GrTexture* blurProfile); + + GrGLFragmentProcessor* onCreateGLInstance() const override; + + void onGetGLProcessorKey(const GrGLSLCaps& caps, GrProcessorKeyBuilder* b) const override; + + bool onIsEqual(const GrFragmentProcessor& other) const override { + const GrCircleBlurFragmentProcessor& cbfp = other.cast(); + // fOffset is computed from the circle width and the sigma + return this->circle().width() == cbfp.circle().width() && fSigma == cbfp.fSigma; + } + + void onComputeInvariantOutput(GrInvariantOutput* inout) const override; + + static GrTexture* CreateCircleBlurProfileTexture(GrTextureProvider*, + const SkRect& circle, + float sigma, float* offset); + + SkRect fCircle; + float fSigma; + float fOffset; + GrTextureAccess fBlurProfileAccess; + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST; + + typedef GrFragmentProcessor INHERITED; +}; + +#endif +#endif diff --git a/src/effects/SkBlurMask.cpp b/src/effects/SkBlurMask.cpp index 0fc554fdd2..acee70ff25 100644 --- a/src/effects/SkBlurMask.cpp +++ b/src/effects/SkBlurMask.cpp @@ -678,7 +678,7 @@ static float gaussianIntegral(float x) { memory returned in profile_out. */ -void SkBlurMask::ComputeBlurProfile(SkScalar sigma, uint8_t **profile_out) { +uint8_t* SkBlurMask::ComputeBlurProfile(SkScalar sigma) { int size = SkScalarCeilToInt(6*sigma); int center = size >> 1; @@ -693,7 +693,7 @@ void SkBlurMask::ComputeBlurProfile(SkScalar sigma, uint8_t **profile_out) { profile[x] = 255 - (uint8_t) (255.f * gi); } - *profile_out = profile; + return profile; } // TODO MAYBE: Maintain a profile cache to avoid recomputing this for @@ -769,10 +769,8 @@ bool SkBlurMask::BlurRect(SkScalar sigma, SkMask *dst, } return true; } - uint8_t *profile = nullptr; - ComputeBlurProfile(sigma, &profile); - SkAutoTDeleteArray ada(profile); + SkAutoTDeleteArray profile(ComputeBlurProfile(sigma)); size_t dstSize = dst->computeImageSize(); if (0 == dstSize) { @@ -791,8 +789,8 @@ bool SkBlurMask::BlurRect(SkScalar sigma, SkMask *dst, SkAutoTMalloc horizontalScanline(dstWidth); SkAutoTMalloc verticalScanline(dstHeight); - ComputeBlurredScanline(horizontalScanline, profile, dstWidth, sigma); - ComputeBlurredScanline(verticalScanline, profile, dstHeight, sigma); + ComputeBlurredScanline(horizontalScanline, profile.get(), dstWidth, sigma); + ComputeBlurredScanline(verticalScanline, profile.get(), dstHeight, sigma); for (int y = 0 ; y < dstHeight ; ++y) { for (int x = 0 ; x < dstWidth ; x++) { diff --git a/src/effects/SkBlurMask.h b/src/effects/SkBlurMask.h index 5c2a82ee1b..b6c37fb461 100644 --- a/src/effects/SkBlurMask.h +++ b/src/effects/SkBlurMask.h @@ -54,15 +54,14 @@ public: @param blurred_width The width of the final, blurred rectangle @param sharp_width The width of the original, unblurred rectangle. */ - static uint8_t ProfileLookup(const uint8_t* profile, int loc, int blurred_width, int sharp_width); + static uint8_t ProfileLookup(const uint8_t* profile, int loc, int blurredWidth, int sharpWidth); /** Allocate memory for and populate the profile of a 1D blurred halfplane. The caller must free the memory. The amount of memory allocated will be exactly 6*sigma bytes. @param sigma The standard deviation of the gaussian blur kernel - @param profile_out The location to store the allocated profile curve */ - static void ComputeBlurProfile(SkScalar sigma, uint8_t** profile_out); + static uint8_t* ComputeBlurProfile(SkScalar sigma); /** Compute an entire scanline of a blurred step function. This is a 1D helper that will produce both the horizontal and vertical profiles of the blurry rectangle. diff --git a/src/effects/SkBlurMaskFilter.cpp b/src/effects/SkBlurMaskFilter.cpp index 2ef86b0162..58242b7017 100644 --- a/src/effects/SkBlurMaskFilter.cpp +++ b/src/effects/SkBlurMaskFilter.cpp @@ -18,6 +18,7 @@ #include "SkStrokeRec.h" #if SK_SUPPORT_GPU +#include "GrCircleBlurFragmentProcessor.h" #include "GrContext.h" #include "GrDrawContext.h" #include "GrTexture.h" @@ -44,7 +45,7 @@ public: SkIPoint* margin) const override; #if SK_SUPPORT_GPU - bool canFilterMaskGPU(const SkRect& devBounds, + bool canFilterMaskGPU(const SkRRect& devRRect, const SkIRect& clipBounds, const SkMatrix& ctm, SkRect* maskRect) const override; @@ -166,27 +167,25 @@ bool SkBlurMaskFilterImpl::asABlur(BlurRec* rec) const { bool SkBlurMaskFilterImpl::filterMask(SkMask* dst, const SkMask& src, const SkMatrix& matrix, - SkIPoint* margin) const{ + SkIPoint* margin) const { SkScalar sigma = this->computeXformedSigma(matrix); return SkBlurMask::BoxBlur(dst, src, sigma, fBlurStyle, this->getQuality(), margin); } bool SkBlurMaskFilterImpl::filterRectMask(SkMask* dst, const SkRect& r, const SkMatrix& matrix, - SkIPoint* margin, SkMask::CreateMode createMode) const{ + SkIPoint* margin, SkMask::CreateMode createMode) const { SkScalar sigma = computeXformedSigma(matrix); - return SkBlurMask::BlurRect(sigma, dst, r, fBlurStyle, - margin, createMode); + return SkBlurMask::BlurRect(sigma, dst, r, fBlurStyle, margin, createMode); } bool SkBlurMaskFilterImpl::filterRRectMask(SkMask* dst, const SkRRect& r, const SkMatrix& matrix, - SkIPoint* margin, SkMask::CreateMode createMode) const{ + SkIPoint* margin, SkMask::CreateMode createMode) const { SkScalar sigma = computeXformedSigma(matrix); - return SkBlurMask::BlurRRect(sigma, dst, r, fBlurStyle, - margin, createMode); + return SkBlurMask::BlurRRect(sigma, dst, r, fBlurStyle, margin, createMode); } #include "SkCanvas.h" @@ -607,51 +606,42 @@ class GrGLRectBlurEffect; class GrRectBlurEffect : public GrFragmentProcessor { public: - virtual ~GrRectBlurEffect(); + ~GrRectBlurEffect() override { } const char* name() const override { return "RectBlur"; } - /** - * Create a simple filter effect with custom bicubic coefficients. - */ - static GrFragmentProcessor* Create(GrTextureProvider *textureProvider, const SkRect& rect, - float sigma) { - GrTexture *blurProfileTexture = nullptr; + static GrFragmentProcessor* Create(GrTextureProvider *textureProvider, + const SkRect& rect, float sigma) { int doubleProfileSize = SkScalarCeilToInt(12*sigma); if (doubleProfileSize >= rect.width() || doubleProfileSize >= rect.height()) { // if the blur sigma is too large so the gaussian overlaps the whole // rect in either direction, fall back to CPU path for now. - return nullptr; } - bool createdBlurProfileTexture = CreateBlurProfileTexture( - textureProvider, sigma, &blurProfileTexture); - SkAutoTUnref hunref(blurProfileTexture); - if (!createdBlurProfileTexture) { + SkAutoTUnref blurProfile(CreateBlurProfileTexture(textureProvider, sigma)); + if (!blurProfile) { return nullptr; } - return new GrRectBlurEffect(rect, sigma, blurProfileTexture); + return new GrRectBlurEffect(rect, sigma, blurProfile); } const SkRect& getRect() const { return fRect; } float getSigma() const { return fSigma; } private: - GrGLFragmentProcessor* onCreateGLInstance() const override; + GrRectBlurEffect(const SkRect& rect, float sigma, GrTexture *blurProfile); - GrRectBlurEffect(const SkRect& rect, float sigma, GrTexture *blur_profile); + GrGLFragmentProcessor* onCreateGLInstance() const override; - virtual void onGetGLProcessorKey(const GrGLSLCaps& caps, - GrProcessorKeyBuilder* b) const override; + void onGetGLProcessorKey(const GrGLSLCaps& caps, GrProcessorKeyBuilder* b) const override; bool onIsEqual(const GrFragmentProcessor&) const override; void onComputeInvariantOutput(GrInvariantOutput* inout) const override; - static bool CreateBlurProfileTexture(GrTextureProvider*, float sigma, - GrTexture **blurProfileTexture); + static GrTexture* CreateBlurProfileTexture(GrTextureProvider*, float sigma); SkRect fRect; float fSigma; @@ -665,7 +655,7 @@ private: class GrGLRectBlurEffect : public GrGLFragmentProcessor { public: GrGLRectBlurEffect(const GrProcessor&) {} - virtual void emitCode(EmitArgs&) override; + void emitCode(EmitArgs&) override; protected: void onSetData(const GrGLProgramDataManager&, const GrProcessor&) override; @@ -738,7 +728,7 @@ void GrGLRectBlurEffect::emitCode(EmitArgs& args) { } void GrGLRectBlurEffect::onSetData(const GrGLProgramDataManager& pdman, - const GrProcessor& proc) { + const GrProcessor& proc) { const GrRectBlurEffect& rbe = proc.cast(); SkRect rect = rbe.getRect(); @@ -746,8 +736,8 @@ void GrGLRectBlurEffect::onSetData(const GrGLProgramDataManager& pdman, pdman.set1f(fProfileSizeUniform, SkScalarCeilToScalar(6*rbe.getSigma())); } -bool GrRectBlurEffect::CreateBlurProfileTexture(GrTextureProvider* textureProvider, float sigma, - GrTexture **blurProfileTexture) { +GrTexture* GrRectBlurEffect::CreateBlurProfileTexture(GrTextureProvider* textureProvider, + float sigma) { GrSurfaceDesc texDesc; unsigned int profileSize = SkScalarCeilToInt(6*sigma); @@ -762,40 +752,29 @@ bool GrRectBlurEffect::CreateBlurProfileTexture(GrTextureProvider* textureProvid builder[0] = profileSize; builder.finish(); - uint8_t *profile = nullptr; - SkAutoTDeleteArray ada(nullptr); - - *blurProfileTexture = textureProvider->findAndRefTextureByUniqueKey(key); + GrTexture *blurProfile = textureProvider->findAndRefTextureByUniqueKey(key); - if (nullptr == *blurProfileTexture) { + if (!blurProfile) { + SkAutoTDeleteArray profile(SkBlurMask::ComputeBlurProfile(sigma)); - SkBlurMask::ComputeBlurProfile(sigma, &profile); - ada.reset(profile); - - *blurProfileTexture = textureProvider->createTexture(texDesc, true, profile, 0); - - if (nullptr == *blurProfileTexture) { - return false; + blurProfile = textureProvider->createTexture(texDesc, true, profile.get(), 0); + if (blurProfile) { + textureProvider->assignUniqueKeyToTexture(key, blurProfile); } - textureProvider->assignUniqueKeyToTexture(key, *blurProfileTexture); } - return true; + return blurProfile; } -GrRectBlurEffect::GrRectBlurEffect(const SkRect& rect, float sigma, - GrTexture *blur_profile) - : fRect(rect), - fSigma(sigma), - fBlurProfileAccess(blur_profile) { +GrRectBlurEffect::GrRectBlurEffect(const SkRect& rect, float sigma, GrTexture *blurProfile) + : fRect(rect) + , fSigma(sigma) + , fBlurProfileAccess(blurProfile) { this->initClassID(); this->addTextureAccess(&fBlurProfileAccess); this->setWillReadFragmentPosition(); } -GrRectBlurEffect::~GrRectBlurEffect() { -} - void GrRectBlurEffect::onGetGLProcessorKey(const GrGLSLCaps& caps, GrProcessorKeyBuilder* b) const { GrGLRectBlurEffect::GenKey(*this, caps, b); @@ -839,22 +818,31 @@ bool SkBlurMaskFilterImpl::directFilterMaskGPU(GrTextureProvider* texProvider, return false; } - SkRect rect; - if (!path.isRect(&rect)) { - return false; - } - + // TODO: we could handle blurred stroked circles if (!strokeRec.isFillStyle()) { return false; } SkScalar xformedSigma = this->computeXformedSigma(viewMatrix); - int pad = SkScalarCeilToInt(6*xformedSigma)/2; - rect.outset(SkIntToScalar(pad), SkIntToScalar(pad)); + SkAutoTUnref fp; + + SkRect rect; + if (path.isRect(&rect)) { + int pad = SkScalarCeilToInt(6*xformedSigma)/2; + rect.outset(SkIntToScalar(pad), SkIntToScalar(pad)); + + fp.reset(GrRectBlurEffect::Create(texProvider, rect, xformedSigma)); + } else if (path.isOval(&rect) && SkScalarNearlyEqual(rect.width(), rect.height())) { + fp.reset(GrCircleBlurFragmentProcessor::Create(texProvider, rect, xformedSigma)); + + // expand the rect for the coverage geometry + int pad = SkScalarCeilToInt(6*xformedSigma)/2; + rect.outset(SkIntToScalar(pad), SkIntToScalar(pad)); + } else { + return false; + } - SkAutoTUnref fp(GrRectBlurEffect::Create( - texProvider, rect, xformedSigma)); if (!fp) { return false; } @@ -870,10 +858,12 @@ bool SkBlurMaskFilterImpl::directFilterMaskGPU(GrTextureProvider* texProvider, return true; } +////////////////////////////////////////////////////////////////////////////// + class GrRRectBlurEffect : public GrFragmentProcessor { public: - static GrFragmentProcessor* Create(GrTextureProvider*, float sigma, const SkRRect&); + static const GrFragmentProcessor* Create(GrTextureProvider*, float sigma, const SkRRect&); virtual ~GrRRectBlurEffect() {}; const char* name() const override { return "GrRRectBlur"; } @@ -903,8 +893,12 @@ private: }; -GrFragmentProcessor* GrRRectBlurEffect::Create(GrTextureProvider* texProvider, float sigma, - const SkRRect& rrect) { +const GrFragmentProcessor* GrRRectBlurEffect::Create(GrTextureProvider* texProvider, float sigma, + const SkRRect& rrect) { + if (rrect.isCircle()) { + return GrCircleBlurFragmentProcessor::Create(texProvider, rrect.rect(), sigma); + } + if (!rrect.isSimpleCircular()) { return nullptr; } @@ -1129,8 +1123,8 @@ bool SkBlurMaskFilterImpl::directFilterRRectMaskGPU(GrTextureProvider* texProvid SkRect proxyRect = rrect.rect(); proxyRect.outset(extra, extra); - SkAutoTUnref fp(GrRRectBlurEffect::Create(texProvider, - xformedSigma, rrect)); + SkAutoTUnref fp(GrRRectBlurEffect::Create(texProvider, + xformedSigma, rrect)); if (!fp) { return false; } @@ -1146,7 +1140,7 @@ bool SkBlurMaskFilterImpl::directFilterRRectMaskGPU(GrTextureProvider* texProvid return true; } -bool SkBlurMaskFilterImpl::canFilterMaskGPU(const SkRect& srcBounds, +bool SkBlurMaskFilterImpl::canFilterMaskGPU(const SkRRect& devRRect, const SkIRect& clipBounds, const SkMatrix& ctm, SkRect* maskRect) const { @@ -1155,14 +1149,17 @@ bool SkBlurMaskFilterImpl::canFilterMaskGPU(const SkRect& srcBounds, return false; } - static const SkScalar kMIN_GPU_BLUR_SIZE = SkIntToScalar(64); - static const SkScalar kMIN_GPU_BLUR_SIGMA = SkIntToScalar(32); + // We always do circles on the GPU + if (!devRRect.isCircle()) { + static const SkScalar kMIN_GPU_BLUR_SIZE = SkIntToScalar(64); + static const SkScalar kMIN_GPU_BLUR_SIGMA = SkIntToScalar(32); - if (srcBounds.width() <= kMIN_GPU_BLUR_SIZE && - srcBounds.height() <= kMIN_GPU_BLUR_SIZE && - xformedSigma <= kMIN_GPU_BLUR_SIGMA) { - // We prefer to blur small rect with small radius via CPU. - return false; + if (devRRect.width() <= kMIN_GPU_BLUR_SIZE && + devRRect.height() <= kMIN_GPU_BLUR_SIZE && + xformedSigma <= kMIN_GPU_BLUR_SIGMA) { + // We prefer to blur small rects with small radii on the CPU. + return false; + } } if (nullptr == maskRect) { @@ -1173,7 +1170,7 @@ bool SkBlurMaskFilterImpl::canFilterMaskGPU(const SkRect& srcBounds, float sigma3 = 3 * SkScalarToFloat(xformedSigma); SkRect clipRect = SkRect::Make(clipBounds); - SkRect srcRect(srcBounds); + SkRect srcRect(devRRect.rect()); // Outset srcRect and clipRect by 3 * sigma, to compute affected blur area. srcRect.outset(sigma3, sigma3); diff --git a/src/gpu/GrBlurUtils.cpp b/src/gpu/GrBlurUtils.cpp index a96d92e764..d37f9bfc64 100644 --- a/src/gpu/GrBlurUtils.cpp +++ b/src/gpu/GrBlurUtils.cpp @@ -235,7 +235,7 @@ void GrBlurUtils::drawPathWithMaskFilter(GrContext* context, pathPtr->transform(viewMatrix, devPathPtr); SkRect maskRect; - if (paint.getMaskFilter()->canFilterMaskGPU(devPathPtr->getBounds(), + if (paint.getMaskFilter()->canFilterMaskGPU(SkRRect::MakeRect(devPathPtr->getBounds()), clipBounds, viewMatrix, &maskRect)) { diff --git a/src/gpu/GrProcessor.cpp b/src/gpu/GrProcessor.cpp index 782b82ba5c..b3544b03cb 100644 --- a/src/gpu/GrProcessor.cpp +++ b/src/gpu/GrProcessor.cpp @@ -50,7 +50,7 @@ GrProcessorTestFactory::GetFactories() { * we verify the count is as expected. If a new factory is added, then these numbers must be * manually adjusted. */ -static const int kFPFactoryCount = 39; +static const int kFPFactoryCount = 40; static const int kGPFactoryCount = 14; static const int kXPFactoryCount = 5; diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp index ea24c8d6d7..bba352569a 100644 --- a/src/gpu/SkGpuDevice.cpp +++ b/src/gpu/SkGpuDevice.cpp @@ -544,7 +544,7 @@ void SkGpuDevice::drawRRect(const SkDraw& draw, const SkRRect& rect, if (rect.transform(*draw.fMatrix, &devRRect)) { if (devRRect.allCornersCircular()) { SkRect maskRect; - if (paint.getMaskFilter()->canFilterMaskGPU(devRRect.rect(), + if (paint.getMaskFilter()->canFilterMaskGPU(devRRect, draw.fClip->getBounds(), *draw.fMatrix, &maskRect)) { @@ -637,7 +637,9 @@ void SkGpuDevice::drawOval(const SkDraw& draw, const SkRect& oval, bool usePath = false; // some basic reasons we might need to call drawPath... if (paint.getMaskFilter()) { - usePath = true; + // The RRect path can handle special case blurring + SkRRect rr = SkRRect::MakeOval(oval); + return this->drawRRect(draw, rr, paint); } else { const SkPathEffect* pe = paint.getPathEffect(); if (pe && !strokeInfo.isDashed()) { -- cgit v1.2.3