aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorrobertphillips <robertphillips@google.com>2015-09-15 10:20:55 -0700
committerCommit bot <commit-bot@chromium.org>2015-09-15 10:20:55 -0700
commit30c4cae7d3a26252e7e45adf6e5722b34adf6848 (patch)
tree51e48fb3326a2a4a76a762d932069357e3b7c56f
parentde5973b05be6ecc80309cded8541f9c305135d15 (diff)
downloadskia-30c4cae7d3a26252e7e45adf6e5722b34adf6848.tar.gz
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
-rw-r--r--gm/blurcircles.cpp32
-rw-r--r--gyp/effects.gypi3
-rw-r--r--include/core/SkColorPriv.h10
-rw-r--r--include/core/SkMaskFilter.h30
-rw-r--r--include/core/SkRRect.h13
-rw-r--r--include/core/SkScalar.h2
-rw-r--r--src/core/SkColor.cpp14
-rw-r--r--src/core/SkMaskFilter.cpp2
-rw-r--r--src/effects/GrCircleBlurFragmentProcessor.cpp259
-rw-r--r--src/effects/GrCircleBlurFragmentProcessor.h78
-rw-r--r--src/effects/SkBlurMask.cpp12
-rw-r--r--src/effects/SkBlurMask.h5
-rw-r--r--src/effects/SkBlurMaskFilter.cpp149
-rw-r--r--src/gpu/GrBlurUtils.cpp2
-rw-r--r--src/gpu/GrProcessor.cpp2
-rw-r--r--src/gpu/SkGpuDevice.cpp6
16 files changed, 494 insertions, 125 deletions
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<SkMaskFilter> 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<GrCircleBlurFragmentProcessor>();
+ 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<GrCircleBlurFragmentProcessor>();
+ 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<float> 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<uint8_t> 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<GrTexture> 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<GrCircleBlurFragmentProcessor>();
+ // 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<uint8_t> ada(profile);
+ SkAutoTDeleteArray<uint8_t> profile(ComputeBlurProfile(sigma));
size_t dstSize = dst->computeImageSize();
if (0 == dstSize) {
@@ -791,8 +789,8 @@ bool SkBlurMask::BlurRect(SkScalar sigma, SkMask *dst,
SkAutoTMalloc<uint8_t> horizontalScanline(dstWidth);
SkAutoTMalloc<uint8_t> 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<GrTexture> hunref(blurProfileTexture);
- if (!createdBlurProfileTexture) {
+ SkAutoTUnref<GrTexture> 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<GrRectBlurEffect>();
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<uint8_t> ada(nullptr);
-
- *blurProfileTexture = textureProvider->findAndRefTextureByUniqueKey(key);
+ GrTexture *blurProfile = textureProvider->findAndRefTextureByUniqueKey(key);
- if (nullptr == *blurProfileTexture) {
+ if (!blurProfile) {
+ SkAutoTDeleteArray<uint8_t> 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<GrRectBlurEffect>();
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<const GrFragmentProcessor> 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<GrFragmentProcessor> 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<GrFragmentProcessor> fp(GrRRectBlurEffect::Create(texProvider,
- xformedSigma, rrect));
+ SkAutoTUnref<const GrFragmentProcessor> 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<GrGeometryProcessor>::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()) {