diff options
-rw-r--r-- | gm/imageblur.cpp | 57 | ||||
-rw-r--r-- | gyp/effects.gyp | 2 | ||||
-rw-r--r-- | gyp/gmslides.gypi | 1 | ||||
-rw-r--r-- | include/effects/SkBlurImageFilter.h | 23 | ||||
-rw-r--r-- | include/gpu/GrConfig.h | 2 | ||||
-rw-r--r-- | src/effects/SkBlurImageFilter.cpp | 18 | ||||
-rw-r--r-- | src/gpu/GrContext.cpp | 8 | ||||
-rw-r--r-- | src/gpu/SkGpuDevice.cpp | 314 |
8 files changed, 294 insertions, 131 deletions
diff --git a/gm/imageblur.cpp b/gm/imageblur.cpp new file mode 100644 index 0000000000..fe9d6c29ac --- /dev/null +++ b/gm/imageblur.cpp @@ -0,0 +1,57 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "gm.h" +#include "SkBlurImageFilter.h" + +namespace skiagm { + +class ImageBlurGM : public GM { +public: + ImageBlurGM() { + this->setBGColor(0xFF000000); + } + +protected: + virtual SkString onShortName() { + return SkString("imageblur"); + } + + virtual SkISize onISize() { + return make_isize(500, 500); + } + + virtual void onDraw(SkCanvas* canvas) { + SkPaint paint; + paint.setImageFilter(new SkBlurImageFilter(24.0f, 0.0f))->unref(); + canvas->saveLayer(NULL, &paint); + paint.setColor(0xFFFFFFFF); + paint.setTextSize(100); + paint.setAntiAlias(true); + const char* str = "The quick brown fox jumped over the lazy dog."; + srand(1234); + SkISize size = canvas->getDeviceSize(); + for (int i = 0; i < 25; ++i) { + int x = rand() % size.fWidth; + int y = rand() % size.fHeight; + paint.setColor(rand() % 0x1000000 | 0xFF000000); + paint.setTextSize(rand() % 300); + canvas->drawText(str, strlen(str), x, y, paint); + } + canvas->restore(); + } + +private: + typedef GM INHERITED; +}; + +////////////////////////////////////////////////////////////////////////////// + +static GM* MyFactory(void*) { return new ImageBlurGM; } +static GMRegistry reg(MyFactory); + +} diff --git a/gyp/effects.gyp b/gyp/effects.gyp index 3b6783833b..81e8fa86f5 100644 --- a/gyp/effects.gyp +++ b/gyp/effects.gyp @@ -16,6 +16,7 @@ '../include/effects/Sk2DPathEffect.h', '../include/effects/SkAvoidXfermode.h', '../include/effects/SkBlurDrawLooper.h', + '../include/effects/SkBlurImageFilter.h', '../include/effects/SkBlurMaskFilter.h', '../include/effects/SkColorMatrix.h', '../include/effects/SkColorMatrixFilter.h', @@ -43,6 +44,7 @@ '../src/effects/SkBlurDrawLooper.cpp', '../src/effects/SkBlurMask.cpp', '../src/effects/SkBlurMask.h', + '../src/effects/SkBlurImageFilter.cpp', '../src/effects/SkBlurMaskFilter.cpp', '../src/effects/SkColorFilters.cpp', '../src/effects/SkColorMatrixFilter.cpp', diff --git a/gyp/gmslides.gypi b/gyp/gmslides.gypi index 8a97b0c685..3eb66045f6 100644 --- a/gyp/gmslides.gypi +++ b/gyp/gmslides.gypi @@ -13,6 +13,7 @@ '../gm/fontscaler.cpp', '../gm/gradients.cpp', '../gm/hairmodes.cpp', + '../gm/imageblur.cpp', '../gm/lcdtext.cpp', '../gm/ninepatchstretch.cpp', '../gm/nocolorbleed.cpp', diff --git a/include/effects/SkBlurImageFilter.h b/include/effects/SkBlurImageFilter.h new file mode 100644 index 0000000000..ceab31a861 --- /dev/null +++ b/include/effects/SkBlurImageFilter.h @@ -0,0 +1,23 @@ +/* + * Copyright 2011 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkBlurImageFilter_DEFINED +#define SkBlurImageFilter_DEFINED + +#include "SkImageFilter.h" + +class SK_API SkBlurImageFilter : public SkImageFilter { +public: + SkBlurImageFilter(SkScalar sigmaX, SkScalar sigmaY); + virtual bool asABlur(SkSize* sigma) const; +private: + SkSize fSigma; +}; + +#endif + diff --git a/include/gpu/GrConfig.h b/include/gpu/GrConfig.h index bc71792305..d6037f4f97 100644 --- a/include/gpu/GrConfig.h +++ b/include/gpu/GrConfig.h @@ -351,7 +351,7 @@ inline void GrCrash(const char* msg) { GrPrintf(msg); GrAlwaysAssert(false); } * program. */ #if !defined(GR_AGGRESSIVE_SHADER_OPTS) - #define GR_AGGRESSIVE_SHADER_OPTS 0 + #define GR_AGGRESSIVE_SHADER_OPTS 1 #endif /** diff --git a/src/effects/SkBlurImageFilter.cpp b/src/effects/SkBlurImageFilter.cpp new file mode 100644 index 0000000000..7777f316a3 --- /dev/null +++ b/src/effects/SkBlurImageFilter.cpp @@ -0,0 +1,18 @@ +/* + * Copyright 2011 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkBlurImageFilter.h" + +SkBlurImageFilter::SkBlurImageFilter(SkScalar sigmaX, SkScalar sigmaY) + : fSigma(SkSize::Make(sigmaX, sigmaY)) { + SkASSERT(sigmaX >= 0 && sigmaY >= 0); +} + +bool SkBlurImageFilter::asABlur(SkSize* sigma) const { + *sigma = fSigma; + return true; +} diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp index 74ed582e2a..2ccd843839 100644 --- a/src/gpu/GrContext.cpp +++ b/src/gpu/GrContext.cpp @@ -2008,7 +2008,7 @@ void GrContext::convolveInX(GrTexture* texture, const SkRect& rect, const float* kernel, int kernelWidth) { - float imageIncrement[2] = {1.0f / texture->width(), 0.0f}; + float imageIncrement[2] = {1.0f / texture->allocatedWidth(), 0.0f}; convolve(texture, rect, imageIncrement, kernel, kernelWidth); } @@ -2016,7 +2016,7 @@ void GrContext::convolveInY(GrTexture* texture, const SkRect& rect, const float* kernel, int kernelWidth) { - float imageIncrement[2] = {0.0f, 1.0f / texture->height()}; + float imageIncrement[2] = {0.0f, 1.0f / texture->allocatedHeight()}; convolve(texture, rect, imageIncrement, kernel, kernelWidth); } @@ -2031,12 +2031,12 @@ void GrContext::convolve(GrTexture* texture, GrSamplerState::kClamp_WrapMode, GrSamplerState::kConvolution_Filter); sampler.setConvolutionParams(kernelWidth, kernel, imageIncrement); - sampleM.setScale(GR_Scalar1 / texture->width(), - GR_Scalar1 / texture->height()); + sampleM.setIDiv(texture->width(), texture->height()); sampler.setMatrix(sampleM); fGpu->setSamplerState(0, sampler); fGpu->setViewMatrix(GrMatrix::I()); fGpu->setTexture(0, texture); + fGpu->setColor(0xFFFFFFFF); fGpu->setBlendFunc(kOne_BlendCoeff, kZero_BlendCoeff); fGpu->drawSimpleRect(rect, NULL, 1 << 0); } diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp index 031333bc28..8ad951dacd 100644 --- a/src/gpu/SkGpuDevice.cpp +++ b/src/gpu/SkGpuDevice.cpp @@ -17,6 +17,7 @@ #include "SkColorFilter.h" #include "SkDrawProcs.h" #include "SkGlyphCache.h" +#include "SkImageFilter.h" #include "SkTLazy.h" #include "SkUtils.h" @@ -689,11 +690,149 @@ static void buildKernel(float sigma, float* kernel, int kernelWidth) { kernel[i] *= scale; } -static void scaleRect(SkRect* rect, float scale) { - rect->fLeft *= scale; - rect->fTop *= scale; - rect->fRight *= scale; - rect->fBottom *= scale; +static void scaleRect(SkRect* rect, float xScale, float yScale) { + rect->fLeft *= xScale; + rect->fTop *= yScale; + rect->fRight *= xScale; + rect->fBottom *= yScale; +} + +static float adjustSigma(float sigma, int *scaleFactor, int *halfWidth, + int *kernelWidth) { + *scaleFactor = 1; + while (sigma > MAX_BLUR_SIGMA) { + *scaleFactor *= 2; + sigma *= 0.5f; + } + *halfWidth = static_cast<int>(ceilf(sigma * 3.0f)); + *kernelWidth = *halfWidth * 2 + 1; + return sigma; +} + +// Apply a Gaussian blur to srcTexture by sigmaX and sigmaY, within the given +// rect. +// temp1 and temp2 are used for allocation of intermediate textures. +// If temp2 is non-NULL, srcTexture will be untouched, and the return +// value will be either temp1 or temp2. +// If temp2 is NULL, srcTexture will be overwritten with intermediate +// results, and the return value will either be temp1 or srcTexture. +static GrTexture* gaussianBlur(GrContext* context, GrTexture* srcTexture, + GrAutoScratchTexture* temp1, + GrAutoScratchTexture* temp2, + const SkRect& rect, + float sigmaX, float sigmaY) { + + GrRenderTarget* oldRenderTarget = context->getRenderTarget(); + GrClip oldClip = context->getClip(); + GrTexture* origTexture = srcTexture; + GrAutoMatrix avm(context, GrMatrix::I()); + SkIRect clearRect; + int scaleFactorX, halfWidthX, kernelWidthX; + int scaleFactorY, halfWidthY, kernelWidthY; + sigmaX = adjustSigma(sigmaX, &scaleFactorX, &halfWidthX, &kernelWidthX); + sigmaY = adjustSigma(sigmaY, &scaleFactorY, &halfWidthY, &kernelWidthY); + + SkRect srcRect(rect); + scaleRect(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY); + srcRect.roundOut(); + scaleRect(&srcRect, scaleFactorX, scaleFactorY); + context->setClip(srcRect); + + const GrTextureDesc desc = { + kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit, + kNone_GrAALevel, + srcRect.width(), + srcRect.height(), + kRGBA_8888_GrPixelConfig + }; + + temp1->set(context, desc); + if (temp2) temp2->set(context, desc); + + GrTexture* dstTexture = temp1->texture(); + GrPaint paint; + paint.reset(); + paint.getTextureSampler(0)->setFilter(GrSamplerState::kBilinear_Filter); + + for (int i = 1; i < scaleFactorX || i < scaleFactorY; i *= 2) { + GrMatrix sampleM; + sampleM.setIDiv(srcTexture->width(), srcTexture->height()); + paint.getTextureSampler(0)->setMatrix(sampleM); + context->setRenderTarget(dstTexture->asRenderTarget()); + SkRect dstRect(srcRect); + scaleRect(&dstRect, i < scaleFactorX ? 0.5f : 1.0f, + i < scaleFactorY ? 0.5f : 1.0f); + paint.setTexture(0, srcTexture); + context->drawRectToRect(paint, dstRect, srcRect); + srcRect = dstRect; + SkTSwap(srcTexture, dstTexture); + // If temp2 is non-NULL, don't render back to origTexture + if (temp2 && dstTexture == origTexture) dstTexture = temp2->texture(); + } + + if (sigmaX > 0.0f) { + SkAutoTMalloc<float> kernelStorageX(kernelWidthX); + float* kernelX = kernelStorageX.get(); + buildKernel(sigmaX, kernelX, kernelWidthX); + + if (scaleFactorX > 1) { + // Clear out a halfWidth to the right of the srcRect to prevent the + // X convolution from reading garbage. + clearRect = SkIRect::MakeXYWH( + srcRect.fRight, srcRect.fTop, halfWidthX, srcRect.height()); + context->clear(&clearRect, 0x0); + } + + context->setRenderTarget(dstTexture->asRenderTarget()); + context->convolveInX(srcTexture, srcRect, kernelX, kernelWidthX); + SkTSwap(srcTexture, dstTexture); + if (temp2 && dstTexture == origTexture) dstTexture = temp2->texture(); + } + + if (sigmaY > 0.0f) { + SkAutoTMalloc<float> kernelStorageY(kernelWidthY); + float* kernelY = kernelStorageY.get(); + buildKernel(sigmaY, kernelY, kernelWidthY); + + if (scaleFactorY > 1 || sigmaX > 0.0f) { + // Clear out a halfWidth below the srcRect to prevent the Y + // convolution from reading garbage. + clearRect = SkIRect::MakeXYWH( + srcRect.fLeft, srcRect.fBottom, srcRect.width(), halfWidthY); + context->clear(&clearRect, 0x0); + } + + context->setRenderTarget(dstTexture->asRenderTarget()); + context->convolveInY(srcTexture, srcRect, kernelY, kernelWidthY); + SkTSwap(srcTexture, dstTexture); + if (temp2 && dstTexture == origTexture) dstTexture = temp2->texture(); + } + + if (scaleFactorX > 1 || scaleFactorY > 1) { + // Clear one pixel to the right and below, to accommodate bilinear + // upsampling. + clearRect = SkIRect::MakeXYWH( + srcRect.fLeft, srcRect.fBottom, srcRect.width() + 1, 1); + context->clear(&clearRect, 0x0); + clearRect = SkIRect::MakeXYWH( + srcRect.fRight, srcRect.fTop, 1, srcRect.height()); + context->clear(&clearRect, 0x0); + // FIXME: This should be mitchell, not bilinear. + paint.getTextureSampler(0)->setFilter(GrSamplerState::kBilinear_Filter); + GrMatrix sampleM; + sampleM.setIDiv(srcTexture->width(), srcTexture->height()); + paint.getTextureSampler(0)->setMatrix(sampleM); + context->setRenderTarget(dstTexture->asRenderTarget()); + paint.setTexture(0, srcTexture); + SkRect dstRect(srcRect); + scaleRect(&dstRect, scaleFactorX, scaleFactorY); + context->drawRectToRect(paint, dstRect, srcRect); + srcRect = dstRect; + SkTSwap(srcTexture, dstTexture); + } + context->setRenderTarget(oldRenderTarget); + context->setClip(oldClip); + return srcTexture; } static bool drawWithGPUMaskFilter(GrContext* context, const SkPath& path, @@ -716,33 +855,17 @@ static bool drawWithGPUMaskFilter(GrContext* context, const SkPath& path, return false; } float sigma = SkScalarToFloat(radius) * BLUR_SIGMA_SCALE; - SkRect srcRect = path.getBounds(); - - int scaleFactor = 1; - - while (sigma > MAX_BLUR_SIGMA) { - scaleFactor *= 2; - sigma *= 0.5f; - } - int halfWidth = static_cast<int>(ceilf(sigma * 3.0f)); - int kernelWidth = halfWidth * 2 + 1; + float sigma3 = sigma * 3.0f; - float invScale = 1.0f / scaleFactor; - scaleRect(&srcRect, invScale); - srcRect.roundOut(); - srcRect.inset(-halfWidth, -halfWidth); - - SkRect clipBounds; - clipBounds.set(clip.getBounds()); - scaleRect(&clipBounds, invScale); - clipBounds.roundOut(); - clipBounds.inset(-halfWidth, -halfWidth); - - srcRect.intersect(clipBounds); + SkRect srcRect = path.getBounds(); + SkRect clipRect; + clipRect.set(clip.getBounds()); - scaleRect(&srcRect, scaleFactor); + // Outset srcRect and clipRect by 3 * sigma, to compute affected blur area. + srcRect.inset(-sigma3, -sigma3); + clipRect.inset(-sigma3, -sigma3); + srcRect.intersect(clipRect); SkRect finalRect = srcRect; - SkIRect finalIRect; finalRect.roundOut(&finalIRect); if (clip.quickReject(finalIRect)) { @@ -752,7 +875,7 @@ static bool drawWithGPUMaskFilter(GrContext* context, const SkPath& path, return true; } GrPoint offset = GrPoint::Make(-srcRect.fLeft, -srcRect.fTop); - srcRect.offset(-srcRect.fLeft, -srcRect.fTop); + srcRect.offset(offset); const GrTextureDesc desc = { kRenderTarget_GrTextureFlagBit, kNone_GrAALevel, @@ -763,21 +886,16 @@ static bool drawWithGPUMaskFilter(GrContext* context, const SkPath& path, kRGBA_8888_PM_GrPixelConfig }; - GrAutoScratchTexture srcEntry(context, desc); - GrAutoScratchTexture dstEntry(context, desc); - if (NULL == srcEntry.texture() || NULL == dstEntry.texture()) { - return false; - } - GrTexture* srcTexture = srcEntry.texture(); - GrTexture* dstTexture = dstEntry.texture(); - if (NULL == srcTexture || NULL == dstTexture) { + GrAutoScratchTexture pathEntry(context, desc); + GrTexture* pathTexture = pathEntry.texture(); + if (NULL == pathTexture) { return false; } GrRenderTarget* oldRenderTarget = context->getRenderTarget(); // Once this code moves into GrContext, this should be changed to use // an AutoClipRestore. GrClip oldClip = context->getClip(); - context->setRenderTarget(dstTexture->asRenderTarget()); + context->setRenderTarget(pathTexture->asRenderTarget()); context->setClip(srcRect); context->clear(NULL, 0); GrPaint tempPaint; @@ -795,95 +913,27 @@ static bool drawWithGPUMaskFilter(GrContext* context, const SkPath& path, tempPaint.fSrcBlendCoeff = kOne_BlendCoeff; tempPaint.fDstBlendCoeff = kISC_BlendCoeff; } - // Draw hard shadow to dstTexture with path topleft at origin 0,0. + // Draw hard shadow to pathTexture with path topleft at origin 0,0. context->drawPath(tempPaint, path, skToGrFillType(path.getFillType()), &offset); - SkTSwap(srcTexture, dstTexture); - - GrMatrix sampleM; - sampleM.setIDiv(srcTexture->width(), srcTexture->height()); - GrPaint paint; - paint.reset(); - paint.getTextureSampler(0)->setFilter(GrSamplerState::kBilinear_Filter); - paint.getTextureSampler(0)->setMatrix(sampleM); - GrAutoScratchTexture origEntry; - - if (blurType != SkMaskFilter::kNormal_BlurType) { - // Stash away a copy of the unblurred image. - origEntry.set(context, desc); - if (NULL == origEntry.texture()) { - return false; - } - context->setRenderTarget(origEntry.texture()->asRenderTarget()); - paint.setTexture(0, srcTexture); - context->drawRect(paint, srcRect); - } - for (int i = 1; i < scaleFactor; i *= 2) { - sampleM.setIDiv(srcTexture->width(), srcTexture->height()); - paint.getTextureSampler(0)->setMatrix(sampleM); - context->setRenderTarget(dstTexture->asRenderTarget()); - SkRect dstRect(srcRect); - scaleRect(&dstRect, 0.5f); - paint.setTexture(0, srcTexture); - context->drawRectToRect(paint, dstRect, srcRect); - srcRect = dstRect; - SkTSwap(srcTexture, dstTexture); - } - SkAutoTMalloc<float> kernelStorage(kernelWidth); - float* kernel = kernelStorage.get(); - buildKernel(sigma, kernel, kernelWidth); - - // Clear out a halfWidth to the right of the srcRect to prevent the - // X convolution from reading garbage. - SkIRect clearRect = SkIRect::MakeXYWH( - srcRect.fRight, srcRect.fTop, halfWidth, srcRect.height()); - context->clear(&clearRect, 0x0); - - context->setRenderTarget(dstTexture->asRenderTarget()); - context->convolveInX(srcTexture, srcRect, kernel, kernelWidth); - SkTSwap(srcTexture, dstTexture); - - // Clear out a halfWidth below the srcRect to prevent the Y - // convolution from reading garbage. - clearRect = SkIRect::MakeXYWH( - srcRect.fLeft, srcRect.fBottom, srcRect.width(), halfWidth); - context->clear(&clearRect, 0x0); - - context->setRenderTarget(dstTexture->asRenderTarget()); - context->convolveInY(srcTexture, srcRect, kernel, kernelWidth); - SkTSwap(srcTexture, dstTexture); - - // Clear one pixel to the right and below, to accommodate bilinear - // upsampling. - clearRect = SkIRect::MakeXYWH( - srcRect.fLeft, srcRect.fBottom, srcRect.width() + 1, 1); - context->clear(&clearRect, 0x0); - clearRect = SkIRect::MakeXYWH( - srcRect.fRight, srcRect.fTop, 1, srcRect.height()); - context->clear(&clearRect, 0x0); - - if (scaleFactor > 1) { - // FIXME: This should be mitchell, not bilinear. - paint.getTextureSampler(0)->setFilter(GrSamplerState::kBilinear_Filter); - sampleM.setIDiv(srcTexture->width(), srcTexture->height()); - paint.getTextureSampler(0)->setMatrix(sampleM); - context->setRenderTarget(dstTexture->asRenderTarget()); - paint.setTexture(0, srcTexture); - SkRect dstRect(srcRect); - scaleRect(&dstRect, scaleFactor); - context->drawRectToRect(paint, dstRect, srcRect); - srcRect = dstRect; - SkTSwap(srcTexture, dstTexture); - } - - if (blurType != SkMaskFilter::kNormal_BlurType) { - GrTexture* origTexture = origEntry.texture(); + GrAutoScratchTexture temp1, temp2; + // If we're doing a normal blur, we can clobber the pathTexture in the + // gaussianBlur. Otherwise, we need to save it for later compositing. + bool isNormalBlur = blurType == SkMaskFilter::kNormal_BlurType; + GrTexture* blurTexture = gaussianBlur(context, pathTexture, + &temp1, isNormalBlur ? NULL : &temp2, + srcRect, sigma, sigma); + + if (!isNormalBlur) { + GrPaint paint; + paint.reset(); paint.getTextureSampler(0)->setFilter(GrSamplerState::kNearest_Filter); - sampleM.setIDiv(origTexture->width(), origTexture->height()); + GrMatrix sampleM; + sampleM.setIDiv(pathTexture->width(), pathTexture->height()); paint.getTextureSampler(0)->setMatrix(sampleM); - // Blend origTexture over srcTexture. - context->setRenderTarget(srcTexture->asRenderTarget()); - paint.setTexture(0, origTexture); + // Blend pathTexture over blurTexture. + context->setRenderTarget(blurTexture->asRenderTarget()); + paint.setTexture(0, pathTexture); if (SkMaskFilter::kInner_BlurType == blurType) { // inner: dst = dst * src paint.fSrcBlendCoeff = kDC_BlendCoeff; @@ -915,12 +965,12 @@ static bool drawWithGPUMaskFilter(GrContext* context, const SkPath& path, static const int MASK_IDX = GrPaint::kMaxMasks - 1; // we assume the last mask index is available for use GrAssert(NULL == grp->getMask(MASK_IDX)); - grp->setMask(MASK_IDX, srcTexture); + grp->setMask(MASK_IDX, blurTexture); grp->getMaskSampler(MASK_IDX)->setClampNoFilter(); GrMatrix m; m.setTranslate(-finalRect.fLeft, -finalRect.fTop); - m.postIDiv(srcTexture->width(), srcTexture->height()); + m.postIDiv(blurTexture->width(), blurTexture->height()); grp->getMaskSampler(MASK_IDX)->setMatrix(m); context->drawRect(*grp, finalRect); return true; @@ -1352,6 +1402,18 @@ void SkGpuDevice::drawDevice(const SkDraw& draw, SkDevice* dev, GrIntToScalar(y), GrIntToScalar(w), GrIntToScalar(h)); + SkImageFilter* imageFilter = paint.getImageFilter(); + SkSize size; + if (NULL != imageFilter && imageFilter->asABlur(&size)) { + GrAutoScratchTexture temp1, temp2; + GrTexture* blurTexture = gaussianBlur(fContext, + devTex, &temp1, &temp2, + GrRect::MakeWH(w, h), + size.width(), + size.height()); + grPaint.setTexture(kBitmapTextureIdx, blurTexture); + devTex = blurTexture; + } // The device being drawn may not fill up its texture (saveLayer uses // the approximate ). GrRect srcRect = GrRect::MakeWH(GR_Scalar1 * w / devTex->width(), |