/* * Copyright 2015 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "src/gpu/ganesh/SurfaceDrawContext.h" #include "include/core/SkAlphaType.h" #include "include/core/SkArc.h" #include "include/core/SkBlendMode.h" #include "include/core/SkColorSpace.h" #include "include/core/SkDrawable.h" #include "include/core/SkMesh.h" #include "include/core/SkPath.h" #include "include/core/SkPathTypes.h" #include "include/core/SkPoint3.h" #include "include/core/SkRRect.h" #include "include/core/SkSamplingOptions.h" #include "include/core/SkScalar.h" #include "include/core/SkSize.h" #include "include/core/SkTypes.h" #include "include/core/SkVertices.h" #include "include/gpu/ganesh/GrBackendSemaphore.h" #include "include/gpu/ganesh/GrBackendSurface.h" #include "include/gpu/ganesh/GrDirectContext.h" #include "include/gpu/ganesh/GrRecordingContext.h" #include "include/private/base/SingleOwner.h" #include "include/private/base/SkDebug.h" #include "include/private/base/SkFloatingPoint.h" #include "include/private/base/SkTemplates.h" #include "include/private/base/SkTo.h" #include "include/utils/SkShadowUtils.h" #include "src/core/SkDevice.h" #include "src/core/SkDrawProcs.h" #include "src/core/SkDrawShadowInfo.h" #include "src/core/SkLatticeIter.h" #include "src/core/SkMeshPriv.h" #include "src/core/SkPointPriv.h" #include "src/core/SkRRectPriv.h" #include "src/core/SkTraceEvent.h" #include "src/gpu/RefCntedCallback.h" #include "src/gpu/SkBackingFit.h" #include "src/gpu/Swizzle.h" #include "src/gpu/ganesh/GrAppliedClip.h" #include "src/gpu/ganesh/GrAuditTrail.h" #include "src/gpu/ganesh/GrCaps.h" #include "src/gpu/ganesh/GrClip.h" #include "src/gpu/ganesh/GrColor.h" #include "src/gpu/ganesh/GrColorInfo.h" #include "src/gpu/ganesh/GrColorSpaceXform.h" #include "src/gpu/ganesh/GrDirectContextPriv.h" #include "src/gpu/ganesh/GrDrawingManager.h" #include "src/gpu/ganesh/GrDstProxyView.h" #include "src/gpu/ganesh/GrFragmentProcessor.h" #include "src/gpu/ganesh/GrProcessorSet.h" #include "src/gpu/ganesh/GrProxyProvider.h" #include "src/gpu/ganesh/GrRecordingContextPriv.h" #include "src/gpu/ganesh/GrResourceProvider.h" #include "src/gpu/ganesh/GrScissorState.h" #include "src/gpu/ganesh/GrSemaphore.h" #include "src/gpu/ganesh/GrStencilSettings.h" #include "src/gpu/ganesh/GrStyle.h" #include "src/gpu/ganesh/GrTextureProxy.h" #include "src/gpu/ganesh/GrTextureResolveManager.h" #include "src/gpu/ganesh/GrTracing.h" #include "src/gpu/ganesh/GrUserStencilSettings.h" #include "src/gpu/ganesh/GrXferProcessor.h" #include "src/gpu/ganesh/PathRenderer.h" #include "src/gpu/ganesh/PathRendererChain.h" #include "src/gpu/ganesh/SkGr.h" #include "src/gpu/ganesh/effects/GrBlendFragmentProcessor.h" #include "src/gpu/ganesh/effects/GrDisableColorXP.h" #include "src/gpu/ganesh/effects/GrTextureEffect.h" #include "src/gpu/ganesh/geometry/GrQuad.h" #include "src/gpu/ganesh/geometry/GrQuadUtils.h" #include "src/gpu/ganesh/geometry/GrStyledShape.h" #include "src/gpu/ganesh/ops/ClearOp.h" #include "src/gpu/ganesh/ops/DrawAtlasOp.h" #include "src/gpu/ganesh/ops/DrawMeshOp.h" #include "src/gpu/ganesh/ops/DrawableOp.h" #include "src/gpu/ganesh/ops/FillRRectOp.h" #include "src/gpu/ganesh/ops/FillRectOp.h" #include "src/gpu/ganesh/ops/GrDrawOp.h" #include "src/gpu/ganesh/ops/GrOp.h" #include "src/gpu/ganesh/ops/GrOvalOpFactory.h" #include "src/gpu/ganesh/ops/LatticeOp.h" #include "src/gpu/ganesh/ops/RegionOp.h" #include "src/gpu/ganesh/ops/ShadowRRectOp.h" #include "src/gpu/ganesh/ops/StrokeRectOp.h" #include "src/gpu/ganesh/ops/TextureOp.h" #include "src/text/gpu/SubRunContainer.h" #include "src/text/gpu/TextBlobRedrawCoordinator.h" #include #include #include struct GrShaderCaps; #define ASSERT_OWNED_RESOURCE(R) SkASSERT(!(R) || (R)->getContext() == this->drawingManager()->getContext()) #define ASSERT_SINGLE_OWNER SKGPU_ASSERT_SINGLE_OWNER(this->singleOwner()) #define RETURN_IF_ABANDONED if (fContext->abandoned()) { return; } #define RETURN_FALSE_IF_ABANDONED if (fContext->abandoned()) { return false; } using namespace skia_private; ////////////////////////////////////////////////////////////////////////////// namespace { void op_bounds(SkRect* bounds, const GrOp* op) { *bounds = op->bounds(); if (op->hasZeroArea()) { if (op->hasAABloat()) { bounds->outset(0.5f, 0.5f); } else { // We don't know which way the particular GPU will snap lines or points at integer // coords. So we ensure that the bounds is large enough for either snap. SkRect before = *bounds; bounds->roundOut(bounds); if (bounds->fLeft == before.fLeft) { bounds->fLeft -= 1; } if (bounds->fTop == before.fTop) { bounds->fTop -= 1; } if (bounds->fRight == before.fRight) { bounds->fRight += 1; } if (bounds->fBottom == before.fBottom) { bounds->fBottom += 1; } } } } } // anonymous namespace namespace skgpu::ganesh { using DoSimplify = GrStyledShape::DoSimplify; class AutoCheckFlush { public: AutoCheckFlush(GrDrawingManager* drawingManager) : fDrawingManager(drawingManager) { SkASSERT(fDrawingManager); } ~AutoCheckFlush() { fDrawingManager->flushIfNecessary(); } private: GrDrawingManager* fDrawingManager; }; std::unique_ptr SurfaceDrawContext::Make(GrRecordingContext* rContext, GrColorType colorType, sk_sp proxy, sk_sp colorSpace, GrSurfaceOrigin origin, const SkSurfaceProps& surfaceProps) { if (!rContext || !proxy || colorType == GrColorType::kUnknown) { return nullptr; } const GrBackendFormat& format = proxy->backendFormat(); skgpu::Swizzle readSwizzle = rContext->priv().caps()->getReadSwizzle(format, colorType); skgpu::Swizzle writeSwizzle = rContext->priv().caps()->getWriteSwizzle(format, colorType); GrSurfaceProxyView readView ( proxy, origin, readSwizzle); GrSurfaceProxyView writeView(std::move(proxy), origin, writeSwizzle); return std::make_unique(rContext, std::move(readView), std::move(writeView), colorType, std::move(colorSpace), surfaceProps); } std::unique_ptr SurfaceDrawContext::Make(GrRecordingContext* rContext, sk_sp colorSpace, SkBackingFit fit, SkISize dimensions, const GrBackendFormat& format, int sampleCnt, skgpu::Mipmapped mipmapped, GrProtected isProtected, skgpu::Swizzle readSwizzle, skgpu::Swizzle writeSwizzle, GrSurfaceOrigin origin, skgpu::Budgeted budgeted, const SkSurfaceProps& surfaceProps, std::string_view label) { // It is probably not necessary to check if the context is abandoned here since uses of the // SurfaceDrawContext which need the context will mostly likely fail later on without an // issue. However having this hear adds some reassurance in case there is a path doesn't handle // an abandoned context correctly. It also lets us early out of some extra work. if (rContext->abandoned()) { return nullptr; } sk_sp proxy = rContext->priv().proxyProvider()->createProxy( format, dimensions, GrRenderable::kYes, sampleCnt, mipmapped, fit, budgeted, isProtected, label); if (!proxy) { return nullptr; } GrSurfaceProxyView readView ( proxy, origin, readSwizzle); GrSurfaceProxyView writeView(std::move(proxy), origin, writeSwizzle); auto sdc = std::make_unique(rContext, std::move(readView), std::move(writeView), GrColorType::kUnknown, std::move(colorSpace), surfaceProps); sdc->discard(); return sdc; } std::unique_ptr SurfaceDrawContext::Make(GrRecordingContext* rContext, GrColorType colorType, sk_sp colorSpace, SkBackingFit fit, SkISize dimensions, const SkSurfaceProps& surfaceProps, std::string_view label, int sampleCnt, skgpu::Mipmapped mipmapped, GrProtected isProtected, GrSurfaceOrigin origin, skgpu::Budgeted budgeted) { if (!rContext) { return nullptr; } auto format = rContext->priv().caps()->getDefaultBackendFormat(colorType, GrRenderable::kYes); if (!format.isValid()) { return nullptr; } sk_sp proxy = rContext->priv().proxyProvider()->createProxy( format, dimensions, GrRenderable::kYes, sampleCnt, mipmapped, fit, budgeted, isProtected, label); if (!proxy) { return nullptr; } return SurfaceDrawContext::Make(rContext, colorType, std::move(proxy), std::move(colorSpace), origin, surfaceProps); } std::unique_ptr SurfaceDrawContext::MakeWithFallback( GrRecordingContext* rContext, GrColorType colorType, sk_sp colorSpace, SkBackingFit fit, SkISize dimensions, const SkSurfaceProps& surfaceProps, int sampleCnt, skgpu::Mipmapped mipmapped, GrProtected isProtected, GrSurfaceOrigin origin, skgpu::Budgeted budgeted) { const GrCaps* caps = rContext->priv().caps(); auto [ct, _] = caps->getFallbackColorTypeAndFormat(colorType, sampleCnt); if (ct == GrColorType::kUnknown) { return nullptr; } return SurfaceDrawContext::Make(rContext, ct, colorSpace, fit, dimensions, surfaceProps, /*label=*/"MakeSurfaceDrawContextWithFallback", sampleCnt, mipmapped, isProtected, origin, budgeted); } std::unique_ptr SurfaceDrawContext::MakeFromBackendTexture( GrRecordingContext* rContext, GrColorType colorType, sk_sp colorSpace, const GrBackendTexture& tex, int sampleCnt, GrSurfaceOrigin origin, const SkSurfaceProps& surfaceProps, sk_sp releaseHelper) { SkASSERT(sampleCnt > 0); sk_sp proxy(rContext->priv().proxyProvider()->wrapRenderableBackendTexture( tex, sampleCnt, kBorrow_GrWrapOwnership, GrWrapCacheable::kNo, std::move(releaseHelper))); if (!proxy) { return nullptr; } return SurfaceDrawContext::Make(rContext, colorType, std::move(proxy), std::move(colorSpace), origin, surfaceProps); } // In MDB mode the reffing of the 'getLastOpsTask' call's result allows in-progress // OpsTask to be picked up and added to by SurfaceDrawContexts lower in the call // stack. When this occurs with a closed OpsTask, a new one will be allocated // when the surfaceDrawContext attempts to use it (via getOpsTask). SurfaceDrawContext::SurfaceDrawContext(GrRecordingContext* rContext, GrSurfaceProxyView readView, GrSurfaceProxyView writeView, GrColorType colorType, sk_sp colorSpace, const SkSurfaceProps& surfaceProps) : SurfaceFillContext(rContext, std::move(readView), std::move(writeView), {colorType, kPremul_SkAlphaType, std::move(colorSpace)}) , fSurfaceProps(surfaceProps) , fCanUseDynamicMSAA( (fSurfaceProps.flags() & SkSurfaceProps::kDynamicMSAA_Flag) && rContext->priv().caps()->supportsDynamicMSAA(this->asRenderTargetProxy())) { SkDEBUGCODE(this->validate();) } SurfaceDrawContext::~SurfaceDrawContext() { ASSERT_SINGLE_OWNER } void SurfaceDrawContext::willReplaceOpsTask(OpsTask* prevTask, OpsTask* nextTask) { if (prevTask && fNeedsStencil) { // Store the stencil values in memory upon completion of fOpsTask. prevTask->setMustPreserveStencil(); // Reload the stencil buffer content at the beginning of newOpsTask. // FIXME: Could the topo sort insert a task between these two that modifies the stencil // values? nextTask->setInitialStencilContent(OpsTask::StencilContent::kPreserved); } #if GR_GPU_STATS && defined(GPU_TEST_UTILS) if (fCanUseDynamicMSAA) { fContext->priv().dmsaaStats().fNumRenderPasses++; } #endif } void SurfaceDrawContext::drawGlyphRunList(SkCanvas* canvas, const GrClip* clip, const SkMatrix& viewMatrix, const sktext::GlyphRunList& glyphRunList, SkStrikeDeviceInfo strikeDeviceInfo, const SkPaint& paint) { ASSERT_SINGLE_OWNER RETURN_IF_ABANDONED SkDEBUGCODE(this->validate();) GR_CREATE_TRACE_MARKER_CONTEXT("SurfaceDrawContext", "drawGlyphRunList", fContext); // Drawing text can cause us to do inline uploads. This is not supported for wrapped vulkan // secondary command buffers because it would require stopping and starting a render pass which // we don't have access to. if (this->wrapsVkSecondaryCB()) { return; } sktext::gpu::TextBlobRedrawCoordinator* textBlobCache = fContext->priv().getTextBlobCache(); auto atlasDelegate = [&](const sktext::gpu::AtlasSubRun* subRun, SkPoint drawOrigin, const SkPaint& paint, sk_sp subRunStorage, sktext::gpu::RendererData) { auto [drawingClip, op] = subRun->makeAtlasTextOp( clip, viewMatrix, drawOrigin, paint, std::move(subRunStorage), this); if (op != nullptr) { this->addDrawOp(drawingClip, std::move(op)); } }; textBlobCache->drawGlyphRunList( canvas, viewMatrix, glyphRunList, paint, strikeDeviceInfo, atlasDelegate); } void SurfaceDrawContext::drawPaint(const GrClip* clip, GrPaint&& paint, const SkMatrix& viewMatrix) { // Start with the render target, since that is the maximum content we could possibly fill. // drawFilledQuad() will automatically restrict it to clip bounds for us if possible. if (!paint.numTotalFragmentProcessors()) { // The paint is trivial so we won't need to use local coordinates, so skip calculating the // inverse view matrix. SkRect r = this->asSurfaceProxy()->getBoundsRect(); this->fillRectToRect(clip, std::move(paint), GrAA::kNo, SkMatrix::I(), r, r); } else { // Use the inverse view matrix to arrive at appropriate local coordinates for the paint. SkMatrix localMatrix; if (!viewMatrix.invert(&localMatrix)) { return; } SkIRect bounds = SkIRect::MakeSize(this->asSurfaceProxy()->dimensions()); this->fillPixelsWithLocalMatrix(clip, std::move(paint), bounds, localMatrix); } } enum class SurfaceDrawContext::QuadOptimization { // The rect to draw doesn't intersect clip or render target, so no draw op should be added kDiscarded, // The rect to draw was converted to some other op and appended to the oplist, so no additional // op is necessary. Currently this can convert it to a clear op or a rrect op. Only valid if // a constColor is provided. kSubmitted, // The clip was folded into the device quad, with updated edge flags and local coords, and // caller is responsible for adding an appropriate op. kClipApplied, // No change to clip, but quad updated to better fit clip/render target, and caller is // responsible for adding an appropriate op. kCropped }; SurfaceDrawContext::QuadOptimization SurfaceDrawContext::attemptQuadOptimization( const GrClip* clip, const GrUserStencilSettings* stencilSettings, DrawQuad* quad, GrPaint* paint) { // Optimization requirements: // 1. kDiscard applies when clip bounds and quad bounds do not intersect // 2a. kSubmitted applies when constColor and final geom is pixel aligned rect; // pixel aligned rect requires rect clip and (rect quad or quad covers clip) OR // 2b. kSubmitted applies when constColor and rrect clip and quad covers clip // 4. kClipApplied applies when rect clip and (rect quad or quad covers clip) // 5. kCropped in all other scenarios (although a crop may be a no-op) const SkPMColor4f* constColor = nullptr; SkPMColor4f paintColor; if (!stencilSettings && paint && !paint->hasCoverageFragmentProcessor() && paint->isConstantBlendedColor(&paintColor)) { // Only consider clears/rrects when it's easy to guarantee 100% fill with single color constColor = &paintColor; } // Save the old AA flags since CropToRect will modify 'quad' and if kCropped is returned, it's // better to just keep the old flags instead of introducing mixed edge flags. GrQuadAAFlags oldFlags = quad->fEdgeFlags; // Use the logical size of the render target, which allows for "fullscreen" clears even if // the render target has an approximate backing fit SkRect rtRect = this->asSurfaceProxy()->getBoundsRect(); // For historical reasons, we assume AA for exact bounds checking in IsOutsideClip. // TODO(michaelludwig) - Hopefully that can be revisited when the clipping optimizations are // refactored to work better with round rects and dmsaa. SkRect drawBounds = quad->fDevice.bounds(); if (!quad->fDevice.isFinite() || drawBounds.isEmpty() || GrClip::IsOutsideClip(SkIRect::MakeSize(this->dimensions()), drawBounds, GrAA::kYes)) { return QuadOptimization::kDiscarded; } else if (GrQuadUtils::WillUseHairline(quad->fDevice, GrAAType::kCoverage, quad->fEdgeFlags)) { // Don't try to apply the clip early if we know rendering will use hairline methods, as this // has an effect on the op bounds not otherwise taken into account in this function. return QuadOptimization::kCropped; } GrAA drawUsesAA{quad->fEdgeFlags != GrQuadAAFlags::kNone}; auto conservativeCrop = [&]() { static constexpr int kLargeDrawLimit = 15000; // Crop the quad to the render target. This doesn't change the visual results of drawing but // is meant to help numerical stability for excessively large draws. if (drawBounds.width() > kLargeDrawLimit || drawBounds.height() > kLargeDrawLimit) { GrQuadUtils::CropToRect(rtRect, drawUsesAA, quad, /* compute local */ !constColor); } }; bool simpleColor = !stencilSettings && constColor; GrClip::PreClipResult result = clip ? clip->preApply(drawBounds, drawUsesAA) : GrClip::PreClipResult(GrClip::Effect::kUnclipped); switch(result.fEffect) { case GrClip::Effect::kClippedOut: return QuadOptimization::kDiscarded; case GrClip::Effect::kUnclipped: if (!simpleColor) { conservativeCrop(); return QuadOptimization::kClipApplied; } else { // Update result to store the render target bounds in order and then fall // through to attempt the draw->native clear optimization. Pick an AA value such // that any geometric clipping doesn't need to change aa or edge flags (since we // know this is on pixel boundaries, it will draw the same regardless). // See skbug.com/13114 for more details. result = GrClip::PreClipResult(SkRRect::MakeRect(rtRect), drawUsesAA); } break; case GrClip::Effect::kClipped: if (!result.fIsRRect || (stencilSettings && result.fAA != drawUsesAA) || (!result.fRRect.isRect() && !simpleColor)) { // The clip and draw state are too complicated to try and reduce conservativeCrop(); return QuadOptimization::kCropped; } // Else fall through to attempt to combine the draw and clip geometry together break; default: SkUNREACHABLE; } // If we reached here, we know we're an axis-aligned clip that is either a rect or a round rect, // so we can potentially combine it with the draw geometry so that no clipping is needed. SkASSERT(result.fEffect == GrClip::Effect::kClipped && result.fIsRRect); SkRect clippedBounds = result.fRRect.getBounds(); clippedBounds.intersect(rtRect); if (!drawBounds.intersect(clippedBounds)) { // Our fractional bounds aren't actually inside the clip. GrClip::preApply() can sometimes // think in terms of rounded-out bounds. Discard the draw. return QuadOptimization::kDiscarded; } // Guard against the clipped draw turning into a hairline draw after intersection if (drawBounds.width() < 1.f || drawBounds.height() < 1.f) { return QuadOptimization::kCropped; } if (result.fRRect.isRect()) { // No rounded corners, so we might be able to become a native clear or we might be able to // modify geometry and edge flags to represent intersected shape of clip and draw. if (GrQuadUtils::CropToRect(clippedBounds, result.fAA, quad, /*compute local*/ !constColor)) { if (simpleColor && quad->fDevice.quadType() == GrQuad::Type::kAxisAligned) { // Clear optimization is possible drawBounds = quad->fDevice.bounds(); if (drawBounds.contains(rtRect)) { // Fullscreen clear this->clear(*constColor); return QuadOptimization::kSubmitted; } else if (GrClip::IsPixelAligned(drawBounds) && drawBounds.width() > 256 && drawBounds.height() > 256) { // Scissor + clear (round shouldn't do anything since we are pixel aligned) SkIRect scissorRect; drawBounds.round(&scissorRect); this->clear(scissorRect, *constColor); return QuadOptimization::kSubmitted; } } return QuadOptimization::kClipApplied; } } else { // Rounded corners and constant filled color (limit ourselves to solid colors because // there is no way to use custom local coordinates with drawRRect). SkASSERT(simpleColor); if (GrQuadUtils::CropToRect(clippedBounds, result.fAA, quad, /* compute local */ false) && quad->fDevice.quadType() == GrQuad::Type::kAxisAligned && quad->fDevice.bounds().contains(clippedBounds)) { // Since the cropped quad became a rectangle which covered the bounds of the rrect, // we can draw the rrect directly and ignore the edge flags this->drawRRect(nullptr, std::move(*paint), result.fAA, SkMatrix::I(), result.fRRect, GrStyle::SimpleFill()); return QuadOptimization::kSubmitted; } } // The quads have been updated to better fit the clip bounds, but can't get rid of // the clip entirely quad->fEdgeFlags = oldFlags; return QuadOptimization::kCropped; } void SurfaceDrawContext::drawFilledQuad(const GrClip* clip, GrPaint&& paint, DrawQuad* quad, const GrUserStencilSettings* ss) { ASSERT_SINGLE_OWNER RETURN_IF_ABANDONED SkDEBUGCODE(this->validate();) GR_CREATE_TRACE_MARKER_CONTEXT("SurfaceDrawContext", "drawFilledQuad", fContext); AutoCheckFlush acf(this->drawingManager()); QuadOptimization opt = this->attemptQuadOptimization(clip, ss, quad, &paint); if (opt >= QuadOptimization::kClipApplied) { // These optimizations require caller to add an op themselves const GrClip* finalClip = opt == QuadOptimization::kClipApplied ? nullptr : clip; // The quad being drawn requires AA if any of its edges requires AA GrAA aa{quad->fEdgeFlags != GrQuadAAFlags::kNone}; GrAAType aaType; if (ss) { aaType = (aa == GrAA::kYes) ? GrAAType::kMSAA : GrAAType::kNone; } else if (fCanUseDynamicMSAA && aa == GrAA::kNo) { // The SkGpuDevice ensures GrAA is always kYes when using dmsaa. If the caller calls // into here with GrAA::kNo, trust that they know what they're doing and that the // rendering will be equal with or without msaa. aaType = GrAAType::kNone; } else { aaType = this->chooseAAType(aa); } this->addDrawOp(finalClip, FillRectOp::Make(fContext, std::move(paint), aaType, quad, ss)); } // All other optimization levels were completely handled inside attempt(), so no extra op needed } void SurfaceDrawContext::drawTexture(const GrClip* clip, GrSurfaceProxyView view, SkAlphaType srcAlphaType, GrSamplerState::Filter filter, GrSamplerState::MipmapMode mm, SkBlendMode blendMode, const SkPMColor4f& color, const SkRect& srcRect, const SkRect& dstRect, GrQuadAAFlags edgeAA, SkCanvas::SrcRectConstraint constraint, const SkMatrix& viewMatrix, sk_sp colorSpaceXform) { // If we are using dmsaa then go through FillRRectOp (via fillRectToRect). if ((this->alwaysAntialias() || this->caps()->reducedShaderMode()) && edgeAA != GrQuadAAFlags::kNone) { auto [mustFilter, mustMM] = FilterAndMipmapHaveNoEffect( GrQuad::MakeFromRect(dstRect, viewMatrix), GrQuad(srcRect)); if (!mustFilter) { // Chromeos-jacuzzi devices (ARM Mali-G72 MP3) have issues with blitting with linear // filtering. Likely some optimization or quantization causes fragments to be produced // with small offset/error. This will result in a slight blending of pixels when // sampling. Normally in most application this would be completely unnoticeable but when // trying to use the gpu as a per pixel blit we will end up with slightly blurry // results. // See https://crbug.com/326980863 filter = GrSamplerState::Filter::kNearest; } GrPaint paint; paint.setColor4f(color); std::unique_ptr fp; if (constraint == SkCanvas::kStrict_SrcRectConstraint) { fp = GrTextureEffect::MakeSubset(view, srcAlphaType, SkMatrix::I(), GrSamplerState(filter, mm), srcRect, *this->caps()); } else { fp = GrTextureEffect::Make(view, srcAlphaType, SkMatrix::I(), filter, mm); } if (colorSpaceXform) { fp = GrColorSpaceXformEffect::Make(std::move(fp), std::move(colorSpaceXform)); } fp = GrBlendFragmentProcessor::Make(std::move(fp), nullptr); paint.setColorFragmentProcessor(std::move(fp)); if (blendMode != SkBlendMode::kSrcOver) { paint.setXPFactory(GrXPFactory::FromBlendMode(blendMode)); } this->fillRectToRect(clip, std::move(paint), GrAA::kYes, viewMatrix, dstRect, srcRect); return; } const SkRect* subset = constraint == SkCanvas::kStrict_SrcRectConstraint ? &srcRect : nullptr; DrawQuad quad{GrQuad::MakeFromRect(dstRect, viewMatrix), GrQuad(srcRect), edgeAA}; this->drawTexturedQuad(clip, std::move(view), srcAlphaType, std::move(colorSpaceXform), filter, mm, color, blendMode, &quad, subset); } void SurfaceDrawContext::drawTexturedQuad(const GrClip* clip, GrSurfaceProxyView proxyView, SkAlphaType srcAlphaType, sk_sp textureXform, GrSamplerState::Filter filter, GrSamplerState::MipmapMode mm, const SkPMColor4f& color, SkBlendMode blendMode, DrawQuad* quad, const SkRect* subset) { ASSERT_SINGLE_OWNER RETURN_IF_ABANDONED SkDEBUGCODE(this->validate();) SkASSERT(proxyView.asTextureProxy()); GR_CREATE_TRACE_MARKER_CONTEXT("SurfaceDrawContext", "drawTexturedQuad", fContext); AutoCheckFlush acf(this->drawingManager()); // Functionally this is very similar to drawFilledQuad except that there's no constColor to // enable the kSubmitted optimizations, no stencil settings support, and its a TextureOp. QuadOptimization opt = this->attemptQuadOptimization(clip, nullptr/*stencil*/, quad, nullptr/*paint*/); SkASSERT(opt != QuadOptimization::kSubmitted); if (opt != QuadOptimization::kDiscarded) { // Add the texture op if not discarded const GrClip* finalClip = opt == QuadOptimization::kClipApplied ? nullptr : clip; GrAAType aaType = this->chooseAAType(GrAA{quad->fEdgeFlags != GrQuadAAFlags::kNone}); auto clampType = GrColorTypeClampType(this->colorInfo().colorType()); auto saturate = clampType == GrClampType::kManual ? ganesh::TextureOp::Saturate::kYes : ganesh::TextureOp::Saturate::kNo; // Use the provided subset, although hypothetically we could detect that the cropped local // quad is sufficiently inside the subset and the constraint could be dropped. this->addDrawOp(finalClip, ganesh::TextureOp::Make(fContext, std::move(proxyView), srcAlphaType, std::move(textureXform), filter, mm, color, saturate, blendMode, aaType, quad, subset)); } } void SurfaceDrawContext::drawRect(const GrClip* clip, GrPaint&& paint, GrAA aa, const SkMatrix& viewMatrix, const SkRect& rect, const GrStyle* style) { if (!style) { style = &GrStyle::SimpleFill(); } ASSERT_SINGLE_OWNER RETURN_IF_ABANDONED SkDEBUGCODE(this->validate();) GR_CREATE_TRACE_MARKER_CONTEXT("SurfaceDrawContext", "drawRect", fContext); // Path effects should've been devolved to a path in SkGpuDevice SkASSERT(!style->pathEffect()); AutoCheckFlush acf(this->drawingManager()); const SkStrokeRec& stroke = style->strokeRec(); if (stroke.getStyle() == SkStrokeRec::kFill_Style) { // Fills the rect, using rect as its own local coordinates this->fillRectToRect(clip, std::move(paint), aa, viewMatrix, rect, rect); return; } else if ((stroke.getStyle() == SkStrokeRec::kStroke_Style || stroke.getStyle() == SkStrokeRec::kHairline_Style) && rect.width() && rect.height() && !this->caps()->reducedShaderMode()) { // Only use the StrokeRectOp for non-empty rectangles. Empty rectangles will be processed by // GrStyledShape to handle stroke caps and dashing properly. // // http://skbug.com/12206 -- there is a double-blend issue with the bevel version of // AAStrokeRectOp, and if we increase the AA bloat for MSAA it becomes more pronounced. // Don't use the bevel version with DMSAA. GrAAType aaType = (fCanUseDynamicMSAA && stroke.getJoin() == SkPaint::kMiter_Join && stroke.getMiter() >= SK_ScalarSqrt2) ? GrAAType::kCoverage : this->chooseAAType(aa); GrOp::Owner op = ganesh::StrokeRectOp::Make(fContext, std::move(paint), aaType, viewMatrix, rect, stroke); // op may be null if the stroke is not supported or if using coverage aa and the view matrix // does not preserve rectangles. if (op) { this->addDrawOp(clip, std::move(op)); return; } } assert_alive(paint); this->drawShapeUsingPathRenderer(clip, std::move(paint), aa, viewMatrix, GrStyledShape(rect, *style, DoSimplify::kNo)); } void SurfaceDrawContext::fillRectToRect(const GrClip* clip, GrPaint&& paint, GrAA aa, const SkMatrix& viewMatrix, const SkRect& rectToDraw, const SkRect& localRect) { DrawQuad quad{GrQuad::MakeFromRect(rectToDraw, viewMatrix), GrQuad(localRect), aa == GrAA::kYes ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone}; // If we are using dmsaa then attempt to draw the rect with FillRRectOp. if ((fContext->priv().caps()->reducedShaderMode() || this->alwaysAntialias()) && this->caps()->drawInstancedSupport() && aa == GrAA::kYes) { // If aa is kNo when using dmsaa, the rect is axis aligned. Don't use // FillRRectOp because it might require dual source blending. // http://skbug.com/11756 QuadOptimization opt = this->attemptQuadOptimization(clip, nullptr/*stencil*/, &quad, &paint); if (opt < QuadOptimization::kClipApplied) { // The optimization was completely handled inside attempt(). return; } SkRect croppedRect, croppedLocal{}; const GrClip* optimizedClip = clip; if (clip && viewMatrix.isScaleTranslate() && quad.fDevice.asRect(&croppedRect) && (!paint.usesLocalCoords() || quad.fLocal.asRect(&croppedLocal))) { // The cropped quad is still a rect, and our view matrix preserves rects. Map it back // to pre-matrix space. SkMatrix inverse; if (!viewMatrix.invert(&inverse)) { return; } SkASSERT(inverse.rectStaysRect()); inverse.mapRect(&croppedRect); if (opt == QuadOptimization::kClipApplied) { optimizedClip = nullptr; } } else { // Even if attemptQuadOptimization gave us an optimized quad, FillRRectOp needs a rect // in pre-matrix space, so use the original rect. Also preserve the original clip. croppedRect = rectToDraw; croppedLocal = localRect; } if (auto op = FillRRectOp::Make(fContext, this->arenaAlloc(), std::move(paint), viewMatrix, SkRRect::MakeRect(croppedRect), croppedLocal, GrAA::kYes)) { this->addDrawOp(optimizedClip, std::move(op)); return; } } assert_alive(paint); this->drawFilledQuad(clip, std::move(paint), &quad); } void SurfaceDrawContext::drawQuadSet(const GrClip* clip, GrPaint&& paint, const SkMatrix& viewMatrix, const GrQuadSetEntry quads[], int cnt) { GrAAType aaType = this->chooseAAType(GrAA::kYes); FillRectOp::AddFillRectOps(this, clip, fContext, std::move(paint), aaType, viewMatrix, quads, cnt); } int SurfaceDrawContext::maxWindowRectangles() const { return this->asRenderTargetProxy()->maxWindowRectangles(*this->caps()); } OpsTask::CanDiscardPreviousOps SurfaceDrawContext::canDiscardPreviousOpsOnFullClear() const { #if defined(GPU_TEST_UTILS) if (fPreserveOpsOnFullClear_TestingOnly) { return OpsTask::CanDiscardPreviousOps::kNo; } #endif // Regardless of how the clear is implemented (native clear or a fullscreen quad), all prior ops // would normally be overwritten. The one exception is if the render target context is marked as // needing a stencil buffer then there may be a prior op that writes to the stencil buffer. // Although the clear will ignore the stencil buffer, following draw ops may not so we can't get // rid of all the preceding ops. Beware! If we ever add any ops that have a side effect beyond // modifying the stencil buffer we will need a more elaborate tracking system (skbug.com/7002). return OpsTask::CanDiscardPreviousOps(!fNeedsStencil); } void SurfaceDrawContext::setNeedsStencil() { // Don't clear stencil until after we've set fNeedsStencil. This ensures we don't loop forever // in the event that there are driver bugs and we need to clear as a draw. bool hasInitializedStencil = fNeedsStencil; fNeedsStencil = true; if (!hasInitializedStencil) { this->asRenderTargetProxy()->setNeedsStencil(); if (this->caps()->performStencilClearsAsDraws()) { // There is a driver bug with clearing stencil. We must use an op to manually clear the // stencil buffer before the op that required 'setNeedsStencil'. this->internalStencilClear(nullptr, /* inside mask */ false); } else { this->getOpsTask()->setInitialStencilContent( OpsTask::StencilContent::kUserBitsCleared); } } } void SurfaceDrawContext::internalStencilClear(const SkIRect* scissor, bool insideStencilMask) { this->setNeedsStencil(); GrScissorState scissorState(this->asSurfaceProxy()->backingStoreDimensions()); if (scissor && !scissorState.set(*scissor)) { // The requested clear region is off screen, so nothing to do. return; } bool clearWithDraw = this->caps()->performStencilClearsAsDraws() || (scissorState.enabled() && this->caps()->performPartialClearsAsDraws()); if (clearWithDraw) { const GrUserStencilSettings* ss = GrStencilSettings::SetClipBitSettings(insideStencilMask); // Configure the paint to have no impact on the color buffer GrPaint paint; paint.setXPFactory(GrDisableColorXPFactory::Get()); this->addDrawOp(nullptr, FillRectOp::MakeNonAARect(fContext, std::move(paint), SkMatrix::I(), SkRect::Make(scissorState.rect()), ss)); } else { this->addOp(ClearOp::MakeStencilClip(fContext, scissorState, insideStencilMask)); } } bool SurfaceDrawContext::stencilPath(const GrHardClip* clip, GrAA doStencilMSAA, const SkMatrix& viewMatrix, const SkPath& path) { SkIRect clipBounds = clip ? clip->getConservativeBounds() : SkIRect::MakeSize(this->dimensions()); GrStyledShape shape(path, GrStyledShape::DoSimplify::kNo); PathRenderer::CanDrawPathArgs canDrawArgs; canDrawArgs.fCaps = fContext->priv().caps(); canDrawArgs.fProxy = this->asRenderTargetProxy(); canDrawArgs.fClipConservativeBounds = &clipBounds; canDrawArgs.fViewMatrix = &viewMatrix; canDrawArgs.fShape = &shape; canDrawArgs.fPaint = nullptr; canDrawArgs.fSurfaceProps = &fSurfaceProps; canDrawArgs.fAAType = (doStencilMSAA == GrAA::kYes) ? GrAAType::kMSAA : GrAAType::kNone; canDrawArgs.fHasUserStencilSettings = false; auto pr = this->drawingManager()->getPathRenderer(canDrawArgs, false, PathRendererChain::DrawType::kStencil); if (!pr) { SkDebugf("WARNING: No path renderer to stencil path.\n"); return false; } PathRenderer::StencilPathArgs args; args.fContext = fContext; args.fSurfaceDrawContext = this; args.fClip = clip; args.fClipConservativeBounds = &clipBounds; args.fViewMatrix = &viewMatrix; args.fShape = &shape; args.fDoStencilMSAA = doStencilMSAA; pr->stencilPath(args); return true; } void SurfaceDrawContext::drawTextureSet(const GrClip* clip, GrTextureSetEntry set[], int cnt, int proxyRunCnt, GrSamplerState::Filter filter, GrSamplerState::MipmapMode mm, SkBlendMode mode, SkCanvas::SrcRectConstraint constraint, const SkMatrix& viewMatrix, sk_sp texXform) { ASSERT_SINGLE_OWNER RETURN_IF_ABANDONED SkDEBUGCODE(this->validate();) GR_CREATE_TRACE_MARKER_CONTEXT("SurfaceDrawContext", "drawTextureSet", fContext); // Create the minimum number of GrTextureOps needed to draw this set. Individual // GrTextureOps can rebind the texture between draws thus avoiding GrPaint (re)creation. AutoCheckFlush acf(this->drawingManager()); GrAAType aaType = this->chooseAAType(GrAA::kYes); auto clampType = GrColorTypeClampType(this->colorInfo().colorType()); auto saturate = clampType == GrClampType::kManual ? ganesh::TextureOp::Saturate::kYes : ganesh::TextureOp::Saturate::kNo; ganesh::TextureOp::AddTextureSetOps(this, clip, fContext, set, cnt, proxyRunCnt, filter, mm, saturate, mode, aaType, constraint, viewMatrix, std::move(texXform)); } void SurfaceDrawContext::drawVertices(const GrClip* clip, GrPaint&& paint, const SkMatrix& viewMatrix, sk_sp vertices, GrPrimitiveType* overridePrimType, bool skipColorXform) { ASSERT_SINGLE_OWNER RETURN_IF_ABANDONED SkDEBUGCODE(this->validate();) GR_CREATE_TRACE_MARKER_CONTEXT("SurfaceDrawContext", "drawVertices", fContext); AutoCheckFlush acf(this->drawingManager()); SkASSERT(vertices); auto xform = skipColorXform ? nullptr : this->colorInfo().refColorSpaceXformFromSRGB(); GrAAType aaType = fCanUseDynamicMSAA ? GrAAType::kMSAA : this->chooseAAType(GrAA::kNo); GrOp::Owner op = DrawMeshOp::Make(fContext, std::move(paint), std::move(vertices), overridePrimType, viewMatrix, aaType, std::move(xform)); this->addDrawOp(clip, std::move(op)); } void SurfaceDrawContext::drawMesh(const GrClip* clip, GrPaint&& paint, const SkMatrix& viewMatrix, const SkMesh& mesh, TArray> children) { ASSERT_SINGLE_OWNER RETURN_IF_ABANDONED SkDEBUGCODE(this->validate();) GR_CREATE_TRACE_MARKER_CONTEXT("SurfaceDrawContext", "drawMesh", fContext); AutoCheckFlush acf(this->drawingManager()); SkASSERT(mesh.isValid()); auto xform = GrColorSpaceXform::Make(SkMeshSpecificationPriv::ColorSpace(*mesh.spec()), SkMeshSpecificationPriv::AlphaType(*mesh.spec()), this->colorInfo().colorSpace(), this->colorInfo().alphaType()); GrAAType aaType = fCanUseDynamicMSAA ? GrAAType::kMSAA : this->chooseAAType(GrAA::kNo); GrOp::Owner op = DrawMeshOp::Make(fContext, std::move(paint), mesh, std::move(children), viewMatrix, aaType, std::move(xform)); this->addDrawOp(clip, std::move(op)); } /////////////////////////////////////////////////////////////////////////////// void SurfaceDrawContext::drawAtlas(const GrClip* clip, GrPaint&& paint, const SkMatrix& viewMatrix, int spriteCount, const SkRSXform xform[], const SkRect texRect[], const SkColor colors[]) { ASSERT_SINGLE_OWNER RETURN_IF_ABANDONED SkDEBUGCODE(this->validate();) GR_CREATE_TRACE_MARKER_CONTEXT("SurfaceDrawContext", "drawAtlas", fContext); AutoCheckFlush acf(this->drawingManager()); GrAAType aaType = this->chooseAAType(GrAA::kNo); GrOp::Owner op = DrawAtlasOp::Make(fContext, std::move(paint), viewMatrix, aaType, spriteCount, xform, texRect, colors); this->addDrawOp(clip, std::move(op)); } /////////////////////////////////////////////////////////////////////////////// void SurfaceDrawContext::drawRRect(const GrClip* origClip, GrPaint&& paint, GrAA aa, const SkMatrix& viewMatrix, const SkRRect& rrect, const GrStyle& style) { ASSERT_SINGLE_OWNER RETURN_IF_ABANDONED SkDEBUGCODE(this->validate();) GR_CREATE_TRACE_MARKER_CONTEXT("SurfaceDrawContext", "drawRRect", fContext); SkASSERT(!style.pathEffect()); // this should've been devolved to a path in SkGpuDevice const SkStrokeRec& stroke = style.strokeRec(); if (stroke.getStyle() == SkStrokeRec::kFill_Style && rrect.isEmpty()) { return; } const GrClip* clip = origClip; // It is not uncommon to clip to a round rect and then draw that same round rect. Since our // lower level clip code works from op bounds, which are SkRects, it doesn't detect that the // clip can be ignored. The following test attempts to mitigate the stencil clip cost but only // works for axis-aligned round rects. This also only works for filled rrects since the stroke // width outsets beyond the rrect itself. // TODO: skbug.com/10462 - There was mixed performance wins and regressions when this // optimization was turned on outside of Android Framework. I (michaelludwig) believe this is // do to the overhead in determining if an SkClipStack is just a rrect. Once that is improved, // re-enable this and see if we avoid the regressions. #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK SkRRect devRRect; if (clip && stroke.getStyle() == SkStrokeRec::kFill_Style && rrect.transform(viewMatrix, &devRRect)) { GrClip::PreClipResult result = clip->preApply(devRRect.getBounds(), aa); switch(result.fEffect) { case GrClip::Effect::kClippedOut: return; case GrClip::Effect::kUnclipped: clip = nullptr; break; case GrClip::Effect::kClipped: // Currently there's no general-purpose rrect-to-rrect contains function, and if we // got here, we know the devRRect's bounds aren't fully contained by the clip. // Testing for equality between the two is a reasonable stop-gap for now. if (result.fIsRRect && result.fRRect == devRRect) { // NOTE: On the android framework, we allow this optimization even when the clip // is non-AA and the draw is AA. if (result.fAA == aa || (result.fAA == GrAA::kNo && aa == GrAA::kYes)) { clip = nullptr; } } break; default: SkUNREACHABLE; } } #endif AutoCheckFlush acf(this->drawingManager()); GrAAType aaType = this->chooseAAType(aa); GrOp::Owner op; #ifndef SK_ENABLE_OPTIMIZE_SIZE if (aaType == GrAAType::kCoverage && !fCanUseDynamicMSAA && !this->caps()->reducedShaderMode() && rrect.isSimple() && rrect.getSimpleRadii().fX == rrect.getSimpleRadii().fY && viewMatrix.rectStaysRect() && viewMatrix.isSimilarity()) { // In specific cases we use a dedicated circular round rect op to try and get better perf. assert_alive(paint); op = GrOvalOpFactory::MakeCircularRRectOp(fContext, std::move(paint), viewMatrix, rrect, stroke, this->caps()->shaderCaps()); } #endif if (!op && style.isSimpleFill()) { assert_alive(paint); op = FillRRectOp::Make(fContext, this->arenaAlloc(), std::move(paint), viewMatrix, rrect, rrect.rect(), GrAA(aaType != GrAAType::kNone)); } #ifndef SK_ENABLE_OPTIMIZE_SIZE if (!op && (aaType == GrAAType::kCoverage || fCanUseDynamicMSAA)) { assert_alive(paint); op = GrOvalOpFactory::MakeRRectOp( fContext, std::move(paint), viewMatrix, rrect, stroke, this->caps()->shaderCaps()); } #endif if (op) { this->addDrawOp(clip, std::move(op)); return; } assert_alive(paint); this->drawShapeUsingPathRenderer(clip, std::move(paint), aa, viewMatrix, GrStyledShape(rrect, style, DoSimplify::kNo)); } /////////////////////////////////////////////////////////////////////////////// bool SurfaceDrawContext::drawFastShadow(const GrClip* clip, const SkMatrix& viewMatrix, const SkPath& path, const SkDrawShadowRec& rec) { ASSERT_SINGLE_OWNER if (fContext->abandoned()) { return true; } SkDEBUGCODE(this->validate();) GR_CREATE_TRACE_MARKER_CONTEXT("SurfaceDrawContext", "drawFastShadow", fContext); // check z plane bool tiltZPlane = SkToBool(!SkScalarNearlyZero(rec.fZPlaneParams.fX) || !SkScalarNearlyZero(rec.fZPlaneParams.fY)); bool skipAnalytic = SkToBool(rec.fFlags & SkShadowFlags::kGeometricOnly_ShadowFlag); if (tiltZPlane || skipAnalytic || !viewMatrix.rectStaysRect() || !viewMatrix.isSimilarity()) { return false; } SkRRect rrect; SkRect rect; // we can only handle rects, circles, and simple rrects with circular corners bool isRRect = path.isRRect(&rrect) && SkRRectPriv::IsNearlySimpleCircular(rrect) && rrect.getSimpleRadii().fX > SK_ScalarNearlyZero; if (!isRRect && path.isOval(&rect) && SkScalarNearlyEqual(rect.width(), rect.height()) && rect.width() > SK_ScalarNearlyZero) { rrect.setOval(rect); isRRect = true; } if (!isRRect && path.isRect(&rect)) { rrect.setRect(rect); isRRect = true; } if (!isRRect) { return false; } if (rrect.isEmpty()) { return true; } AutoCheckFlush acf(this->drawingManager()); SkPoint3 devLightPos = rec.fLightPos; bool directional = SkToBool(rec.fFlags & kDirectionalLight_ShadowFlag); if (!directional) { // transform light viewMatrix.mapPoints((SkPoint*)&devLightPos.fX, 1); } // 1/scale SkScalar devToSrcScale = viewMatrix.isScaleTranslate() ? SkScalarInvert(SkScalarAbs(viewMatrix[SkMatrix::kMScaleX])) : sk_float_rsqrt(viewMatrix[SkMatrix::kMScaleX] * viewMatrix[SkMatrix::kMScaleX] + viewMatrix[SkMatrix::kMSkewX] * viewMatrix[SkMatrix::kMSkewX]); SkScalar occluderHeight = rec.fZPlaneParams.fZ; bool transparent = SkToBool(rec.fFlags & SkShadowFlags::kTransparentOccluder_ShadowFlag); if (SkColorGetA(rec.fAmbientColor) > 0) { SkScalar devSpaceInsetWidth = SkDrawShadowMetrics::AmbientBlurRadius(occluderHeight); const SkScalar umbraRecipAlpha = SkDrawShadowMetrics::AmbientRecipAlpha(occluderHeight); const SkScalar devSpaceAmbientBlur = devSpaceInsetWidth * umbraRecipAlpha; // Outset the shadow rrect to the border of the penumbra SkScalar ambientPathOutset = devSpaceInsetWidth * devToSrcScale; SkRRect ambientRRect; SkRect outsetRect = rrect.rect().makeOutset(ambientPathOutset, ambientPathOutset); // If the rrect was an oval then its outset will also be one. // We set it explicitly to avoid errors. if (rrect.isOval()) { ambientRRect = SkRRect::MakeOval(outsetRect); } else { SkScalar outsetRad = SkRRectPriv::GetSimpleRadii(rrect).fX + ambientPathOutset; ambientRRect = SkRRect::MakeRectXY(outsetRect, outsetRad, outsetRad); } // The ShadowRRectOp still uses 8888 colors, so it might get clamped if the shadow color // does not fit in bytes after being transformed to the destination color space. This can // happen if the destination color space is smaller than sRGB, which is highly unlikely. GrColor ambientColor = SkColorToPMColor4f(rec.fAmbientColor, colorInfo()).toBytes_RGBA(); if (transparent) { // set a large inset to force a fill devSpaceInsetWidth = ambientRRect.width(); } GrOp::Owner op = ShadowRRectOp::Make(fContext, ambientColor, viewMatrix, ambientRRect, devSpaceAmbientBlur, devSpaceInsetWidth); if (op) { this->addDrawOp(clip, std::move(op)); } } if (SkColorGetA(rec.fSpotColor) > 0) { SkScalar devSpaceSpotBlur; SkScalar spotScale; SkVector spotOffset; if (directional) { SkDrawShadowMetrics::GetDirectionalParams(occluderHeight, devLightPos.fX, devLightPos.fY, devLightPos.fZ, rec.fLightRadius, &devSpaceSpotBlur, &spotScale, &spotOffset); } else { SkDrawShadowMetrics::GetSpotParams(occluderHeight, devLightPos.fX, devLightPos.fY, devLightPos.fZ, rec.fLightRadius, &devSpaceSpotBlur, &spotScale, &spotOffset); } // handle scale of radius due to CTM const SkScalar srcSpaceSpotBlur = devSpaceSpotBlur * devToSrcScale; // Adjust translate for the effect of the scale. spotOffset.fX += spotScale*viewMatrix[SkMatrix::kMTransX]; spotOffset.fY += spotScale*viewMatrix[SkMatrix::kMTransY]; // This offset is in dev space, need to transform it into source space. SkMatrix ctmInverse; if (viewMatrix.invert(&ctmInverse)) { ctmInverse.mapPoints(&spotOffset, 1); } else { // Since the matrix is a similarity, this should never happen, but just in case... SkDebugf("Matrix is degenerate. Will not render spot shadow correctly!\n"); SkASSERT(false); } // Compute the transformed shadow rrect SkRRect spotShadowRRect; SkMatrix shadowTransform; shadowTransform.setScaleTranslate(spotScale, spotScale, spotOffset.fX, spotOffset.fY); rrect.transform(shadowTransform, &spotShadowRRect); SkScalar spotRadius = spotShadowRRect.getSimpleRadii().fX; // Compute the insetWidth SkScalar blurOutset = srcSpaceSpotBlur; SkScalar insetWidth = blurOutset; if (transparent) { // If transparent, just do a fill insetWidth += spotShadowRRect.width(); } else { // For shadows, instead of using a stroke we specify an inset from the penumbra // border. We want to extend this inset area so that it meets up with the caster // geometry. The inset geometry will by default already be inset by the blur width. // // We compare the min and max corners inset by the radius between the original // rrect and the shadow rrect. The distance between the two plus the difference // between the scaled radius and the original radius gives the distance from the // transformed shadow shape to the original shape in that corner. The max // of these gives the maximum distance we need to cover. // // Since we are outsetting by 1/2 the blur distance, we just add the maxOffset to // that to get the full insetWidth. SkScalar maxOffset; if (rrect.isRect()) { // Manhattan distance works better for rects maxOffset = std::max(std::max(SkTAbs(spotShadowRRect.rect().fLeft - rrect.rect().fLeft), SkTAbs(spotShadowRRect.rect().fTop - rrect.rect().fTop)), std::max(SkTAbs(spotShadowRRect.rect().fRight - rrect.rect().fRight), SkTAbs(spotShadowRRect.rect().fBottom - rrect.rect().fBottom))); } else { SkScalar dr = spotRadius - SkRRectPriv::GetSimpleRadii(rrect).fX; SkPoint upperLeftOffset = SkPoint::Make(spotShadowRRect.rect().fLeft - rrect.rect().fLeft + dr, spotShadowRRect.rect().fTop - rrect.rect().fTop + dr); SkPoint lowerRightOffset = SkPoint::Make(spotShadowRRect.rect().fRight - rrect.rect().fRight - dr, spotShadowRRect.rect().fBottom - rrect.rect().fBottom - dr); maxOffset = SkScalarSqrt(std::max(SkPointPriv::LengthSqd(upperLeftOffset), SkPointPriv::LengthSqd(lowerRightOffset))) + dr; } insetWidth += std::max(blurOutset, maxOffset); } // Outset the shadow rrect to the border of the penumbra SkRect outsetRect = spotShadowRRect.rect().makeOutset(blurOutset, blurOutset); if (spotShadowRRect.isOval()) { spotShadowRRect = SkRRect::MakeOval(outsetRect); } else { SkScalar outsetRad = spotRadius + blurOutset; spotShadowRRect = SkRRect::MakeRectXY(outsetRect, outsetRad, outsetRad); } // The ShadowRRectOp still uses 8888 colors, so it might get clamped if the shadow color // does not fit in bytes after being transformed to the destination color space. This can // happen if the destination color space is smaller than sRGB, which is highly unlikely. GrColor spotColor = SkColorToPMColor4f(rec.fSpotColor, colorInfo()).toBytes_RGBA(); GrOp::Owner op = ShadowRRectOp::Make(fContext, spotColor, viewMatrix, spotShadowRRect, 2.0f * devSpaceSpotBlur, insetWidth); if (op) { this->addDrawOp(clip, std::move(op)); } } return true; } /////////////////////////////////////////////////////////////////////////////// void SurfaceDrawContext::drawRegion(const GrClip* clip, GrPaint&& paint, GrAA aa, const SkMatrix& viewMatrix, const SkRegion& region, const GrStyle& style, const GrUserStencilSettings* ss) { ASSERT_SINGLE_OWNER RETURN_IF_ABANDONED SkDEBUGCODE(this->validate();) GR_CREATE_TRACE_MARKER_CONTEXT("SurfaceDrawContext", "drawRegion", fContext); if (GrAA::kYes == aa) { // GrRegionOp performs no antialiasing but is much faster, so here we check the matrix // to see whether aa is really required. if (!SkToBool(viewMatrix.getType() & ~(SkMatrix::kTranslate_Mask)) && SkScalarIsInt(viewMatrix.getTranslateX()) && SkScalarIsInt(viewMatrix.getTranslateY())) { aa = GrAA::kNo; } } bool complexStyle = !style.isSimpleFill(); if (complexStyle || GrAA::kYes == aa) { SkPath path; region.getBoundaryPath(&path); path.setIsVolatile(true); return this->drawPath(clip, std::move(paint), aa, viewMatrix, path, style); } GrAAType aaType = (this->numSamples() > 1) ? GrAAType::kMSAA : GrAAType::kNone; GrOp::Owner op = RegionOp::Make(fContext, std::move(paint), viewMatrix, region, aaType, ss); this->addDrawOp(clip, std::move(op)); } void SurfaceDrawContext::drawOval(const GrClip* clip, GrPaint&& paint, GrAA aa, const SkMatrix& viewMatrix, const SkRect& oval, const GrStyle& style) { ASSERT_SINGLE_OWNER RETURN_IF_ABANDONED SkDEBUGCODE(this->validate();) GR_CREATE_TRACE_MARKER_CONTEXT("SurfaceDrawContext", "drawOval", fContext); const SkStrokeRec& stroke = style.strokeRec(); if (oval.isEmpty() && !style.pathEffect()) { if (stroke.getStyle() == SkStrokeRec::kFill_Style) { return; } this->drawRect(clip, std::move(paint), aa, viewMatrix, oval, &style); return; } AutoCheckFlush acf(this->drawingManager()); GrAAType aaType = this->chooseAAType(aa); GrOp::Owner op; #ifndef SK_ENABLE_OPTIMIZE_SIZE if (aaType == GrAAType::kCoverage && !fCanUseDynamicMSAA && !this->caps()->reducedShaderMode() && oval.width() > SK_ScalarNearlyZero && oval.width() == oval.height() && viewMatrix.isSimilarity()) { // In specific cases we use a dedicated circle op to try and get better perf. assert_alive(paint); op = GrOvalOpFactory::MakeCircleOp(fContext, std::move(paint), viewMatrix, oval, style, this->caps()->shaderCaps()); } #endif if (!op && style.isSimpleFill()) { // FillRRectOp has special geometry and a fragment-shader branch to conditionally evaluate // the arc equation. This same special geometry and fragment branch also turn out to be a // substantial optimization for drawing ovals (namely, by not evaluating the arc equation // inside the oval's inner diamond). Given these optimizations, it's a clear win to draw // ovals the exact same way we do round rects. assert_alive(paint); op = FillRRectOp::Make(fContext, this->arenaAlloc(), std::move(paint), viewMatrix, SkRRect::MakeOval(oval), oval, GrAA(aaType != GrAAType::kNone)); } #ifndef SK_ENABLE_OPTIMIZE_SIZE if (!op && (aaType == GrAAType::kCoverage || fCanUseDynamicMSAA)) { assert_alive(paint); op = GrOvalOpFactory::MakeOvalOp(fContext, std::move(paint), viewMatrix, oval, style, this->caps()->shaderCaps()); } #endif if (op) { this->addDrawOp(clip, std::move(op)); return; } assert_alive(paint); this->drawShapeUsingPathRenderer(clip, std::move(paint), aa, viewMatrix, GrStyledShape(SkRRect::MakeOval(oval), SkPathDirection::kCW, 2, false, style, DoSimplify::kNo)); } void SurfaceDrawContext::drawArc(const GrClip* clip, GrPaint&& paint, GrAA aa, const SkMatrix& viewMatrix, const SkArc& arc, const GrStyle& style) { ASSERT_SINGLE_OWNER RETURN_IF_ABANDONED SkDEBUGCODE(this->validate();) GR_CREATE_TRACE_MARKER_CONTEXT("SurfaceDrawContext", "drawArc", fContext); AutoCheckFlush acf(this->drawingManager()); #ifndef SK_ENABLE_OPTIMIZE_SIZE GrAAType aaType = this->chooseAAType(aa); if (aaType == GrAAType::kCoverage) { const GrShaderCaps* shaderCaps = this->caps()->shaderCaps(); GrOp::Owner op = GrOvalOpFactory::MakeArcOp(fContext, std::move(paint), viewMatrix, arc.fOval, arc.fStartAngle, arc.fSweepAngle, arc.isWedge(), style, shaderCaps); if (op) { this->addDrawOp(clip, std::move(op)); return; } assert_alive(paint); } #endif this->drawShapeUsingPathRenderer(clip, std::move(paint), aa, viewMatrix, GrStyledShape::MakeArc(arc, style, DoSimplify::kNo)); } void SurfaceDrawContext::drawImageLattice(const GrClip* clip, GrPaint&& paint, const SkMatrix& viewMatrix, GrSurfaceProxyView view, SkAlphaType alphaType, sk_sp csxf, GrSamplerState::Filter filter, std::unique_ptr iter, const SkRect& dst) { ASSERT_SINGLE_OWNER RETURN_IF_ABANDONED SkDEBUGCODE(this->validate();) GR_CREATE_TRACE_MARKER_CONTEXT("SurfaceDrawContext", "drawImageLattice", fContext); AutoCheckFlush acf(this->drawingManager()); GrOp::Owner op = LatticeOp::MakeNonAA(fContext, std::move(paint), viewMatrix, std::move(view), alphaType, std::move(csxf), filter, std::move(iter), dst); this->addDrawOp(clip, std::move(op)); } void SurfaceDrawContext::drawDrawable(std::unique_ptr drawable, const SkRect& bounds) { ASSERT_SINGLE_OWNER RETURN_IF_ABANDONED SkDEBUGCODE(this->validate();) GR_CREATE_TRACE_MARKER_CONTEXT("SurfaceDrawContext", "drawDrawable", fContext); GrOp::Owner op(DrawableOp::Make(fContext, std::move(drawable), bounds)); SkASSERT(op); this->addOp(std::move(op)); } void SurfaceDrawContext::setLastClip(uint32_t clipStackGenID, const SkIRect& devClipBounds, int numClipAnalyticElements) { auto opsTask = this->getOpsTask(); opsTask->fLastClipStackGenID = clipStackGenID; opsTask->fLastDevClipBounds = devClipBounds; opsTask->fLastClipNumAnalyticElements = numClipAnalyticElements; } bool SurfaceDrawContext::mustRenderClip(uint32_t clipStackGenID, const SkIRect& devClipBounds, int numClipAnalyticElements) { auto opsTask = this->getOpsTask(); return opsTask->fLastClipStackGenID != clipStackGenID || !opsTask->fLastDevClipBounds.contains(devClipBounds) || opsTask->fLastClipNumAnalyticElements != numClipAnalyticElements; } bool SurfaceDrawContext::waitOnSemaphores(int numSemaphores, const GrBackendSemaphore waitSemaphores[], bool deleteSemaphoresAfterWait) { ASSERT_SINGLE_OWNER RETURN_FALSE_IF_ABANDONED SkDEBUGCODE(this->validate();) GR_CREATE_TRACE_MARKER_CONTEXT("SurfaceDrawContext", "waitOnSemaphores", fContext); AutoCheckFlush acf(this->drawingManager()); if (numSemaphores && !this->caps()->backendSemaphoreSupport()) { return false; } auto direct = fContext->asDirectContext(); if (!direct) { return false; } auto resourceProvider = direct->priv().resourceProvider(); GrWrapOwnership ownership = deleteSemaphoresAfterWait ? kAdopt_GrWrapOwnership : kBorrow_GrWrapOwnership; std::unique_ptr[]> grSemaphores( new std::unique_ptr[numSemaphores]); for (int i = 0; i < numSemaphores; ++i) { grSemaphores[i] = resourceProvider->wrapBackendSemaphore(waitSemaphores[i], GrSemaphoreWrapType::kWillWait, ownership); } this->drawingManager()->newWaitRenderTask(this->asSurfaceProxyRef(), std::move(grSemaphores), numSemaphores); return true; } void SurfaceDrawContext::drawPath(const GrClip* clip, GrPaint&& paint, GrAA aa, const SkMatrix& viewMatrix, const SkPath& path, const GrStyle& style) { ASSERT_SINGLE_OWNER RETURN_IF_ABANDONED SkDEBUGCODE(this->validate();) GR_CREATE_TRACE_MARKER_CONTEXT("SurfaceDrawContext", "drawPath", fContext); GrStyledShape shape(path, style, DoSimplify::kNo); this->drawShape(clip, std::move(paint), aa, viewMatrix, std::move(shape)); } void SurfaceDrawContext::drawShape(const GrClip* clip, GrPaint&& paint, GrAA aa, const SkMatrix& viewMatrix, GrStyledShape&& shape) { ASSERT_SINGLE_OWNER RETURN_IF_ABANDONED SkDEBUGCODE(this->validate();) GR_CREATE_TRACE_MARKER_CONTEXT("SurfaceDrawContext", "drawShape", fContext); if (shape.isEmpty()) { if (shape.inverseFilled()) { this->drawPaint(clip, std::move(paint), viewMatrix); } return; } AutoCheckFlush acf(this->drawingManager()); // If we get here in drawShape(), we definitely need to use path rendering this->drawShapeUsingPathRenderer(clip, std::move(paint), aa, viewMatrix, std::move(shape), /* attemptDrawSimple */ true); } static SkIRect get_clip_bounds(const SurfaceDrawContext* sdc, const GrClip* clip) { return clip ? clip->getConservativeBounds() : SkIRect::MakeWH(sdc->width(), sdc->height()); } bool SurfaceDrawContext::drawAndStencilPath(const GrHardClip* clip, const GrUserStencilSettings* ss, SkRegion::Op op, bool invert, GrAA aa, const SkMatrix& viewMatrix, const SkPath& path) { ASSERT_SINGLE_OWNER RETURN_FALSE_IF_ABANDONED SkDEBUGCODE(this->validate();) GR_CREATE_TRACE_MARKER_CONTEXT("SurfaceDrawContext", "drawAndStencilPath", fContext); if (path.isEmpty() && path.isInverseFillType()) { GrPaint paint; paint.setCoverageSetOpXPFactory(op, invert); this->stencilRect(clip, ss, std::move(paint), GrAA::kNo, SkMatrix::I(), SkRect::Make(this->dimensions())); return true; } AutoCheckFlush acf(this->drawingManager()); // An Assumption here is that path renderer would use some form of tweaking // the src color (either the input alpha or in the frag shader) to implement // aa. If we have some future driver-mojo path AA that can do the right // thing WRT to the blend then we'll need some query on the PR. GrAAType aaType = this->chooseAAType(aa); bool hasUserStencilSettings = !ss->isUnused(); SkIRect clipConservativeBounds = get_clip_bounds(this, clip); GrPaint paint; paint.setCoverageSetOpXPFactory(op, invert); GrStyledShape shape(path, GrStyle::SimpleFill()); PathRenderer::CanDrawPathArgs canDrawArgs; canDrawArgs.fCaps = this->caps(); canDrawArgs.fProxy = this->asRenderTargetProxy(); canDrawArgs.fViewMatrix = &viewMatrix; canDrawArgs.fShape = &shape; canDrawArgs.fPaint = &paint; canDrawArgs.fSurfaceProps = &fSurfaceProps; canDrawArgs.fClipConservativeBounds = &clipConservativeBounds; canDrawArgs.fAAType = aaType; canDrawArgs.fHasUserStencilSettings = hasUserStencilSettings; using DrawType = PathRendererChain::DrawType; // Don't allow the SW renderer auto pr = this->drawingManager()->getPathRenderer(canDrawArgs, false, DrawType::kStencilAndColor); if (!pr) { return false; } PathRenderer::DrawPathArgs args{this->drawingManager()->getContext(), std::move(paint), ss, this, clip, &clipConservativeBounds, &viewMatrix, &shape, aaType, this->colorInfo().isLinearlyBlended()}; pr->drawPath(args); return true; } skgpu::Budgeted SurfaceDrawContext::isBudgeted() const { ASSERT_SINGLE_OWNER if (fContext->abandoned()) { return skgpu::Budgeted::kNo; } SkDEBUGCODE(this->validate();) return this->asSurfaceProxy()->isBudgeted(); } void SurfaceDrawContext::drawStrokedLine(const GrClip* clip, GrPaint&& paint, GrAA aa, const SkMatrix& viewMatrix, const SkPoint points[2], const SkStrokeRec& stroke) { ASSERT_SINGLE_OWNER SkASSERT(stroke.getStyle() == SkStrokeRec::kStroke_Style); SkASSERT(stroke.getWidth() > 0); // Adding support for round capping would require a // SurfaceDrawContext::fillRRectWithLocalMatrix entry point SkASSERT(SkPaint::kRound_Cap != stroke.getCap()); const SkScalar halfWidth = 0.5f * stroke.getWidth(); if (halfWidth <= 0.f) { // Prevents underflow when stroke width is epsilon > 0 (so technically not a hairline). // The CTM would need to have a scale near 1/epsilon in order for this to have meaningful // coverage (although that would likely overflow elsewhere and cause the draw to drop due // to non-finite bounds). At any other scale, this line is so thin, it's coverage is // negligible, so discarding the draw is visually equivalent. return; } SkVector parallel = points[1] - points[0]; if (!SkPoint::Normalize(¶llel)) { parallel.fX = 1.0f; parallel.fY = 0.0f; } parallel *= halfWidth; SkVector ortho = { parallel.fY, -parallel.fX }; SkPoint p0 = points[0], p1 = points[1]; if (stroke.getCap() == SkPaint::kSquare_Cap) { // Extra extension for square caps p0 -= parallel; p1 += parallel; } // If we are using dmsaa or reduced shader mode then attempt to draw with FillRRectOp. if (this->caps()->drawInstancedSupport() && (this->alwaysAntialias() || (fContext->priv().caps()->reducedShaderMode() && aa == GrAA::kYes))) { SkMatrix localMatrix = SkMatrix::MakeAll(p1.fX - p0.fX, ortho.fX, p0.fX, p1.fY - p0.fY, ortho.fY, p0.fY, 0, 0, 1); if (auto op = FillRRectOp::Make(fContext, this->arenaAlloc(), std::move(paint), SkMatrix::Concat(viewMatrix, localMatrix), SkRRect::MakeRect({0,-1,1,1}), localMatrix, GrAA::kYes)) { this->addDrawOp(clip, std::move(op)); return; } } // Order is TL, TR, BR, BL where arbitrarily "down" is p0 to p1 and "right" is positive SkPoint corners[4] = { p0 - ortho, p0 + ortho, p1 + ortho, p1 - ortho }; GrQuadAAFlags edgeAA = (aa == GrAA::kYes) ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone; assert_alive(paint); this->fillQuadWithEdgeAA(clip, std::move(paint), edgeAA, viewMatrix, corners, nullptr); } bool SurfaceDrawContext::drawSimpleShape(const GrClip* clip, GrPaint* paint, GrAA aa, const SkMatrix& viewMatrix, const GrStyledShape& shape) { if (!shape.style().hasPathEffect()) { GrAAType aaType = this->chooseAAType(aa); SkPoint linePts[2]; SkRRect rrect; // We can ignore the starting point and direction since there is no path effect. bool inverted; if (shape.asLine(linePts, &inverted) && !inverted && shape.style().strokeRec().getStyle() == SkStrokeRec::kStroke_Style && shape.style().strokeRec().getCap() != SkPaint::kRound_Cap) { // The stroked line is an oriented rectangle, which looks the same or better (if // perspective) compared to path rendering. The exception is subpixel/hairline lines // that are non-AA or MSAA, in which case the default path renderer achieves higher // quality. // FIXME(michaelludwig): If the fill rect op could take an external coverage, or checks // for and outsets thin non-aa rects to 1px, the path renderer could be skipped. SkScalar coverage; if (aaType == GrAAType::kCoverage || !SkDrawTreatAAStrokeAsHairline(shape.style().strokeRec().getWidth(), viewMatrix, &coverage)) { this->drawStrokedLine(clip, std::move(*paint), aa, viewMatrix, linePts, shape.style().strokeRec()); return true; } } else if (shape.asRRect(&rrect, &inverted) && !inverted) { if (rrect.isRect()) { this->drawRect(clip, std::move(*paint), aa, viewMatrix, rrect.rect(), &shape.style()); return true; } else if (rrect.isOval()) { this->drawOval(clip, std::move(*paint), aa, viewMatrix, rrect.rect(), shape.style()); return true; } this->drawRRect(clip, std::move(*paint), aa, viewMatrix, rrect, shape.style()); return true; } else if (GrAAType::kCoverage == aaType && shape.style().isSimpleFill() && viewMatrix.rectStaysRect() && !this->caps()->reducedShaderMode()) { // TODO: the rectStaysRect restriction could be lifted if we were willing to apply the // matrix to all the points individually rather than just to the rect SkRect rects[2]; if (shape.asNestedRects(rects)) { // Concave AA paths are expensive - try to avoid them for special cases GrOp::Owner op = ganesh::StrokeRectOp::MakeNested(fContext, std::move(*paint), viewMatrix, rects); if (op) { this->addDrawOp(clip, std::move(op)); return true; } // Fall through to let path renderer handle subpixel nested rects with unequal // stroke widths along X/Y. } } } return false; } void SurfaceDrawContext::drawShapeUsingPathRenderer(const GrClip* clip, GrPaint&& paint, GrAA aa, const SkMatrix& viewMatrix, GrStyledShape&& shape, bool attemptDrawSimple) { ASSERT_SINGLE_OWNER RETURN_IF_ABANDONED GR_CREATE_TRACE_MARKER_CONTEXT("SurfaceDrawContext", "internalDrawPath", fContext); if (!viewMatrix.isFinite() || !shape.bounds().isFinite()) { return; } SkIRect clipConservativeBounds = get_clip_bounds(this, clip); // Always allow paths to trigger DMSAA. GrAAType aaType = fCanUseDynamicMSAA ? GrAAType::kMSAA : this->chooseAAType(aa); PathRenderer::CanDrawPathArgs canDrawArgs; canDrawArgs.fCaps = this->caps(); canDrawArgs.fProxy = this->asRenderTargetProxy(); canDrawArgs.fViewMatrix = &viewMatrix; canDrawArgs.fShape = &shape; canDrawArgs.fPaint = &paint; canDrawArgs.fSurfaceProps = &fSurfaceProps; canDrawArgs.fClipConservativeBounds = &clipConservativeBounds; canDrawArgs.fHasUserStencilSettings = false; canDrawArgs.fAAType = aaType; constexpr static bool kDisallowSWPathRenderer = false; using DrawType = PathRendererChain::DrawType; PathRenderer* pr = nullptr; if (!shape.style().strokeRec().isFillStyle() && !shape.isEmpty()) { // Give the tessellation path renderer a chance to claim this stroke before we simplify it. PathRenderer* tess = this->drawingManager()->getTessellationPathRenderer(); if (tess && tess->canDrawPath(canDrawArgs) == PathRenderer::CanDrawPath::kYes) { pr = tess; } } if (!pr) { // The shape isn't a stroke that can be drawn directly. Simplify if possible. shape.simplify(); if (shape.isEmpty() && !shape.inverseFilled()) { return; } if (attemptDrawSimple || shape.simplified()) { // Usually we enter drawShapeUsingPathRenderer() because the shape+style was too complex // for dedicated draw ops. However, if GrStyledShape was able to reduce something we // ought to try again instead of going right to path rendering. if (this->drawSimpleShape(clip, &paint, aa, viewMatrix, shape)) { return; } } // Try a 1st time without applying any of the style to the geometry (and barring sw) pr = this->drawingManager()->getPathRenderer(canDrawArgs, kDisallowSWPathRenderer, DrawType::kColor); } SkScalar styleScale = GrStyle::MatrixToScaleFactor(viewMatrix); if (styleScale == 0.0f) { return; } if (!pr && shape.style().pathEffect()) { // It didn't work above, so try again with the path effect applied. shape = shape.applyStyle(GrStyle::Apply::kPathEffectOnly, styleScale); if (shape.isEmpty()) { return; } pr = this->drawingManager()->getPathRenderer(canDrawArgs, kDisallowSWPathRenderer, DrawType::kColor); } if (!pr && shape.style().applies()) { shape = shape.applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, styleScale); if (shape.isEmpty()) { return; } pr = this->drawingManager()->getPathRenderer(canDrawArgs, kDisallowSWPathRenderer, DrawType::kColor); } if (!pr) { // Fall back on SW renderer as a last resort. if (GrAATypeIsHW(aaType)) { // No point in trying SW renderer with MSAA. aaType = GrAAType::kCoverage; canDrawArgs.fAAType = aaType; } // This can only fail if a) AA type is MSAA or the style is not applied (already checked), // or b) the SW renderer's proxy provider is null, which should never happen. pr = this->drawingManager()->getSoftwarePathRenderer(); SkASSERT(pr->canDrawPath(canDrawArgs) != PathRenderer::CanDrawPath::kNo); #if GR_PATH_RENDERER_SPEW SkDebugf("falling back to: %s\n", pr->name()); #endif } PathRenderer::DrawPathArgs args{this->drawingManager()->getContext(), std::move(paint), &GrUserStencilSettings::kUnused, this, clip, &clipConservativeBounds, &viewMatrix, canDrawArgs.fShape, aaType, this->colorInfo().isLinearlyBlended()}; pr->drawPath(args); } void SurfaceDrawContext::addDrawOp(const GrClip* clip, GrOp::Owner op, const std::function& willAddFn) { ASSERT_SINGLE_OWNER if (fContext->abandoned()) { return; } GrDrawOp* drawOp = (GrDrawOp*)op.get(); SkDEBUGCODE(this->validate();) SkDEBUGCODE(drawOp->fAddDrawOpCalled = true;) GR_CREATE_TRACE_MARKER_CONTEXT("SurfaceDrawContext", "addDrawOp", fContext); // Setup clip SkRect bounds; op_bounds(&bounds, op.get()); GrAppliedClip appliedClip(this->dimensions(), this->asSurfaceProxy()->backingStoreDimensions()); const bool opUsesMSAA = drawOp->usesMSAA(); bool skipDraw = false; if (clip) { // Have a complex clip, so defer to its early clip culling GrAAType aaType; if (opUsesMSAA) { aaType = GrAAType::kMSAA; } else { aaType = op->hasAABloat() ? GrAAType::kCoverage : GrAAType::kNone; } skipDraw = clip->apply(fContext, this, drawOp, aaType, &appliedClip, &bounds) == GrClip::Effect::kClippedOut; } else { // No clipping, so just clip the bounds against the logical render target dimensions skipDraw = !bounds.intersect(this->asSurfaceProxy()->getBoundsRect()); } if (skipDraw) { return; } GrClampType clampType = GrColorTypeClampType(this->colorInfo().colorType()); GrProcessorSet::Analysis analysis = drawOp->finalize(*this->caps(), &appliedClip, clampType); const bool opUsesStencil = drawOp->usesStencil(); // Always trigger DMSAA when there is stencil. This ensures stencil contents get properly // preserved between render passes, if needed. const bool drawNeedsMSAA = opUsesMSAA || (fCanUseDynamicMSAA && opUsesStencil); // Must be called before setDstProxyView so that it sees the final bounds of the op. op->setClippedBounds(bounds); // Determine if the Op will trigger the use of a separate DMSAA attachment that requires manual // resolves. // TODO: Currently usesAttachmentIfDMSAA checks if this is a textureProxy or not. This check is // really only for GL which uses a normal texture sampling when using barriers. For Vulkan it // is possible to use the msaa buffer as an input attachment even if this is not a texture. // However, support for that is not fully implemented yet in Vulkan. Once it is, this check // should change to a virtual caps check that returns whether we need to break up an OpsTask // if it has barriers and we are about to promote to MSAA. bool usesAttachmentIfDMSAA = fCanUseDynamicMSAA && (!this->caps()->msaaResolvesAutomatically() || !this->asTextureProxy()); bool opRequiresDMSAAAttachment = usesAttachmentIfDMSAA && drawNeedsMSAA; bool opTriggersDMSAAAttachment = opRequiresDMSAAAttachment && !this->getOpsTask()->usesMSAASurface(); if (opTriggersDMSAAAttachment) { // This will be the op that actually triggers use of a DMSAA attachment. Texture barriers // can't be moved to a DMSAA attachment, so if there already are any on the current opsTask // then we need to split. if (this->getOpsTask()->renderPassXferBarriers() & GrXferBarrierFlags::kTexture) { SkASSERT(!this->getOpsTask()->isColorNoOp()); this->replaceOpsTask()->setCannotMergeBackward(); } } GrDstProxyView dstProxyView; if (analysis.requiresDstTexture()) { if (!this->setupDstProxyView(drawOp->bounds(), drawNeedsMSAA, &dstProxyView)) { return; } #ifdef SK_DEBUG if (fCanUseDynamicMSAA && drawNeedsMSAA && !this->caps()->msaaResolvesAutomatically()) { // Since we aren't literally writing to the render target texture while using a DMSAA // attachment, we need to resolve that texture before sampling it. Ensure the current // opsTask got closed off in order to initiate an implicit resolve. SkASSERT(this->getOpsTask()->isEmpty()); } #endif } auto opsTask = this->getOpsTask(); if (willAddFn) { willAddFn(op.get(), opsTask->uniqueID()); } // Note if the op needs stencil. Stencil clipping already called setNeedsStencil for itself, if // needed. if (opUsesStencil) { this->setNeedsStencil(); } #if GR_GPU_STATS && defined(GPU_TEST_UTILS) if (fCanUseDynamicMSAA && drawNeedsMSAA) { if (!opsTask->usesMSAASurface()) { fContext->priv().dmsaaStats().fNumMultisampleRenderPasses++; } fContext->priv().dmsaaStats().fTriggerCounts[op->name()]++; } #endif opsTask->addDrawOp(this->drawingManager(), std::move(op), drawNeedsMSAA, analysis, std::move(appliedClip), dstProxyView, GrTextureResolveManager(this->drawingManager()), *this->caps()); #ifdef SK_DEBUG if (fCanUseDynamicMSAA && drawNeedsMSAA) { SkASSERT(opsTask->usesMSAASurface()); } #endif } bool SurfaceDrawContext::setupDstProxyView(const SkRect& opBounds, bool opRequiresMSAA, GrDstProxyView* dstProxyView) { // If we are wrapping a vulkan secondary command buffer, we can't make a dst copy because we // don't actually have a VkImage to make a copy of. Additionally we don't have the power to // start and stop the render pass in order to make the copy. if (this->asRenderTargetProxy()->wrapsVkSecondaryCB()) { return false; } // First get the dstSampleFlags as if we will put the draw into the current OpsTask auto dstSampleFlags = this->caps()->getDstSampleFlagsForProxy( this->asRenderTargetProxy(), this->getOpsTask()->usesMSAASurface() || opRequiresMSAA); // If we don't have barriers for this draw then we will definitely be breaking up the OpsTask. // However, if using dynamic MSAA, the new OpsTask will not have MSAA already enabled on it // and that may allow us to use texture barriers. So we check if we can use barriers on the new // ops task and then break it up if so. if (!(dstSampleFlags & GrDstSampleFlags::kRequiresTextureBarrier) && fCanUseDynamicMSAA && this->getOpsTask()->usesMSAASurface() && !opRequiresMSAA) { auto newFlags = this->caps()->getDstSampleFlagsForProxy(this->asRenderTargetProxy(), false/*=opRequiresMSAA*/); if (newFlags & GrDstSampleFlags::kRequiresTextureBarrier) { // We can't have an empty ops task if we are in DMSAA and the ops task already returns // true for usesMSAASurface. SkASSERT(!this->getOpsTask()->isColorNoOp()); this->replaceOpsTask()->setCannotMergeBackward(); dstSampleFlags = newFlags; } } if (dstSampleFlags & GrDstSampleFlags::kRequiresTextureBarrier) { // If we require a barrier to sample the dst it means we are sampling the RT itself // either as a texture or input attachment. In this case we don't need to break up the // OpsTask. dstProxyView->setProxyView(this->readSurfaceView()); dstProxyView->setOffset(0, 0); dstProxyView->setDstSampleFlags(dstSampleFlags); return true; } SkASSERT(dstSampleFlags == GrDstSampleFlags::kNone); // We are using a different surface from the main color attachment to sample the dst from. If we // are in DMSAA we can just use the single sampled RT texture itself. Otherwise, we must do a // copy. // We do have to check if we ended up here becasue we don't have texture barriers but do have // msaaResolvesAutomatically (i.e. render-to-msaa-texture). In that case there will be no op or // barrier between draws to flush the render target before being used as a texture in the next // draw. So in that case we just fall through to doing a copy. if (fCanUseDynamicMSAA && opRequiresMSAA && this->asTextureProxy() && !this->caps()->msaaResolvesAutomatically() && this->caps()->dmsaaResolveCanBeUsedAsTextureInSameRenderPass()) { this->replaceOpsTaskIfModifiesColor()->setCannotMergeBackward(); dstProxyView->setProxyView(this->readSurfaceView()); dstProxyView->setOffset(0, 0); dstProxyView->setDstSampleFlags(dstSampleFlags); return true; } // Now we fallback to doing a copy. GrColorType colorType = this->colorInfo().colorType(); // 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. GrCaps::DstCopyRestrictions restrictions = this->caps()->getDstCopyRestrictions( this->asRenderTargetProxy(), colorType); SkIRect copyRect = SkIRect::MakeSize(this->asSurfaceProxy()->backingStoreDimensions()); if (!restrictions.fMustCopyWholeSrc) { // If we don't need the whole source, restrict to the op's bounds. We add an extra pixel // of padding to account for AA bloat and the unpredictable rounding of coords near pixel // centers during rasterization. SkIRect conservativeDrawBounds = opBounds.roundOut(); conservativeDrawBounds.outset(1, 1); SkAssertResult(copyRect.intersect(conservativeDrawBounds)); } SkIPoint dstOffset; SkBackingFit fit; if (restrictions.fRectsMustMatch == GrSurfaceProxy::RectsMustMatch::kYes) { dstOffset = {0, 0}; fit = SkBackingFit::kExact; } else { dstOffset = {copyRect.fLeft, copyRect.fTop}; fit = SkBackingFit::kApprox; } auto copy = GrSurfaceProxy::Copy(fContext, this->asSurfaceProxyRef(), this->origin(), skgpu::Mipmapped::kNo, copyRect, fit, skgpu::Budgeted::kYes, /*label=*/{}, restrictions.fRectsMustMatch); SkASSERT(copy); dstProxyView->setProxyView({std::move(copy), this->origin(), this->readSwizzle()}); dstProxyView->setOffset(dstOffset); dstProxyView->setDstSampleFlags(dstSampleFlags); return true; } OpsTask* SurfaceDrawContext::replaceOpsTaskIfModifiesColor() { if (!this->getOpsTask()->isColorNoOp()) { this->replaceOpsTask(); } return this->getOpsTask(); } } // namespace skgpu::ganesh