From 24e91ba3029a3a1fb60cab3ae4e68c3c44776f25 Mon Sep 17 00:00:00 2001 From: "commit-bot@chromium.org" Date: Mon, 28 Apr 2014 15:09:14 +0000 Subject: [PATCH] Fast path for blurred round rects -- blur a small 9patch rect on the CPU And nonlinearly stretch the resulting texture across proxy geometry. BUG= Committed: http://code.google.com/p/skia/source/detail?r=14392 R=bsalomon@google.com, reed@google.com Author: humper@google.com Review URL: https://codereview.chromium.org/248613004 git-svn-id: http://skia.googlecode.com/svn/trunk@14404 2bbb7eff-a529-9590-31e7-b0007b416f81 --- expectations/gm/ignored-tests.txt | 3 + gm/blurroundrect.cpp | 16 +- src/effects/SkBlurMask.cpp | 4 +- src/effects/SkBlurMask.h | 9 +- src/effects/SkBlurMaskFilter.cpp | 281 +++++++++++++++++++++++++- src/images/SkImageDecoder_libwebp.cpp | 12 -- 6 files changed, 300 insertions(+), 25 deletions(-) diff --git a/expectations/gm/ignored-tests.txt b/expectations/gm/ignored-tests.txt index 51f2c8bb5f..4e5eb97726 100644 --- a/expectations/gm/ignored-tests.txt +++ b/expectations/gm/ignored-tests.txt @@ -57,3 +57,6 @@ aaclip composeshader peekpixels +# humper: https://codereview.chromium.org/248613004/ +# Changed the test in a few ways, will need rebaselining. +simpleblurrrect diff --git a/gm/blurroundrect.cpp b/gm/blurroundrect.cpp index f688380f0e..08086aca91 100644 --- a/gm/blurroundrect.cpp +++ b/gm/blurroundrect.cpp @@ -106,30 +106,32 @@ protected: } virtual SkISize onISize() SK_OVERRIDE { - return SkISize::Make(750, 750); + return SkISize::Make(950, 950); } virtual void onDraw(SkCanvas* canvas) SK_OVERRIDE { canvas->scale(1.5f, 1.5f); + canvas->translate(50,50); - const int blurRadii[] = { 1, 3, 6, 10 }; - const int cornerRadii[] = { 1, 3, 6, 10 }; + const float blurRadii[] = { 1,5,10,20 }; + const int cornerRadii[] = { 1,5,10,20 }; const SkRect r = SkRect::MakeWH(SkIntToScalar(100), SkIntToScalar(100)); for (size_t i = 0; i < SK_ARRAY_COUNT(blurRadii); ++i) { SkAutoCanvasRestore autoRestore(canvas, true); - canvas->translate(0, (r.height() + SkIntToScalar(20)) * i); + canvas->translate(0, (r.height() + SkIntToScalar(50)) * i); for (size_t j = 0; j < SK_ARRAY_COUNT(cornerRadii); ++j) { SkMaskFilter* filter = SkBlurMaskFilter::Create( SkBlurMaskFilter::kNormal_BlurStyle, - SkBlurMask::ConvertRadiusToSigma(SkIntToScalar(blurRadii[i]))); + SkBlurMask::ConvertRadiusToSigma(SkIntToScalar(blurRadii[i])), + SkBlurMaskFilter::kHighQuality_BlurFlag); SkPaint paint; - paint.setColor(SK_ColorBLUE); + paint.setColor(SK_ColorBLACK); paint.setMaskFilter(filter)->unref(); SkRRect rrect; rrect.setRectXY(r, SkIntToScalar(cornerRadii[j]), SkIntToScalar(cornerRadii[j])); canvas->drawRRect(rrect, paint); - canvas->translate(r.width() + SkIntToScalar(10), 0); + canvas->translate(r.width() + SkIntToScalar(50), 0); } } } diff --git a/src/effects/SkBlurMask.cpp b/src/effects/SkBlurMask.cpp index 9712ecc475..f33817adee 100644 --- a/src/effects/SkBlurMask.cpp +++ b/src/effects/SkBlurMask.cpp @@ -477,14 +477,14 @@ void SkMask_FreeImage(uint8_t* image) { bool SkBlurMask::BoxBlur(SkMask* dst, const SkMask& src, SkScalar sigma, Style style, Quality quality, - SkIPoint* margin) { + SkIPoint* margin, bool force_quality) { if (src.fFormat != SkMask::kA8_Format) { return false; } // Force high quality off for small radii (performance) - if (sigma <= SkIntToScalar(2)) { + if (!force_quality && sigma <= SkIntToScalar(2)) { quality = kLow_Quality; } diff --git a/src/effects/SkBlurMask.h b/src/effects/SkBlurMask.h index eb67d4c9f8..d4cd3d1ea3 100644 --- a/src/effects/SkBlurMask.h +++ b/src/effects/SkBlurMask.h @@ -40,9 +40,16 @@ public: SkIPoint *margin = NULL, SkMask::CreateMode createMode = SkMask::kComputeBoundsAndRenderImage_CreateMode); + + // forceQuality will prevent BoxBlur from falling back to the low quality approach when sigma + // is very small -- this can be used predict the margin bump ahead of time without completely + // replicating the internal logic. This permits not only simpler caching of blurred results, + // but also being able to predict precisely at what pixels the blurred profile of e.g. a + // rectangle will lie. + static bool BoxBlur(SkMask* dst, const SkMask& src, SkScalar sigma, Style style, Quality quality, - SkIPoint* margin = NULL); + SkIPoint* margin = NULL, bool forceQuality = false); // the "ground truth" blur does a gaussian convolution; it's slow // but useful for comparison purposes. diff --git a/src/effects/SkBlurMaskFilter.cpp b/src/effects/SkBlurMaskFilter.cpp index ff98daf048..9f8935abe9 100644 --- a/src/effects/SkBlurMaskFilter.cpp +++ b/src/effects/SkBlurMaskFilter.cpp @@ -25,6 +25,7 @@ #include "effects/GrSimpleTextureEffect.h" #include "GrTBackendEffectFactory.h" #include "SkGrPixelRef.h" +#include "SkDraw.h" #endif class SkBlurMaskFilterImpl : public SkMaskFilter { @@ -260,9 +261,9 @@ static bool rect_exceeds(const SkRect& r, SkScalar v) { } #ifdef SK_IGNORE_FAST_RRECT_BLUR -SK_CONF_DECLARE( bool, c_analyticBlurRRect, "mask.filter.blur.analyticRRect", false, "Use the faster analytic blur approach for ninepatch rects" ); +SK_CONF_DECLARE( bool, c_analyticBlurRRect, "mask.filter.blur.analyticblurrrect", false, "Use the faster analytic blur approach for ninepatch rects" ); #else -SK_CONF_DECLARE( bool, c_analyticBlurRRect, "mask.filter.blur.analyticRRect", true, "Use the faster analytic blur approach for ninepatch round rects" ); +SK_CONF_DECLARE( bool, c_analyticBlurRRect, "mask.filter.blur.analyticblurrrect", true, "Use the faster analytic blur approach for ninepatch round rects" ); #endif SkMaskFilter::FilterReturn @@ -819,11 +820,285 @@ bool SkBlurMaskFilterImpl::directFilterMaskGPU(GrContext* context, return true; } +class GrGLRRectBlurEffect; + +class GrRRectBlurEffect : public GrEffect { +public: + + static GrEffectRef* Create(GrContext* context, float sigma, const SkRRect&); + + virtual ~GrRRectBlurEffect() {}; + static const char* Name() { return "GrRRectBlur"; } + + const SkRRect& getRRect() const { return fRRect; } + float getSigma() const { return fSigma; } + + typedef GrGLRRectBlurEffect GLEffect; + + virtual void getConstantColorComponents(GrColor* color, uint32_t* validFlags) const SK_OVERRIDE; + + virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE; + +private: + GrRRectBlurEffect(float sigma, const SkRRect&, GrTexture* profileTexture); + + virtual bool onIsEqual(const GrEffect& other) const SK_OVERRIDE; + + SkRRect fRRect; + float fSigma; + GrTextureAccess fNinePatchAccess; + + GR_DECLARE_EFFECT_TEST; + + typedef GrEffect INHERITED; +}; + + +GrEffectRef* GrRRectBlurEffect::Create(GrContext* context, float sigma, const SkRRect& rrect) { + if (!rrect.isSimpleCircular()) { + SkDebugf( "not simple circular\n" ); + return NULL; + } + + // Make sure we can successfully ninepatch this rrect -- the blur sigma has to be + // sufficiently small relative to both the size of the corner radius and the + // width (and height) of the rrect. + + unsigned int blurRadius = 3*SkScalarCeilToInt(sigma-1/6.0f); + unsigned int cornerRadius = SkScalarCeilToInt(rrect.getSimpleRadii().x()); + if (cornerRadius + blurRadius > rrect.width()/2 || + cornerRadius + blurRadius > rrect.height()/2) { + return NULL; + } + + static const GrCacheID::Domain gRRectBlurDomain = GrCacheID::GenerateDomain(); + GrCacheID::Key key; + memset(&key, 0, sizeof(key)); + key.fData32[0] = blurRadius; + key.fData32[1] = cornerRadius; + GrCacheID blurRRectNinePatchID(gRRectBlurDomain, key); + + GrTextureParams params; + params.setFilterMode(GrTextureParams::kBilerp_FilterMode); + + unsigned int smallRectSide = 2*(blurRadius + cornerRadius) + 1; + unsigned int texSide = smallRectSide + 2*blurRadius; + GrTextureDesc texDesc; + texDesc.fWidth = texSide; + texDesc.fHeight = texSide; + texDesc.fConfig = kAlpha_8_GrPixelConfig; + + GrTexture *blurNinePatchTexture = context->findAndRefTexture(texDesc, blurRRectNinePatchID, ¶ms); + + if (NULL == blurNinePatchTexture) { + SkMask mask; + + mask.fBounds = SkIRect::MakeWH(smallRectSide, smallRectSide); + mask.fFormat = SkMask::kA8_Format; + mask.fRowBytes = mask.fBounds.width(); + mask.fImage = SkMask::AllocImage(mask.computeTotalImageSize()); + SkAutoMaskFreeImage amfi(mask.fImage); + + memset(mask.fImage, 0, mask.computeTotalImageSize()); + + SkRect smallRect; + smallRect.setWH(SkIntToScalar(smallRectSide), SkIntToScalar(smallRectSide)); + + SkRRect smallRRect; + smallRRect.setRectXY(smallRect, SkIntToScalar(cornerRadius), SkIntToScalar(cornerRadius)); + + SkPath path; + path.addRRect( smallRRect ); + + SkDraw::DrawToMask(path, &mask.fBounds, NULL, NULL, &mask, SkMask::kJustRenderImage_CreateMode, SkPaint::kFill_Style); + + SkMask blurred_mask; + SkBlurMask::BoxBlur(&blurred_mask, mask, sigma, SkBlurMask::kNormal_Style, SkBlurMask::kHigh_Quality, NULL, true ); + + blurNinePatchTexture = context->createTexture(¶ms, texDesc, blurRRectNinePatchID, blurred_mask.fImage, 0); + } + + if (NULL == blurNinePatchTexture) { + return NULL; + } + + return CreateEffectRef(AutoEffectUnref(SkNEW_ARGS(GrRRectBlurEffect, + (sigma, rrect, blurNinePatchTexture)))); +} + +void GrRRectBlurEffect::getConstantColorComponents(GrColor* color, uint32_t* validFlags) const { + *validFlags = 0; +} + +const GrBackendEffectFactory& GrRRectBlurEffect::getFactory() const { + return GrTBackendEffectFactory::getInstance(); +} + +GrRRectBlurEffect::GrRRectBlurEffect(float sigma, const SkRRect& rrect, GrTexture *ninePatchTexture) + : fRRect(rrect), + fSigma(sigma), + fNinePatchAccess(ninePatchTexture) { + this->addTextureAccess(&fNinePatchAccess); + this->setWillReadFragmentPosition(); +} + +bool GrRRectBlurEffect::onIsEqual(const GrEffect& other) const { + const GrRRectBlurEffect& rrbe = CastEffect(other); + return fRRect.getSimpleRadii().fX == rrbe.fRRect.getSimpleRadii().fX && fSigma == rrbe.fSigma; +} + +////////////////////////////////////////////////////////////////////////////// + +GR_DEFINE_EFFECT_TEST(GrRRectBlurEffect); + +GrEffectRef* GrRRectBlurEffect::TestCreate(SkRandom* random, + GrContext* context, + const GrDrawTargetCaps& caps, + GrTexture*[]) { + SkScalar w = random->nextRangeScalar(100.f, 1000.f); + SkScalar h = random->nextRangeScalar(100.f, 1000.f); + SkScalar r = random->nextRangeF(1.f, 9.f); + SkScalar sigma = random->nextRangeF(1.f,20.f); + SkRRect rrect; + rrect.setRectXY(SkRect::MakeWH(w, h), r, r); + return GrRRectBlurEffect::Create(context, sigma, rrect); +} + +////////////////////////////////////////////////////////////////////////////// + +class GrGLRRectBlurEffect : public GrGLEffect { +public: + GrGLRRectBlurEffect(const GrBackendEffectFactory&, const GrDrawEffect&); + + virtual void emitCode(GrGLShaderBuilder* builder, + const GrDrawEffect& drawEffect, + EffectKey key, + const char* outputColor, + const char* inputColor, + const TransformedCoordsArray&, + const TextureSamplerArray&) SK_OVERRIDE; + + virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE; + +private: + GrGLUniformManager::UniformHandle fProxyRectUniform; + GrGLUniformManager::UniformHandle fCornerRadiusUniform; + GrGLUniformManager::UniformHandle fBlurRadiusUniform; + typedef GrGLEffect INHERITED; +}; + +GrGLRRectBlurEffect::GrGLRRectBlurEffect(const GrBackendEffectFactory& factory, + const GrDrawEffect& drawEffect) + : INHERITED (factory) { +} + +void GrGLRRectBlurEffect::emitCode(GrGLShaderBuilder* builder, + const GrDrawEffect& drawEffect, + EffectKey key, + const char* outputColor, + const char* inputColor, + const TransformedCoordsArray&, + const TextureSamplerArray& samplers) { + const char *rectName; + const char *cornerRadiusName; + const char *blurRadiusName; + + // The proxy rect has left, top, right, and bottom edges correspond to + // components x, y, z, and w, respectively. + + fProxyRectUniform = builder->addUniform(GrGLShaderBuilder::kFragment_Visibility, + kVec4f_GrSLType, + "proxyRect", + &rectName); + fCornerRadiusUniform = builder->addUniform(GrGLShaderBuilder::kFragment_Visibility, + kFloat_GrSLType, + "cornerRadius", + &cornerRadiusName); + fBlurRadiusUniform = builder->addUniform(GrGLShaderBuilder::kFragment_Visibility, + kFloat_GrSLType, + "blurRadius", + &blurRadiusName); + const char* fragmentPos = builder->fragmentPosition(); + + // warp the fragment position to the appropriate part of the 9patch blur texture + + builder->fsCodeAppendf("\t\tvec2 rectCenter = (%s.xy + %s.zw)/2.0;\n", rectName, rectName); + builder->fsCodeAppendf("\t\tvec2 translatedFragPos = %s.xy - %s.xy;\n", fragmentPos, rectName); + builder->fsCodeAppendf("\t\tfloat threshold = %s + 2.0*%s;\n", cornerRadiusName, blurRadiusName ); + builder->fsCodeAppendf("\t\tvec2 middle = %s.zw - %s.xy - 2.0*threshold;\n", rectName, rectName ); + + builder->fsCodeAppendf("\t\tif (translatedFragPos.x >= threshold && translatedFragPos.x < (middle.x+threshold)) {\n" ); + builder->fsCodeAppendf("\t\t\ttranslatedFragPos.x = threshold;\n"); + builder->fsCodeAppendf("\t\t} else if (translatedFragPos.x >= (middle.x + threshold)) {\n"); + builder->fsCodeAppendf("\t\t\ttranslatedFragPos.x -= middle.x;\n"); + builder->fsCodeAppendf("\t\t}\n"); + + builder->fsCodeAppendf("\t\tif (translatedFragPos.y > threshold && translatedFragPos.y < (middle.y+threshold)) {\n" ); + builder->fsCodeAppendf("\t\t\ttranslatedFragPos.y = threshold;\n"); + builder->fsCodeAppendf("\t\t} else if (translatedFragPos.y >= (middle.y + threshold)) {\n"); + builder->fsCodeAppendf("\t\t\ttranslatedFragPos.y -= middle.y;\n"); + builder->fsCodeAppendf("\t\t}\n"); + + builder->fsCodeAppendf("\t\tvec2 proxyDims = vec2(2.0*threshold+1.0);\n"); + builder->fsCodeAppendf("\t\tvec2 texCoord = translatedFragPos / proxyDims;\n"); + + builder->fsCodeAppendf("\t%s = ", outputColor); + builder->fsAppendTextureLookupAndModulate(inputColor, samplers[0], "texCoord"); + builder->fsCodeAppend(";\n"); +} + +void GrGLRRectBlurEffect::setData(const GrGLUniformManager& uman, + const GrDrawEffect& drawEffect) { + const GrRRectBlurEffect& brre = drawEffect.castEffect(); + SkRRect rrect = brre.getRRect(); + + float blurRadius = 3.f*SkScalarCeilToScalar(brre.getSigma()-1/6.0f); + uman.set1f(fBlurRadiusUniform, blurRadius); + + SkRect rect = rrect.getBounds(); + rect.outset(blurRadius, blurRadius); + uman.set4f(fProxyRectUniform, rect.fLeft, rect.fTop, rect.fRight, rect.fBottom); + + SkScalar radius = 0; + SkASSERT(rrect.isSimpleCircular() || rrect.isRect()); + radius = rrect.getSimpleRadii().fX; + uman.set1f(fCornerRadiusUniform, radius); +} + + bool SkBlurMaskFilterImpl::directFilterRRectMaskGPU(GrContext* context, GrPaint* grp, const SkStrokeRec& strokeRec, const SkRRect& rrect) const { - return false; + if (fBlurStyle != SkBlurMaskFilter::kNormal_BlurStyle) { + return false; + } + + if (!strokeRec.isFillStyle()) { + return false; + } + + SkRect proxy_rect = rrect.rect(); + SkMatrix ctm = context->getMatrix(); + SkScalar xformedSigma = this->computeXformedSigma(ctm); + float extra=3.f*SkScalarCeilToScalar(xformedSigma-1/6.0f); + proxy_rect.outset(extra, extra); + + SkAutoTUnref effect(GrRRectBlurEffect::Create( + context, xformedSigma, rrect)); + if (!effect) { + return false; + } + + GrContext::AutoMatrix am; + if (!am.setIdentity(context, grp)) { + return false; + } + + grp->addCoverageEffect(effect); + + context->drawRect(*grp, proxy_rect); + return true; } bool SkBlurMaskFilterImpl::canFilterMaskGPU(const SkRect& srcBounds, diff --git a/src/images/SkImageDecoder_libwebp.cpp b/src/images/SkImageDecoder_libwebp.cpp index 4e23e50f57..02990258eb 100644 --- a/src/images/SkImageDecoder_libwebp.cpp +++ b/src/images/SkImageDecoder_libwebp.cpp @@ -559,15 +559,6 @@ static void Index8_To_RGB(const uint8_t* in, uint8_t* rgb, int width, } } -static void Alpha8_To_RGB(const uint8_t* in, uint8_t* rgb, int width, - const SkPMColor* SK_RESTRICT ctable) { - const uint8_t* SK_RESTRICT src = (const uint8_t*)in; - for (int i = 0; i < width; ++i) { - rgb[0] = rgb[1] = rgb[2] = *src++; - rgb += 3; - } -} - static ScanlineImporter ChooseImporter(const SkBitmap::Config& config, bool hasAlpha, int* bpp) { @@ -594,9 +585,6 @@ static ScanlineImporter ChooseImporter(const SkBitmap::Config& config, case SkBitmap::kIndex8_Config: *bpp = 3; return Index8_To_RGB; - case SkBitmap::kA8_Config: - *bpp = 3; - return Alpha8_To_RGB; default: return NULL; }