diff options
-rw-r--r-- | bench/BlurBench.cpp | 8 | ||||
-rw-r--r-- | bench/BlurImageFilterBench.cpp | 3 | ||||
-rw-r--r-- | expectations/gm/ignored-tests.txt | 2 | ||||
-rw-r--r-- | gm/imageblur2.cpp | 96 | ||||
-rw-r--r-- | gyp/gmslides.gypi | 1 | ||||
-rw-r--r-- | src/effects/SkGpuBlurUtils.cpp | 129 | ||||
-rw-r--r-- | src/gpu/effects/GrMatrixConvolutionEffect.cpp | 77 | ||||
-rw-r--r-- | src/gpu/effects/GrMatrixConvolutionEffect.h | 14 |
8 files changed, 270 insertions, 60 deletions
diff --git a/bench/BlurBench.cpp b/bench/BlurBench.cpp index cafc6f3a2a..4133adc691 100644 --- a/bench/BlurBench.cpp +++ b/bench/BlurBench.cpp @@ -14,6 +14,7 @@ #include "SkShader.h" #include "SkString.h" +#define MINI 0.01f #define SMALL SkIntToScalar(2) #define REAL 1.5f #define BIG SkIntToScalar(10) @@ -78,6 +79,11 @@ private: typedef Benchmark INHERITED; }; +DEF_BENCH(return new BlurBench(MINI, kNormal_SkBlurStyle);) +DEF_BENCH(return new BlurBench(MINI, kSolid_SkBlurStyle);) +DEF_BENCH(return new BlurBench(MINI, kOuter_SkBlurStyle);) +DEF_BENCH(return new BlurBench(MINI, kInner_SkBlurStyle);) + DEF_BENCH(return new BlurBench(SMALL, kNormal_SkBlurStyle);) DEF_BENCH(return new BlurBench(SMALL, kSolid_SkBlurStyle);) DEF_BENCH(return new BlurBench(SMALL, kOuter_SkBlurStyle);) @@ -98,6 +104,8 @@ DEF_BENCH(return new BlurBench(REAL, kSolid_SkBlurStyle);) DEF_BENCH(return new BlurBench(REAL, kOuter_SkBlurStyle);) DEF_BENCH(return new BlurBench(REAL, kInner_SkBlurStyle);) +DEF_BENCH(return new BlurBench(MINI, kNormal_SkBlurStyle, SkBlurMaskFilter::kHighQuality_BlurFlag);) + DEF_BENCH(return new BlurBench(SMALL, kNormal_SkBlurStyle, SkBlurMaskFilter::kHighQuality_BlurFlag);) DEF_BENCH(return new BlurBench(BIG, kNormal_SkBlurStyle, SkBlurMaskFilter::kHighQuality_BlurFlag);) diff --git a/bench/BlurImageFilterBench.cpp b/bench/BlurImageFilterBench.cpp index 4e8688d9e3..9021d1b1d2 100644 --- a/bench/BlurImageFilterBench.cpp +++ b/bench/BlurImageFilterBench.cpp @@ -17,6 +17,7 @@ #define FILTER_HEIGHT_SMALL 32 #define FILTER_WIDTH_LARGE 256 #define FILTER_HEIGHT_LARGE 256 +#define BLUR_SIGMA_MINI 0.5f #define BLUR_SIGMA_SMALL 1.0f #define BLUR_SIGMA_LARGE 10.0f #define BLUR_SIGMA_HUGE 80.0f @@ -86,6 +87,8 @@ DEF_BENCH(return new BlurImageFilterBench(BLUR_SIGMA_LARGE, 0, false);) DEF_BENCH(return new BlurImageFilterBench(BLUR_SIGMA_SMALL, 0, false);) DEF_BENCH(return new BlurImageFilterBench(0, BLUR_SIGMA_LARGE, false);) DEF_BENCH(return new BlurImageFilterBench(0, BLUR_SIGMA_SMALL, false);) +DEF_BENCH(return new BlurImageFilterBench(BLUR_SIGMA_MINI, BLUR_SIGMA_MINI, true);) +DEF_BENCH(return new BlurImageFilterBench(BLUR_SIGMA_MINI, BLUR_SIGMA_MINI, false);) DEF_BENCH(return new BlurImageFilterBench(BLUR_SIGMA_SMALL, BLUR_SIGMA_SMALL, true);) DEF_BENCH(return new BlurImageFilterBench(BLUR_SIGMA_SMALL, BLUR_SIGMA_SMALL, false);) DEF_BENCH(return new BlurImageFilterBench(BLUR_SIGMA_LARGE, BLUR_SIGMA_LARGE, true);) diff --git a/expectations/gm/ignored-tests.txt b/expectations/gm/ignored-tests.txt index 226482c40d..241ce18649 100644 --- a/expectations/gm/ignored-tests.txt +++ b/expectations/gm/ignored-tests.txt @@ -59,3 +59,5 @@ circles simpleaaclip_rect complexclip2_rect_aa +#joshualitt +matrixconvolution
\ No newline at end of file diff --git a/gm/imageblur2.cpp b/gm/imageblur2.cpp new file mode 100644 index 0000000000..b7c9f9d9c8 --- /dev/null +++ b/gm/imageblur2.cpp @@ -0,0 +1,96 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "gm.h" +#include "SkBlurImageFilter.h" +#include "SkRandom.h" + +// TODO deprecate imageblur + +#define WIDTH 500 +#define HEIGHT 500 + +static const float kBlurSigmas[] = { + 0.0, 0.3f, 0.5f, 2.0f, 32.0f, 80.0f }; + +const char* kTestStrings[] = { + "The quick`~", + "brown fox[]", + "jumped over", + "the lazy@#$", + "dog.{}!%^&", + "*()+=-\\'\"/", +}; + +namespace skiagm { + +class BlurImageFilter : public GM { +public: + BlurImageFilter() { + this->setBGColor(0xFFFFFFFF); + fName.printf("imageblur2"); + } + +protected: + virtual uint32_t onGetFlags() const SK_OVERRIDE { + return kSkipTiled_Flag; + } + + virtual SkString onShortName() { + return fName; + } + + virtual SkISize onISize() { + return SkISize::Make(WIDTH, HEIGHT); + } + + virtual void onDraw(SkCanvas* canvas) { + const int sigmaCount = SK_ARRAY_COUNT(kBlurSigmas); + const int testStringCount = SK_ARRAY_COUNT(kTestStrings); + SkScalar dx = WIDTH / sigmaCount; + SkScalar dy = HEIGHT / sigmaCount; + const SkScalar textSize = 12; + + for (int x = 0; x < sigmaCount; x++) { + SkScalar sigmaX = kBlurSigmas[x]; + for (int y = 0; y < sigmaCount; y++) { + SkScalar sigmaY = kBlurSigmas[y]; + + SkPaint paint; + paint.setImageFilter(SkBlurImageFilter::Create(sigmaX, sigmaY))->unref(); + canvas->saveLayer(NULL, &paint); + + SkRandom rand; + SkPaint textPaint; + textPaint.setAntiAlias(false); + textPaint.setColor(rand.nextBits(24) | 0xFF000000); + textPaint.setTextSize(textSize); + + for (int i = 0; i < testStringCount; i++) { + canvas->drawText(kTestStrings[i], + strlen(kTestStrings[i]), + SkIntToScalar(x * dx), + SkIntToScalar(y * dy + textSize * i + textSize), + textPaint); + } + canvas->restore(); + } + } + } + +private: + SkString fName; + + typedef GM INHERITED; +}; + +////////////////////////////////////////////////////////////////////////////// + +static GM* MyFactory(void*) { return new BlurImageFilter; } +static GMRegistry reg(MyFactory); + +} diff --git a/gyp/gmslides.gypi b/gyp/gmslides.gypi index fbd2462531..e1cb1a6fd8 100644 --- a/gyp/gmslides.gypi +++ b/gyp/gmslides.gypi @@ -96,6 +96,7 @@ '../gm/hittestpath.cpp', '../gm/imagealphathreshold.cpp', '../gm/imageblur.cpp', + '../gm/imageblur2.cpp', '../gm/imageblurtiled.cpp', '../gm/imagemagnifier.cpp', '../gm/imageresizetiled.cpp', diff --git a/src/effects/SkGpuBlurUtils.cpp b/src/effects/SkGpuBlurUtils.cpp index 5888108755..aa7f9952f9 100644 --- a/src/effects/SkGpuBlurUtils.cpp +++ b/src/effects/SkGpuBlurUtils.cpp @@ -11,7 +11,7 @@ #if SK_SUPPORT_GPU #include "effects/GrConvolutionEffect.h" -#include "effects/GrTextureDomain.h" +#include "effects/GrMatrixConvolutionEffect.h" #include "GrContext.h" #endif @@ -43,15 +43,15 @@ static float adjust_sigma(float sigma, int maxTextureSize, int *scaleFactor, int return sigma; } -static void convolve_gaussian_pass(GrContext* context, - const SkRect& srcRect, - const SkRect& dstRect, - GrTexture* texture, - Gr1DKernelEffect::Direction direction, - int radius, - float sigma, - bool useBounds, - float bounds[2]) { +static void convolve_gaussian_1d(GrContext* context, + const SkRect& srcRect, + const SkRect& dstRect, + GrTexture* texture, + Gr1DKernelEffect::Direction direction, + int radius, + float sigma, + bool useBounds, + float bounds[2]) { GrPaint paint; paint.reset(); SkAutoTUnref<GrEffect> conv(GrConvolutionEffect::CreateGaussian( @@ -61,6 +61,29 @@ static void convolve_gaussian_pass(GrContext* context, context->drawRectToRect(paint, dstRect, srcRect); } +static void convolve_gaussian_2d(GrContext* context, + const SkRect& srcRect, + const SkRect& dstRect, + GrTexture* texture, + int radiusX, + int radiusY, + SkScalar sigmaX, + SkScalar sigmaY, + bool useBounds, + SkIRect bounds) { + SkISize size = SkISize::Make(2 * radiusX + 1, 2 * radiusY + 1); + SkIPoint kernelOffset = SkIPoint::Make(radiusX, radiusY); + GrPaint paint; + paint.reset(); + SkAutoTUnref<GrEffect> conv(GrMatrixConvolutionEffect::CreateGaussian( + texture, bounds, size, 1.0, 0.0, kernelOffset, + useBounds ? GrTextureDomain::kClamp_Mode : GrTextureDomain::kIgnore_Mode, + true, sigmaX, sigmaY)); + paint.reset(); + paint.addColorEffect(conv); + context->drawRectToRect(paint, dstRect, srcRect); +} + static void convolve_gaussian(GrContext* context, const SkRect& srcRect, const SkRect& dstRect, @@ -71,8 +94,8 @@ static void convolve_gaussian(GrContext* context, bool cropToSrcRect) { float bounds[2] = { 0.0f, 1.0f }; if (!cropToSrcRect) { - convolve_gaussian_pass(context, srcRect, dstRect, texture, - direction, radius, sigma, false, bounds); + convolve_gaussian_1d(context, srcRect, dstRect, texture, + direction, radius, sigma, false, bounds); return; } SkRect lowerSrcRect = srcRect, lowerDstRect = dstRect; @@ -103,16 +126,16 @@ static void convolve_gaussian(GrContext* context, } if (radius >= size * SK_ScalarHalf) { // Blur radius covers srcRect; use bounds over entire draw - convolve_gaussian_pass(context, srcRect, dstRect, texture, - direction, radius, sigma, true, bounds); + convolve_gaussian_1d(context, srcRect, dstRect, texture, + direction, radius, sigma, true, bounds); } else { // Draw upper and lower margins with bounds; middle without. - convolve_gaussian_pass(context, lowerSrcRect, lowerDstRect, texture, - direction, radius, sigma, true, bounds); - convolve_gaussian_pass(context, upperSrcRect, upperDstRect, texture, - direction, radius, sigma, true, bounds); - convolve_gaussian_pass(context, middleSrcRect, middleDstRect, texture, - direction, radius, sigma, false, bounds); + convolve_gaussian_1d(context, lowerSrcRect, lowerDstRect, texture, + direction, radius, sigma, true, bounds); + convolve_gaussian_1d(context, upperSrcRect, upperDstRect, texture, + direction, radius, sigma, true, bounds); + convolve_gaussian_1d(context, middleSrcRect, middleDstRect, texture, + direction, radius, sigma, false, bounds); } } @@ -196,39 +219,55 @@ GrTexture* GaussianBlur(GrContext* context, SkIRect srcIRect; srcRect.roundOut(&srcIRect); - if (sigmaX > 0.0f) { - if (scaleFactorX > 1) { - // Clear out a radius to the right of the srcRect to prevent the - // X convolution from reading garbage. - clearRect = SkIRect::MakeXYWH(srcIRect.fRight, srcIRect.fTop, - radiusX, srcIRect.height()); - context->clear(&clearRect, 0x0, false); - } + // For really small blurs(Certainly no wider than 5x5 on desktop gpus) it is faster to just + // launch a single non separable kernel vs two launches + if (sigmaX > 0.0f && sigmaY > 0 && + (2 * radiusX + 1) * (2 * radiusY + 1) <= MAX_KERNEL_SIZE) { + // We shouldn't be scaling because this is a small size blur + SkASSERT((scaleFactorX == scaleFactorY) == 1); context->setRenderTarget(dstTexture->asRenderTarget()); SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height()); - convolve_gaussian(context, srcRect, dstRect, srcTexture, - Gr1DKernelEffect::kX_Direction, radiusX, sigmaX, cropToRect); + convolve_gaussian_2d(context, srcRect, dstRect, srcTexture, + radiusX, radiusY, sigmaX, sigmaY, cropToRect, srcIRect); srcTexture = dstTexture; srcRect = dstRect; SkTSwap(dstTexture, tempTexture); - } - if (sigmaY > 0.0f) { - if (scaleFactorY > 1 || sigmaX > 0.0f) { - // Clear out a radius below the srcRect to prevent the Y - // convolution from reading garbage. - clearRect = SkIRect::MakeXYWH(srcIRect.fLeft, srcIRect.fBottom, - srcIRect.width(), radiusY); - context->clear(&clearRect, 0x0, false); + } else { + if (sigmaX > 0.0f) { + if (scaleFactorX > 1) { + // Clear out a radius to the right of the srcRect to prevent the + // X convolution from reading garbage. + clearRect = SkIRect::MakeXYWH(srcIRect.fRight, srcIRect.fTop, + radiusX, srcIRect.height()); + context->clear(&clearRect, 0x0, false); + } + context->setRenderTarget(dstTexture->asRenderTarget()); + SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height()); + convolve_gaussian(context, srcRect, dstRect, srcTexture, + Gr1DKernelEffect::kX_Direction, radiusX, sigmaX, cropToRect); + srcTexture = dstTexture; + srcRect = dstRect; + SkTSwap(dstTexture, tempTexture); } - context->setRenderTarget(dstTexture->asRenderTarget()); - SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height()); - convolve_gaussian(context, srcRect, dstRect, srcTexture, - Gr1DKernelEffect::kY_Direction, radiusY, sigmaY, cropToRect); - srcTexture = dstTexture; - srcRect = dstRect; - SkTSwap(dstTexture, tempTexture); + if (sigmaY > 0.0f) { + if (scaleFactorY > 1 || sigmaX > 0.0f) { + // Clear out a radius below the srcRect to prevent the Y + // convolution from reading garbage. + clearRect = SkIRect::MakeXYWH(srcIRect.fLeft, srcIRect.fBottom, + srcIRect.width(), radiusY); + context->clear(&clearRect, 0x0, false); + } + + context->setRenderTarget(dstTexture->asRenderTarget()); + SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height()); + convolve_gaussian(context, srcRect, dstRect, srcTexture, + Gr1DKernelEffect::kY_Direction, radiusY, sigmaY, cropToRect); + srcTexture = dstTexture; + srcRect = dstRect; + SkTSwap(dstTexture, tempTexture); + } } if (scaleFactorX > 1 || scaleFactorY > 1) { diff --git a/src/gpu/effects/GrMatrixConvolutionEffect.cpp b/src/gpu/effects/GrMatrixConvolutionEffect.cpp index 242aba8f86..01cf944592 100644 --- a/src/gpu/effects/GrMatrixConvolutionEffect.cpp +++ b/src/gpu/effects/GrMatrixConvolutionEffect.cpp @@ -84,34 +84,38 @@ void GrGLMatrixConvolutionEffect::emitCode(GrGLShaderBuilder* builder, int kWidth = fKernelSize.width(); int kHeight = fKernelSize.height(); - builder->fsCodeAppend("\t\tvec4 sum = vec4(0, 0, 0, 0);\n"); - builder->fsCodeAppendf("\t\tvec2 coord = %s - %s * %s;\n", coords2D.c_str(), kernelOffset, + builder->fsCodeAppend("vec4 sum = vec4(0, 0, 0, 0);"); + builder->fsCodeAppendf("vec2 coord = %s - %s * %s;", coords2D.c_str(), kernelOffset, imgInc); - builder->fsCodeAppend("\t\tvec4 c;\n"); + builder->fsCodeAppend("vec4 c;"); for (int y = 0; y < kHeight; y++) { for (int x = 0; x < kWidth; x++) { GrGLShaderBuilder::FSBlock block(builder); - builder->fsCodeAppendf("\t\tfloat k = %s[%d * %d + %d];\n", kernel, y, kWidth, x); + builder->fsCodeAppendf("float k = %s[%d * %d + %d];", kernel, y, kWidth, x); SkString coord; coord.printf("coord + vec2(%d, %d) * %s", x, y, imgInc); fDomain.sampleTexture(builder, domain, "c", coord, samplers[0]); if (!fConvolveAlpha) { - builder->fsCodeAppend("\t\tc.rgb /= c.a;\n"); + builder->fsCodeAppend("c.rgb /= c.a;"); } - builder->fsCodeAppend("\t\tsum += c * k;\n"); + builder->fsCodeAppend("sum += c * k;"); } } if (fConvolveAlpha) { - builder->fsCodeAppendf("\t\t%s = sum * %s + %s;\n", outputColor, gain, bias); - builder->fsCodeAppendf("\t\t%s.rgb = clamp(%s.rgb, 0.0, %s.a);\n", + builder->fsCodeAppendf("%s = sum * %s + %s;", outputColor, gain, bias); + builder->fsCodeAppendf("%s.rgb = clamp(%s.rgb, 0.0, %s.a);", outputColor, outputColor, outputColor); } else { fDomain.sampleTexture(builder, domain, "c", coords2D, samplers[0]); - builder->fsCodeAppendf("\t\t%s.a = c.a;\n", outputColor); - builder->fsCodeAppendf("\t\t%s.rgb = sum.rgb * %s + %s;\n", outputColor, gain, bias); - builder->fsCodeAppendf("\t\t%s.rgb *= %s.a;\n", outputColor, outputColor); + builder->fsCodeAppendf("%s.a = c.a;", outputColor); + builder->fsCodeAppendf("%s.rgb = sum.rgb * %s + %s;", outputColor, gain, bias); + builder->fsCodeAppendf("%s.rgb *= %s.a;", outputColor, outputColor); } + + SkString modulate; + GrGLSLMulVarBy4f(&modulate, 2, outputColor, inputColor); + builder->fsCodeAppend(modulate.c_str()); } void GrGLMatrixConvolutionEffect::GenKey(const GrDrawEffect& drawEffect, @@ -157,17 +161,14 @@ GrMatrixConvolutionEffect::GrMatrixConvolutionEffect(GrTexture* texture, fBias(SkScalarToFloat(bias) / 255.0f), fConvolveAlpha(convolveAlpha), fDomain(GrTextureDomain::MakeTexelDomain(texture, bounds), tileMode) { - fKernel = new float[kernelSize.width() * kernelSize.height()]; for (int i = 0; i < kernelSize.width() * kernelSize.height(); i++) { fKernel[i] = SkScalarToFloat(kernel[i]); } fKernelOffset[0] = static_cast<float>(kernelOffset.x()); fKernelOffset[1] = static_cast<float>(kernelOffset.y()); - this->setWillNotUseInputColor(); } GrMatrixConvolutionEffect::~GrMatrixConvolutionEffect() { - delete[] fKernel; } const GrBackendEffectFactory& GrMatrixConvolutionEffect::getFactory() const { @@ -187,6 +188,54 @@ bool GrMatrixConvolutionEffect::onIsEqual(const GrEffect& sBase) const { fDomain == s.domain(); } +// Static function to create a 2D convolution +GrEffect* GrMatrixConvolutionEffect::CreateGaussian(GrTexture* texture, + const SkIRect& bounds, + const SkISize& kernelSize, + SkScalar gain, + SkScalar bias, + const SkIPoint& kernelOffset, + GrTextureDomain::Mode tileMode, + bool convolveAlpha, + SkScalar sigmaX, + SkScalar sigmaY) { + float kernel[MAX_KERNEL_SIZE]; + int width = kernelSize.width(); + int height = kernelSize.height(); + SkASSERT(width * height <= MAX_KERNEL_SIZE); + float sum = 0.0f; + float sigmaXDenom = 1.0f / (2.0f * SkScalarToFloat(SkScalarSquare(sigmaX))); + float sigmaYDenom = 1.0f / (2.0f * SkScalarToFloat(SkScalarSquare(sigmaY))); + int xRadius = width / 2; + int yRadius = height / 2; + for (int x = 0; x < width; x++) { + float xTerm = static_cast<float>(x - xRadius); + xTerm = xTerm * xTerm * sigmaXDenom; + for (int y = 0; y < height; y++) { + float yTerm = static_cast<float>(y - yRadius); + float xyTerm = sk_float_exp(-(xTerm + yTerm * yTerm * sigmaYDenom)); + // Note that the constant term (1/(sqrt(2*pi*sigma^2)) of the Gaussian + // is dropped here, since we renormalize the kernel below. + kernel[y * width + x] = xyTerm; + sum += xyTerm; + } + } + // Normalize the kernel + float scale = 1.0f / sum; + for (int i = 0; i < width * height; ++i) { + kernel[i] *= scale; + } + return SkNEW_ARGS(GrMatrixConvolutionEffect, (texture, + bounds, + kernelSize, + kernel, + gain, + bias, + kernelOffset, + tileMode, + convolveAlpha)); +} + GR_DEFINE_EFFECT_TEST(GrMatrixConvolutionEffect); GrEffect* GrMatrixConvolutionEffect::TestCreate(SkRandom* random, diff --git a/src/gpu/effects/GrMatrixConvolutionEffect.h b/src/gpu/effects/GrMatrixConvolutionEffect.h index 24c3bdb285..814299f2d1 100644 --- a/src/gpu/effects/GrMatrixConvolutionEffect.h +++ b/src/gpu/effects/GrMatrixConvolutionEffect.h @@ -38,6 +38,18 @@ public: tileMode, convolveAlpha)); } + + static GrEffect* CreateGaussian(GrTexture* texture, + const SkIRect& bounds, + const SkISize& kernelSize, + SkScalar gain, + SkScalar bias, + const SkIPoint& kernelOffset, + GrTextureDomain::Mode tileMode, + bool convolveAlpha, + SkScalar sigmaX, + SkScalar sigmaY); + virtual ~GrMatrixConvolutionEffect(); virtual void getConstantColorComponents(GrColor* color, @@ -75,7 +87,7 @@ private: SkIRect fBounds; SkISize fKernelSize; - float* fKernel; + float fKernel[MAX_KERNEL_SIZE]; float fGain; float fBias; float fKernelOffset[2]; |