diff --git a/src/gpu/ganesh/ops/OpsTask.cpp b/src/gpu/ganesh/ops/OpsTask.cpp index 24ce9280464..63e293d5669 100644 --- a/src/gpu/ganesh/ops/OpsTask.cpp +++ b/src/gpu/ganesh/ops/OpsTask.cpp @@ -603,7 +603,12 @@ bool OpsTask::onExecute(GrOpFlushState* flushState) { } if (!stencil->hasPerformedInitialClear()) { stencilLoadOp = GrLoadOp::kClear; - markStencilCleared = true; + // The clear load op is applied over fClippedContentBounds, which can be a sub-rect + // of the attachment. Since the attachment is shared across render targets of the + // same dimensions, only mark it as cleared once a clear has actually covered the + // full attachment. + markStencilCleared = + fClippedContentBounds.contains(SkIRect::MakeSize(stencil->dimensions())); break; } // SurfaceDrawContexts are required to leave the user stencil bits in a cleared state diff --git a/tests/LazyStencilAttachmentTest.cpp b/tests/LazyStencilAttachmentTest.cpp index c6d1ed47913..a401db27fe0 100644 --- a/tests/LazyStencilAttachmentTest.cpp +++ b/tests/LazyStencilAttachmentTest.cpp @@ -11,6 +11,7 @@ #include "include/core/SkColorSpace.h" #include "include/core/SkColorType.h" #include "include/core/SkImageInfo.h" +#include "include/core/SkMatrix.h" #include "include/core/SkPaint.h" #include "include/core/SkPath.h" #include "include/core/SkPathBuilder.h" @@ -18,18 +19,30 @@ #include "include/core/SkRect.h" #include "include/core/SkRefCnt.h" #include "include/core/SkSurface.h" +#include "include/core/SkSurfaceProps.h" #include "include/core/SkTypes.h" #include "include/gpu/GpuTypes.h" +#include "include/gpu/ganesh/GrContextOptions.h" #include "include/gpu/ganesh/GrDirectContext.h" #include "include/gpu/ganesh/GrTypes.h" #include "include/gpu/ganesh/SkSurfaceGanesh.h" +#include "include/private/gpu/ganesh/GrTypesPriv.h" #include "src/core/SkAutoPixmapStorage.h" +#include "src/core/SkColorData.h" +#include "src/gpu/SkBackingFit.h" +#include "src/gpu/ganesh/GrAttachment.h" +#include "src/gpu/ganesh/GrCaps.h" +#include "src/gpu/ganesh/GrDirectContextPriv.h" +#include "src/gpu/ganesh/GrPaint.h" +#include "src/gpu/ganesh/GrRenderTarget.h" +#include "src/gpu/ganesh/GrStyle.h" +#include "src/gpu/ganesh/SurfaceDrawContext.h" #include "tests/CtsEnforcement.h" #include "tests/Test.h" +#include "tools/gpu/ContextType.h" #include - -struct GrContextOptions; +#include DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(crbug_1271431, reporter, @@ -103,3 +116,97 @@ DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(crbug_1271431, } } } + +static void only_allow_default_path_renderer(GrContextOptions* options) { + options->fGpuPathRenderers = GpuPathRenderers::kNone; +} + +// The render area used for a render pass's stencil clear load-op is derived from the bounds of the +// recorded ops, which can be a sub-rect of the full attachment. A stencil attachment can also be +// shared by multiple render targets that have the same backing dimensions. This test verifies that +// after such a partial-bounds clear the attachment is not treated as fully cleared, since a later +// render target may use a region that wasn't covered by the earlier clear. +DEF_GANESH_TEST_FOR_CONTEXTS(SharedStencilAttachmentPartialClearTracking, + skgpu::IsRenderingContext, + reporter, + context_info, + only_allow_default_path_renderer, + CtsEnforcement::kNextRelease) { + GrDirectContext* dc = context_info.directContext(); + if (dc->priv().caps()->performStencilClearsAsDraws() || + dc->priv().caps()->avoidStencilBuffers()) { + return; + } + + static const int kSize = 64; + auto sdc = skgpu::ganesh::SurfaceDrawContext::Make(dc, + GrColorType::kRGBA_8888, + nullptr, + SkBackingFit::kExact, + {kSize, kSize}, + SkSurfaceProps(), + /*label=*/{}); + if (!sdc) { + return; + } + + // Clear the surface in its own ops task so the next ops task uses a color load (and therefore + // derives its render area from the recorded op bounds rather than the full surface). + sdc->clear(SK_PMColor4fWHITE); + dc->priv().flushSurface(sdc->asSurfaceProxy()); + + // Concave path drawn without AA so it goes through a stencil-using path renderer. The path is + // confined to the top-left corner, giving the ops task a render area that is a sub-rect of the + // full attachment. + SkPath path = SkPathBuilder() + .addRect(SkRect::MakeLTRB(4, 4, 28, 28), SkPathDirection::kCW) + .addRect(SkRect::MakeLTRB(8, 8, 24, 24), SkPathDirection::kCCW) + .detach(); + GrPaint paint; + paint.setColor4f({1.0f, 0.0f, 0.0f, 1.0f}); + sdc->drawPath(nullptr, std::move(paint), GrAA::kNo, SkMatrix::I(), path, + GrStyle::SimpleFill()); + dc->priv().flushSurface(sdc->asSurfaceProxy()); + + GrRenderTarget* rt = sdc->accessRenderTarget(); + REPORTER_ASSERT(reporter, rt); + if (!rt) { + return; + } + GrAttachment* stencil = rt->getStencilAttachment(sdc->numSamples() > 1); + REPORTER_ASSERT(reporter, stencil); + if (!stencil) { + return; + } + + // The clear load op only covered the sub-region containing the recorded ops. Regions outside + // of that were not written, so the attachment must not be considered fully cleared yet. + REPORTER_ASSERT(reporter, !stencil->hasPerformedInitialClear()); + + // Once an ops task with a render area that does cover the full attachment performs the clear, + // it should be marked as such (modulo configurations that always clear on every render pass). + auto sdc2 = skgpu::ganesh::SurfaceDrawContext::Make(dc, + GrColorType::kRGBA_8888, + nullptr, + SkBackingFit::kExact, + {kSize, kSize}, + SkSurfaceProps(), + /*label=*/{}); + if (!sdc2) { + return; + } + sdc2->clear(SK_PMColor4fWHITE); + GrPaint paint2; + paint2.setColor4f({0.0f, 1.0f, 0.0f, 1.0f}); + sdc2->drawPath(nullptr, std::move(paint2), GrAA::kNo, SkMatrix::I(), path, + GrStyle::SimpleFill()); + dc->priv().flushSurface(sdc2->asSurfaceProxy()); + + GrRenderTarget* rt2 = sdc2->accessRenderTarget(); + if (rt2) { + GrAttachment* stencil2 = rt2->getStencilAttachment(sdc2->numSamples() > 1); + if (stencil2 && !dc->priv().caps()->discardStencilValuesAfterRenderPass()) { + REPORTER_ASSERT(reporter, stencil2->hasPerformedInitialClear()); + } + } +}