//////////////////////////////////////////////////////////////////////////////// // Create a 8-bit clip mask in alpha GrTexture* GrClipMaskManager::createAlphaClipMask(int32_t clipStackGenID, InitialState initialState, const ElementList& elements, const SkIRect& clipSpaceIBounds) { GrAssert(kNone_ClipMaskType == fCurrClipMaskType); GrTexture* result; if (this->getMaskTexture(clipStackGenID, clipSpaceIBounds, &result)) { fCurrClipMaskType = kAlpha_ClipMaskType; return result; } if (NULL == result) { fAACache.reset(); return NULL; } GrDrawTarget::AutoGeometryAndStatePush agasp(fGpu, GrDrawTarget::kReset_ASRInit); GrDrawState* drawState = fGpu->drawState(); // The top-left of the mask corresponds to the top-left corner of the bounds. SkVector clipToMaskOffset = { SkIntToScalar(-clipSpaceIBounds.fLeft), SkIntToScalar(-clipSpaceIBounds.fTop) }; // The texture may be larger than necessary, this rect represents the part of the texture // we populate with a rasterization of the clip. SkIRect maskSpaceIBounds = SkIRect::MakeWH(clipSpaceIBounds.width(), clipSpaceIBounds.height()); // We're drawing a coverage mask and want coverage to be run through the blend function. drawState->enableState(GrDrawState::kCoverageDrawing_StateBit); // Set the matrix so that rendered clip elements are transformed to mask space from clip space. drawState->viewMatrix()->setTranslate(clipToMaskOffset); // 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(&maskSpaceIBounds, kAllIn_InitialState == initialState ? 0xffffffff : 0x00000000, result->asRenderTarget()); // When we use the stencil in the below loop it is important to have this clip installed. // The second pass that zeros the stencil buffer renders the rect maskSpaceIBounds so the first // pass must not set values outside of this bounds or stencil values outside the rect won't be // cleared. GrDrawTarget::AutoClipRestore acr(fGpu, maskSpaceIBounds); drawState->enableState(GrDrawState::kClip_StateBit); GrAutoScratchTexture temp; // walk through each clip element and perform its set op for (ElementList::Iter iter = elements.headIter(); iter.get(); iter.next()) { const Element* element = iter.get(); SkRegion::Op op = element->getOp(); bool invert = element->isInverseFilled(); if (invert || SkRegion::kIntersect_Op == op || SkRegion::kReverseDifference_Op == op) { GrPathRenderer* pr = NULL; bool useTemp = !this->canStencilAndDrawElement(result, element, &pr); GrTexture* dst; // 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. GrIRect maskSpaceElementIBounds; if (useTemp) { if (invert) { maskSpaceElementIBounds = maskSpaceIBounds; } else { GrRect elementBounds = element->getBounds(); elementBounds.offset(clipToMaskOffset); elementBounds.roundOut(&maskSpaceElementIBounds); } this->getTemp(maskSpaceIBounds.fRight, maskSpaceIBounds.fBottom, &temp); if (NULL == temp.texture()) { fAACache.reset(); return NULL; } dst = temp.texture(); // clear the temp target and set blend to replace fGpu->clear(&maskSpaceElementIBounds, invert ? 0xffffffff : 0x00000000, dst->asRenderTarget()); setup_boolean_blendcoeffs(drawState, SkRegion::kReplace_Op); } else { // draw directly into the result with the stencil set to make the pixels affected // by the clip shape be non-zero. dst = result; GR_STATIC_CONST_SAME_STENCIL(kStencilInElement, kReplace_StencilOp, kReplace_StencilOp, kAlways_StencilFunc, 0xffff, 0xffff, 0xffff); drawState->setStencil(kStencilInElement); setup_boolean_blendcoeffs(drawState, op); } drawState->setAlpha(invert ? 0x00 : 0xff); if (!this->drawElement(dst, element, pr)) { fAACache.reset(); return NULL; } if (useTemp) { // Now draw into the accumulator using the real operation and the temp buffer as a // texture this->mergeMask(result, temp.texture(), op, maskSpaceIBounds, maskSpaceElementIBounds); } else { // Draw to the exterior pixels (those with a zero stencil value). drawState->setAlpha(invert ? 0xff : 0x00); GR_STATIC_CONST_SAME_STENCIL(kDrawOutsideElement, kZero_StencilOp, kZero_StencilOp, kEqual_StencilFunc, 0xffff, 0x0000, 0xffff); drawState->setStencil(kDrawOutsideElement); fGpu->drawSimpleRect(clipSpaceIBounds); drawState->disableStencil(); } } else { // all the remaining ops can just be directly draw into the accumulation buffer drawState->setAlpha(0xff); setup_boolean_blendcoeffs(drawState, op); this->drawElement(result, element); } } fCurrClipMaskType = kAlpha_ClipMaskType; return result; }
void GrDefaultPathRenderer::onDrawPath(GrDrawTarget::StageBitfield stages, bool stencilOnly) { GrMatrix viewM = fTarget->getViewMatrix(); GrScalar tol = GR_Scalar1; tol = GrPathUtils::scaleToleranceToSrc(tol, viewM, fPath->getBounds()); // FIXME: It's really dumb that we recreate the verts for a new vertex // layout. We only do that because the GrDrawTarget API doesn't allow // us to change the vertex layout after reserveVertexSpace(). We won't // actually change the vertex data when the layout changes since all the // stages reference the positions (rather than having separate tex coords) // and we don't ever have per-vert colors. In practice our call sites // won't change the stages in use inside a setPath / removePath pair. But // it is a silly limitation of the GrDrawTarget design that should be fixed. if (tol != fPreviousSrcTol || stages != fPreviousStages) { if (!this->createGeom(tol, stages)) { return; } } GrAssert(NULL != fTarget); GrDrawTarget::AutoStateRestore asr(fTarget); bool colorWritesWereDisabled = fTarget->isColorWriteDisabled(); // face culling doesn't make sense here GrAssert(GrDrawTarget::kBoth_DrawFace == fTarget->getDrawFace()); int passCount = 0; const GrStencilSettings* passes[3]; GrDrawTarget::DrawFace drawFace[3]; bool reverse = false; bool lastPassIsBounds; if (kHairLine_PathFill == fFill) { passCount = 1; if (stencilOnly) { passes[0] = &gDirectToStencil; } else { passes[0] = NULL; } lastPassIsBounds = false; drawFace[0] = GrDrawTarget::kBoth_DrawFace; } else { if (single_pass_path(*fTarget, *fPath, fFill)) { passCount = 1; if (stencilOnly) { passes[0] = &gDirectToStencil; } else { passes[0] = NULL; } drawFace[0] = GrDrawTarget::kBoth_DrawFace; lastPassIsBounds = false; } else { switch (fFill) { case kInverseEvenOdd_PathFill: reverse = true; // fallthrough case kEvenOdd_PathFill: passes[0] = &gEOStencilPass; if (stencilOnly) { passCount = 1; lastPassIsBounds = false; } else { passCount = 2; lastPassIsBounds = true; if (reverse) { passes[1] = &gInvEOColorPass; } else { passes[1] = &gEOColorPass; } } drawFace[0] = drawFace[1] = GrDrawTarget::kBoth_DrawFace; break; case kInverseWinding_PathFill: reverse = true; // fallthrough case kWinding_PathFill: if (fSeparateStencil) { if (fStencilWrapOps) { passes[0] = &gWindStencilSeparateWithWrap; } else { passes[0] = &gWindStencilSeparateNoWrap; } passCount = 2; drawFace[0] = GrDrawTarget::kBoth_DrawFace; } else { if (fStencilWrapOps) { passes[0] = &gWindSingleStencilWithWrapInc; passes[1] = &gWindSingleStencilWithWrapDec; } else { passes[0] = &gWindSingleStencilNoWrapInc; passes[1] = &gWindSingleStencilNoWrapDec; } // which is cw and which is ccw is arbitrary. drawFace[0] = GrDrawTarget::kCW_DrawFace; drawFace[1] = GrDrawTarget::kCCW_DrawFace; passCount = 3; } if (stencilOnly) { lastPassIsBounds = false; --passCount; } else { lastPassIsBounds = true; drawFace[passCount-1] = GrDrawTarget::kBoth_DrawFace; if (reverse) { passes[passCount-1] = &gInvWindColorPass; } else { passes[passCount-1] = &gWindColorPass; } } break; default: GrAssert(!"Unknown path fFill!"); return; } } } { for (int p = 0; p < passCount; ++p) { fTarget->setDrawFace(drawFace[p]); if (NULL != passes[p]) { fTarget->setStencil(*passes[p]); } if (lastPassIsBounds && (p == passCount-1)) { if (!colorWritesWereDisabled) { fTarget->disableState(GrDrawTarget::kNoColorWrites_StateBit); } GrRect bounds; if (reverse) { GrAssert(NULL != fTarget->getRenderTarget()); // draw over the whole world. bounds.setLTRB(0, 0, GrIntToScalar(fTarget->getRenderTarget()->width()), GrIntToScalar(fTarget->getRenderTarget()->height())); GrMatrix vmi; // mapRect through persp matrix may not be correct if (!fTarget->getViewMatrix().hasPerspective() && fTarget->getViewInverse(&vmi)) { vmi.mapRect(&bounds); } else { if (stages) { if (!fTarget->getViewInverse(&vmi)) { GrPrintf("Could not invert matrix."); return; } fTarget->preConcatSamplerMatrices(stages, vmi); } fTarget->setViewMatrix(GrMatrix::I()); } } else { bounds = fPath->getBounds(); bounds.offset(fTranslate); } GrDrawTarget::AutoGeometryPush agp(fTarget); fTarget->drawSimpleRect(bounds, NULL, stages); } else { if (passCount > 1) { fTarget->enableState(GrDrawTarget::kNoColorWrites_StateBit); } if (fUseIndexedDraw) { fTarget->drawIndexed(fPrimitiveType, 0, 0, fVertexCnt, fIndexCnt); } else { int baseVertex = 0; for (int sp = 0; sp < fSubpathCount; ++sp) { fTarget->drawNonIndexed(fPrimitiveType, baseVertex, fSubpathVertCount[sp]); baseVertex += fSubpathVertCount[sp]; } } } } } }
//////////////////////////////////////////////////////////////////////////////// // Create a 8-bit clip mask in alpha bool GrClipMaskManager::createAlphaClipMask(const GrClipData& clipDataIn, GrTexture** result, GrIRect *devResultBounds) { GrAssert(NULL != devResultBounds); GrAssert(kNone_ClipMaskType == fCurrClipMaskType); if (this->clipMaskPreamble(clipDataIn, result, devResultBounds)) { fCurrClipMaskType = kAlpha_ClipMaskType; return true; } // Note: 'resultBounds' is in device (as opposed to canvas) coordinates GrTexture* accum = fAACache.getLastMask(); if (NULL == accum) { fAACache.reset(); return false; } GrDrawTarget::AutoStateRestore asr(fGpu, GrDrawTarget::kReset_ASRInit); GrDrawState* drawState = fGpu->drawState(); GrDrawTarget::AutoGeometryPush agp(fGpu); // 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 SkClipStack::Iter iter(*clipDataIn.fClipStack, SkClipStack::Iter::kBottom_IterStart); const SkClipStack::Iter::Clip* clip = process_initial_clip_elements(&iter, *devResultBounds, &clearToInside, &firstOp, clipDataIn); // 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; // walk through each clip element and perform its set op for ( ; NULL != clip; clip = iter.nextCombined()) { SkRegion::Op op = clip->fOp; if (first) { first = false; op = firstOp; } if (SkRegion::kReplace_Op == op) { // clear the accumulator and draw the new object directly into it if (!accumClearedToZero) { fGpu->clear(&maskResultBounds, 0x00000000, accum->asRenderTarget()); } setup_boolean_blendcoeffs(drawState, op); this->drawClipShape(accum, clip, *devResultBounds); } else if (SkRegion::kReverseDifference_Op == op || SkRegion::kIntersect_Op == op) { // there is no point in intersecting a screen filling rectangle. if (SkRegion::kIntersect_Op == op && NULL != clip->fRect && contains(*clip->fRect, *devResultBounds, clipDataIn.fOrigin)) { continue; } getTemp(*devResultBounds, &temp); if (NULL == temp.texture()) { fAACache.reset(); 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(&elementMaskIBounds, 0x00000000, temp.texture()->asRenderTarget()); setup_boolean_blendcoeffs(drawState, SkRegion::kReplace_Op); this->drawClipShape(temp.texture(), clip, elementMaskIBounds); // Now draw into the accumulator using the real operation // and the temp buffer as a texture 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; fCurrClipMaskType = kAlpha_ClipMaskType; return true; }