| /* |
| * Copyright 2021 Google LLC |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "src/gpu/ganesh/SurfaceFillContext.h" |
| |
| #include "src/gpu/ganesh/GrCaps.h" |
| #include "src/gpu/ganesh/effects/GrMatrixEffect.h" |
| #include "src/gpu/ganesh/effects/GrTextureEffect.h" |
| #include "src/gpu/ganesh/geometry/GrRect.h" |
| #include "src/gpu/ganesh/ops/ClearOp.h" |
| #include "src/gpu/ganesh/ops/FillRectOp.h" |
| |
| #define ASSERT_SINGLE_OWNER SKGPU_ASSERT_SINGLE_OWNER(this->singleOwner()) |
| #define RETURN_IF_ABANDONED if (fContext->abandoned()) { return; } |
| |
| class AutoCheckFlush { |
| public: |
| AutoCheckFlush(GrDrawingManager* drawingManager) : fDrawingManager(drawingManager) { |
| SkASSERT(fDrawingManager); |
| } |
| ~AutoCheckFlush() { fDrawingManager->flushIfNecessary(); } |
| |
| private: |
| GrDrawingManager* fDrawingManager; |
| }; |
| |
| namespace skgpu::ganesh { |
| |
| // In MDB mode the reffing of the 'getLastOpsTask' call's result allows in-progress |
| // OpsTask to be picked up and added to by SurfaceFillContext lower in the call |
| // stack. When this occurs with a closed OpsTask, a new one will be allocated |
| // when the SurfaceFillContext attempts to use it (via getOpsTask). |
| SurfaceFillContext::SurfaceFillContext(GrRecordingContext* rContext, |
| GrSurfaceProxyView readView, |
| GrSurfaceProxyView writeView, |
| const GrColorInfo& colorInfo) |
| : SurfaceContext(rContext, std::move(readView), colorInfo) |
| , fWriteView(std::move(writeView)) { |
| SkASSERT(this->asSurfaceProxy() == fWriteView.proxy()); |
| SkASSERT(this->origin() == fWriteView.origin()); |
| |
| fOpsTask = sk_ref_sp(rContext->priv().drawingManager()->getLastOpsTask(this->asSurfaceProxy())); |
| |
| SkDEBUGCODE(this->validate();) |
| } |
| |
| OpsTask* SurfaceFillContext::getOpsTask() { |
| ASSERT_SINGLE_OWNER |
| SkDEBUGCODE(this->validate();) |
| |
| if (!fOpsTask || fOpsTask->isClosed()) { |
| this->replaceOpsTask(); |
| } |
| SkASSERT(!fOpsTask->isClosed()); |
| return fOpsTask.get(); |
| } |
| |
| void SurfaceFillContext::discard() { |
| ASSERT_SINGLE_OWNER |
| RETURN_IF_ABANDONED |
| SkDEBUGCODE(this->validate();) |
| GR_CREATE_TRACE_MARKER_CONTEXT("SurfaceFillContext", "discard", fContext); |
| |
| AutoCheckFlush acf(this->drawingManager()); |
| |
| this->getOpsTask()->discard(); |
| } |
| |
| void SurfaceFillContext::resolveMSAA() { |
| ASSERT_SINGLE_OWNER |
| RETURN_IF_ABANDONED |
| SkDEBUGCODE(this->validate();) |
| GR_CREATE_TRACE_MARKER_CONTEXT("SurfaceFillContext", "resolveMSAA", fContext); |
| |
| AutoCheckFlush acf(this->drawingManager()); |
| |
| this->drawingManager()->newTextureResolveRenderTask(this->asSurfaceProxyRef(), |
| GrSurfaceProxy::ResolveFlags::kMSAA, |
| *this->caps()); |
| } |
| |
| void SurfaceFillContext::fillRectWithFP(const SkIRect& dstRect, |
| std::unique_ptr<GrFragmentProcessor> fp) { |
| ASSERT_SINGLE_OWNER |
| RETURN_IF_ABANDONED |
| SkDEBUGCODE(this->validate();) |
| GR_CREATE_TRACE_MARKER_CONTEXT("SurfaceFillContext", "fillRectWithFP", fContext); |
| |
| AutoCheckFlush acf(this->drawingManager()); |
| |
| GrPaint paint; |
| paint.setColorFragmentProcessor(std::move(fp)); |
| paint.setPorterDuffXPFactory(SkBlendMode::kSrc); |
| auto op = FillRectOp::MakeNonAARect(fContext, std::move(paint), SkMatrix::I(), |
| SkRect::Make(dstRect)); |
| this->addDrawOp(std::move(op)); |
| } |
| |
| void SurfaceFillContext::fillRectWithFP(const SkIRect& dstRect, |
| const SkMatrix& localMatrix, |
| std::unique_ptr<GrFragmentProcessor> fp) { |
| fp = GrMatrixEffect::Make(localMatrix, std::move(fp)); |
| this->fillRectWithFP(dstRect, std::move(fp)); |
| } |
| |
| bool SurfaceFillContext::blitTexture(GrSurfaceProxyView view, |
| const SkIRect& srcRect, |
| const SkIPoint& dstPoint) { |
| SkASSERT(view.asTextureProxy()); |
| |
| SkIPoint clippedDstPoint = dstPoint; |
| SkIRect clippedSrcRect = srcRect; |
| if (!GrClipSrcRectAndDstPoint(this->dimensions(), |
| &clippedDstPoint, |
| view.dimensions(), |
| &clippedSrcRect)) { |
| return false; |
| } |
| |
| SkIRect clippedDstRect = SkIRect::MakePtSize(clippedDstPoint, clippedSrcRect.size()); |
| |
| auto fp = GrTextureEffect::Make(std::move(view), kUnknown_SkAlphaType); |
| this->fillRectToRectWithFP(SkRect::Make(clippedSrcRect), clippedDstRect, std::move(fp)); |
| return true; |
| } |
| |
| sk_sp<GrRenderTask> SurfaceFillContext::refRenderTask() { |
| return sk_ref_sp(this->getOpsTask()); |
| } |
| |
| OpsTask* SurfaceFillContext::replaceOpsTask() { |
| sk_sp<OpsTask> newOpsTask = this->drawingManager()->newOpsTask(this->writeSurfaceView(), |
| this->arenas()); |
| this->willReplaceOpsTask(fOpsTask.get(), newOpsTask.get()); |
| fOpsTask = std::move(newOpsTask); |
| return fOpsTask.get(); |
| } |
| |
| void SurfaceFillContext::ClearToGrPaint(std::array<float, 4> color, GrPaint* paint) { |
| paint->setColor4f({color[0], color[1], color[2], color[3]}); |
| if (color[3] == 1.f) { |
| // Can just rely on the src-over blend mode to do the right thing. |
| // This may improve batching. |
| paint->setPorterDuffXPFactory(SkBlendMode::kSrcOver); |
| } else { |
| // A clear overwrites the prior color, so even if it's transparent, it behaves as if it |
| // were src blended |
| paint->setPorterDuffXPFactory(SkBlendMode::kSrc); |
| } |
| } |
| |
| void SurfaceFillContext::addOp(GrOp::Owner op) { |
| GrDrawingManager* drawingMgr = this->drawingManager(); |
| this->getOpsTask()->addOp(drawingMgr, |
| std::move(op), |
| GrTextureResolveManager(drawingMgr), |
| *this->caps()); |
| } |
| |
| |
| void SurfaceFillContext::addDrawOp(GrOp::Owner owner) { |
| GrDrawOp* op = static_cast<GrDrawOp*>(owner.get()); |
| GrClampType clampType = GrColorTypeClampType(this->colorInfo().colorType()); |
| auto clip = GrAppliedClip::Disabled(); |
| const GrCaps& caps = *this->caps(); |
| GrProcessorSet::Analysis analysis = op->finalize(caps, &clip, clampType); |
| SkASSERT(!op->usesStencil()); |
| SkASSERT(!analysis.requiresDstTexture()); |
| SkRect bounds = owner->bounds(); |
| // We shouldn't have coverage AA or hairline draws in fill contexts. |
| SkASSERT(!op->hasAABloat() && !op->hasZeroArea()); |
| if (!bounds.intersect(this->asSurfaceProxy()->getBoundsRect())) { |
| return; |
| } |
| op->setClippedBounds(op->bounds()); |
| SkDEBUGCODE(op->fAddDrawOpCalled = true;) |
| |
| GrDstProxyView dstProxyView; |
| this->getOpsTask()->addDrawOp(fContext->priv().drawingManager(), |
| std::move(owner), |
| op->usesMSAA(), |
| analysis, |
| std::move(clip), |
| dstProxyView, |
| GrTextureResolveManager(this->drawingManager()), |
| caps); |
| } |
| |
| void SurfaceFillContext::internalClear(const SkIRect* scissor, |
| std::array<float, 4> color, |
| bool upgradePartialToFull) { |
| ASSERT_SINGLE_OWNER |
| RETURN_IF_ABANDONED |
| SkDEBUGCODE(this->validate();) |
| GR_CREATE_TRACE_MARKER_CONTEXT("SurfaceFillContext", "clear", fContext); |
| |
| // There are three ways clears are handled: load ops, native clears, and draws. Load ops are |
| // only for fullscreen clears; native clears can be fullscreen or with scissors if the backend |
| // supports then. Drawing an axis-aligned rect is the fallback path. |
| GrScissorState scissorState(this->asSurfaceProxy()->backingStoreDimensions()); |
| if (scissor && !scissorState.set(*scissor)) { |
| // The clear is offscreen, so skip it (normally this would be handled by addDrawOp, |
| // except clear ops are not draw ops). |
| return; |
| } |
| |
| // If we have a scissor but it's okay to clear beyond it for performance reasons, then disable |
| // the test. We only do this when the clear would be handled by a load op or natively. |
| if (scissorState.enabled() && !this->caps()->performColorClearsAsDraws()) { |
| if (upgradePartialToFull && (this->caps()->preferFullscreenClears() || |
| this->caps()->shouldInitializeTextures())) { |
| // TODO: wrt the shouldInitializeTextures path, it would be more performant to |
| // only clear the entire target if we knew it had not been cleared before. As |
| // is this could end up doing a lot of redundant clears. |
| scissorState.setDisabled(); |
| } else { |
| // Unlike with stencil clears, we also allow clears up to the logical dimensions of the |
| // render target to overflow into any approx-fit padding of the backing store dimensions |
| scissorState.relaxTest(this->dimensions()); |
| } |
| } |
| |
| if (!scissorState.enabled()) { |
| // This is a fullscreen clear, so could be handled as a load op. Regardless, we can also |
| // discard all prior ops in the current task since the color buffer will be overwritten. |
| auto opsTask = this->getOpsTask(); |
| if (opsTask->resetForFullscreenClear(this->canDiscardPreviousOpsOnFullClear()) && |
| !this->caps()->performColorClearsAsDraws()) { |
| color = this->writeSurfaceView().swizzle().applyTo(color); |
| // The op list was emptied and native clears are allowed, so just use the load op |
| opsTask->setColorLoadOp(GrLoadOp::kClear, color); |
| return; |
| } else { |
| // Will use an op for the clear, reset the load op to discard since the op will |
| // blow away the color buffer contents |
| opsTask->setColorLoadOp(GrLoadOp::kDiscard); |
| } |
| } |
| |
| // At this point we are either a partial clear or a fullscreen clear that couldn't be applied |
| // as a load op. |
| bool clearAsDraw = this->caps()->performColorClearsAsDraws() || |
| (scissorState.enabled() && this->caps()->performPartialClearsAsDraws()); |
| if (clearAsDraw) { |
| GrPaint paint; |
| ClearToGrPaint(color, &paint); |
| auto op = FillRectOp::MakeNonAARect(fContext, std::move(paint), SkMatrix::I(), |
| SkRect::Make(scissorState.rect())); |
| this->addDrawOp(std::move(op)); |
| } else { |
| color = this->writeSurfaceView().swizzle().applyTo(color); |
| this->addOp(ClearOp::MakeColor(fContext, scissorState, color)); |
| } |
| } |
| |
| #ifdef SK_DEBUG |
| void SurfaceFillContext::onValidate() const { |
| if (fOpsTask && !fOpsTask->isClosed()) { |
| SkASSERT(this->drawingManager()->getLastRenderTask(fWriteView.proxy()) == fOpsTask.get()); |
| } |
| } |
| #endif |
| |
| } // namespace skgpu::ganesh |