blob: d2fabfa0844f01abd3b484f6f692b728ced8c871 [file] [log] [blame]
/*
* 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