Fix texture barriers on DMSAA

Bug: skia:11396
Change-Id: Iad74958c05ed086fe85656b9dc5418d5ab4589e7
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/419838
Commit-Queue: Greg Daniel <egdaniel@google.com>
Reviewed-by: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
This commit is contained in:
Greg Daniel 2021-06-23 14:10:25 -04:00 committed by Skia Commit-Bot
parent d0f4d0d5d4
commit 1a3f4ab490
12 changed files with 244 additions and 34 deletions

View File

@ -292,6 +292,17 @@ struct SK_API GrContextOptions {
*/
bool fSuppressDualSourceBlending = false;
/**
* Prevents the use of non-coefficient-based blend equations, for testing dst reads, barriers,
* and in-shader blending.
*/
bool fSuppressAdvancedBlendEquations = false;
/**
* Prevents the use of framebuffer fetches, for testing dst reads and texture barriers.
*/
bool fSuppressFramebufferFetch = false;
/**
* If true, the caps will never support geometry shaders.
*/

View File

@ -133,6 +133,9 @@ void GrCaps::applyOptionsOverrides(const GrContextOptions& options) {
fMaxTextureSize = std::min(fMaxTextureSize, options.fMaxTextureSizeOverride);
#if GR_TEST_UTILS
if (options.fSuppressAdvancedBlendEquations) {
fBlendEquationSupport = kBasic_BlendEquationSupport;
}
if (options.fClearAllTextures) {
fShouldInitializeTextures = true;
}
@ -140,6 +143,9 @@ void GrCaps::applyOptionsOverrides(const GrContextOptions& options) {
fWritePixelsRowBytesSupport = false;
fTransferPixelsToRowBytesSupport = false;
}
if (options.fAlwaysPreferHardwareTessellation) {
fMinPathVerbsForHwTessellation = fMinStrokeVerbsForHwTessellation = 0;
}
#endif
if (options.fSuppressMipmapSupport) {
fMipmapSupport = false;
@ -153,12 +159,6 @@ void GrCaps::applyOptionsOverrides(const GrContextOptions& options) {
fInternalMultisampleCount = options.fInternalMultisampleCount;
#if GR_TEST_UTILS
if (options.fAlwaysPreferHardwareTessellation) {
fMinPathVerbsForHwTessellation = fMinStrokeVerbsForHwTessellation = 0;
}
#endif
fAvoidStencilBuffers = options.fAvoidStencilBuffers;
fDriverBugWorkarounds.applyOverrides(options.fDriverBugWorkarounds);
@ -438,9 +438,10 @@ bool GrCaps::isFormatCompressed(const GrBackendFormat& format) const {
return GrBackendFormatToCompressionType(format) != SkImage::CompressionType::kNone;
}
GrDstSampleFlags GrCaps::getDstSampleFlagsForProxy(const GrRenderTargetProxy* rt) const {
GrDstSampleFlags GrCaps::getDstSampleFlagsForProxy(const GrRenderTargetProxy* rt,
bool drawUsesMSAA) const {
SkASSERT(rt);
if (this->textureBarrierSupport() && !rt->requiresManualMSAAResolve()) {
if (this->textureBarrierSupport() && (!drawUsesMSAA || this->msaaResolvesAutomatically())) {
return this->onGetDstSampleFlagsForProxy(rt);
}
return GrDstSampleFlags::kNone;

View File

@ -400,7 +400,7 @@ public:
bool alwaysDrawQuadsIndexed() const { return fAlwaysDrawQuadsIndexed; }
// Returns how to sample the dst values for the passed in GrRenderTargetProxy.
GrDstSampleFlags getDstSampleFlagsForProxy(const GrRenderTargetProxy*) const;
GrDstSampleFlags getDstSampleFlagsForProxy(const GrRenderTargetProxy*, bool drawUsesMSAA) const;
/**
* This is used to try to ensure a successful copy a dst in order to perform shader-based

View File

@ -373,14 +373,14 @@ void GrDrawingManager::sortTasks() {
#ifdef SK_DEBUG
// This block checks for any unnecessary splits in the opsTasks. If two sequential opsTasks
// share the same backing GrSurfaceProxy it means the opsTask was artificially split.
// could have merged it means the opsTask was artificially split.
if (!fDAG.empty()) {
GrOpsTask* prevOpsTask = fDAG[0]->asOpsTask();
for (int i = 1; i < fDAG.count(); ++i) {
GrOpsTask* curOpsTask = fDAG[i]->asOpsTask();
if (prevOpsTask && curOpsTask) {
SkASSERT(prevOpsTask->target(0) != curOpsTask->target(0));
SkASSERT(!prevOpsTask->canMerge(curOpsTask));
}
prevOpsTask = curOpsTask;

View File

@ -442,7 +442,8 @@ void GrOpsTask::onPrePrepare(GrRecordingContext* context) {
// can end up with GrOpsTasks that only have a discard load op and no ops. For vulkan validation
// we need to keep that discard and not drop it. Once we have reduce op list splitting enabled
// we shouldn't end up with GrOpsTasks with only discard.
if (this->isNoOp() || (fClippedContentBounds.isEmpty() && fColorLoadOp != GrLoadOp::kDiscard)) {
if (this->isColorNoOp() ||
(fClippedContentBounds.isEmpty() && fColorLoadOp != GrLoadOp::kDiscard)) {
return;
}
TRACE_EVENT0("skia.gpu", TRACE_FUNC);
@ -467,7 +468,8 @@ void GrOpsTask::onPrepare(GrOpFlushState* flushState) {
// can end up with GrOpsTasks that only have a discard load op and no ops. For vulkan validation
// we need to keep that discard and not drop it. Once we have reduce op list splitting enabled
// we shouldn't end up with GrOpsTasks with only discard.
if (this->isNoOp() || (fClippedContentBounds.isEmpty() && fColorLoadOp != GrLoadOp::kDiscard)) {
if (this->isColorNoOp() ||
(fClippedContentBounds.isEmpty() && fColorLoadOp != GrLoadOp::kDiscard)) {
return;
}
TRACE_EVENT0("skia.gpu", TRACE_FUNC);
@ -548,7 +550,8 @@ bool GrOpsTask::onExecute(GrOpFlushState* flushState) {
// can end up with GrOpsTasks that only have a discard load op and no ops. For vulkan validation
// we need to keep that discard and not drop it. Once we have reduce op list splitting enabled
// we shouldn't end up with GrOpsTasks with only discard.
if (this->isNoOp() || (fClippedContentBounds.isEmpty() && fColorLoadOp != GrLoadOp::kDiscard)) {
if (this->isColorNoOp() ||
(fClippedContentBounds.isEmpty() && fColorLoadOp != GrLoadOp::kDiscard)) {
return false;
}
@ -684,12 +687,17 @@ void GrOpsTask::reset() {
fRenderPassXferBarriers = GrXferBarrierFlags::kNone;
}
bool GrOpsTask::canMerge(const GrOpsTask* opsTask) const {
return this->target(0) == opsTask->target(0) &&
fArenas == opsTask->fArenas &&
!opsTask->fCannotMergeBackward;
}
int GrOpsTask::mergeFrom(SkSpan<const sk_sp<GrRenderTask>> tasks) {
int mergedCount = 0;
for (const sk_sp<GrRenderTask>& task : tasks) {
auto opsTask = task->asOpsTask();
if (!opsTask || opsTask->target(0) != this->target(0)
|| this->fArenas != opsTask->fArenas) {
if (!opsTask || !this->canMerge(opsTask)) {
break;
}
SkASSERT(fTargetSwizzle == opsTask->fTargetSwizzle);
@ -871,7 +879,7 @@ void GrOpsTask::onMakeSkippable() {
this->deleteOps();
fDeferredProxies.reset();
fColorLoadOp = GrLoadOp::kLoad;
SkASSERT(this->isNoOp());
SkASSERT(this->isColorNoOp());
}
bool GrOpsTask::onIsUsed(GrSurfaceProxy* proxyToCheck) const {
@ -898,7 +906,7 @@ bool GrOpsTask::onIsUsed(GrSurfaceProxy* proxyToCheck) const {
void GrOpsTask::gatherProxyIntervals(GrResourceAllocator* alloc) const {
SkASSERT(this->isClosed());
if (this->isNoOp()) {
if (this->isColorNoOp()) {
return;
}
@ -1043,7 +1051,7 @@ void GrOpsTask::forwardCombine(const GrCaps& caps) {
GrRenderTask::ExpectedOutcome GrOpsTask::onMakeClosed(const GrCaps& caps,
SkIRect* targetUpdateBounds) {
this->forwardCombine(caps);
if (!this->isNoOp()) {
if (!this->isColorNoOp()) {
GrSurfaceProxy* proxy = this->target(0);
// Use the entire backing store bounds since the GPU doesn't clip automatically to the
// logical dimensions.

View File

@ -43,6 +43,7 @@ public:
bool isEmpty() const { return fOpChains.empty(); }
bool usesMSAASurface() const { return fUsesMSAASurface; }
GrXferBarrierFlags renderPassXferBarriers() const { return fRenderPassXferBarriers; }
/**
* Empties the draw buffer of any queued up draws.
@ -87,6 +88,9 @@ public:
// Must only be called if native color buffer clearing is enabled.
void setColorLoadOp(GrLoadOp op, std::array<float, 4> color = {0, 0, 0, 0});
// Returns whether the given opsTask can be appended at the end of this one.
bool canMerge(const GrOpsTask*) const;
// Merge as many opsTasks as possible from the head of 'tasks'. They should all be
// renderPass compatible. Return the number of tasks merged into 'this'.
int mergeFrom(SkSpan<const sk_sp<GrRenderTask>> tasks);
@ -107,12 +111,9 @@ public:
#endif
private:
bool isNoOp() const {
bool isColorNoOp() const {
// TODO: GrLoadOp::kDiscard (i.e., storing a discard) should also be grounds for skipping
// execution. We currently don't because of Vulkan. See http://skbug.com/9373.
//
// TODO: We should also consider stencil load/store here. We get away with it for now
// because we never discard stencil buffers.
return fOpChains.empty() && GrLoadOp::kLoad == fColorLoadOp;
}
@ -142,6 +143,11 @@ private:
// get preserved across its split tasks.
void setMustPreserveStencil() { fMustPreserveStencil = true; }
// Prevents this opsTask from merging backward. This is used by DMSAA when a non-multisampled
// opsTask cannot be promoted to MSAA, or when we split a multisampled opsTask in order to
// resolve its texture.
void setCannotMergeBackward() { fCannotMergeBackward = true; }
class OpChain {
public:
OpChain(GrOp::Owner, GrProcessorSet::Analysis, GrAppliedClip*, const GrDstProxyView*);
@ -254,6 +260,7 @@ private:
std::array<float, 4> fLoadClearColor = {0, 0, 0, 0};
StencilContent fInitialStencilContent = StencilContent::kDontCare;
bool fMustPreserveStencil = false;
bool fCannotMergeBackward = false;
uint32_t fLastClipStackGenID = SK_InvalidUniqueID;
SkIRect fLastDevClipBounds;

View File

@ -196,6 +196,9 @@ void GrShaderCaps::applyOptionsOverrides(const GrContextOptions& options) {
if (options.fSuppressDualSourceBlending) {
fDualSourceBlendingSupport = false;
}
if (options.fSuppressFramebufferFetch) {
fFBFetchSupport = false;
}
if (options.fSuppressGeometryShaders) {
fGeometryShaderSupport = false;
}

View File

@ -1931,11 +1931,43 @@ void GrSurfaceDrawContext::addDrawOp(const GrClip* clip,
// 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 && usesMSAA;
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(*op, &dstProxyView)) {
if (!this->setupDstProxyView(drawOp->bounds(), usesMSAA, &dstProxyView)) {
return;
}
#ifdef SK_DEBUG
if (fCanUseDynamicMSAA && usesMSAA && !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();
@ -1963,7 +1995,9 @@ void GrSurfaceDrawContext::addDrawOp(const GrClip* clip,
#endif
}
bool GrSurfaceDrawContext::setupDstProxyView(const GrOp& op, GrDstProxyView* dstProxyView) {
bool GrSurfaceDrawContext::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.
@ -1971,16 +2005,56 @@ bool GrSurfaceDrawContext::setupDstProxyView(const GrOp& op, GrDstProxyView* dst
return false;
}
auto dstSampleFlags = this->caps()->getDstSampleFlagsForProxy(this->asRenderTargetProxy());
// First get the dstSampleFlags as if we will put the draw into the current GrOpsTask
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 GrOpsTask.
// However, if using dynamic MSAA, the new GrOpsTask 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.
// 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
// GrOpsTask.
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->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
@ -1993,7 +2067,7 @@ bool GrSurfaceDrawContext::setupDstProxyView(const GrOp& op, GrDstProxyView* dst
// 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 = op.bounds().roundOut();
SkIRect conservativeDrawBounds = opBounds.roundOut();
conservativeDrawBounds.outset(1, 1);
SkAssertResult(copyRect.intersect(conservativeDrawBounds));
}
@ -2022,3 +2096,11 @@ bool GrSurfaceDrawContext::setupDstProxyView(const GrOp& op, GrDstProxyView* dst
dstProxyView->setDstSampleFlags(dstSampleFlags);
return true;
}
GrOpsTask* GrSurfaceDrawContext::replaceOpsTaskIfModifiesColor() {
if (!this->getOpsTask()->isColorNoOp()) {
this->replaceOpsTask();
}
return this->getOpsTask();
}

View File

@ -712,7 +712,11 @@ private:
// value is false then a texture copy could not be made.
//
// The op should have already had setClippedBounds called on it.
bool SK_WARN_UNUSED_RESULT setupDstProxyView(const GrOp& op, GrDstProxyView* result);
bool SK_WARN_UNUSED_RESULT setupDstProxyView(const SkRect& opBounds,
bool opRequiresMSAA,
GrDstProxyView* result);
GrOpsTask* replaceOpsTaskIfModifiesColor();
SkGlyphRunListPainter* glyphPainter() { return &fGlyphPainter; }

View File

@ -306,15 +306,20 @@ GrOpsTask* GrSurfaceFillContext::getOpsTask() {
SkDEBUGCODE(this->validate();)
if (!fOpsTask || fOpsTask->isClosed()) {
sk_sp<GrOpsTask> newOpsTask = this->drawingManager()->newOpsTask(
this->writeSurfaceView(), this->arenas(), fFlushTimeOpsTask);
this->willReplaceOpsTask(fOpsTask.get(), newOpsTask.get());
fOpsTask = std::move(newOpsTask);
this->replaceOpsTask();
}
SkASSERT(!fOpsTask->isClosed());
return fOpsTask.get();
}
GrOpsTask* GrSurfaceFillContext::replaceOpsTask() {
sk_sp<GrOpsTask> newOpsTask = this->drawingManager()->newOpsTask(
this->writeSurfaceView(), this->arenas(), fFlushTimeOpsTask);
this->willReplaceOpsTask(fOpsTask.get(), newOpsTask.get());
fOpsTask = std::move(newOpsTask);
return fOpsTask.get();
}
#ifdef SK_DEBUG
void GrSurfaceFillContext::onValidate() const {
if (fOpsTask && !fOpsTask->isClosed()) {

View File

@ -200,6 +200,8 @@ protected:
void addOp(GrOp::Owner);
GrOpsTask* replaceOpsTask();
private:
sk_sp<GrArenas> arenas() { return fWriteView.proxy()->asRenderTargetProxy()->arenas(); }

View File

@ -19,6 +19,15 @@ constexpr static SkPMColor4f kTransYellow = {.5f,.5f,.0f,.5f};
constexpr static SkPMColor4f kTransCyan = {.0f,.5f,.5f,.5f};
constexpr static int w=10, h=10;
static void draw_paint_with_aa(GrSurfaceDrawContext* sdc, const SkPMColor4f& color,
SkBlendMode blendMode) {
GrPaint paint;
paint.setColor4f(color);
paint.setXPFactory(SkBlendMode_AsXPFactory(blendMode));
sdc->drawRect(nullptr, std::move(paint), GrAA::kYes, SkMatrix::I(), SkRect::MakeIWH(w, h),
nullptr);
}
static void draw_paint_with_dmsaa(GrSurfaceDrawContext* sdc, const SkPMColor4f& color,
SkBlendMode blendMode) {
// drawVertices should always trigger dmsaa, but draw something non-rectangular just to be 100%
@ -89,3 +98,81 @@ DEF_GPUTEST_FOR_CONTEXTS(DMSAA_preserve_contents,
check_sdc_color(reporter, sdc.get(), ctx, dstColor);
}
static void require_dst_reads(GrContextOptions* options) {
options->fSuppressAdvancedBlendEquations = true;
options->fSuppressFramebufferFetch = true;
}
DEF_GPUTEST_FOR_CONTEXTS(DMSAA_dst_read, &sk_gpu_test::GrContextFactory::IsRenderingContext,
reporter, ctxInfo, require_dst_reads) {
auto ctx = ctxInfo.directContext();
auto sdc = GrSurfaceDrawContext::Make(ctx, GrColorType::kRGBA_8888, nullptr,
SkBackingFit::kApprox, {w, h}, kDMSAAProps);
// Initialize the texture and dmsaa attachment with transparent.
draw_paint_with_dmsaa(sdc.get(), SK_PMColor4fTRANSPARENT, SkBlendMode::kSrc);
check_sdc_color(reporter, sdc.get(), ctx, SK_PMColor4fTRANSPARENT);
sdc->clear(SK_PMColor4fWHITE);
SkPMColor4f dstColor = SK_PMColor4fWHITE;
draw_paint_with_dmsaa(sdc.get(), kTransYellow, SkBlendMode::kDarken);
dstColor = SkBlendMode_Apply(SkBlendMode::kDarken, kTransYellow, dstColor);
draw_paint_with_dmsaa(sdc.get(), kTransCyan, SkBlendMode::kDarken);
dstColor = SkBlendMode_Apply(SkBlendMode::kDarken, kTransCyan, dstColor);
check_sdc_color(reporter, sdc.get(), ctx, dstColor);
}
DEF_GPUTEST_FOR_CONTEXTS(DMSAA_aa_dst_read_after_dmsaa,
&sk_gpu_test::GrContextFactory::IsRenderingContext, reporter, ctxInfo,
require_dst_reads) {
auto ctx = ctxInfo.directContext();
auto sdc = GrSurfaceDrawContext::Make(ctx, GrColorType::kRGBA_8888, nullptr,
SkBackingFit::kApprox, {w, h}, kDMSAAProps);
// Initialize the texture and dmsaa attachment with transparent.
draw_paint_with_dmsaa(sdc.get(), SK_PMColor4fTRANSPARENT, SkBlendMode::kSrc);
check_sdc_color(reporter, sdc.get(), ctx, SK_PMColor4fTRANSPARENT);
sdc->clear(SK_PMColor4fWHITE);
SkPMColor4f dstColor = SK_PMColor4fWHITE;
draw_paint_with_dmsaa(sdc.get(), kTransYellow, SkBlendMode::kDarken);
dstColor = SkBlendMode_Apply(SkBlendMode::kDarken, kTransYellow, dstColor);
// Draw with aa after dmsaa. This should break up the render pass and issue a texture barrier.
draw_paint_with_aa(sdc.get(), kTransCyan, SkBlendMode::kDarken);
dstColor = SkBlendMode_Apply(SkBlendMode::kDarken, kTransCyan, dstColor);
check_sdc_color(reporter, sdc.get(), ctx, dstColor);
}
DEF_GPUTEST_FOR_CONTEXTS(DMSAA_dst_read_with_existing_barrier,
&sk_gpu_test::GrContextFactory::IsRenderingContext, reporter, ctxInfo,
require_dst_reads) {
auto ctx = ctxInfo.directContext();
auto sdc = GrSurfaceDrawContext::Make(ctx, GrColorType::kRGBA_8888, nullptr,
SkBackingFit::kApprox, {w, h}, kDMSAAProps);
// Initialize the texture and dmsaa attachment with transparent.
draw_paint_with_dmsaa(sdc.get(), SK_PMColor4fTRANSPARENT, SkBlendMode::kSrc);
check_sdc_color(reporter, sdc.get(), ctx, SK_PMColor4fTRANSPARENT);
sdc->clear(SK_PMColor4fWHITE);
SkPMColor4f dstColor = SK_PMColor4fWHITE;
// Blend to the texture (not the dmsaa attachment) with a dst read. This creates a texture
// barrier.
draw_paint_with_aa(sdc.get(), kTransYellow, SkBlendMode::kDarken);
dstColor = SkBlendMode_Apply(SkBlendMode::kDarken, kTransYellow, dstColor);
// Blend to the msaa attachment _without_ a dst read. This ensures we respect the prior texture
// barrier by splitting the opsTask.
draw_paint_with_dmsaa(sdc.get(), kTransCyan, SkBlendMode::kSrcOver);
dstColor = SkBlendMode_Apply(SkBlendMode::kSrcOver, kTransCyan, dstColor);
check_sdc_color(reporter, sdc.get(), ctx, dstColor);
}