//////////////////////////////////////////////////////////////////////////////// // Shared preamble between gpu and SW-only AA clip mask creation paths. // Handles caching, determination of clip mask bound & allocation (if needed) // of the result texture // Returns true if there is no more work to be done (i.e., we got a cache hit) bool GrClipMaskManager::clipMaskPreamble(GrGpu* gpu, const GrClip& clipIn, GrTexture** result, GrIRect *resultBounds) { GrDrawState* origDrawState = gpu->drawState(); GrAssert(origDrawState->isClipState()); GrRenderTarget* rt = origDrawState->getRenderTarget(); GrAssert(NULL != rt); GrRect rtRect; rtRect.setLTRB(0, 0, GrIntToScalar(rt->width()), GrIntToScalar(rt->height())); // unlike the stencil path the alpha path is not bound to the size of the // render target - determine the minimum size required for the mask GrRect bounds; if (clipIn.hasConservativeBounds()) { bounds = clipIn.getConservativeBounds(); if (!bounds.intersect(rtRect)) { // the mask will be empty in this case GrAssert(false); bounds.setEmpty(); } } else { // still locked to the size of the render target bounds = rtRect; } GrIRect intBounds; bounds.roundOut(&intBounds); // need to outset a pixel since the standard bounding box computation // path doesn't leave any room for antialiasing (esp. w.r.t. rects) intBounds.outset(1, 1); // TODO: make sure we don't outset if bounds are still 0,0 @ min if (fAACache.canReuse(clipIn, intBounds.width(), intBounds.height())) { *result = fAACache.getLastMask(); fAACache.getLastBound(resultBounds); return true; } this->setupCache(clipIn, intBounds); *resultBounds = intBounds; return false; }
bool GrClipMaskManager::drawClipShape(GrGpu* gpu, GrTexture* target, const GrClip& clipIn, int index) { GrDrawState* drawState = gpu->drawState(); GrAssert(NULL != drawState); drawState->setRenderTarget(target->asRenderTarget()); if (kRect_ClipType == clipIn.getElementType(index)) { if (clipIn.getDoAA(index)) { // convert the rect to a path for AA SkPath temp; temp.addRect(clipIn.getRect(index)); return this->drawPath(gpu, temp, kEvenOdd_PathFill, clipIn.getDoAA(index)); } else { gpu->drawSimpleRect(clipIn.getRect(index), NULL, 0); } } else { return this->drawPath(gpu, clipIn.getPath(index), clipIn.getPathFill(index), clipIn.getDoAA(index)); } return true; }
static void add_rect_to_clip(const GrClip& clip, const SkRect& devRect, GrClip* out) { switch (clip.clipType()) { case GrClip::kClipStack_ClipType: { SkClipStack* stack = new SkClipStack; *stack = *clip.clipStack(); // The stack is actually in clip space not device space. SkRect clipRect = devRect; SkPoint origin = { SkIntToScalar(clip.origin().fX), SkIntToScalar(clip.origin().fY) }; clipRect.offset(origin); SkIRect iclipRect; clipRect.roundOut(&iclipRect); clipRect = SkRect::Make(iclipRect); stack->clipDevRect(clipRect, SkRegion::kIntersect_Op, false); out->setClipStack(stack, &clip.origin()); break; } case GrClip::kWideOpen_ClipType: *out = GrClip(devRect); break; case GrClip::kIRect_ClipType: { SkIRect intersect; devRect.roundOut(&intersect); if (intersect.intersect(clip.irect())) { *out = GrClip(intersect); } else { *out = clip; } break; } } }
void GrDrawTarget::stencilPath(const GrPipelineBuilder& pipelineBuilder, GrDrawContext* drawContext, const GrClip& clip, const SkMatrix& viewMatrix, const GrPath* path, GrPathRendering::FillType fill) { // TODO: extract portions of checkDraw that are relevant to path stenciling. SkASSERT(path); SkASSERT(this->caps()->shaderCaps()->pathRenderingSupport()); // Setup clip GrAppliedClip appliedClip; if (!clip.apply(fContext, pipelineBuilder, drawContext, nullptr, &appliedClip)) { return; } // TODO: respect fClipBatchToBounds if we ever start computing bounds here. // Coverage AA does not make sense when rendering to the stencil buffer. The caller should never // attempt this in a situation that would require coverage AA. SkASSERT(!appliedClip.getClipCoverageFragmentProcessor()); GrStencilAttachment* stencilAttachment = fResourceProvider->attachStencilAttachment( drawContext->accessRenderTarget()); if (!stencilAttachment) { SkDebugf("ERROR creating stencil attachment. Draw skipped.\n"); return; } GrBatch* batch = GrStencilPathBatch::Create(viewMatrix, pipelineBuilder.isHWAntialias(), fill, appliedClip.hasStencilClip(), stencilAttachment->bits(), appliedClip.scissorState(), drawContext->accessRenderTarget(), path); this->recordBatch(batch); batch->unref(); }
void SkInternalAtlasTextTarget::addDrawOp(const GrClip& clip, std::unique_ptr<GrAtlasTextOp> op) { SkASSERT(clip.quickContains(SkRect::MakeIWH(fWidth, fHeight))); // The SkAtlasTextRenderer currently only handles grayscale SDF glyphs. if (op->maskType() != GrAtlasTextOp::kGrayscaleDistanceField_MaskType) { return; } const GrCaps& caps = *this->context()->internal().grContext()->contextPriv().caps(); op->finalizeForTextTarget(fColor, caps); int n = SkTMin(kMaxBatchLookBack, fOps.count()); for (int i = 0; i < n; ++i) { GrAtlasTextOp* other = fOps.fromBack(i).get(); if (other->combineIfPossible(op.get(), caps) == GrOp::CombineResult::kMerged) { fOpMemoryPool->release(std::move(op)); return; } if (GrRectsOverlap(op->bounds(), other->bounds())) { break; } } op->visitProxies([](GrSurfaceProxy*) {}); fOps.emplace_back(std::move(op)); }
//////////////////////////////////////////////////////////////////////////////// // sort out what kind of clip mask needs to be created: alpha, stencil, // scissor, or entirely software bool GrClipMaskManager::createClipMask(GrGpu* gpu, const GrClip& clipIn, ScissoringSettings* scissorSettings) { GrAssert(scissorSettings); scissorSettings->fEnableScissoring = false; fClipMaskInStencil = false; fClipMaskInAlpha = false; GrDrawState* drawState = gpu->drawState(); if (!drawState->isClipState()) { return true; } GrRenderTarget* rt = drawState->getRenderTarget(); // GrDrawTarget should have filtered this for us GrAssert(NULL != rt); #if GR_SW_CLIP if (create_mask_in_sw()) { // The clip geometry is complex enough that it will be more // efficient to create it entirely in software GrTexture* result = NULL; GrIRect bound; if (this->createSoftwareClipMask(gpu, clipIn, &result, &bound)) { fClipMaskInAlpha = true; setup_drawstate_aaclip(gpu, result, bound); return true; } } #endif #if GR_AA_CLIP // If MSAA is enabled use the (faster) stencil path for AA clipping // otherwise the alpha clip mask is our only option if (clipIn.requiresAA() && 0 == rt->numSamples()) { // Since we are going to create a destination texture of the correct // size for the mask (rather than being bound by the size of the // render target) we aren't going to use scissoring like the stencil // path does (see scissorSettings below) GrTexture* result = NULL; GrIRect bound; if (this->createAlphaClipMask(gpu, clipIn, &result, &bound)) { fClipMaskInAlpha = true; setup_drawstate_aaclip(gpu, result, bound); return true; } // if alpha clip mask creation fails fall through to the stencil // buffer method } #endif // GR_AA_CLIP GrRect bounds; GrRect rtRect; rtRect.setLTRB(0, 0, GrIntToScalar(rt->width()), GrIntToScalar(rt->height())); if (clipIn.hasConservativeBounds()) { bounds = clipIn.getConservativeBounds(); if (!bounds.intersect(rtRect)) { bounds.setEmpty(); } } else { bounds = rtRect; } bounds.roundOut(&scissorSettings->fScissorRect); if (scissorSettings->fScissorRect.isEmpty()) { scissorSettings->fScissorRect.setLTRB(0,0,0,0); // TODO: I think we can do an early exit here - after refactoring try: // set fEnableScissoring to true but leave fClipMaskInStencil false // and return - everything is going to be scissored away anyway! } scissorSettings->fEnableScissoring = true; // use the stencil clip if we can't represent the clip as a rectangle. fClipMaskInStencil = !clipIn.isRect() && !clipIn.isEmpty() && !bounds.isEmpty(); if (fClipMaskInStencil) { return this->createStencilClipMask(gpu, clipIn, bounds, scissorSettings); } 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; }
static void draw_path_with_mask_filter(GrContext* context, GrDrawContext* drawContext, const GrClip& clip, GrPaint* paint, const SkMatrix& viewMatrix, const SkMaskFilter* maskFilter, const GrStyle& style, const SkPath* path, bool pathIsMutable) { SkASSERT(maskFilter); SkIRect clipBounds; clip.getConservativeBounds(drawContext->width(), drawContext->height(), &clipBounds); SkTLazy<SkPath> tmpPath; SkStrokeRec::InitStyle fillOrHairline; // We just fully apply the style here. if (style.applies()) { if (!style.applyToPath(tmpPath.init(), &fillOrHairline, *path, GrStyle::MatrixToScaleFactor(viewMatrix))) { return; } pathIsMutable = true; path = tmpPath.get(); } else if (style.isSimpleHairline()) { fillOrHairline = SkStrokeRec::kHairline_InitStyle; } else { SkASSERT(style.isSimpleFill()); fillOrHairline = SkStrokeRec::kFill_InitStyle; } // transform the path into device space if (!viewMatrix.isIdentity()) { SkPath* result; if (pathIsMutable) { result = const_cast<SkPath*>(path); } else { if (!tmpPath.isValid()) { tmpPath.init(); } result = tmpPath.get(); } path->transform(viewMatrix, result); path = result; result->setIsVolatile(true); pathIsMutable = true; } SkRect maskRect; if (maskFilter->canFilterMaskGPU(SkRRect::MakeRect(path->getBounds()), clipBounds, viewMatrix, &maskRect)) { // This mask will ultimately be drawn as a non-AA rect (see draw_mask). // Non-AA rects have a bad habit of snapping arbitrarily. Integerize here // so the mask draws in a reproducible manner. SkIRect finalIRect; maskRect.roundOut(&finalIRect); if (clip_bounds_quick_reject(clipBounds, finalIRect)) { // clipped out return; } if (maskFilter->directFilterMaskGPU(context->textureProvider(), drawContext, paint, clip, viewMatrix, SkStrokeRec(fillOrHairline), *path)) { // the mask filter was able to draw itself directly, so there's nothing // left to do. return; } sk_sp<GrTexture> mask(create_mask_GPU(context, finalIRect, *path, fillOrHairline, paint->isAntiAlias(), drawContext->numColorSamples())); if (mask) { GrTexture* filtered; if (maskFilter->filterMaskGPU(mask.get(), viewMatrix, finalIRect, &filtered, true)) { // filterMaskGPU gives us ownership of a ref to the result SkAutoTUnref<GrTexture> atu(filtered); if (draw_mask(drawContext, clip, viewMatrix, finalIRect, paint, filtered)) { // This path is completely drawn return; } } } } sw_draw_with_mask_filter(drawContext, context->textureProvider(), clip, viewMatrix, *path, maskFilter, clipBounds, paint, fillOrHairline); }
void GrDrawTarget::drawBatch(const GrPipelineBuilder& pipelineBuilder, GrDrawContext* drawContext, const GrClip& clip, GrDrawBatch* batch) { // Setup clip GrAppliedClip appliedClip; if (!clip.apply(fContext, pipelineBuilder, drawContext, &batch->bounds(), &appliedClip)) { return; } // TODO: this is the only remaining usage of the AutoRestoreFragmentProcessorState - remove it GrPipelineBuilder::AutoRestoreFragmentProcessorState arfps; if (appliedClip.getClipCoverageFragmentProcessor()) { arfps.set(&pipelineBuilder); arfps.addCoverageFragmentProcessor(sk_ref_sp(appliedClip.getClipCoverageFragmentProcessor())); } GrPipeline::CreateArgs args; args.fPipelineBuilder = &pipelineBuilder; args.fDrawContext = drawContext; args.fCaps = this->caps(); args.fScissor = &appliedClip.scissorState(); args.fHasStencilClip = appliedClip.hasStencilClip(); if (pipelineBuilder.hasUserStencilSettings() || appliedClip.hasStencilClip()) { if (!fResourceProvider->attachStencilAttachment(drawContext->accessRenderTarget())) { SkDebugf("ERROR creating stencil attachment. Draw skipped.\n"); return; } } batch->getPipelineOptimizations(&args.fOpts); GrScissorState finalScissor; if (args.fOpts.fOverrides.fUsePLSDstRead || fClipBatchToBounds) { GrGLIRect viewport; viewport.fLeft = 0; viewport.fBottom = 0; viewport.fWidth = drawContext->width(); viewport.fHeight = drawContext->height(); SkIRect ibounds; ibounds.fLeft = SkTPin(SkScalarFloorToInt(batch->bounds().fLeft), viewport.fLeft, viewport.fWidth); ibounds.fTop = SkTPin(SkScalarFloorToInt(batch->bounds().fTop), viewport.fBottom, viewport.fHeight); ibounds.fRight = SkTPin(SkScalarCeilToInt(batch->bounds().fRight), viewport.fLeft, viewport.fWidth); ibounds.fBottom = SkTPin(SkScalarCeilToInt(batch->bounds().fBottom), viewport.fBottom, viewport.fHeight); if (appliedClip.scissorState().enabled()) { const SkIRect& scissorRect = appliedClip.scissorState().rect(); if (!ibounds.intersect(scissorRect)) { return; } } finalScissor.set(ibounds); args.fScissor = &finalScissor; } args.fOpts.fColorPOI.completeCalculations( sk_sp_address_as_pointer_address(pipelineBuilder.fColorFragmentProcessors.begin()), pipelineBuilder.numColorFragmentProcessors()); args.fOpts.fCoveragePOI.completeCalculations( sk_sp_address_as_pointer_address(pipelineBuilder.fCoverageFragmentProcessors.begin()), pipelineBuilder.numCoverageFragmentProcessors()); if (!this->setupDstReadIfNecessary(pipelineBuilder, drawContext->accessRenderTarget(), clip, args.fOpts, &args.fDstTexture, batch->bounds())) { return; } if (!batch->installPipeline(args)) { return; } #ifdef ENABLE_MDB SkASSERT(fRenderTarget); batch->pipeline()->addDependenciesTo(fRenderTarget); #endif this->recordBatch(batch); }
bool GrDrawTarget::setupDstReadIfNecessary(const GrPipelineBuilder& pipelineBuilder, GrRenderTarget* rt, const GrClip& clip, const GrPipelineOptimizations& optimizations, GrXferProcessor::DstTexture* dstTexture, const SkRect& batchBounds) { SkRect bounds = batchBounds; bounds.outset(0.5f, 0.5f); if (!pipelineBuilder.willXPNeedDstTexture(*this->caps(), optimizations)) { return true; } if (this->caps()->textureBarrierSupport()) { if (GrTexture* rtTex = rt->asTexture()) { // The render target is a texture, so we can read from it directly in the shader. The XP // will be responsible to detect this situation and request a texture barrier. dstTexture->setTexture(rtTex); dstTexture->setOffset(0, 0); return true; } } SkIRect copyRect; clip.getConservativeBounds(rt->width(), rt->height(), ©Rect); SkIRect drawIBounds; bounds.roundOut(&drawIBounds); if (!copyRect.intersect(drawIBounds)) { #ifdef SK_DEBUG GrCapsDebugf(this->caps(), "Missed an early reject. " "Bailing on draw from setupDstReadIfNecessary.\n"); #endif return false; } // MSAA consideration: When there is support for reading MSAA samples in the shader we could // have per-sample dst values by making the copy multisampled. GrSurfaceDesc desc; if (!fGpu->initCopySurfaceDstDesc(rt, &desc)) { desc.fOrigin = kDefault_GrSurfaceOrigin; desc.fFlags = kRenderTarget_GrSurfaceFlag; desc.fConfig = rt->config(); } desc.fWidth = copyRect.width(); desc.fHeight = copyRect.height(); static const uint32_t kFlags = 0; SkAutoTUnref<GrTexture> copy(fResourceProvider->createApproxTexture(desc, kFlags)); if (!copy) { SkDebugf("Failed to create temporary copy of destination texture.\n"); return false; } SkIPoint dstPoint = {0, 0}; this->copySurface(copy, rt, copyRect, dstPoint); dstTexture->setTexture(copy); dstTexture->setOffset(copyRect.fLeft, copyRect.fTop); return true; }
static void draw_path_with_mask_filter(GrContext* context, GrDrawContext* drawContext, const GrClip& clip, GrPaint* paint, const SkMatrix& viewMatrix, const SkMaskFilter* maskFilter, const SkPathEffect* pathEffect, const GrStrokeInfo& origStrokeInfo, SkPath* pathPtr, bool pathIsMutable) { SkASSERT(maskFilter); SkIRect clipBounds; clip.getConservativeBounds(drawContext->width(), drawContext->height(), &clipBounds); SkTLazy<SkPath> tmpPath; GrStrokeInfo strokeInfo(origStrokeInfo); static const SkRect* cullRect = nullptr; // TODO: what is our bounds? SkASSERT(strokeInfo.isDashed() || !pathEffect); if (!strokeInfo.isHairlineStyle()) { SkPath* strokedPath = pathIsMutable ? pathPtr : tmpPath.init(); if (strokeInfo.isDashed()) { if (pathEffect->filterPath(strokedPath, *pathPtr, &strokeInfo, cullRect)) { pathPtr = strokedPath; pathPtr->setIsVolatile(true); pathIsMutable = true; } strokeInfo.removeDash(); } if (strokeInfo.applyToPath(strokedPath, *pathPtr)) { // Apply the stroke to the path if there is one pathPtr = strokedPath; pathPtr->setIsVolatile(true); pathIsMutable = true; strokeInfo.setFillStyle(); } } // avoid possibly allocating a new path in transform if we can SkPath* devPathPtr = pathIsMutable ? pathPtr : tmpPath.init(); if (!pathIsMutable) { devPathPtr->setIsVolatile(true); } // transform the path into device space pathPtr->transform(viewMatrix, devPathPtr); SkRect maskRect; if (maskFilter->canFilterMaskGPU(SkRRect::MakeRect(devPathPtr->getBounds()), clipBounds, viewMatrix, &maskRect)) { SkIRect finalIRect; maskRect.roundOut(&finalIRect); if (clip_bounds_quick_reject(clipBounds, finalIRect)) { // clipped out return; } if (maskFilter->directFilterMaskGPU(context->textureProvider(), drawContext, paint, clip, viewMatrix, strokeInfo, *devPathPtr)) { // the mask filter was able to draw itself directly, so there's nothing // left to do. return; } SkAutoTUnref<GrTexture> mask(create_mask_GPU(context, &maskRect, *devPathPtr, strokeInfo, paint->isAntiAlias(), drawContext->numColorSamples())); if (mask) { GrTexture* filtered; if (maskFilter->filterMaskGPU(mask, viewMatrix, maskRect, &filtered, true)) { // filterMaskGPU gives us ownership of a ref to the result SkAutoTUnref<GrTexture> atu(filtered); if (draw_mask(drawContext, clip, viewMatrix, maskRect, paint, 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 = strokeInfo.isHairlineStyle() ? SkPaint::kStroke_Style : SkPaint::kFill_Style; sw_draw_with_mask_filter(drawContext, context->textureProvider(), clip, viewMatrix, *devPathPtr, maskFilter, clipBounds, paint, style); }
void GrTextBlob::flush(GrTextTarget* target, const SkSurfaceProps& props, const GrDistanceFieldAdjustTable* distanceAdjustTable, const SkPaint& paint, GrColor filteredColor, const GrClip& clip, const SkMatrix& viewMatrix, SkScalar x, SkScalar y) { // GrTextBlob::makeOp only takes uint16_t values for run and subRun indices. // Encountering something larger than this is highly unlikely, so we'll just not draw it. int lastRun = SkTMin(fRunCount, (1 << 16)) - 1; // For each run in the GrTextBlob we're going to churn through all the glyphs. // Each run is broken into a path part and a Mask / DFT / ARGB part. for (int runIndex = 0; runIndex <= lastRun; runIndex++) { Run& run = fRuns[runIndex]; // first flush any path glyphs if (run.fPathGlyphs.count()) { SkPaint runPaint{paint}; runPaint.setFlags((runPaint.getFlags() & ~Run::kPaintFlagsMask) | run.fPaintFlags); for (int i = 0; i < run.fPathGlyphs.count(); i++) { GrTextBlob::Run::PathGlyph& pathGlyph = run.fPathGlyphs[i]; SkMatrix ctm; const SkPath* path = &pathGlyph.fPath; // TmpPath must be in the same scope as GrShape shape below. SkTLazy<SkPath> tmpPath; // The glyph positions and glyph outlines are either in device space or in source // space based on fPreTransformed. if (!pathGlyph.fPreTransformed) { // Positions and outlines are in source space. ctm = viewMatrix; SkMatrix pathMatrix = SkMatrix::MakeScale(pathGlyph.fScale, pathGlyph.fScale); // The origin for the blob may have changed, so figure out the delta. SkVector originShift = SkPoint{x, y} - SkPoint{fInitialX, fInitialY}; // Shift the original glyph location in source space to the position of the new // blob. pathMatrix.postTranslate(originShift.x() + pathGlyph.fX, originShift.y() + pathGlyph.fY); // If there are shaders, blurs or styles, the path must be scaled into source // space independently of the CTM. This allows the CTM to be correct for the // different effects. GrStyle style(runPaint); bool scalePath = runPaint.getShader() || style.applies() || runPaint.getMaskFilter(); if (!scalePath) { // Scale can be applied to CTM -- no effects. ctm.preConcat(pathMatrix); } else { // Scale the outline into source space. // Transform the path form the normalized outline to source space. This // way the CTM will remain the same so it can be used by the effects. SkPath* sourceOutline = tmpPath.init(); path->transform(pathMatrix, sourceOutline); sourceOutline->setIsVolatile(true); path = sourceOutline; } } else { // Positions and outlines are in device space. SkPoint originalOrigin = {fInitialX, fInitialY}; fInitialViewMatrix.mapPoints(&originalOrigin, 1); SkPoint newOrigin = {x, y}; viewMatrix.mapPoints(&newOrigin, 1); // The origin shift in device space. SkPoint originShift = newOrigin - originalOrigin; // Shift the original glyph location in device space to the position of the // new blob. ctm = SkMatrix::MakeTrans(originShift.x() + pathGlyph.fX, originShift.y() + pathGlyph.fY); } // TODO: we are losing the mutability of the path here GrShape shape(*path, paint); target->drawShape(clip, runPaint, ctm, shape); } } // then flush each subrun, if any if (!run.fInitialized) { continue; } int lastSubRun = SkTMin(run.fSubRunInfo.count(), 1 << 16) - 1; for (int subRun = 0; subRun <= lastSubRun; subRun++) { const Run::SubRunInfo& info = run.fSubRunInfo[subRun]; int glyphCount = info.glyphCount(); if (0 == glyphCount) { continue; } bool skipClip = false; bool submitOp = true; SkIRect clipRect = SkIRect::MakeEmpty(); SkRect rtBounds = SkRect::MakeWH(target->width(), target->height()); SkRRect clipRRect; GrAA aa; // We can clip geometrically if we're not using SDFs or transformed glyphs, // and we have an axis-aligned rectangular non-AA clip if (!info.drawAsDistanceFields() && !info.needsTransform() && clip.isRRect(rtBounds, &clipRRect, &aa) && clipRRect.isRect() && GrAA::kNo == aa) { skipClip = true; // We only need to do clipping work if the subrun isn't contained by the clip SkRect subRunBounds; this->computeSubRunBounds(&subRunBounds, runIndex, subRun, viewMatrix, x, y, false); if (!clipRRect.getBounds().contains(subRunBounds)) { // If the subrun is completely outside, don't add an op for it if (!clipRRect.getBounds().intersects(subRunBounds)) { submitOp = false; } else { clipRRect.getBounds().round(&clipRect); } } } if (submitOp) { auto op = this->makeOp(info, glyphCount, runIndex, subRun, viewMatrix, x, y, clipRect, paint, filteredColor, props, distanceAdjustTable, target); if (op) { if (skipClip) { target->addDrawOp(GrNoClip(), std::move(op)); } else { target->addDrawOp(clip, std::move(op)); } } } } } }
/* * This method traverses the clip stack to see if the GrSoftwarePathRenderer * will be used on any element. If so, it returns true to indicate that the * entire clip should be rendered in SW and then uploaded en masse to the gpu. */ bool GrClipMaskManager::useSWOnlyPath(GrGpu* gpu, const GrClip& clipIn) { if (!clipIn.requiresAA()) { // The stencil buffer can handle this case return false; } // TODO: generalize this test so that when // a clip gets complex enough it can just be done in SW regardless // of whether it would invoke the GrSoftwarePathRenderer. bool useSW = false; for (int i = 0; i < clipIn.getElementCount(); ++i) { if (SkRegion::kReplace_Op == clipIn.getOp(i)) { // Everything before a replace op can be ignored so start // afresh w.r.t. determining if any element uses the SW path useSW = false; } if (!clipIn.getDoAA(i)) { // non-anti-aliased rects and paths can always be drawn either // directly or by the GrDefaultPathRenderer continue; } if (kRect_ClipType == clipIn.getElementType(i)) { // Antialiased rects are converted to paths and then drawn with // kEvenOdd_PathFill. if (!GrAAConvexPathRenderer::staticCanDrawPath( true, // always convex kEvenOdd_PathFill, gpu, true)) { // anti-aliased // if the GrAAConvexPathRenderer can't render this rect (due // to lack of derivative support in the shaders) then // the GrSoftwarePathRenderer will be used useSW = true; } continue; } // only paths need to be considered in the rest of the loop body if (GrAAHairLinePathRenderer::staticCanDrawPath(clipIn.getPath(i), clipIn.getPathFill(i), gpu, clipIn.getDoAA(i))) { // the hair line path renderer can handle this one continue; } if (GrAAConvexPathRenderer::staticCanDrawPath( clipIn.getPath(i).isConvex(), clipIn.getPathFill(i), gpu, clipIn.getDoAA(i))) { // the convex path renderer can handle this one continue; } // otherwise the GrSoftwarePathRenderer is going to be invoked useSW = true; } return useSW; }
//////////////////////////////////////////////////////////////////////////////// // sort out what kind of clip mask needs to be created: alpha, stencil, // scissor, or entirely software bool GrClipMaskManager::createClipMask(GrGpu* gpu, const GrClip& clipIn, ScissoringSettings* scissorSettings) { GrAssert(scissorSettings); scissorSettings->fEnableScissoring = false; fClipMaskInStencil = false; fClipMaskInAlpha = false; GrDrawState* drawState = gpu->drawState(); if (!drawState->isClipState()) { return true; } GrRenderTarget* rt = drawState->getRenderTarget(); // GrDrawTarget should have filtered this for us GrAssert(NULL != rt); #if GR_SW_CLIP // If MSAA is enabled we can do everything in the stencil buffer. // Otherwise check if we should just create the entire clip mask // in software (this will only happen if the clip mask is anti-aliased // and too complex for the gpu to handle in its entirety) if (0 == rt->numSamples() && useSWOnlyPath(gpu, clipIn)) { // The clip geometry is complex enough that it will be more // efficient to create it entirely in software GrTexture* result = NULL; GrIRect bound; if (this->createSoftwareClipMask(gpu, clipIn, &result, &bound)) { fClipMaskInAlpha = true; setup_drawstate_aaclip(gpu, result, bound); return true; } // if SW clip mask creation fails fall through to the other // two possible methods (bottoming out at stencil clipping) } #endif // GR_SW_CLIP #if GR_AA_CLIP // If MSAA is enabled use the (faster) stencil path for AA clipping // otherwise the alpha clip mask is our only option if (0 == rt->numSamples() && clipIn.requiresAA()) { // Since we are going to create a destination texture of the correct // size for the mask (rather than being bound by the size of the // render target) we aren't going to use scissoring like the stencil // path does (see scissorSettings below) GrTexture* result = NULL; GrIRect bound; if (this->createAlphaClipMask(gpu, clipIn, &result, &bound)) { fClipMaskInAlpha = true; setup_drawstate_aaclip(gpu, result, bound); return true; } // if alpha clip mask creation fails fall through to the stencil // buffer method } #endif // GR_AA_CLIP // Either a hard (stencil buffer) clip was explicitly requested or // an antialiased clip couldn't be created. In either case, free up // the texture in the antialiased mask cache. // TODO: this may require more investigation. Ganesh performs a lot of // utility draws (e.g., clears, InOrderDrawBuffer playbacks) that hit // the stencil buffer path. These may be "incorrectly" clearing the // AA cache. fAACache.reset(); GrRect bounds; GrRect rtRect; rtRect.setLTRB(0, 0, GrIntToScalar(rt->width()), GrIntToScalar(rt->height())); if (clipIn.hasConservativeBounds()) { bounds = clipIn.getConservativeBounds(); if (!bounds.intersect(rtRect)) { bounds.setEmpty(); } } else { bounds = rtRect; } bounds.roundOut(&scissorSettings->fScissorRect); if (scissorSettings->fScissorRect.isEmpty()) { scissorSettings->fScissorRect.setLTRB(0,0,0,0); // TODO: I think we can do an early exit here - after refactoring try: // set fEnableScissoring to true but leave fClipMaskInStencil false // and return - everything is going to be scissored away anyway! } scissorSettings->fEnableScissoring = true; // use the stencil clip if we can't represent the clip as a rectangle. fClipMaskInStencil = !clipIn.isRect() && !clipIn.isEmpty() && !bounds.isEmpty(); if (fClipMaskInStencil) { return this->createStencilClipMask(gpu, clipIn, bounds, scissorSettings); } return true; }