diff options
author | commit-bot@chromium.org <commit-bot@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81> | 2014-01-27 22:41:45 +0000 |
---|---|---|
committer | commit-bot@chromium.org <commit-bot@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81> | 2014-01-27 22:41:45 +0000 |
commit | d1829151a5f5fb69faa50c326ed018376d117b3e (patch) | |
tree | e424d35b0b3d4be436b10ca361577e18a68702ef /src | |
parent | 01260b2dbcc070006c0b960015e44ef83dcbaa56 (diff) |
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
Diffstat (limited to 'src')
-rw-r--r-- | src/core/SkMaskFilter.cpp | 7 | ||||
-rw-r--r-- | src/effects/SkBlurMask.cpp | 70 | ||||
-rw-r--r-- | src/effects/SkBlurMask.h | 35 | ||||
-rw-r--r-- | src/effects/SkBlurMaskFilter.cpp | 276 | ||||
-rw-r--r-- | src/gpu/SkGpuDevice.cpp | 9 |
5 files changed, 362 insertions, 35 deletions
diff --git a/src/core/SkMaskFilter.cpp b/src/core/SkMaskFilter.cpp index ebcdbea4d2..b96743d129 100644 --- a/src/core/SkMaskFilter.cpp +++ b/src/core/SkMaskFilter.cpp @@ -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, diff --git a/src/effects/SkBlurMask.cpp b/src/effects/SkBlurMask.cpp index 32a3d20d31..f61536e65a 100644 --- a/src/effects/SkBlurMask.cpp +++ b/src/effects/SkBlurMask.cpp @@ -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); - - 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))); - } - } + SkAutoTMalloc<uint8_t> verticalScanline(dstHeight); + + 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; } } diff --git a/src/effects/SkBlurMask.h b/src/effects/SkBlurMask.h index e0b8d54ce1..f95c110c90 100644 --- a/src/effects/SkBlurMask.h +++ b/src/effects/SkBlurMask.h @@ -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 diff --git a/src/effects/SkBlurMaskFilter.cpp b/src/effects/SkBlurMaskFilter.cpp index 14be6a50b3..05f9e88594 100644 --- a/src/effects/SkBlurMaskFilter.cpp +++ b/src/effects/SkBlurMaskFilter.cpp @@ -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, ¶ms); + + if (NULL == *horizontalScanline) { + + SkBlurMask::ComputeBlurProfile(sigma, &profile); + + SkAutoTMalloc<uint8_t> horizontalPixels(width); + SkBlurMask::ComputeBlurredScanline(horizontalPixels, profile, width, sigma); + + *horizontalScanline = context->createTexture(¶ms, 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, ¶ms); + if (NULL == *verticalScanline) { + if (NULL == profile) { + SkBlurMask::ComputeBlurProfile(sigma, &profile); + } + + SkAutoTMalloc<uint8_t> verticalPixels(height); + SkBlurMask::ComputeBlurredScanline(verticalPixels, profile, height, sigma); + + *verticalScanline = context->createTexture(¶ms, 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, diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp index 73eb65b661..6160876dd1 100644 --- a/src/gpu/SkGpuDevice.cpp +++ b/src/gpu/SkGpuDevice.cpp @@ -933,7 +933,7 @@ void SkGpuDevice::drawPath(const SkDraw& draw, const SkPath& origSrcPath, // transform the path into device space pathPtr->transform(fContext->getMatrix(), devPathPtr); - + SkRect maskRect; if (paint.getMaskFilter()->canFilterMaskGPU(devPathPtr->getBounds(), draw.fClip->getBounds(), @@ -953,6 +953,13 @@ void SkGpuDevice::drawPath(const SkDraw& draw, const SkPath& origSrcPath, // nothing to draw 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; |