diff --git a/include/gpu/GrContextOptions.h b/include/gpu/GrContextOptions.h index e3bbe068bc..674d44635d 100644 --- a/include/gpu/GrContextOptions.h +++ b/include/gpu/GrContextOptions.h @@ -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. */ diff --git a/src/gpu/GrCaps.cpp b/src/gpu/GrCaps.cpp index f2aea463e9..49ba93dca5 100644 --- a/src/gpu/GrCaps.cpp +++ b/src/gpu/GrCaps.cpp @@ -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; diff --git a/src/gpu/GrCaps.h b/src/gpu/GrCaps.h index aafdb86d39..38e2552f87 100644 --- a/src/gpu/GrCaps.h +++ b/src/gpu/GrCaps.h @@ -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 diff --git a/src/gpu/GrDrawingManager.cpp b/src/gpu/GrDrawingManager.cpp index 3c9b49fd95..ab32cd952a 100644 --- a/src/gpu/GrDrawingManager.cpp +++ b/src/gpu/GrDrawingManager.cpp @@ -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; diff --git a/src/gpu/GrOpsTask.cpp b/src/gpu/GrOpsTask.cpp index 9e00676e5a..623084a8d6 100644 --- a/src/gpu/GrOpsTask.cpp +++ b/src/gpu/GrOpsTask.cpp @@ -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> tasks) { int mergedCount = 0; for (const sk_sp& 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. diff --git a/src/gpu/GrOpsTask.h b/src/gpu/GrOpsTask.h index 6c93c0c729..f0d016f24b 100644 --- a/src/gpu/GrOpsTask.h +++ b/src/gpu/GrOpsTask.h @@ -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 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> 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 fLoadClearColor = {0, 0, 0, 0}; StencilContent fInitialStencilContent = StencilContent::kDontCare; bool fMustPreserveStencil = false; + bool fCannotMergeBackward = false; uint32_t fLastClipStackGenID = SK_InvalidUniqueID; SkIRect fLastDevClipBounds; diff --git a/src/gpu/GrShaderCaps.cpp b/src/gpu/GrShaderCaps.cpp index d723a50ab4..aa2cf3e634 100644 --- a/src/gpu/GrShaderCaps.cpp +++ b/src/gpu/GrShaderCaps.cpp @@ -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; } diff --git a/src/gpu/GrSurfaceDrawContext.cpp b/src/gpu/GrSurfaceDrawContext.cpp index b3af050c81..8544d0d526 100644 --- a/src/gpu/GrSurfaceDrawContext.cpp +++ b/src/gpu/GrSurfaceDrawContext.cpp @@ -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(); +} + diff --git a/src/gpu/GrSurfaceDrawContext.h b/src/gpu/GrSurfaceDrawContext.h index 56f6f8de82..49e8e8014c 100644 --- a/src/gpu/GrSurfaceDrawContext.h +++ b/src/gpu/GrSurfaceDrawContext.h @@ -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; } diff --git a/src/gpu/GrSurfaceFillContext.cpp b/src/gpu/GrSurfaceFillContext.cpp index 4e3ee036c3..4d50f30dd4 100644 --- a/src/gpu/GrSurfaceFillContext.cpp +++ b/src/gpu/GrSurfaceFillContext.cpp @@ -306,15 +306,20 @@ GrOpsTask* GrSurfaceFillContext::getOpsTask() { SkDEBUGCODE(this->validate();) if (!fOpsTask || fOpsTask->isClosed()) { - sk_sp 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 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()) { diff --git a/src/gpu/GrSurfaceFillContext.h b/src/gpu/GrSurfaceFillContext.h index b85c76a162..0741e5bce8 100644 --- a/src/gpu/GrSurfaceFillContext.h +++ b/src/gpu/GrSurfaceFillContext.h @@ -200,6 +200,8 @@ protected: void addOp(GrOp::Owner); + GrOpsTask* replaceOpsTask(); + private: sk_sp arenas() { return fWriteView.proxy()->asRenderTargetProxy()->arenas(); } diff --git a/tests/DMSAATest.cpp b/tests/DMSAATest.cpp index dd550560b9..6bffd75570 100644 --- a/tests/DMSAATest.cpp +++ b/tests/DMSAATest.cpp @@ -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); +}