From 7b7cdd147f5528865238e5ed98c79e6d319fde9b Mon Sep 17 00:00:00 2001 From: "bsalomon@google.com" Date: Wed, 7 Nov 2012 16:17:24 +0000 Subject: Some improvements to reduce the number of pixels touched in generating alpha clip masks Review URL: https://codereview.appspot.com/6828043 git-svn-id: http://skia.googlecode.com/svn/trunk@6329 2bbb7eff-a529-9590-31e7-b0007b416f81 --- include/core/SkClipStack.h | 5 ++ include/core/SkMatrix.h | 2 + include/core/SkRandom.h | 2 +- include/core/SkRect.h | 9 +++ src/core/SkClipStack.cpp | 11 +++ src/gpu/GrClipMaskManager.cpp | 109 ++++++++++++++++-------------- src/gpu/GrClipMaskManager.h | 7 +- src/gpu/SkGpuDevice.cpp | 6 +- src/gpu/effects/GrTextureDomainEffect.cpp | 76 +++++++++++++++------ src/gpu/effects/GrTextureDomainEffect.h | 53 ++++++++++++--- 10 files changed, 198 insertions(+), 82 deletions(-) diff --git a/include/core/SkClipStack.h b/include/core/SkClipStack.h index a67e0a5dd9..79fd2c9dd4 100644 --- a/include/core/SkClipStack.h +++ b/include/core/SkClipStack.h @@ -134,6 +134,11 @@ public: fDoAA(false) {} friend bool operator==(const Clip& a, const Clip& b); friend bool operator!=(const Clip& a, const Clip& b); + /** + * Gets the bounds of the clip element, either the rect or path bounds. + */ + const SkRect& getBounds() const; + const SkRect* fRect; // if non-null, this is a rect clip const SkPath* fPath; // if non-null, this is a path clip SkRegion::Op fOp; diff --git a/include/core/SkMatrix.h b/include/core/SkMatrix.h index 980bcaa27a..2d3786cf31 100644 --- a/include/core/SkMatrix.h +++ b/include/core/SkMatrix.h @@ -173,6 +173,8 @@ public: /** Set the matrix to translate by (dx, dy). */ void setTranslate(SkScalar dx, SkScalar dy); + void setTranslate(const SkVector& v) { this->setTranslate(v.fX, v.fY); } + /** Set the matrix to scale by sx and sy, with a pivot point at (px, py). The pivot point is the coordinate that should remain unchanged by the specified transformation. diff --git a/include/core/SkRandom.h b/include/core/SkRandom.h index 0f9b9aa003..4731bbed4b 100644 --- a/include/core/SkRandom.h +++ b/include/core/SkRandom.h @@ -84,7 +84,7 @@ public: in the range [min..max). */ SkScalar nextRangeScalar(SkScalar min, SkScalar max) { - return SkScalarMul(this->nextSScalar1(), (max - min)) + min; + return SkScalarMul(this->nextUScalar1(), (max - min)) + min; } /** Return the next pseudo random number expressed as a SkScalar diff --git a/include/core/SkRect.h b/include/core/SkRect.h index 7210bf9cd6..f5e8c3cc3a 100644 --- a/include/core/SkRect.h +++ b/include/core/SkRect.h @@ -366,6 +366,15 @@ struct SK_API SkRect { return r; } + static SkRect SK_WARN_UNUSED_RESULT MakeFromIRect(const SkIRect& irect) { + SkRect r; + r.set(SkIntToScalar(irect.fLeft), + SkIntToScalar(irect.fTop), + SkIntToScalar(irect.fRight), + SkIntToScalar(irect.fBottom)); + return r; + } + /** * Return true if the rectangle's width or height are <= 0 */ diff --git a/src/core/SkClipStack.cpp b/src/core/SkClipStack.cpp index 0f2d632a84..a5b591e72a 100644 --- a/src/core/SkClipStack.cpp +++ b/src/core/SkClipStack.cpp @@ -724,6 +724,17 @@ bool operator!=(const SkClipStack::Iter::Clip& a, return !(a == b); } +const SkRect& SkClipStack::Iter::Clip::getBounds() const { + if (NULL != fRect) { + return *fRect; + } else if (NULL != fPath) { + return fPath->getBounds(); + } else { + static const SkRect kEmpty = {0, 0, 0, 0}; + return kEmpty; + } +} + SkClipStack::Iter::Iter(const SkClipStack& stack, IterStart startLoc) : fStack(&stack) { this->reset(stack, startLoc); diff --git a/src/gpu/GrClipMaskManager.cpp b/src/gpu/GrClipMaskManager.cpp index 2be92dc24a..606b90eeeb 100644 --- a/src/gpu/GrClipMaskManager.cpp +++ b/src/gpu/GrClipMaskManager.cpp @@ -7,6 +7,7 @@ */ #include "GrClipMaskManager.h" +#include "effects/GrTextureDomainEffect.h" #include "GrGpu.h" #include "GrRenderTarget.h" #include "GrStencilBuffer.h" @@ -42,7 +43,13 @@ void setup_drawstate_aaclip(GrGpu* gpu, mat.preConcat(drawState->getViewMatrix()); drawState->stage(kMaskStage)->reset(); - drawState->createTextureEffect(kMaskStage, result, mat); + + SkIRect domainTexels = SkIRect::MakeWH(devBound.width(), devBound.height()); + drawState->stage(kMaskStage)->setEffect( + GrTextureDomainEffect::Create(result, + mat, + GrTextureDomainEffect::MakeTexelDomain(result, domainTexels), + GrTextureDomainEffect::kDecal_WrapMode))->unref(); } bool path_needs_SW_renderer(GrContext* context, @@ -152,8 +159,7 @@ bool GrClipMaskManager::setupClipping(const GrClipData* clipDataIn) { GrIRect devClipBounds; bool isIntersectionOfRects = false; - clipDataIn->getConservativeBounds(rt, &devClipBounds, - &isIntersectionOfRects); + clipDataIn->getConservativeBounds(rt, &devClipBounds, &isIntersectionOfRects); if (devClipBounds.isEmpty()) { return false; } @@ -483,25 +489,31 @@ bool GrClipMaskManager::drawClipShape(GrTexture* target, return true; } -void GrClipMaskManager::drawTexture(GrTexture* target, - GrTexture* texture) { +void GrClipMaskManager::mergeMask(GrTexture* dstMask, + GrTexture* srcMask, + SkRegion::Op op, + const GrIRect& dstBound, + const GrIRect& srcBound) { GrDrawState* drawState = fGpu->drawState(); GrAssert(NULL != drawState); + SkMatrix oldMatrix = drawState->getViewMatrix(); + drawState->viewMatrix()->reset(); - // no AA here since it is encoded in the texture - drawState->setRenderTarget(target->asRenderTarget()); + drawState->setRenderTarget(dstMask->asRenderTarget()); + setup_boolean_blendcoeffs(drawState, op); + SkMatrix sampleM; - sampleM.setIDiv(texture->width(), texture->height()); - - drawState->createTextureEffect(0, texture, sampleM); - - GrRect rect = GrRect::MakeWH(SkIntToScalar(target->width()), - SkIntToScalar(target->height())); - - fGpu->drawSimpleRect(rect, NULL); + sampleM.setIDiv(srcMask->width(), srcMask->height()); + drawState->stage(0)->setEffect( + GrTextureDomainEffect::Create(srcMask, + sampleM, + GrTextureDomainEffect::MakeTexelDomain(srcMask, srcBound), + GrTextureDomainEffect::kDecal_WrapMode))->unref(); + fGpu->drawSimpleRect(SkRect::MakeFromIRect(dstBound), NULL); drawState->disableStage(0); + drawState->setViewMatrix(oldMatrix); } // get a texture to act as a temporary buffer for AA clip boolean operations @@ -599,14 +611,18 @@ bool GrClipMaskManager::createAlphaClipMask(const GrClipData& clipDataIn, GrDrawTarget::AutoGeometryPush agp(fGpu); - if (0 != devResultBounds->fTop || 0 != devResultBounds->fLeft || - 0 != clipDataIn.fOrigin.fX || 0 != clipDataIn.fOrigin.fY) { - // if we were able to trim down the size of the mask we need to - // offset the paths & rects that will be used to compute it - drawState->viewMatrix()->setTranslate( - SkIntToScalar(-devResultBounds->fLeft-clipDataIn.fOrigin.fX), - SkIntToScalar(-devResultBounds->fTop-clipDataIn.fOrigin.fY)); - } + // The mask we generate is translated so that its upper-left corner is at devResultBounds + // upper-left corner in device space. + GrIRect maskResultBounds = GrIRect::MakeWH(devResultBounds->width(), devResultBounds->height()); + + // Set the matrix so that rendered clip elements are transformed from the space of the clip + // stack to the alpha-mask. This accounts for both translation due to the clip-origin and the + // placement of the mask within the device. + SkVector clipToMaskOffset = { + SkIntToScalar(-devResultBounds->fLeft - clipDataIn.fOrigin.fX), + SkIntToScalar(-devResultBounds->fTop - clipDataIn.fOrigin.fY) + }; + drawState->viewMatrix()->setTranslate(clipToMaskOffset); bool clearToInside; SkRegion::Op firstOp = SkRegion::kReplace_Op; // suppress warning @@ -618,10 +634,12 @@ bool GrClipMaskManager::createAlphaClipMask(const GrClipData& clipDataIn, &clearToInside, &firstOp, clipDataIn); - - fGpu->clear(NULL, + // The scratch texture that we are drawing into can be substantially larger than the mask. Only + // clear the part that we care about. + fGpu->clear(&maskResultBounds, clearToInside ? 0xffffffff : 0x00000000, accum->asRenderTarget()); + bool accumClearedToZero = !clearToInside; GrAutoScratchTexture temp; bool first = true; @@ -636,8 +654,10 @@ bool GrClipMaskManager::createAlphaClipMask(const GrClipData& clipDataIn, if (SkRegion::kReplace_Op == op) { // clear the accumulator and draw the new object directly into it - fGpu->clear(NULL, 0x00000000, accum->asRenderTarget()); - + if (!accumClearedToZero) { + fGpu->clear(&maskResultBounds, 0x00000000, accum->asRenderTarget()); + } + setup_boolean_blendcoeffs(drawState, op); this->drawClipShape(accum, clip, *devResultBounds); @@ -655,40 +675,31 @@ bool GrClipMaskManager::createAlphaClipMask(const GrClipData& clipDataIn, return false; } + // this is the bounds of the clip element in the space of the alpha-mask. The temporary + // mask buffer can be substantially larger than the actually clip stack element. We + // touch the minimum number of pixels necessary and use decal mode to combine it with + // the accumulator + GrRect elementMaskBounds = clip->getBounds(); + elementMaskBounds.offset(clipToMaskOffset); + GrIRect elementMaskIBounds; + elementMaskBounds.roundOut(&elementMaskIBounds); + // clear the temp target & draw into it - fGpu->clear(NULL, 0x00000000, temp.texture()->asRenderTarget()); + fGpu->clear(&elementMaskIBounds, 0x00000000, temp.texture()->asRenderTarget()); setup_boolean_blendcoeffs(drawState, SkRegion::kReplace_Op); - this->drawClipShape(temp.texture(), clip, *devResultBounds); - - // TODO: rather than adding these two translations here - // compute the bounding box needed to render the texture - // into temp - if (0 != devResultBounds->fTop || 0 != devResultBounds->fLeft || - 0 != clipDataIn.fOrigin.fX || 0 != clipDataIn.fOrigin.fY) { - // In order for the merge of the temp clip into the accumulator - // to work we need to disable the translation - drawState->viewMatrix()->reset(); - } + this->drawClipShape(temp.texture(), clip, elementMaskIBounds); // Now draw into the accumulator using the real operation // and the temp buffer as a texture - setup_boolean_blendcoeffs(drawState, op); - this->drawTexture(accum, temp.texture()); - - if (0 != devResultBounds->fTop || 0 != devResultBounds->fLeft || - 0 != clipDataIn.fOrigin.fX || 0 != clipDataIn.fOrigin.fY) { - drawState->viewMatrix()->setTranslate( - SkIntToScalar(-devResultBounds->fLeft-clipDataIn.fOrigin.fX), - SkIntToScalar(-devResultBounds->fTop-clipDataIn.fOrigin.fY)); - } - + this->mergeMask(accum, temp.texture(), op, maskResultBounds, elementMaskIBounds); } else { // all the remaining ops can just be directly draw into // the accumulation buffer setup_boolean_blendcoeffs(drawState, op); this->drawClipShape(accum, clip, *devResultBounds); } + accumClearedToZero = false; } *result = accum; diff --git a/src/gpu/GrClipMaskManager.h b/src/gpu/GrClipMaskManager.h index d23a525e7a..89b1ef8249 100644 --- a/src/gpu/GrClipMaskManager.h +++ b/src/gpu/GrClipMaskManager.h @@ -128,8 +128,11 @@ private: const SkClipStack::Iter::Clip* clip, const GrIRect& resultBounds); - void drawTexture(GrTexture* target, - GrTexture* texture); + void mergeMask(GrTexture* dstMask, + GrTexture* srcMask, + SkRegion::Op op, + const GrIRect& dstBound, + const GrIRect& srcBound); void getTemp(const GrIRect& bounds, GrAutoScratchTexture* temp); diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp index 14eeace563..2d79991b3f 100644 --- a/src/gpu/SkGpuDevice.cpp +++ b/src/gpu/SkGpuDevice.cpp @@ -1435,7 +1435,11 @@ void SkGpuDevice::internalDrawBitmap(const SkBitmap& bitmap, top = bottom = SkScalarHalf(paintRect.top() + paintRect.bottom()); } textureDomain.setLTRB(left, top, right, bottom); - effect.reset(SkNEW_ARGS(GrTextureDomainEffect, (texture, textureDomain, params))); + effect.reset(GrTextureDomainEffect::Create(texture, + SkMatrix::I(), + textureDomain, + GrTextureDomainEffect::kClamp_WrapMode, + params.isBilerp())); } else { effect.reset(SkNEW_ARGS(GrSingleTextureEffect, (texture, params))); } diff --git a/src/gpu/effects/GrTextureDomainEffect.cpp b/src/gpu/effects/GrTextureDomainEffect.cpp index 823b0722a1..e500fad4a8 100644 --- a/src/gpu/effects/GrTextureDomainEffect.cpp +++ b/src/gpu/effects/GrTextureDomainEffect.cpp @@ -43,28 +43,40 @@ GrGLTextureDomainEffect::GrGLTextureDomainEffect(const GrBackendEffectFactory& f } void GrGLTextureDomainEffect::emitCode(GrGLShaderBuilder* builder, - const GrEffectStage&, + const GrEffectStage& stage, EffectKey key, const char* vertexCoords, const char* outputColor, const char* inputColor, const TextureSamplerArray& samplers) { + const GrTextureDomainEffect& effect = + static_cast(*stage.getEffect()); + const char* coords; fEffectMatrix.emitCodeMakeFSCoords2D(builder, key, vertexCoords, &coords); + const char* domain; fNameUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType, - kVec4f_GrSLType, "TexDom"); - - builder->fFSCode.appendf("\tvec2 clampCoord = clamp(%s, %s.xy, %s.zw);\n", - coords, - builder->getUniformCStr(fNameUni), - builder->getUniformCStr(fNameUni)); - - builder->fFSCode.appendf("\t%s = ", outputColor); - builder->appendTextureLookupAndModulate(&builder->fFSCode, - inputColor, - samplers[0], - "clampCoord"); - builder->fFSCode.append(";\n"); + kVec4f_GrSLType, "TexDom", &domain); + if (GrTextureDomainEffect::kClamp_WrapMode == effect.wrapMode()) { + + builder->fFSCode.appendf("\tvec2 clampCoord = clamp(%s, %s.xy, %s.zw);\n", + coords, domain, domain); + + builder->fFSCode.appendf("\t%s = ", outputColor); + builder->appendTextureLookupAndModulate(&builder->fFSCode, + inputColor, + samplers[0], + "clampCoord"); + builder->fFSCode.append(";\n"); + } else { + GrAssert(GrTextureDomainEffect::kDecal_WrapMode == effect.wrapMode()); + builder->fFSCode.append("\tbvec4 outside;\n"); + builder->fFSCode.appendf("\toutside.xy = lessThan(%s, %s.xy);\n", coords, domain); + builder->fFSCode.appendf("\toutside.zw = greaterThan(%s, %s.zw);\n", coords, domain); + builder->fFSCode.appendf("\t%s = any(outside) ? vec4(0.0, 0.0, 0.0, 0.0) : ", outputColor); + builder->appendTextureLookupAndModulate(&builder->fFSCode, inputColor, samplers[0], coords); + builder->fFSCode.append(";\n"); + } } void GrGLTextureDomainEffect::setData(const GrGLUniformManager& uman, const GrEffectStage& stage) { @@ -98,21 +110,41 @@ void GrGLTextureDomainEffect::setData(const GrGLUniformManager& uman, const GrEf GrGLEffect::EffectKey GrGLTextureDomainEffect::GenKey(const GrEffectStage& stage, const GrGLCaps&) { const GrTextureDomainEffect& effect = static_cast(*stage.getEffect()); - return GrGLEffectMatrix::GenKey(effect.getMatrix(), stage.getCoordChangeMatrix(), effect.texture(0)); + EffectKey key = effect.wrapMode(); + key <<= GrGLEffectMatrix::kKeyBits; + EffectKey matrixKey = GrGLEffectMatrix::GenKey(effect.getMatrix(), + stage.getCoordChangeMatrix(), + effect.texture(0)); + return key | matrixKey; } /////////////////////////////////////////////////////////////////////////////// -GrTextureDomainEffect::GrTextureDomainEffect(GrTexture* texture, const GrRect& domain) - : GrSingleTextureEffect(texture) - , fTextureDomain(domain) { +GrEffect* GrTextureDomainEffect::Create(GrTexture* texture, + const SkMatrix& matrix, + const GrRect& domain, + WrapMode wrapMode, + bool bilerp) { + static const SkRect kFullRect = {0, 0, SK_Scalar1, SK_Scalar1}; + if (kClamp_WrapMode == wrapMode && domain.contains(kFullRect)) { + return SkNEW_ARGS(GrSingleTextureEffect, (texture, matrix, bilerp)); + } else { + SkRect clippedDomain; + // We don't currently handle domains that are empty or don't intersect the texture. + SkAssertResult(clippedDomain.intersect(kFullRect, domain)); + return SkNEW_ARGS(GrTextureDomainEffect, + (texture, matrix, clippedDomain, wrapMode, bilerp)); + } } GrTextureDomainEffect::GrTextureDomainEffect(GrTexture* texture, + const SkMatrix& matrix, const GrRect& domain, - const GrTextureParams& params) - : GrSingleTextureEffect(texture, params) + WrapMode wrapMode, + bool bilerp) + : GrSingleTextureEffect(texture, matrix, bilerp) + , fWrapMode(wrapMode) , fTextureDomain(domain) { } @@ -143,5 +175,7 @@ GrEffect* GrTextureDomainEffect::TestCreate(SkRandom* random, domain.fRight = random->nextRangeScalar(domain.fLeft, SK_Scalar1); domain.fTop = random->nextUScalar1(); domain.fBottom = random->nextRangeScalar(domain.fTop, SK_Scalar1); - return SkNEW_ARGS(GrTextureDomainEffect, (textures[texIdx], domain)); + WrapMode wrapMode = random->nextBool() ? kClamp_WrapMode : kDecal_WrapMode; + const SkMatrix& matrix = GrEffectUnitTest::TestMatrix(random); + return GrTextureDomainEffect::Create(textures[texIdx], matrix, domain, wrapMode); } diff --git a/src/gpu/effects/GrTextureDomainEffect.h b/src/gpu/effects/GrTextureDomainEffect.h index 322f8d05cf..c1ce6d11a6 100644 --- a/src/gpu/effects/GrTextureDomainEffect.h +++ b/src/gpu/effects/GrTextureDomainEffect.h @@ -14,15 +14,31 @@ class GrGLTextureDomainEffect; /** - * Limits a texture's lookup coordinates to a domain. + * Limits a texture's lookup coordinates to a domain. Samples outside the domain are either clamped + * the edge of the domain or result in a vec4 of zeros. The domain is clipped to normalized texture + * coords ([0,1]x[0,1] square). Bilinear filtering can cause texels outside the domain to affect the + * read value unless the caller considers this when calculating the domain. TODO: This should be a + * helper that can assist an effect rather than effect unto itself. */ class GrTextureDomainEffect : public GrSingleTextureEffect { public: - /** Uses default texture params (no filter, clamp) */ - GrTextureDomainEffect(GrTexture*, const GrRect& domain); - - GrTextureDomainEffect(GrTexture*, const GrRect& domain, const GrTextureParams& params); + /** + * If SkShader::kDecal_TileMode sticks then this enum could be replaced by SkShader::TileMode. + * We could also consider replacing/augmenting Decal mode with Border mode where the color + * outside of the domain is user-specifiable. Decal mode currently has a hard (non-lerped) + * transition between the border and the interior. + */ + enum WrapMode { + kClamp_WrapMode, + kDecal_WrapMode, + }; + + static GrEffect* Create(GrTexture*, + const SkMatrix&, + const SkRect& domain, + WrapMode, + bool bilerp = false); virtual ~GrTextureDomainEffect(); @@ -33,13 +49,34 @@ public: virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE; virtual bool isEqual(const GrEffect&) const SK_OVERRIDE; - const GrRect& domain() const { return fTextureDomain; } + const SkRect& domain() const { return fTextureDomain; } + WrapMode wrapMode() const { return fWrapMode; } + + /* Computes a domain that bounds all the texels in texelRect. Note that with bilerp enabled + texels neighboring the domain may be read. */ + static const SkRect MakeTexelDomain(const GrTexture* texture, const SkIRect& texelRect) { + SkScalar wInv = SK_Scalar1 / texture->width(); + SkScalar hInv = SK_Scalar1 / texture->height(); + SkRect result = { + texelRect.fLeft * wInv, + texelRect.fTop * hInv, + texelRect.fRight * wInv, + texelRect.fBottom * hInv + }; + return result; + } protected: - - GrRect fTextureDomain; + WrapMode fWrapMode; + SkRect fTextureDomain; private: + GrTextureDomainEffect(GrTexture*, + const SkMatrix&, + const GrRect& domain, + WrapMode, + bool bilerp); + GR_DECLARE_EFFECT_TEST; typedef GrSingleTextureEffect INHERITED; -- cgit v1.2.3