Perform the same analytic blur calculation on the GPU that we do on the CPU. Results in significant performance gains when using Ganesh to render drop shadows in Chrome.

BUG=
R=bsalomon@google.com, reed@google.com

Author: humper@google.com

Review URL: https://codereview.chromium.org/119343003

git-svn-id: http://skia.googlecode.com/svn/trunk@13210 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
commit-bot@chromium.org 2014-01-27 22:41:45 +00:00
parent 01260b2dbc
commit d1829151a5
7 changed files with 374 additions and 36 deletions

View File

@ -16,7 +16,6 @@
'<(skia_src_path)/effects/SkBitmapSource.cpp',
'<(skia_src_path)/effects/SkBlurDrawLooper.cpp',
'<(skia_src_path)/effects/SkBlurMask.cpp',
'<(skia_src_path)/effects/SkBlurMask.h',
'<(skia_src_path)/effects/SkBlurImageFilter.cpp',
'<(skia_src_path)/effects/SkBlurMaskFilter.cpp',
'<(skia_src_path)/effects/SkColorFilters.cpp',
@ -88,6 +87,7 @@
'<(skia_include_path)/effects/SkBitmapSource.h',
'<(skia_include_path)/effects/SkBlurDrawLooper.h',
'<(skia_include_path)/effects/SkBlurImageFilter.h',
'<(skia_include_path)/effects/SkBlurMask.h',
'<(skia_include_path)/effects/SkBlurMaskFilter.h',
'<(skia_include_path)/effects/SkColorMatrix.h',
'<(skia_include_path)/effects/SkColorMatrixFilter.h',

View File

@ -15,6 +15,7 @@
#include "SkPaint.h"
class GrContext;
class GrPaint;
class SkBitmap;
class SkBlitter;
class SkBounder;
@ -22,6 +23,7 @@ class SkMatrix;
class SkPath;
class SkRasterClip;
class SkRRect;
class SkStrokeRec;
/** \class SkMaskFilter
@ -93,6 +95,15 @@ public:
const SkMatrix& ctm,
SkRect* maskRect) const;
/**
* Try to directly render the mask filter into the target. Returns
* true if drawing was successful.
*/
virtual bool directFilterMaskGPU(GrContext* context,
GrPaint* grp,
const SkStrokeRec& strokeRec,
const SkPath& path) const;
/**
* This function is used to implement filters that require an explicit src mask. It should only
* be called if canFilterMaskGPU returned true and the maskRect param should be the output from

View File

@ -309,6 +309,13 @@ bool SkMaskFilter::canFilterMaskGPU(const SkRect& devBounds,
return false;
}
bool SkMaskFilter::directFilterMaskGPU(GrContext* context,
GrPaint* grp,
const SkStrokeRec& strokeRec,
const SkPath& path) const {
return false;
}
bool SkMaskFilter::filterMaskGPU(GrTexture* src,
const SkMatrix& ctm,

View File

@ -671,7 +671,7 @@ static float gaussianIntegral(float x) {
return 0.4375f + (-x3 / 6.0f - 3.0f * x2 * 0.25f - 1.125f * x);
}
/* compute_profile allocates and fills in an array of floating
/* ComputeBlurProfile allocates and fills in an array of floating
point values between 0 and 255 for the profile signature of
a blurred half-plane with the given blur radius. Since we're
going to be doing screened multiplications (i.e., 1 - (1-x)(1-y))
@ -682,11 +682,11 @@ static float gaussianIntegral(float x) {
memory returned in profile_out.
*/
static void compute_profile(SkScalar sigma, unsigned int **profile_out) {
void SkBlurMask::ComputeBlurProfile(SkScalar sigma, uint8_t **profile_out) {
int size = SkScalarCeilToInt(6*sigma);
int center = size >> 1;
unsigned int *profile = SkNEW_ARRAY(unsigned int, size);
uint8_t *profile = SkNEW_ARRAY(uint8_t, size);
float invr = 1.f/(2*sigma);
@ -707,7 +707,7 @@ static void compute_profile(SkScalar sigma, unsigned int **profile_out) {
// Implementation adapted from Michael Herf's approach:
// http://stereopsis.com/shadowrect/
static inline unsigned int profile_lookup( unsigned int *profile, int loc, int blurred_width, int sharp_width ) {
uint8_t SkBlurMask::ProfileLookup(const uint8_t *profile, int loc, int blurred_width, int sharp_width) {
int dx = SkAbs32(((loc << 1) + 1) - blurred_width) - sharp_width; // how far are we from the original edge?
int ox = dx >> 1;
if (ox < 0) {
@ -717,6 +717,30 @@ static inline unsigned int profile_lookup( unsigned int *profile, int loc, int b
return profile[ox];
}
void SkBlurMask::ComputeBlurredScanline(uint8_t *pixels, const uint8_t *profile,
unsigned int width, SkScalar sigma) {
unsigned int profile_size = SkScalarCeilToInt(6*sigma);
SkAutoTMalloc<uint8_t> horizontalScanline(width);
unsigned int sw = width - profile_size;
// nearest odd number less than the profile size represents the center
// of the (2x scaled) profile
int center = ( profile_size & ~1 ) - 1;
int w = sw - center;
for (unsigned int x = 0 ; x < width ; ++x) {
if (profile_size <= sw) {
pixels[x] = ProfileLookup(profile, x, width, w);
} else {
float span = float(sw)/(2*sigma);
float giX = 1.5f - (x+.5f)/(2*sigma);
pixels[x] = (uint8_t) (255 * (gaussianIntegral(giX) - gaussianIntegral(giX + span)));
}
}
}
bool SkBlurMask::BlurRect(SkMask *dst, const SkRect &src,
SkScalar radius, Style style,
SkIPoint *margin, SkMask::CreateMode createMode) {
@ -757,10 +781,10 @@ bool SkBlurMask::BlurRect(SkScalar sigma, SkMask *dst,
}
return true;
}
unsigned int *profile = NULL;
uint8_t *profile = NULL;
compute_profile(sigma, &profile);
SkAutoTDeleteArray<unsigned int> ada(profile);
ComputeBlurProfile(sigma, &profile);
SkAutoTDeleteArray<uint8_t> ada(profile);
size_t dstSize = dst->computeImageSize();
if (0 == dstSize) {
@ -774,39 +798,17 @@ bool SkBlurMask::BlurRect(SkScalar sigma, SkMask *dst,
int dstHeight = dst->fBounds.height();
int dstWidth = dst->fBounds.width();
// nearest odd number less than the profile size represents the center
// of the (2x scaled) profile
int center = ( profile_size & ~1 ) - 1;
int w = sw - center;
int h = sh - center;
uint8_t *outptr = dp;
SkAutoTMalloc<uint8_t> horizontalScanline(dstWidth);
SkAutoTMalloc<uint8_t> verticalScanline(dstHeight);
for (int x = 0 ; x < dstWidth ; ++x) {
if (profile_size <= sw) {
horizontalScanline[x] = profile_lookup(profile, x, dstWidth, w);
} else {
float span = float(sw)/(2*sigma);
float giX = 1.5f - (x+.5f)/(2*sigma);
horizontalScanline[x] = (uint8_t) (255 * (gaussianIntegral(giX) - gaussianIntegral(giX + span)));
}
}
ComputeBlurredScanline(horizontalScanline, profile, dstWidth, sigma);
ComputeBlurredScanline(verticalScanline, profile, dstHeight, sigma);
for (int y = 0 ; y < dstHeight ; ++y) {
unsigned int profile_y;
if (profile_size <= sh) {
profile_y = profile_lookup(profile, y, dstHeight, h);
} else {
float span = float(sh)/(2*sigma);
float giY = 1.5f - (y+.5f)/(2*sigma);
profile_y = (uint8_t) (255 * (gaussianIntegral(giY) - gaussianIntegral(giY + span)));
}
for (int x = 0 ; x < dstWidth ; x++) {
unsigned int maskval = SkMulDiv255Round(horizontalScanline[x], profile_y);
unsigned int maskval = SkMulDiv255Round(horizontalScanline[x], verticalScanline[y]);
*(outptr++) = maskval;
}
}

View File

@ -62,6 +62,41 @@ public:
SkIPoint* margin = NULL);
static SkScalar ConvertRadiusToSigma(SkScalar radius);
/* Helper functions for analytic rectangle blurs */
/** Look up the intensity of the (one dimnensional) blurred half-plane.
@param profile The precomputed 1D blur profile; memory allocated by and managed by
ComputeBlurProfile below.
@param loc the location to look up; The lookup will clamp invalid inputs, but
meaningful data are available between 0 and blurred_width
@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);
/** 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);
/** 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.
@param pixels Location to store the resulting pixel data; allocated and managed by caller
@param profile Precomputed blur profile computed by ComputeBlurProfile above.
@param width Size of the pixels array.
@param sigma Standard deviation of the gaussian blur kernel used to compute the profile;
this implicitly gives the size of the pixels array.
*/
static void ComputeBlurredScanline(uint8_t* pixels, const uint8_t* profile,
unsigned int width, SkScalar sigma);
};
#endif

View File

@ -19,7 +19,10 @@
#if SK_SUPPORT_GPU
#include "GrContext.h"
#include "GrTexture.h"
#include "GrEffect.h"
#include "gl/GrGLEffect.h"
#include "effects/GrSimpleTextureEffect.h"
#include "GrTBackendEffectFactory.h"
#include "SkGrPixelRef.h"
#endif
@ -37,6 +40,11 @@ public:
const SkIRect& clipBounds,
const SkMatrix& ctm,
SkRect* maskRect) const SK_OVERRIDE;
virtual bool directFilterMaskGPU(GrContext* context,
GrPaint* grp,
const SkStrokeRec& strokeRec,
const SkPath& path) const SK_OVERRIDE;
virtual bool filterMaskGPU(GrTexture* src,
const SkMatrix& ctm,
const SkRect& maskRect,
@ -500,6 +508,274 @@ void SkBlurMaskFilterImpl::flatten(SkFlattenableWriteBuffer& buffer) const {
#if SK_SUPPORT_GPU
class GrGLRectBlurEffect;
class GrRectBlurEffect : public GrEffect {
public:
virtual ~GrRectBlurEffect();
static const char* Name() { return "RectBlur"; }
typedef GrGLRectBlurEffect GLEffect;
virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
virtual void getConstantColorComponents(GrColor* color, uint32_t* validFlags) const SK_OVERRIDE;
/**
* Create a simple filter effect with custom bicubic coefficients.
*/
static GrEffectRef* Create(GrContext *context, const SkRect& rect,
float sigma) {
GrTexture *horizontalScanline, *verticalScanline;
bool createdScanlines = CreateScanlineTextures(context, sigma,
SkScalarCeilToInt(rect.width()),
SkScalarCeilToInt(rect.height()),
&horizontalScanline, &verticalScanline);
if (!createdScanlines) {
return NULL;
}
AutoEffectUnref effect(SkNEW_ARGS(GrRectBlurEffect, (rect, sigma,
horizontalScanline, verticalScanline)));
return CreateEffectRef(effect);
}
unsigned int getWidth() const { return fWidth; }
unsigned int getHeight() const { return fHeight; }
float getSigma() const { return fSigma; }
private:
GrRectBlurEffect(const SkRect& rect, float sigma,
GrTexture *horizontal_scanline, GrTexture *vertical_scanline);
virtual bool onIsEqual(const GrEffect&) const SK_OVERRIDE;
static bool CreateScanlineTextures(GrContext *context, float sigma,
unsigned int width, unsigned int height,
GrTexture **horizontalScanline,
GrTexture **verticalScanline);
unsigned int fWidth, fHeight;
float fSigma;
GrTextureAccess fHorizontalScanlineAccess;
GrTextureAccess fVerticalScanlineAccess;
GrCoordTransform fTransform;
GR_DECLARE_EFFECT_TEST;
typedef GrEffect INHERITED;
};
class GrGLRectBlurEffect : public GrGLEffect {
public:
GrGLRectBlurEffect(const GrBackendEffectFactory& factory,
const GrDrawEffect&);
virtual void emitCode(GrGLShaderBuilder*,
const GrDrawEffect&,
EffectKey,
const char* outputColor,
const char* inputColor,
const TransformedCoordsArray&,
const TextureSamplerArray&) SK_OVERRIDE;
virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
private:
typedef GrGLUniformManager::UniformHandle UniformHandle;
UniformHandle fWidthUni;
UniformHandle fHeightUni;
typedef GrGLEffect INHERITED;
};
GrGLRectBlurEffect::GrGLRectBlurEffect(const GrBackendEffectFactory& factory, const GrDrawEffect&)
: INHERITED(factory) {
}
void GrGLRectBlurEffect::emitCode(GrGLShaderBuilder* builder,
const GrDrawEffect&,
EffectKey key,
const char* outputColor,
const char* inputColor,
const TransformedCoordsArray& coords,
const TextureSamplerArray& samplers) {
SkString texture_coords = builder->ensureFSCoords2D(coords, 0);
if (inputColor) {
builder->fsCodeAppendf("\tvec4 src=%s;\n", inputColor);
} else {
builder->fsCodeAppendf("\tvec4 src=vec4(1)\n;");
}
builder->fsCodeAppendf("\tvec4 horiz = ");
builder->fsAppendTextureLookup( samplers[0], texture_coords.c_str() );
builder->fsCodeAppendf(";\n");
builder->fsCodeAppendf("\tvec4 vert = ");
builder->fsAppendTextureLookup( samplers[1], texture_coords.c_str() );
builder->fsCodeAppendf(";\n");
builder->fsCodeAppendf("\tfloat final = (horiz*vert).r;\n");
builder->fsCodeAppendf("\t%s = final*src;\n", outputColor);
}
void GrGLRectBlurEffect::setData(const GrGLUniformManager& uman,
const GrDrawEffect& drawEffect) {
}
bool GrRectBlurEffect::CreateScanlineTextures(GrContext *context, float sigma,
unsigned int width, unsigned int height,
GrTexture **horizontalScanline,
GrTexture **verticalScanline) {
GrTextureParams params;
GrTextureDesc texDesc;
unsigned int profile_size = SkScalarFloorToInt(6*sigma);
texDesc.fWidth = width;
texDesc.fHeight = 1;
texDesc.fConfig = kAlpha_8_GrPixelConfig;
static const GrCacheID::Domain gBlurProfileDomain = GrCacheID::GenerateDomain();
GrCacheID::Key key;
memset(&key, 0, sizeof(key));
key.fData32[0] = profile_size;
key.fData32[1] = width;
key.fData32[2] = 1;
GrCacheID horizontalCacheID(gBlurProfileDomain, key);
uint8_t *profile = NULL;
SkAutoTDeleteArray<uint8_t> ada(profile);
*horizontalScanline = context->findAndRefTexture(texDesc, horizontalCacheID, &params);
if (NULL == *horizontalScanline) {
SkBlurMask::ComputeBlurProfile(sigma, &profile);
SkAutoTMalloc<uint8_t> horizontalPixels(width);
SkBlurMask::ComputeBlurredScanline(horizontalPixels, profile, width, sigma);
*horizontalScanline = context->createTexture(&params, texDesc, horizontalCacheID,
horizontalPixels, 0);
if (NULL == *horizontalScanline) {
return false;
}
}
texDesc.fWidth = 1;
texDesc.fHeight = height;
key.fData32[1] = 1;
key.fData32[2] = height;
GrCacheID verticalCacheID(gBlurProfileDomain, key);
*verticalScanline = context->findAndRefTexture(texDesc, verticalCacheID, &params);
if (NULL == *verticalScanline) {
if (NULL == profile) {
SkBlurMask::ComputeBlurProfile(sigma, &profile);
}
SkAutoTMalloc<uint8_t> verticalPixels(height);
SkBlurMask::ComputeBlurredScanline(verticalPixels, profile, height, sigma);
*verticalScanline = context->createTexture(&params, texDesc, verticalCacheID,
verticalPixels, 0);
if (NULL == *verticalScanline) {
return false;
}
}
return true;
}
GrRectBlurEffect::GrRectBlurEffect(const SkRect& rect, float sigma,
GrTexture *horizontal_scanline, GrTexture *vertical_scanline)
: INHERITED(),
fWidth(horizontal_scanline->width()),
fHeight(vertical_scanline->width()),
fSigma(sigma),
fHorizontalScanlineAccess(horizontal_scanline),
fVerticalScanlineAccess(vertical_scanline) {
SkMatrix mat;
mat.setRectToRect(rect, SkRect::MakeWH(1,1), SkMatrix::kFill_ScaleToFit);
fTransform = GrCoordTransform(kLocal_GrCoordSet, mat);
this->addTextureAccess(&fHorizontalScanlineAccess);
this->addTextureAccess(&fVerticalScanlineAccess);
this->addCoordTransform(&fTransform);
}
GrRectBlurEffect::~GrRectBlurEffect() {
}
const GrBackendEffectFactory& GrRectBlurEffect::getFactory() const {
return GrTBackendEffectFactory<GrRectBlurEffect>::getInstance();
}
bool GrRectBlurEffect::onIsEqual(const GrEffect& sBase) const {
const GrRectBlurEffect& s = CastEffect<GrRectBlurEffect>(sBase);
return this->getWidth() == s.getWidth() &&
this->getHeight() == s.getHeight() &&
this->getSigma() == s.getSigma();
}
void GrRectBlurEffect::getConstantColorComponents(GrColor* color, uint32_t* validFlags) const {
*validFlags = 0;
return;
}
GR_DEFINE_EFFECT_TEST(GrRectBlurEffect);
GrEffectRef* GrRectBlurEffect::TestCreate(SkRandom* random,
GrContext* context,
const GrDrawTargetCaps&,
GrTexture**) {
float sigma = random->nextRangeF(3,8);
float width = random->nextRangeF(200,300);
float height = random->nextRangeF(200,300);
return GrRectBlurEffect::Create(context, SkRect::MakeWH(width, height), sigma);
}
bool SkBlurMaskFilterImpl::directFilterMaskGPU(GrContext* context,
GrPaint* grp,
const SkStrokeRec& strokeRec,
const SkPath& path) const {
if (fBlurStyle != SkBlurMaskFilter::kNormal_BlurStyle) {
return false;
}
SkRect rect;
if (!path.isRect(&rect)) {
return false;
}
if (!strokeRec.isFillStyle()) {
return false;
}
SkMatrix ctm = context->getMatrix();
SkScalar xformedSigma = this->computeXformedSigma(ctm);
rect.outset(3*xformedSigma, 3*xformedSigma);
SkAutoTUnref<GrEffectRef> effect(GrRectBlurEffect::Create(
context, rect, xformedSigma));
if (!effect) {
return false;
}
GrContext::AutoMatrix am;
if (!am.setIdentity(context, grp)) {
return false;
}
grp->addCoverageEffect(effect);
context->drawRect(*grp, rect);
return true;
}
bool SkBlurMaskFilterImpl::canFilterMaskGPU(const SkRect& srcBounds,
const SkIRect& clipBounds,
const SkMatrix& ctm,

View File

@ -954,6 +954,13 @@ void SkGpuDevice::drawPath(const SkDraw& draw, const SkPath& origSrcPath,
return;
}
if (paint.getMaskFilter()->directFilterMaskGPU(fContext, &grPaint,
SkStrokeRec(paint), *devPathPtr)) {
// the mask filter was able to draw itself directly, so there's nothing
// left to do.
return;
}
GrAutoScratchTexture mask;
if (create_mask_GPU(fContext, maskRect, *devPathPtr, stroke,