//////////////////////////////////////////////////////////////////////////////// // return true on success; false on failure bool GrSoftwarePathRenderer::onDrawPath(const SkPath& path, GrPathFill fill, const GrVec* translate, GrDrawTarget* target, GrDrawState::StageMask stageMask, bool antiAlias) { if (NULL == fContext) { return false; } GrAutoScratchTexture ast; GrIRect pathBounds, clipBounds; if (!get_path_and_clip_bounds(target, path, translate, &pathBounds, &clipBounds)) { return true; // path is empty so there is nothing to do } if (sw_draw_path_to_mask_texture(path, pathBounds, fill, fContext, translate, &ast, antiAlias)) { GrTexture* texture = ast.texture(); GrAssert(NULL != texture); GrDrawTarget::AutoDeviceCoordDraw adcd(target, stageMask); enum { // the SW path renderer shares this stage with glyph // rendering (kGlyphMaskStage in GrBatchedTextContext) kPathMaskStage = GrPaint::kTotalStages, }; GrAssert(NULL == target->drawState()->getTexture(kPathMaskStage)); target->drawState()->setTexture(kPathMaskStage, texture); target->drawState()->sampler(kPathMaskStage)->reset(); GrScalar w = GrIntToScalar(pathBounds.width()); GrScalar h = GrIntToScalar(pathBounds.height()); GrRect maskRect = GrRect::MakeWH(w / texture->width(), h / texture->height()); const GrRect* srcRects[GrDrawState::kNumStages] = {NULL}; srcRects[kPathMaskStage] = &maskRect; stageMask |= 1 << kPathMaskStage; GrRect dstRect = GrRect::MakeLTRB( SK_Scalar1* pathBounds.fLeft, SK_Scalar1* pathBounds.fTop, SK_Scalar1* pathBounds.fRight, SK_Scalar1* pathBounds.fBottom); target->drawRect(dstRect, NULL, stageMask, srcRects, NULL); target->drawState()->setTexture(kPathMaskStage, NULL); if (GrIsFillInverted(fill)) { draw_around_inv_path(target, stageMask, clipBounds, pathBounds); } return true; } return false; }
/** * Software rasterizes path to A8 mask (possibly using the context's matrix) * and uploads the result to a scratch texture. Returns the resulting * texture on success; NULL on failure. */ GrTexture* GrSWMaskHelper::DrawPathMaskToTexture(GrContext* context, const SkPath& path, const SkStrokeRec& stroke, const SkIRect& resultBounds, bool antiAlias, SkMatrix* matrix) { GrSWMaskHelper helper(context); if (!helper.init(resultBounds, matrix)) { return NULL; } helper.draw(path, stroke, SkRegion::kReplace_Op, antiAlias, 0xFF); GrAutoScratchTexture ast; if (!helper.getTexture(&ast)) { return NULL; } helper.toTexture(ast.texture()); return ast.detach(); }
/** * Software rasterizes path to A8 mask (possibly using the context's matrix) * and uploads the result to a scratch texture. Returns the resulting * texture on success; NULL on failure. */ GrTexture* GrSWMaskHelper::DrawPathMaskToTexture(GrContext* context, const SkPath& path, const GrIRect& resultBounds, GrPathFill fill, bool antiAlias, GrMatrix* matrix) { GrAutoScratchTexture ast; GrSWMaskHelper helper(context); if (!helper.init(resultBounds, matrix)) { return NULL; } helper.draw(path, SkRegion::kReplace_Op, fill, antiAlias, 0xFF); if (!helper.getTexture(&ast)) { return NULL; } helper.toTexture(ast.texture(), 0x00); return ast.detach(); }
//////////////////////////////////////////////////////////////////////////////// // 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; }
//////////////////////////////////////////////////////////////////////////////// // 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; }
//////////////////////////////////////////////////////////////////////////////// // Create a 8-bit clip mask in alpha bool GrClipMaskManager::createAlphaClipMask(GrGpu* gpu, const GrClip& clipIn, GrTexture** result, GrIRect *resultBounds) { if (this->clipMaskPreamble(gpu, clipIn, result, resultBounds)) { return true; } GrTexture* accum = fAACache.getLastMask(); if (NULL == accum) { fClipMaskInAlpha = false; fAACache.reset(); return false; } GrDrawTarget::AutoStateRestore asr(gpu, GrDrawTarget::kReset_ASRInit); GrDrawState* drawState = gpu->drawState(); GrDrawTarget::AutoGeometryPush agp(gpu); int count = clipIn.getElementCount(); if (0 != resultBounds->fTop || 0 != resultBounds->fLeft) { // 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 GrMatrix m; m.setTranslate(SkIntToScalar(-resultBounds->fLeft), SkIntToScalar(-resultBounds->fTop)); drawState->setViewMatrix(m); } bool clearToInside; SkRegion::Op startOp = SkRegion::kReplace_Op; // suppress warning int start = process_initial_clip_elements(clipIn, *resultBounds, &clearToInside, &startOp); clear(gpu, accum, clearToInside ? 0xffffffff : 0x00000000); GrAutoScratchTexture temp; // walk through each clip element and perform its set op for (int c = start; c < count; ++c) { SkRegion::Op op = (c == start) ? startOp : clipIn.getOp(c); if (SkRegion::kReplace_Op == op) { // TODO: replace is actually a lot faster then intersection // for this path - refactor the stencil path so it can handle // replace ops and alter GrClip to allow them through // clear the accumulator and draw the new object directly into it clear(gpu, accum, 0x00000000); setup_boolean_blendcoeffs(drawState, op); this->drawClipShape(gpu, accum, clipIn, c); } 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 && kRect_ClipType == clipIn.getElementType(c) && contains(clipIn.getRect(c), *resultBounds)) { continue; } getTemp(*resultBounds, &temp); if (NULL == temp.texture()) { fClipMaskInAlpha = false; fAACache.reset(); return false; } // clear the temp target & draw into it clear(gpu, temp.texture(), 0x00000000); setup_boolean_blendcoeffs(drawState, SkRegion::kReplace_Op); this->drawClipShape(gpu, temp.texture(), clipIn, c); // TODO: rather than adding these two translations here // compute the bounding box needed to render the texture // into temp if (0 != resultBounds->fTop || 0 != resultBounds->fLeft) { GrMatrix m; m.setTranslate(SkIntToScalar(resultBounds->fLeft), SkIntToScalar(resultBounds->fTop)); drawState->preConcatViewMatrix(m); } // Now draw into the accumulator using the real operation // and the temp buffer as a texture setup_boolean_blendcoeffs(drawState, op); this->drawTexture(gpu, accum, temp.texture()); if (0 != resultBounds->fTop || 0 != resultBounds->fLeft) { GrMatrix m; m.setTranslate(SkIntToScalar(-resultBounds->fLeft), SkIntToScalar(-resultBounds->fTop)); drawState->preConcatViewMatrix(m); } } else { // all the remaining ops can just be directly draw into // the accumulation buffer setup_boolean_blendcoeffs(drawState, op); this->drawClipShape(gpu, accum, clipIn, c); } } *result = accum; return true; }
//////////////////////////////////////////////////////////////////////////////// // 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); 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)); } 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); fGpu->clear(NULL, clearToInside ? 0xffffffff : 0x00000000, accum->asRenderTarget()); GrAutoScratchTexture temp; bool first = true; // walk through each clip element and perform its set op for ( ; NULL != clip; clip = iter.next()) { 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 fGpu->clear(NULL, 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; } // clear the temp target & draw into it fGpu->clear(NULL, 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(); } // 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)); } } else { // all the remaining ops can just be directly draw into // the accumulation buffer setup_boolean_blendcoeffs(drawState, op); this->drawClipShape(accum, clip, *devResultBounds); } } *result = accum; fCurrClipMaskType = kAlpha_ClipMaskType; return true; }