diff options
-rw-r--r-- | include/core/SkMaskFilter.h | 64 | ||||
-rw-r--r-- | src/core/SkMaskFilter.cpp | 69 | ||||
-rw-r--r-- | src/device/xps/SkXPSDevice.cpp | 15 | ||||
-rw-r--r-- | src/effects/SkBlurMaskFilter.cpp | 160 | ||||
-rw-r--r-- | src/gpu/SkGpuDevice.cpp | 299 | ||||
-rw-r--r-- | tests/BlurTest.cpp | 12 | ||||
-rw-r--r-- | tools/PictureRenderer.cpp | 5 | ||||
-rw-r--r-- | tools/PictureRenderer.h | 4 |
8 files changed, 366 insertions, 262 deletions
diff --git a/include/core/SkMaskFilter.h b/include/core/SkMaskFilter.h index aa499ecd23..40a03a5205 100644 --- a/include/core/SkMaskFilter.h +++ b/include/core/SkMaskFilter.h @@ -14,6 +14,7 @@ #include "SkMask.h" #include "SkPaint.h" +class SkBitmap; class SkBlitter; class SkBounder; class SkMatrix; @@ -58,27 +59,56 @@ public: virtual bool filterMask(SkMask* dst, const SkMask& src, const SkMatrix&, SkIPoint* margin) const; - enum BlurType { - kNone_BlurType, //!< this maskfilter is not a blur - kNormal_BlurType, //!< fuzzy inside and outside - kSolid_BlurType, //!< solid inside, fuzzy outside - kOuter_BlurType, //!< nothing inside, fuzzy outside - kInner_BlurType //!< fuzzy inside, nothing outside - }; +#if SK_SUPPORT_GPU + /** + * Returns true if the filter can be expressed a single-pass + * GrEffect, used to process this filter on the GPU, or false if + * not. + * + * If effect is non-NULL, a new GrEffect instance is stored + * in it. The caller assumes ownership of the stage, and it is up to the + * caller to unref it. + */ + virtual bool asNewEffect(GrEffectRef** effect, GrTexture*) const; - struct BlurInfo { - SkScalar fRadius; - bool fIgnoreTransform; - bool fHighQuality; - }; + /** + * Returns true if the filter can be processed on the GPU. This is most + * often used for multi-pass effects, where intermediate results must be + * rendered to textures. For single-pass effects, use asNewEffect(). + * + * 'maskRect' returns the device space portion of the mask the the filter + * needs. The mask passed into 'filterMaskGPU' should have the same extent + * as 'maskRect' but be translated to the upper-left corner of the mask + * (i.e., (maskRect.fLeft, maskRect.fTop) appears at (0, 0) in the mask). + */ + virtual bool canFilterMaskGPU(const SkRect& devBounds, + const SkIRect& clipBounds, + const SkMatrix& ctm, + SkRect* maskRect) const; + + /** + * Perform this mask filter on the GPU. This is most often used for + * multi-pass effects, where intermediate results must be rendered to + * textures. For single-pass effects, use asNewEffect(). 'src' is the + * source image for processing, as a texture-backed bitmap. 'result' is + * the destination bitmap, which should contain a texture-backed pixelref + * on success. 'maskRect' should be the rect returned from canFilterMaskGPU. + */ + bool filterMaskGPU(GrContext* context, + const SkBitmap& src, + const SkRect& maskRect, + SkBitmap* result) const; /** - * Optional method for maskfilters that can be described as a blur. If so, - * they return the corresponding BlurType and set the fields in BlurInfo - * (if not null). If they cannot be described as a blur, they return - * kNone_BlurType and ignore the info parameter. + * This flavor of 'filterMaskGPU' provides a more direct means of accessing + * the filtering capabilities. Setting 'canOverwriteSrc' can allow some + * filters to skip the allocation of an additional texture. */ - virtual BlurType asABlur(BlurInfo*) const; + virtual bool filterMaskGPU(GrTexture* src, + const SkRect& maskRect, + GrTexture** result, + bool canOverwriteSrc) const; +#endif /** * The fast bounds function is used to enable the paint to be culled early diff --git a/src/core/SkMaskFilter.cpp b/src/core/SkMaskFilter.cpp index 0346ce0e9c..fbee4dc09b 100644 --- a/src/core/SkMaskFilter.cpp +++ b/src/core/SkMaskFilter.cpp @@ -11,7 +11,11 @@ #include "SkBlitter.h" #include "SkBounder.h" #include "SkDraw.h" +#include "SkGr.h" +#include "SkGrPixelRef.h" #include "SkRasterClip.h" +#include "SkTypes.h" +#include "GrTexture.h" SK_DEFINE_INST_COUNT(SkMaskFilter) @@ -266,8 +270,69 @@ SkMaskFilter::filterRectsToNine(const SkRect[], int count, const SkMatrix&, return kUnimplemented_FilterReturn; } -SkMaskFilter::BlurType SkMaskFilter::asABlur(BlurInfo*) const { - return kNone_BlurType; +bool SkMaskFilter::asNewEffect(GrEffectRef** effect, GrTexture*) const { + return false; +} + +bool SkMaskFilter::canFilterMaskGPU(const SkRect& devBounds, + const SkIRect& clipBounds, + const SkMatrix& ctm, + SkRect* maskRect) const { + return false; +} + +bool SkMaskFilter::filterMaskGPU(GrContext* context, + const SkBitmap& srcBM, + const SkRect& maskRect, + SkBitmap* resultBM) const { + SkAutoTUnref<GrTexture> src; + bool canOverwriteSrc = false; + if (NULL == srcBM.getTexture()) { + GrTextureDesc desc; + // Needs to be a render target to be overwritten in filterMaskGPU + desc.fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit; + desc.fConfig = SkBitmapConfig2GrPixelConfig(srcBM.config()); + desc.fWidth = srcBM.width(); + desc.fHeight = srcBM.height(); + + // TODO: right now this is exact to guard against out of bounds reads + // by the filter code. More thought needs to be devoted to the + // "filterMaskGPU" contract and then enforced (i.e., clamp the code + // in "filterMaskGPU" so it never samples beyond maskRect) + GrAutoScratchTexture ast(context, desc, GrContext::kExact_ScratchTexMatch); + if (NULL == ast.texture()) { + return false; + } + + SkAutoLockPixels alp(srcBM); + ast.texture()->writePixels(0, 0, srcBM.width(), srcBM.height(), + desc.fConfig, + srcBM.getPixels(), srcBM.rowBytes()); + + src.reset(ast.detach()); + canOverwriteSrc = true; + } else { + src.reset((GrTexture*) srcBM.getTexture()); + src.get()->ref(); + } + GrTexture* dst; + + bool result = this->filterMaskGPU(src, maskRect, &dst, canOverwriteSrc); + if (!result) { + return false; + } + + resultBM->setConfig(srcBM.config(), dst->width(), dst->height()); + resultBM->setPixelRef(SkNEW_ARGS(SkGrPixelRef, (dst)))->unref(); + dst->unref(); + return true; +} + +bool SkMaskFilter::filterMaskGPU(GrTexture* src, + const SkRect& maskRect, + GrTexture** result, + bool canOverwriteSrc) const { + return false; } void SkMaskFilter::computeFastBounds(const SkRect& src, SkRect* dst) const { diff --git a/src/device/xps/SkXPSDevice.cpp b/src/device/xps/SkXPSDevice.cpp index d7a559865f..bfae6de2af 100644 --- a/src/device/xps/SkXPSDevice.cpp +++ b/src/device/xps/SkXPSDevice.cpp @@ -1542,21 +1542,6 @@ void SkXPSDevice::convertToPpm(const SkMaskFilter* filter, SkMatrix* matrix, SkVector* ppuScale, const SkIRect& clip, SkIRect* clipIRect) { - //TODO: currently ignoring the ppm if blur ignoring transform. - if (filter) { - SkMaskFilter::BlurInfo blurInfo; - SkMaskFilter::BlurType blurType = filter->asABlur(&blurInfo); - - if (SkMaskFilter::kNone_BlurType != blurType - && blurInfo.fIgnoreTransform) { - - ppuScale->fX = SK_Scalar1; - ppuScale->fY = SK_Scalar1; - *clipIRect = clip; - return; - } - } - //This action is in unit space, but the ppm is specified in physical space. ppuScale->fX = SkScalarDiv(this->fCurrentPixelsPerMeter.fX, this->fCurrentUnitsPerMeter.fX); diff --git a/src/effects/SkBlurMaskFilter.cpp b/src/effects/SkBlurMaskFilter.cpp index eaf7704bce..8c14bc8845 100644 --- a/src/effects/SkBlurMaskFilter.cpp +++ b/src/effects/SkBlurMaskFilter.cpp @@ -10,10 +10,16 @@ #include "SkBlurMask.h" #include "SkFlattenableBuffers.h" #include "SkMaskFilter.h" -#include "SkBounder.h" -#include "SkRasterClip.h" #include "SkRTConf.h" #include "SkStringUtils.h" +#include "SkStrokeRec.h" + +#if SK_SUPPORT_GPU +#include "GrContext.h" +#include "GrTexture.h" +#include "effects/GrSimpleTextureEffect.h" +#include "SkGrPixelRef.h" +#endif class SkBlurMaskFilterImpl : public SkMaskFilter { public: @@ -25,7 +31,17 @@ public: virtual bool filterMask(SkMask* dst, const SkMask& src, const SkMatrix&, SkIPoint* margin) const SK_OVERRIDE; - virtual BlurType asABlur(BlurInfo*) const SK_OVERRIDE; +#if SK_SUPPORT_GPU + virtual bool canFilterMaskGPU(const SkRect& devBounds, + const SkIRect& clipBounds, + const SkMatrix& ctm, + SkRect* maskRect) const SK_OVERRIDE; + virtual bool filterMaskGPU(GrTexture* src, + const SkRect& maskRect, + GrTexture** result, + bool canOverwriteSrc) const; +#endif + virtual void computeFastBounds(const SkRect&, SkRect*) const SK_OVERRIDE; SkDEVCODE(virtual void toString(SkString* str) const SK_OVERRIDE;) @@ -40,16 +56,41 @@ protected: SkIPoint* margin, SkMask::CreateMode createMode) const; private: + // To avoid unseemly allocation requests (esp. for finite platforms like + // handset) we limit the radius so something manageable. (as opposed to + // a request like 10,000) + static const SkScalar kMAX_RADIUS; + // This constant approximates the scaling done in the software path's + // "high quality" mode, in SkBlurMask::Blur() (1 / sqrt(3)). + // IMHO, it actually should be 1: we blur "less" than we should do + // according to the CSS and canvas specs, simply because Safari does the same. + // Firefox used to do the same too, until 4.0 where they fixed it. So at some + // point we should probably get rid of these scaling constants and rebaseline + // all the blur tests. + static const SkScalar kBLUR_SIGMA_SCALE; + SkScalar fRadius; SkBlurMaskFilter::BlurStyle fBlurStyle; uint32_t fBlurFlags; SkBlurMaskFilterImpl(SkFlattenableReadBuffer&); virtual void flatten(SkFlattenableWriteBuffer&) const SK_OVERRIDE; +#if SK_SUPPORT_GPU + SkScalar computeXformedRadius(const SkMatrix& ctm) const { + bool ignoreTransform = SkToBool(fBlurFlags & SkBlurMaskFilter::kIgnoreTransform_BlurFlag); + + SkScalar xformedRadius = ignoreTransform ? fRadius + : ctm.mapRadius(fRadius); + return SkMinScalar(xformedRadius, kMAX_RADIUS); + } +#endif typedef SkMaskFilter INHERITED; }; +const SkScalar SkBlurMaskFilterImpl::kMAX_RADIUS = SkIntToScalar(128); +const SkScalar SkBlurMaskFilterImpl::kBLUR_SIGMA_SCALE = 0.6f; + SkMaskFilter* SkBlurMaskFilter::Create(SkScalar radius, SkBlurMaskFilter::BlurStyle style, uint32_t flags) { @@ -97,11 +138,7 @@ bool SkBlurMaskFilterImpl::filterMask(SkMask* dst, const SkMask& src, radius = matrix.mapRadius(fRadius); } - // To avoid unseemly allocation requests (esp. for finite platforms like - // handset) we limit the radius so something manageable. (as opposed to - // a request like 10,000) - static const SkScalar MAX_RADIUS = SkIntToScalar(128); - radius = SkMinScalar(radius, MAX_RADIUS); + radius = SkMinScalar(radius, kMAX_RADIUS); SkBlurMask::Quality blurQuality = (fBlurFlags & SkBlurMaskFilter::kHighQuality_BlurFlag) ? SkBlurMask::kHigh_Quality : SkBlurMask::kLow_Quality; @@ -120,11 +157,7 @@ bool SkBlurMaskFilterImpl::filterRectMask(SkMask* dst, const SkRect& r, radius = matrix.mapRadius(fRadius); } - // To avoid unseemly allocation requests (esp. for finite platforms like - // handset) we limit the radius so something manageable. (as opposed to - // a request like 10,000) - static const SkScalar MAX_RADIUS = SkIntToScalar(128); - radius = SkMinScalar(radius, MAX_RADIUS); + radius = SkMinScalar(radius, kMAX_RADIUS); return SkBlurMask::BlurRect(dst, r, radius, (SkBlurMask::Style)fBlurStyle, margin, createMode); @@ -320,22 +353,99 @@ void SkBlurMaskFilterImpl::flatten(SkFlattenableWriteBuffer& buffer) const { buffer.writeUInt(fBlurFlags); } -static const SkMaskFilter::BlurType gBlurStyle2BlurType[] = { - SkMaskFilter::kNormal_BlurType, - SkMaskFilter::kSolid_BlurType, - SkMaskFilter::kOuter_BlurType, - SkMaskFilter::kInner_BlurType, -}; +#if SK_SUPPORT_GPU -SkMaskFilter::BlurType SkBlurMaskFilterImpl::asABlur(BlurInfo* info) const { - if (info) { - info->fRadius = fRadius; - info->fIgnoreTransform = SkToBool(fBlurFlags & SkBlurMaskFilter::kIgnoreTransform_BlurFlag); - info->fHighQuality = SkToBool(fBlurFlags & SkBlurMaskFilter::kHighQuality_BlurFlag); +bool SkBlurMaskFilterImpl::canFilterMaskGPU(const SkRect& srcBounds, + const SkIRect& clipBounds, + const SkMatrix& ctm, + SkRect* maskRect) const { + SkScalar xformedRadius = this->computeXformedRadius(ctm); + if (xformedRadius <= 0) { + return false; } - return gBlurStyle2BlurType[fBlurStyle]; + + static const SkScalar kMIN_GPU_BLUR_SIZE = SkIntToScalar(64); + static const SkScalar kMIN_GPU_BLUR_RADIUS = SkIntToScalar(32); + + if (srcBounds.width() <= kMIN_GPU_BLUR_SIZE && + srcBounds.height() <= kMIN_GPU_BLUR_SIZE && + xformedRadius <= kMIN_GPU_BLUR_RADIUS) { + // We prefer to blur small rect with small radius via CPU. + return false; + } + + if (NULL == maskRect) { + // don't need to compute maskRect + return true; + } + + float sigma3 = 3 * SkScalarToFloat(xformedRadius) * kBLUR_SIGMA_SCALE; + + SkRect clipRect = SkRect::MakeFromIRect(clipBounds); + SkRect srcRect(srcBounds); + + // Outset srcRect and clipRect by 3 * sigma, to compute affected blur area. + srcRect.outset(SkFloatToScalar(sigma3), SkFloatToScalar(sigma3)); + clipRect.outset(SkFloatToScalar(sigma3), SkFloatToScalar(sigma3)); + srcRect.intersect(clipRect); + *maskRect = srcRect; + return true; +} + +bool SkBlurMaskFilterImpl::filterMaskGPU(GrTexture* src, + const SkRect& maskRect, + GrTexture** result, + bool canOverwriteSrc) const { + SkRect clipRect = SkRect::MakeWH(maskRect.width(), maskRect.height()); + + GrContext* context = src->getContext(); + + GrContext::AutoWideOpenIdentityDraw awo(context, NULL); + + SkScalar xformedRadius = this->computeXformedRadius(context->getMatrix()); + SkASSERT(xformedRadius > 0); + + float sigma = SkScalarToFloat(xformedRadius) * kBLUR_SIGMA_SCALE; + + // 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 = (SkBlurMaskFilter::kNormal_BlurStyle == fBlurStyle); + *result = context->gaussianBlur(src, isNormalBlur && canOverwriteSrc, + clipRect, sigma, sigma); + if (NULL == result) { + return false; + } + + if (!isNormalBlur) { + context->setIdentityMatrix(); + GrPaint paint; + SkMatrix matrix; + matrix.setIDiv(src->width(), src->height()); + // Blend pathTexture over blurTexture. + GrContext::AutoRenderTarget art(context, (*result)->asRenderTarget()); + paint.colorStage(0)->setEffect( + GrSimpleTextureEffect::Create(src, matrix))->unref(); + if (SkBlurMaskFilter::kInner_BlurStyle == fBlurStyle) { + // inner: dst = dst * src + paint.setBlendFunc(kDC_GrBlendCoeff, kZero_GrBlendCoeff); + } else if (SkBlurMaskFilter::kSolid_BlurStyle == fBlurStyle) { + // solid: dst = src + dst - src * dst + // = (1 - dst) * src + 1 * dst + paint.setBlendFunc(kIDC_GrBlendCoeff, kOne_GrBlendCoeff); + } else if (SkBlurMaskFilter::kOuter_BlurStyle == fBlurStyle) { + // outer: dst = dst * (1 - src) + // = 0 * src + (1 - src) * dst + paint.setBlendFunc(kZero_GrBlendCoeff, kISC_GrBlendCoeff); + } + context->drawRect(paint, clipRect); + } + + return true; } +#endif // SK_SUPPORT_GPU + + #ifdef SK_DEVELOPER void SkBlurMaskFilterImpl::toString(SkString* str) const { str->append("SkBlurMaskFilterImpl: ("); diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp index bfc3695f3e..01b80213a9 100644 --- a/src/gpu/SkGpuDevice.cpp +++ b/src/gpu/SkGpuDevice.cpp @@ -47,18 +47,6 @@ enum { kXfermodeEffectIdx = 2, }; -#define MAX_BLUR_SIGMA 4.0f -// FIXME: This value comes from from SkBlurMaskFilter.cpp. -// Should probably be put in a common header someplace. -#define MAX_BLUR_RADIUS SkIntToScalar(128) -// This constant approximates the scaling done in the software path's -// "high quality" mode, in SkBlurMask::Blur() (1 / sqrt(3)). -// IMHO, it actually should be 1: we blur "less" than we should do -// according to the CSS and canvas specs, simply because Safari does the same. -// Firefox used to do the same too, until 4.0 where they fixed it. So at some -// point we should probably get rid of these scaling constants and rebaseline -// all the blur tests. -#define BLUR_SIGMA_SCALE 0.6f // This constant represents the screen alignment criterion in texels for // requiring texture domain clamping to prevent color bleeding when drawing // a sub region of a larger source image. @@ -776,138 +764,11 @@ void SkGpuDevice::drawOval(const SkDraw& draw, const SkRect& oval, // helpers for applying mask filters namespace { -// We prefer to blur small rect with small radius via CPU. -#define MIN_GPU_BLUR_SIZE SkIntToScalar(64) -#define MIN_GPU_BLUR_RADIUS SkIntToScalar(32) -inline bool shouldDrawBlurWithCPU(const SkRect& rect, SkScalar radius) { - if (rect.width() <= MIN_GPU_BLUR_SIZE && - rect.height() <= MIN_GPU_BLUR_SIZE && - radius <= MIN_GPU_BLUR_RADIUS) { - return true; - } - return false; -} - -bool drawWithGPUMaskFilter(GrContext* context, const SkPath& devPath, const SkStrokeRec& stroke, - SkMaskFilter* filter, const SkRegion& clip, - SkBounder* bounder, GrPaint* grp) { - SkMaskFilter::BlurInfo info; - SkMaskFilter::BlurType blurType = filter->asABlur(&info); - if (SkMaskFilter::kNone_BlurType == blurType) { - return false; - } - SkScalar radius = info.fIgnoreTransform ? info.fRadius - : context->getMatrix().mapRadius(info.fRadius); - radius = SkMinScalar(radius, MAX_BLUR_RADIUS); - if (radius <= 0) { - return false; - } - - SkRect srcRect = devPath.getBounds(); - if (shouldDrawBlurWithCPU(srcRect, radius)) { - return false; - } - - float sigma = SkScalarToFloat(radius) * BLUR_SIGMA_SCALE; - float sigma3 = sigma * 3.0f; - - SkRect clipRect; - clipRect.set(clip.getBounds()); - - // Outset srcRect and clipRect by 3 * sigma, to compute affected blur area. - srcRect.inset(SkFloatToScalar(-sigma3), SkFloatToScalar(-sigma3)); - clipRect.inset(SkFloatToScalar(-sigma3), SkFloatToScalar(-sigma3)); - srcRect.intersect(clipRect); - SkRect finalRect = srcRect; - SkIRect finalIRect; - finalRect.roundOut(&finalIRect); - if (clip.quickReject(finalIRect)) { - return true; - } - if (bounder && !bounder->doIRect(finalIRect)) { - return true; - } - GrPoint offset = GrPoint::Make(-srcRect.fLeft, -srcRect.fTop); - srcRect.offset(offset); - GrTextureDesc desc; - desc.fFlags = kRenderTarget_GrTextureFlagBit; - desc.fWidth = SkScalarCeilToInt(srcRect.width()); - desc.fHeight = SkScalarCeilToInt(srcRect.height()); - // We actually only need A8, but it often isn't supported as a - // render target so default to RGBA_8888 - desc.fConfig = kRGBA_8888_GrPixelConfig; - - if (context->isConfigRenderable(kAlpha_8_GrPixelConfig)) { - desc.fConfig = kAlpha_8_GrPixelConfig; - } - - GrAutoScratchTexture pathEntry(context, desc); - GrTexture* pathTexture = pathEntry.texture(); - if (NULL == pathTexture) { - return false; - } - - SkAutoTUnref<GrTexture> blurTexture; - - { - GrContext::AutoRenderTarget art(context, pathTexture->asRenderTarget()); - GrContext::AutoClip ac(context, srcRect); - - context->clear(NULL, 0); - - GrPaint tempPaint; - if (grp->isAntiAlias()) { - tempPaint.setAntiAlias(true); - // AA uses the "coverage" stages on GrDrawTarget. Coverage with a dst - // blend coeff of zero requires dual source blending support in order - // to properly blend partially covered pixels. This means the AA - // code path may not be taken. So we use a dst blend coeff of ISA. We - // could special case AA draws to a dst surface with known alpha=0 to - // use a zero dst coeff when dual source blending isn't available.f - tempPaint.setBlendFunc(kOne_GrBlendCoeff, kISC_GrBlendCoeff); - } - - GrContext::AutoMatrix am; - - // Draw hard shadow to pathTexture with path top-left at origin using tempPaint. - SkMatrix translate; - translate.setTranslate(offset.fX, offset.fY); - am.set(context, translate); - context->drawPath(tempPaint, devPath, stroke); - - // 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; - blurTexture.reset(context->gaussianBlur(pathTexture, isNormalBlur, - srcRect, sigma, sigma)); - if (NULL == blurTexture) { - return false; - } - - if (!isNormalBlur) { - context->setIdentityMatrix(); - GrPaint paint; - SkMatrix matrix; - matrix.setIDiv(pathTexture->width(), pathTexture->height()); - // Blend pathTexture over blurTexture. - context->setRenderTarget(blurTexture->asRenderTarget()); - paint.colorStage(0)->setEffect( - GrSimpleTextureEffect::Create(pathTexture, matrix))->unref(); - if (SkMaskFilter::kInner_BlurType == blurType) { - // inner: dst = dst * src - paint.setBlendFunc(kDC_GrBlendCoeff, kZero_GrBlendCoeff); - } else if (SkMaskFilter::kSolid_BlurType == blurType) { - // solid: dst = src + dst - src * dst - // = (1 - dst) * src + 1 * dst - paint.setBlendFunc(kIDC_GrBlendCoeff, kOne_GrBlendCoeff); - } else if (SkMaskFilter::kOuter_BlurType == blurType) { - // outer: dst = dst * (1 - src) - // = 0 * src + (1 - src) * dst - paint.setBlendFunc(kZero_GrBlendCoeff, kISC_GrBlendCoeff); - } - context->drawRect(paint, srcRect); - } - } +// Draw a mask using the supplied paint. Since the coverage/geometry +// is already burnt into the mask this boils down to a rect draw. +// Return true if the mask was successfully drawn. +bool draw_mask(GrContext* context, const SkRect& maskRect, + GrPaint* grp, GrTexture* mask) { GrContext::AutoMatrix am; if (!am.setIdentity(context, grp)) { @@ -919,19 +780,18 @@ bool drawWithGPUMaskFilter(GrContext* context, const SkPath& devPath, const SkSt GrAssert(!grp->isCoverageStageEnabled(MASK_IDX)); SkMatrix matrix; - matrix.setTranslate(-finalRect.fLeft, -finalRect.fTop); - matrix.postIDiv(blurTexture->width(), blurTexture->height()); + matrix.setTranslate(-maskRect.fLeft, -maskRect.fTop); + matrix.postIDiv(mask->width(), mask->height()); grp->coverageStage(MASK_IDX)->reset(); - grp->coverageStage(MASK_IDX)->setEffect( - GrSimpleTextureEffect::Create(blurTexture, matrix))->unref(); - context->drawRect(*grp, finalRect); + grp->coverageStage(MASK_IDX)->setEffect(GrSimpleTextureEffect::Create(mask, matrix))->unref(); + context->drawRect(*grp, maskRect); return true; } -bool drawWithMaskFilter(GrContext* context, const SkPath& devPath, - SkMaskFilter* filter, const SkRegion& clip, SkBounder* bounder, - GrPaint* grp, SkPaint::Style style) { +bool draw_with_mask_filter(GrContext* context, const SkPath& devPath, + SkMaskFilter* filter, const SkRegion& clip, SkBounder* bounder, + GrPaint* grp, SkPaint::Style style) { SkMask srcM, dstM; if (!SkDraw::DrawToMask(devPath, &clip.getBounds(), filter, &context->getMatrix(), &srcM, @@ -955,9 +815,6 @@ bool drawWithMaskFilter(GrContext* context, const SkPath& devPath, // we now have a device-aligned 8bit mask in dstM, ready to be drawn using // the current clip (and identity matrix) and GrPaint settings - GrContext::AutoMatrix am; - am.setIdentity(context, grp); - GrTextureDesc desc; desc.fWidth = dstM.fBounds.width(); desc.fHeight = dstM.fBounds.height(); @@ -972,28 +829,75 @@ bool drawWithMaskFilter(GrContext* context, const SkPath& devPath, texture->writePixels(0, 0, desc.fWidth, desc.fHeight, desc.fConfig, dstM.fImage, dstM.fRowBytes); - static const int MASK_IDX = GrPaint::kMaxCoverageStages - 1; - // we assume the last mask index is available for use - GrAssert(!grp->isCoverageStageEnabled(MASK_IDX)); + SkRect maskRect = SkRect::MakeFromIRect(dstM.fBounds); - SkMatrix m; - m.setTranslate(-dstM.fBounds.fLeft*SK_Scalar1, -dstM.fBounds.fTop*SK_Scalar1); - m.postIDiv(texture->width(), texture->height()); + return draw_mask(context, maskRect, grp, texture); +} + +// Create a mask of 'devPath' and place the result in 'mask'. Return true on +// success; false otherwise. +bool create_mask_GPU(GrContext* context, + const SkRect& maskRect, + const SkPath& devPath, + const SkStrokeRec& stroke, + bool doAA, + GrAutoScratchTexture* mask) { + GrTextureDesc desc; + desc.fFlags = kRenderTarget_GrTextureFlagBit; + desc.fWidth = SkScalarCeilToInt(maskRect.width()); + desc.fHeight = SkScalarCeilToInt(maskRect.height()); + // We actually only need A8, but it often isn't supported as a + // render target so default to RGBA_8888 + desc.fConfig = kRGBA_8888_GrPixelConfig; + if (context->isConfigRenderable(kAlpha_8_GrPixelConfig)) { + desc.fConfig = kAlpha_8_GrPixelConfig; + } - grp->coverageStage(MASK_IDX)->setEffect(GrSimpleTextureEffect::Create(texture, m))->unref(); - GrRect d; - d.setLTRB(SkIntToScalar(dstM.fBounds.fLeft), - SkIntToScalar(dstM.fBounds.fTop), - SkIntToScalar(dstM.fBounds.fRight), - SkIntToScalar(dstM.fBounds.fBottom)); + mask->set(context, desc); + if (NULL == mask->texture()) { + return false; + } - context->drawRect(*grp, d); + GrTexture* maskTexture = mask->texture(); + SkRect clipRect = SkRect::MakeWH(maskRect.width(), maskRect.height()); + + GrContext::AutoRenderTarget art(context, maskTexture->asRenderTarget()); + GrContext::AutoClip ac(context, clipRect); + + context->clear(NULL, 0x0); + + GrPaint tempPaint; + if (doAA) { + tempPaint.setAntiAlias(true); + // AA uses the "coverage" stages on GrDrawTarget. Coverage with a dst + // blend coeff of zero requires dual source blending support in order + // to properly blend partially covered pixels. This means the AA + // code path may not be taken. So we use a dst blend coeff of ISA. We + // could special case AA draws to a dst surface with known alpha=0 to + // use a zero dst coeff when dual source blending isn't available. + tempPaint.setBlendFunc(kOne_GrBlendCoeff, kISC_GrBlendCoeff); + } + + GrContext::AutoMatrix am; + + // Draw the mask into maskTexture with the path's top-left at the origin using tempPaint. + SkMatrix translate; + translate.setTranslate(-maskRect.fLeft, -maskRect.fTop); + am.set(context, translate); + context->drawPath(tempPaint, devPath, stroke); return true; } +SkBitmap wrap_texture(GrTexture* texture) { + SkBitmap result; + bool dummy; + SkBitmap::Config config = grConfig2skConfig(texture->config(), &dummy); + result.setConfig(config, texture->width(), texture->height()); + result.setPixelRef(SkNEW_ARGS(SkGrPixelRef, (texture)))->unref(); + return result; } -/////////////////////////////////////////////////////////////////////////////// +}; void SkGpuDevice::drawPath(const SkDraw& draw, const SkPath& origSrcPath, const SkPaint& paint, const SkMatrix* prePathMatrix, @@ -1051,6 +955,7 @@ void SkGpuDevice::drawPath(const SkDraw& draw, const SkPath& origSrcPath, if (!stroke.isHairlineStyle()) { if (stroke.applyToPath(&tmpPath, *pathPtr)) { pathPtr = &tmpPath; + pathIsMutable = true; stroke.setFillStyle(); } } @@ -1060,13 +965,46 @@ void SkGpuDevice::drawPath(const SkDraw& draw, const SkPath& origSrcPath, // transform the path into device space pathPtr->transform(fContext->getMatrix(), devPathPtr); - if (!drawWithGPUMaskFilter(fContext, *devPathPtr, stroke, paint.getMaskFilter(), - *draw.fClip, draw.fBounder, &grPaint)) { - SkPaint::Style style = stroke.isHairlineStyle() ? SkPaint::kStroke_Style : - SkPaint::kFill_Style; - drawWithMaskFilter(fContext, *devPathPtr, paint.getMaskFilter(), - *draw.fClip, draw.fBounder, &grPaint, style); + + SkRect maskRect; + if (paint.getMaskFilter()->canFilterMaskGPU(devPathPtr->getBounds(), + draw.fClip->getBounds(), + fContext->getMatrix(), + &maskRect)) { + SkIRect finalIRect; + maskRect.roundOut(&finalIRect); + if (draw.fClip->quickReject(finalIRect)) { + // clipped out + return; + } + if (NULL != draw.fBounder && !draw.fBounder->doIRect(finalIRect)) { + // nothing to draw + return; + } + + GrAutoScratchTexture mask; + + if (create_mask_GPU(fContext, maskRect, *devPathPtr, stroke, + grPaint.isAntiAlias(), &mask)) { + GrTexture* filtered; + + if (paint.getMaskFilter()->filterMaskGPU(mask.texture(), maskRect, &filtered, true)) { + SkAutoTUnref<GrTexture> atu(filtered); + + if (draw_mask(fContext, maskRect, &grPaint, filtered)) { + // This path is completely drawn + return; + } + } + } } + + // draw the mask on the CPU - this is a fallthrough path in case the + // GPU path fails + SkPaint::Style style = stroke.isHairlineStyle() ? SkPaint::kStroke_Style : + SkPaint::kFill_Style; + draw_with_mask_filter(fContext, *devPathPtr, paint.getMaskFilter(), + *draw.fClip, draw.fBounder, &grPaint, style); return; } @@ -1437,15 +1375,6 @@ void SkGpuDevice::internalDrawBitmap(const SkBitmap& bitmap, fContext->drawRectToRect(*grPaint, dstRect, paintRect, &m); } -static SkBitmap wrap_texture(GrTexture* texture) { - SkBitmap result; - bool dummy; - SkBitmap::Config config = grConfig2skConfig(texture->config(), &dummy); - result.setConfig(config, texture->width(), texture->height()); - result.setPixelRef(SkNEW_ARGS(SkGrPixelRef, (texture)))->unref(); - return result; -} - static bool filter_texture(SkDevice* device, GrContext* context, GrTexture* texture, SkImageFilter* filter, int w, int h, SkBitmap* result) { @@ -1608,8 +1537,6 @@ bool SkGpuDevice::filterImage(SkImageFilter* filter, const SkBitmap& src, return false; } - GrPaint paint; - GrTexture* texture; // We assume here that the filter will not attempt to tile the src. Otherwise, this cache lookup // must be pushed upstack. diff --git a/tests/BlurTest.cpp b/tests/BlurTest.cpp index 57dc940a6d..6fb36b6217 100644 --- a/tests/BlurTest.cpp +++ b/tests/BlurTest.cpp @@ -105,18 +105,6 @@ static void test_blur(skiatest::Reporter* reporter) { SkMaskFilter* filter; filter = SkBlurMaskFilter::Create(radius, blurStyle, flags); - SkMaskFilter::BlurInfo info; - sk_bzero(&info, sizeof(info)); - SkMaskFilter::BlurType type = filter->asABlur(&info); - - REPORTER_ASSERT(reporter, type == - static_cast<SkMaskFilter::BlurType>(style + 1)); - REPORTER_ASSERT(reporter, info.fRadius == radius); - REPORTER_ASSERT(reporter, info.fIgnoreTransform == - SkToBool(flags & SkBlurMaskFilter::kIgnoreTransform_BlurFlag)); - REPORTER_ASSERT(reporter, info.fHighQuality == - SkToBool(flags & SkBlurMaskFilter::kHighQuality_BlurFlag)); - paint.setMaskFilter(filter); filter->unref(); diff --git a/tools/PictureRenderer.cpp b/tools/PictureRenderer.cpp index 12a10707a7..2730a23f52 100644 --- a/tools/PictureRenderer.cpp +++ b/tools/PictureRenderer.cpp @@ -64,10 +64,9 @@ public: virtual bool filter(SkPaint* paint, Type t) { paint->setFlags(paint->getFlags() & ~fFlags[t] & SkPaint::kAllFlags); - if (PictureRenderer::kBlur_DrawFilterFlag & fFlags[t]) { + if (PictureRenderer::kMaskFilter_DrawFilterFlag & fFlags[t]) { SkMaskFilter* maskFilter = paint->getMaskFilter(); - SkMaskFilter::BlurInfo blurInfo; - if (maskFilter && maskFilter->asABlur(&blurInfo)) { + if (NULL != maskFilter) { paint->setMaskFilter(NULL); } } diff --git a/tools/PictureRenderer.h b/tools/PictureRenderer.h index cb06679a78..738b812279 100644 --- a/tools/PictureRenderer.h +++ b/tools/PictureRenderer.h @@ -59,13 +59,13 @@ public: // this uses SkPaint::Flags as a base and adds additional flags enum DrawFilterFlags { kNone_DrawFilterFlag = 0, - kBlur_DrawFilterFlag = 0x8000, // toggles between blur and no blur + kMaskFilter_DrawFilterFlag = 0x8000, // toggles on/off mask filters (e.g., blurs) kHinting_DrawFilterFlag = 0x10000, // toggles between no hinting and normal hinting kSlightHinting_DrawFilterFlag = 0x20000, // toggles between slight and normal hinting kAAClip_DrawFilterFlag = 0x40000, // toggles between soft and hard clip }; - SK_COMPILE_ASSERT(!(kBlur_DrawFilterFlag & SkPaint::kAllFlags), blur_flag_must_be_greater); + SK_COMPILE_ASSERT(!(kMaskFilter_DrawFilterFlag & SkPaint::kAllFlags), maskfilter_flag_must_be_greater); SK_COMPILE_ASSERT(!(kHinting_DrawFilterFlag & SkPaint::kAllFlags), hinting_flag_must_be_greater); SK_COMPILE_ASSERT(!(kSlightHinting_DrawFilterFlag & SkPaint::kAllFlags), |