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

Committed: http://code.google.com/p/skia/source/detail?r=14404

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@14420 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
commit-bot@chromium.org 2014-04-28 19:49:24 +00:00
parent 0974a618fe
commit 3d8bf232bd
6 changed files with 303 additions and 26 deletions

View File

@ -57,3 +57,6 @@ aaclip
composeshader composeshader
peekpixels peekpixels
# humper: https://codereview.chromium.org/248613004/
# Changed the test in a few ways, will need rebaselining.
simpleblurroundrect

View File

@ -106,30 +106,32 @@ protected:
} }
virtual SkISize onISize() SK_OVERRIDE { virtual SkISize onISize() SK_OVERRIDE {
return SkISize::Make(750, 750); return SkISize::Make(950, 950);
} }
virtual void onDraw(SkCanvas* canvas) SK_OVERRIDE { virtual void onDraw(SkCanvas* canvas) SK_OVERRIDE {
canvas->scale(1.5f, 1.5f); canvas->scale(1.5f, 1.5f);
canvas->translate(50,50);
const int blurRadii[] = { 1, 3, 6, 10 }; const float blurRadii[] = { 1,5,10,20 };
const int cornerRadii[] = { 1, 3, 6, 10 }; const int cornerRadii[] = { 1,5,10,20 };
const SkRect r = SkRect::MakeWH(SkIntToScalar(100), SkIntToScalar(100)); const SkRect r = SkRect::MakeWH(SkIntToScalar(100), SkIntToScalar(100));
for (size_t i = 0; i < SK_ARRAY_COUNT(blurRadii); ++i) { for (size_t i = 0; i < SK_ARRAY_COUNT(blurRadii); ++i) {
SkAutoCanvasRestore autoRestore(canvas, true); 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) { for (size_t j = 0; j < SK_ARRAY_COUNT(cornerRadii); ++j) {
SkMaskFilter* filter = SkBlurMaskFilter::Create( SkMaskFilter* filter = SkBlurMaskFilter::Create(
kNormal_SkBlurStyle, kNormal_SkBlurStyle,
SkBlurMask::ConvertRadiusToSigma(SkIntToScalar(blurRadii[i]))); SkBlurMask::ConvertRadiusToSigma(SkIntToScalar(blurRadii[i])),
SkBlurMaskFilter::kHighQuality_BlurFlag);
SkPaint paint; SkPaint paint;
paint.setColor(SK_ColorBLUE); paint.setColor(SK_ColorBLACK);
paint.setMaskFilter(filter)->unref(); paint.setMaskFilter(filter)->unref();
SkRRect rrect; SkRRect rrect;
rrect.setRectXY(r, SkIntToScalar(cornerRadii[j]), SkIntToScalar(cornerRadii[j])); rrect.setRectXY(r, SkIntToScalar(cornerRadii[j]), SkIntToScalar(cornerRadii[j]));
canvas->drawRRect(rrect, paint); canvas->drawRRect(rrect, paint);
canvas->translate(r.width() + SkIntToScalar(10), 0); canvas->translate(r.width() + SkIntToScalar(50), 0);
} }
} }
} }

View File

@ -475,14 +475,16 @@ void SkMask_FreeImage(uint8_t* image) {
SkMask::FreeImage(image); SkMask::FreeImage(image);
} }
bool SkBlurMask::BoxBlur(SkMask* dst, const SkMask& src, SkScalar sigma, SkBlurStyle style, bool SkBlurMask::BoxBlur(SkMask* dst, const SkMask& src,
SkBlurQuality quality, SkIPoint* margin) { SkScalar sigma, SkBlurStyle style, SkBlurQuality quality,
SkIPoint* margin, bool force_quality) {
if (src.fFormat != SkMask::kA8_Format) { if (src.fFormat != SkMask::kA8_Format) {
return false; return false;
} }
// Force high quality off for small radii (performance) // Force high quality off for small radii (performance)
if (sigma <= SkIntToScalar(2)) { if (!force_quality && sigma <= SkIntToScalar(2)) {
quality = kLow_SkBlurQuality; quality = kLow_SkBlurQuality;
} }

View File

@ -23,9 +23,16 @@ public:
SkIPoint *margin = NULL, SkIPoint *margin = NULL,
SkMask::CreateMode createMode = SkMask::CreateMode createMode =
SkMask::kComputeBoundsAndRenderImage_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, static bool BoxBlur(SkMask* dst, const SkMask& src,
SkScalar sigma, SkBlurStyle style, SkBlurQuality quality, SkScalar sigma, SkBlurStyle style, SkBlurQuality quality,
SkIPoint* margin = NULL); SkIPoint* margin = NULL, bool force_quality=false);
// the "ground truth" blur does a gaussian convolution; it's slow // the "ground truth" blur does a gaussian convolution; it's slow
// but useful for comparison purposes. // but useful for comparison purposes.

View File

@ -25,6 +25,7 @@
#include "effects/GrSimpleTextureEffect.h" #include "effects/GrSimpleTextureEffect.h"
#include "GrTBackendEffectFactory.h" #include "GrTBackendEffectFactory.h"
#include "SkGrPixelRef.h" #include "SkGrPixelRef.h"
#include "SkDraw.h"
#endif #endif
class SkBlurMaskFilterImpl : public SkMaskFilter { class SkBlurMaskFilterImpl : public SkMaskFilter {
@ -251,9 +252,9 @@ static bool rect_exceeds(const SkRect& r, SkScalar v) {
} }
#ifdef SK_IGNORE_FAST_RRECT_BLUR #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 #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 #endif
SkMaskFilter::FilterReturn SkMaskFilter::FilterReturn
@ -809,11 +810,285 @@ bool SkBlurMaskFilterImpl::directFilterMaskGPU(GrContext* context,
return true; 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, &params);
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, kNormal_SkBlurStyle, kHigh_SkBlurQuality, NULL, true );
blurNinePatchTexture = context->createTexture(&params, 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<GrRRectBlurEffect>::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<GrRRectBlurEffect>(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,10.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<GrRRectBlurEffect>();
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, bool SkBlurMaskFilterImpl::directFilterRRectMaskGPU(GrContext* context,
GrPaint* grp, GrPaint* grp,
const SkStrokeRec& strokeRec, const SkStrokeRec& strokeRec,
const SkRRect& rrect) const { const SkRRect& rrect) const {
return false; if (fBlurStyle != kNormal_SkBlurStyle) {
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<GrEffectRef> 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, bool SkBlurMaskFilterImpl::canFilterMaskGPU(const SkRect& srcBounds,

View File

@ -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, static ScanlineImporter ChooseImporter(const SkBitmap::Config& config,
bool hasAlpha, bool hasAlpha,
int* bpp) { int* bpp) {
@ -594,9 +585,6 @@ static ScanlineImporter ChooseImporter(const SkBitmap::Config& config,
case SkBitmap::kIndex8_Config: case SkBitmap::kIndex8_Config:
*bpp = 3; *bpp = 3;
return Index8_To_RGB; return Index8_To_RGB;
case SkBitmap::kA8_Config:
*bpp = 3;
return Alpha8_To_RGB;
default: default:
return NULL; return NULL;
} }