| /* | |
| * Copyright 2020 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/StencilMaskHelper.h" | |
| #include "include/core/SkMatrix.h" | |
| #include "include/core/SkPath.h" | |
| #include "include/core/SkRect.h" | |
| #include "include/core/SkRegion.h" | |
| #include "include/gpu/ganesh/GrRecordingContext.h" | |
| #include "include/private/base/SkAssert.h" | |
| #include "include/private/gpu/ganesh/GrTypesPriv.h" | |
| #include "src/base/SkTLazy.h" | |
| #include "src/gpu/ganesh/GrClip.h" | |
| #include "src/gpu/ganesh/GrDrawingManager.h" | |
| #include "src/gpu/ganesh/GrFixedClip.h" | |
| #include "src/gpu/ganesh/GrPaint.h" | |
| #include "src/gpu/ganesh/GrRecordingContextPriv.h" | |
| #include "src/gpu/ganesh/GrStencilSettings.h" | |
| #include "src/gpu/ganesh/GrStyle.h" | |
| #include "src/gpu/ganesh/GrUserStencilSettings.h" | |
| #include "src/gpu/ganesh/GrWindowRectangles.h" | |
| #include "src/gpu/ganesh/GrWindowRectsState.h" | |
| #include "src/gpu/ganesh/PathRenderer.h" | |
| #include "src/gpu/ganesh/PathRendererChain.h" | |
| #include "src/gpu/ganesh/SurfaceDrawContext.h" | |
| #include "src/gpu/ganesh/effects/GrDisableColorXP.h" | |
| #include "src/gpu/ganesh/geometry/GrShape.h" | |
| #include "src/gpu/ganesh/geometry/GrStyledShape.h" | |
| #include <utility> | |
| namespace { | |
| //////////////////////////////////////////////////////////////////////////////// | |
| // Stencil Rules for Merging user stencil space into clip | |
| // | |
| /////// | |
| // Replace | |
| static constexpr GrUserStencilSettings gUserToClipReplace( | |
| GrUserStencilSettings::StaticInit< | |
| 0x0000, | |
| GrUserStencilTest::kNotEqual, | |
| 0xffff, | |
| GrUserStencilOp::kSetClipAndReplaceUserBits, | |
| GrUserStencilOp::kZeroClipAndUserBits, | |
| 0xffff>() | |
| ); | |
| static constexpr GrUserStencilSettings gInvUserToClipReplace( | |
| GrUserStencilSettings::StaticInit< | |
| 0x0000, | |
| GrUserStencilTest::kEqual, | |
| 0xffff, | |
| GrUserStencilOp::kSetClipAndReplaceUserBits, | |
| GrUserStencilOp::kZeroClipAndUserBits, | |
| 0xffff>() | |
| ); | |
| /////// | |
| // Intersect | |
| static constexpr GrUserStencilSettings gUserToClipIsect( | |
| GrUserStencilSettings::StaticInit< | |
| 0x0000, | |
| GrUserStencilTest::kLessIfInClip, // "0 < userBits" is equivalent to "0 != userBits". | |
| 0xffff, | |
| GrUserStencilOp::kSetClipAndReplaceUserBits, | |
| GrUserStencilOp::kZeroClipAndUserBits, | |
| 0xffff>() | |
| ); | |
| /////// | |
| // Difference | |
| static constexpr GrUserStencilSettings gUserToClipDiff( | |
| GrUserStencilSettings::StaticInit< | |
| 0x0000, | |
| GrUserStencilTest::kEqualIfInClip, | |
| 0xffff, | |
| GrUserStencilOp::kSetClipAndReplaceUserBits, | |
| GrUserStencilOp::kZeroClipAndUserBits, | |
| 0xffff>() | |
| ); | |
| /////// | |
| // Union | |
| static constexpr GrUserStencilSettings gUserToClipUnion( | |
| GrUserStencilSettings::StaticInit< | |
| 0x0000, | |
| GrUserStencilTest::kNotEqual, | |
| 0xffff, | |
| GrUserStencilOp::kSetClipAndReplaceUserBits, | |
| GrUserStencilOp::kKeep, | |
| 0xffff>() | |
| ); | |
| static constexpr GrUserStencilSettings gInvUserToClipUnionPass0( // Does not zero user bits. | |
| GrUserStencilSettings::StaticInit< | |
| 0x0000, | |
| GrUserStencilTest::kEqual, | |
| 0xffff, | |
| GrUserStencilOp::kSetClipBit, | |
| GrUserStencilOp::kKeep, | |
| 0x0000>() | |
| ); | |
| /////// | |
| // Xor | |
| static constexpr GrUserStencilSettings gUserToClipXorPass0( // Does not zero user bits. | |
| GrUserStencilSettings::StaticInit< | |
| 0x0000, | |
| GrUserStencilTest::kNotEqual, | |
| 0xffff, | |
| GrUserStencilOp::kInvertClipBit, | |
| GrUserStencilOp::kKeep, | |
| 0x0000>() | |
| ); | |
| static constexpr GrUserStencilSettings gInvUserToClipXorPass0( // Does not zero user bits. | |
| GrUserStencilSettings::StaticInit< | |
| 0x0000, | |
| GrUserStencilTest::kEqual, | |
| 0xffff, | |
| GrUserStencilOp::kInvertClipBit, | |
| GrUserStencilOp::kKeep, | |
| 0x0000>() | |
| ); | |
| /////// | |
| // Reverse Diff | |
| static constexpr GrUserStencilSettings gUserToClipRDiffPass0( // Does not zero user bits. | |
| GrUserStencilSettings::StaticInit< | |
| 0x0000, | |
| GrUserStencilTest::kNotEqual, | |
| 0xffff, | |
| GrUserStencilOp::kInvertClipBit, | |
| GrUserStencilOp::kZeroClipBit, | |
| 0x0000>() | |
| ); | |
| static constexpr GrUserStencilSettings gInvUserToClipRDiffPass0( // Does not zero user bits. | |
| GrUserStencilSettings::StaticInit< | |
| 0x0000, | |
| GrUserStencilTest::kEqual, | |
| 0xffff, | |
| GrUserStencilOp::kInvertClipBit, | |
| GrUserStencilOp::kZeroClipBit, | |
| 0x0000>() | |
| ); | |
| /////// | |
| // Second pass to clear user bits (only needed sometimes) | |
| static constexpr GrUserStencilSettings gZeroUserBits( | |
| GrUserStencilSettings::StaticInit< | |
| 0x0000, | |
| GrUserStencilTest::kNotEqual, | |
| 0xffff, | |
| GrUserStencilOp::kZero, | |
| GrUserStencilOp::kKeep, | |
| 0xffff>() | |
| ); | |
| static constexpr const GrUserStencilSettings* gUserToClipTable[2][1 + SkRegion::kLastOp][3] = { | |
| { /* Normal fill. */ | |
| {&gUserToClipDiff, nullptr, nullptr}, // kDifference_Op. | |
| {&gUserToClipIsect, nullptr, nullptr}, // kIntersect_Op. | |
| {&gUserToClipUnion, nullptr, nullptr}, // kUnion_Op. | |
| {&gUserToClipXorPass0, &gZeroUserBits, nullptr}, // kXOR_Op. | |
| {&gUserToClipRDiffPass0, &gZeroUserBits, nullptr}, // kReverseDifference_Op. | |
| {&gUserToClipReplace, nullptr, nullptr} // kReplace_Op. | |
| }, /* Inverse fill. */ { | |
| {&gUserToClipIsect, nullptr, nullptr}, // ~diff (aka isect). | |
| {&gUserToClipDiff, nullptr, nullptr}, // ~isect (aka diff). | |
| {&gInvUserToClipUnionPass0, &gZeroUserBits, nullptr}, // ~union. | |
| {&gInvUserToClipXorPass0, &gZeroUserBits, nullptr}, // ~xor. | |
| {&gInvUserToClipRDiffPass0, &gZeroUserBits, nullptr}, // ~reverse diff. | |
| {&gInvUserToClipReplace, nullptr, nullptr} // ~replace. | |
| } | |
| }; | |
| /////// | |
| // Direct to Stencil | |
| // We can render a clip element directly without first writing to the client | |
| // portion of the clip when the fill is not inverse and the set operation will | |
| // only modify the in/out status of samples covered by the clip element. | |
| // this one only works if used right after stencil clip was cleared. | |
| // Our clip mask creation code doesn't allow midstream replace ops. | |
| static constexpr GrUserStencilSettings gReplaceClip( | |
| GrUserStencilSettings::StaticInit< | |
| 0x0000, | |
| GrUserStencilTest::kAlways, | |
| 0xffff, | |
| GrUserStencilOp::kSetClipBit, | |
| GrUserStencilOp::kSetClipBit, | |
| 0x0000>() | |
| ); | |
| static constexpr GrUserStencilSettings gUnionClip( | |
| GrUserStencilSettings::StaticInit< | |
| 0x0000, | |
| GrUserStencilTest::kAlwaysIfInClip, | |
| 0xffff, | |
| GrUserStencilOp::kKeep, | |
| GrUserStencilOp::kSetClipBit, | |
| 0x0000>() | |
| ); | |
| static constexpr GrUserStencilSettings gXorClip( | |
| GrUserStencilSettings::StaticInit< | |
| 0x0000, | |
| GrUserStencilTest::kAlways, | |
| 0xffff, | |
| GrUserStencilOp::kInvertClipBit, | |
| GrUserStencilOp::kInvertClipBit, | |
| 0x0000>() | |
| ); | |
| static constexpr GrUserStencilSettings gDiffClip( | |
| GrUserStencilSettings::StaticInit< | |
| 0x0000, | |
| GrUserStencilTest::kAlwaysIfInClip, | |
| 0xffff, | |
| GrUserStencilOp::kZeroClipBit, | |
| GrUserStencilOp::kKeep, | |
| 0x0000>() | |
| ); | |
| static constexpr const GrUserStencilSettings* gDirectDrawTable[1 + SkRegion::kLastOp][2] = { | |
| {&gDiffClip, nullptr}, // kDifference_Op. | |
| {nullptr, nullptr}, // kIntersect_Op. | |
| {&gUnionClip, nullptr}, // kUnion_Op. | |
| {&gXorClip, nullptr}, // kXOR_Op. | |
| {nullptr, nullptr}, // kReverseDifference_Op. | |
| {&gReplaceClip, nullptr} // kReplace_Op. | |
| }; | |
| static_assert(0 == SkRegion::kDifference_Op); | |
| static_assert(1 == SkRegion::kIntersect_Op); | |
| static_assert(2 == SkRegion::kUnion_Op); | |
| static_assert(3 == SkRegion::kXOR_Op); | |
| static_assert(4 == SkRegion::kReverseDifference_Op); | |
| static_assert(5 == SkRegion::kReplace_Op); | |
| // Settings used to when not allowed to draw directly to the clip to fill the user stencil bits | |
| // before applying the covering clip stencil passes. | |
| static constexpr GrUserStencilSettings gDrawToStencil( | |
| GrUserStencilSettings::StaticInit< | |
| 0x0000, | |
| GrUserStencilTest::kAlways, | |
| 0xffff, | |
| GrUserStencilOp::kIncMaybeClamp, | |
| GrUserStencilOp::kIncMaybeClamp, | |
| 0xffff>() | |
| ); | |
| // Get the stencil settings per-pass to achieve the given fill+region op effect on the | |
| // stencil buffer. | |
| // | |
| // If drawDirectToClip comes back false, the caller must first draw the element into the user | |
| // stencil bits, and then cover the clip area with multiple passes using the returned | |
| // stencil settings. | |
| // If drawDirectToClip is true, the returned array will only have one pass and the | |
| // caller should use those stencil settings while drawing the element directly. | |
| // | |
| // This returns a null-terminated list of const GrUserStencilSettings* | |
| GrUserStencilSettings const* const* get_stencil_passes( | |
| SkRegion::Op op, | |
| skgpu::ganesh::PathRenderer::StencilSupport stencilSupport, | |
| bool fillInverted, | |
| bool* drawDirectToClip) { | |
| bool canRenderDirectToStencil = | |
| skgpu::ganesh::PathRenderer::kNoRestriction_StencilSupport == stencilSupport; | |
| // TODO: inverse fill + intersect op can be direct. | |
| // TODO: this can be greatly simplified when we only need intersect and difference ops and | |
| // none of the paths will be inverse-filled (just toggle the op instead). | |
| SkASSERT((unsigned)op <= SkRegion::kLastOp); | |
| if (canRenderDirectToStencil && !fillInverted) { | |
| GrUserStencilSettings const* const* directPass = gDirectDrawTable[op]; | |
| if (directPass[0]) { | |
| *drawDirectToClip = true; | |
| return directPass; | |
| } | |
| } | |
| *drawDirectToClip = false; | |
| return gUserToClipTable[fillInverted][op]; | |
| } | |
| void draw_stencil_rect(skgpu::ganesh::SurfaceDrawContext* sdc, | |
| const GrHardClip& clip, | |
| const GrUserStencilSettings* ss, | |
| const SkMatrix& matrix, | |
| const SkRect& rect, | |
| GrAA aa) { | |
| GrPaint paint; | |
| paint.setXPFactory(GrDisableColorXPFactory::Get()); | |
| sdc->stencilRect(&clip, ss, std::move(paint), aa, matrix, rect); | |
| } | |
| void draw_path(GrRecordingContext* rContext, | |
| skgpu::ganesh::SurfaceDrawContext* sdc, | |
| skgpu::ganesh::PathRenderer* pr, | |
| const GrHardClip& clip, | |
| const SkIRect& bounds, | |
| const GrUserStencilSettings* ss, | |
| const SkMatrix& matrix, | |
| const GrStyledShape& shape, | |
| GrAA aa) { | |
| GrPaint paint; | |
| paint.setXPFactory(GrDisableColorXPFactory::Get()); | |
| // kMSAA is the only type of AA that's possible on a stencil buffer. | |
| GrAAType pathAAType = aa == GrAA::kYes ? GrAAType::kMSAA : GrAAType::kNone; | |
| skgpu::ganesh::PathRenderer::DrawPathArgs args{rContext, | |
| std::move(paint), | |
| ss, | |
| sdc, | |
| &clip, | |
| &bounds, | |
| &matrix, | |
| &shape, | |
| pathAAType, | |
| false}; | |
| pr->drawPath(args); | |
| } | |
| void stencil_path(GrRecordingContext* rContext, | |
| skgpu::ganesh::SurfaceDrawContext* sdc, | |
| skgpu::ganesh::PathRenderer* pr, | |
| const GrFixedClip& clip, | |
| const SkMatrix& matrix, | |
| const GrStyledShape& shape, | |
| GrAA aa) { | |
| skgpu::ganesh::PathRenderer::StencilPathArgs args; | |
| args.fContext = rContext; | |
| args.fSurfaceDrawContext = sdc; | |
| args.fClip = &clip; | |
| args.fClipConservativeBounds = &clip.scissorRect(); | |
| args.fViewMatrix = &matrix; | |
| args.fShape = &shape; | |
| args.fDoStencilMSAA = aa; | |
| pr->stencilPath(args); | |
| } | |
| GrAA supported_aa(skgpu::ganesh::SurfaceDrawContext* sdc, GrAA aa) { | |
| return GrAA(sdc->numSamples() > 1 || sdc->canUseDynamicMSAA()); | |
| } | |
| } // namespace | |
| namespace skgpu::ganesh { | |
| StencilMaskHelper::StencilMaskHelper(GrRecordingContext* rContext, | |
| SurfaceDrawContext* sdc) | |
| : fContext(rContext) | |
| , fSDC(sdc) | |
| , fClip(sdc->dimensions()) { | |
| } | |
| bool StencilMaskHelper::init(const SkIRect& bounds, uint32_t genID, | |
| const GrWindowRectangles& windowRects, int numFPs) { | |
| if (!fSDC->mustRenderClip(genID, bounds, numFPs)) { | |
| return false; | |
| } | |
| fClip.setStencilClip(genID); | |
| // Should have caught bounds not intersecting the render target much earlier in clip application | |
| SkAssertResult(fClip.fixedClip().setScissor(bounds)); | |
| if (!windowRects.empty()) { | |
| fClip.fixedClip().setWindowRectangles( | |
| windowRects, GrWindowRectsState::Mode::kExclusive); | |
| } | |
| fNumFPs = numFPs; | |
| return true; | |
| } | |
| void StencilMaskHelper::drawRect(const SkRect& rect, | |
| const SkMatrix& matrix, | |
| SkRegion::Op op, | |
| GrAA aa) { | |
| if (rect.isEmpty()) { | |
| return; | |
| } | |
| bool drawDirectToClip; | |
| auto passes = get_stencil_passes(op, PathRenderer::kNoRestriction_StencilSupport, | |
| false, &drawDirectToClip); | |
| aa = supported_aa(fSDC, aa); | |
| if (!drawDirectToClip) { | |
| // Draw to client stencil bits first | |
| draw_stencil_rect(fSDC, fClip.fixedClip(), &gDrawToStencil, matrix, rect, aa); | |
| } | |
| // Now modify the clip bit (either by rendering directly), or by covering the bounding box | |
| // of the clip | |
| for (GrUserStencilSettings const* const* pass = passes; *pass; ++pass) { | |
| if (drawDirectToClip) { | |
| draw_stencil_rect(fSDC, fClip, *pass, matrix, rect, aa); | |
| } else { | |
| draw_stencil_rect(fSDC, fClip, *pass, SkMatrix::I(), | |
| SkRect::Make(fClip.fixedClip().scissorRect()), aa); | |
| } | |
| } | |
| } | |
| bool StencilMaskHelper::drawPath(const SkPath& path, | |
| const SkMatrix& matrix, | |
| SkRegion::Op op, | |
| GrAA aa) { | |
| if (path.isEmpty()) { | |
| return true; | |
| } | |
| // drawPath follows a similar approach to drawRect(), where we either draw directly to the clip | |
| // bit or first draw to client bits and then apply a cover pass. The complicating factor is that | |
| // we rely on path rendering and how the chosen path renderer uses the stencil buffer. | |
| aa = supported_aa(fSDC, aa); | |
| GrAAType pathAAType = aa == GrAA::kYes ? GrAAType::kMSAA : GrAAType::kNone; | |
| // This will be used to determine whether the clip shape can be rendered into the | |
| // stencil with arbitrary stencil settings. | |
| PathRenderer::StencilSupport stencilSupport; | |
| // Make path canonical with regards to fill type (inverse handled by stencil settings). | |
| bool fillInverted = path.isInverseFillType(); | |
| SkTCopyOnFirstWrite<SkPath> clipPath(path); | |
| if (fillInverted) { | |
| clipPath.writable()->toggleInverseFillType(); | |
| } | |
| GrStyledShape shape(*clipPath, GrStyle::SimpleFill()); | |
| SkASSERT(!shape.inverseFilled()); | |
| PathRenderer::CanDrawPathArgs canDrawArgs; | |
| canDrawArgs.fCaps = fContext->priv().caps(); | |
| canDrawArgs.fProxy = fSDC->asRenderTargetProxy(); | |
| canDrawArgs.fClipConservativeBounds = &fClip.fixedClip().scissorRect(); | |
| canDrawArgs.fViewMatrix = &matrix; | |
| canDrawArgs.fShape = &shape; | |
| canDrawArgs.fPaint = nullptr; | |
| canDrawArgs.fSurfaceProps = &fSDC->surfaceProps(); | |
| canDrawArgs.fAAType = pathAAType; | |
| canDrawArgs.fHasUserStencilSettings = false; | |
| auto pr = fContext->priv().drawingManager()->getPathRenderer( | |
| canDrawArgs, false, PathRendererChain::DrawType::kStencil, &stencilSupport); | |
| if (!pr) { | |
| return false; | |
| } | |
| bool drawDirectToClip; | |
| auto passes = get_stencil_passes(op, stencilSupport, fillInverted, &drawDirectToClip); | |
| // Write to client bits if necessary | |
| if (!drawDirectToClip) { | |
| if (stencilSupport == PathRenderer::kNoRestriction_StencilSupport) { | |
| draw_path(fContext, fSDC, pr, fClip.fixedClip(), fClip.fixedClip().scissorRect(), | |
| &gDrawToStencil, matrix, shape, aa); | |
| } else { | |
| stencil_path(fContext, fSDC, pr, fClip.fixedClip(), matrix, shape, aa); | |
| } | |
| } | |
| // Now modify the clip bit (either by rendering directly), or by covering the bounding box | |
| // of the clip | |
| for (GrUserStencilSettings const* const* pass = passes; *pass; ++pass) { | |
| if (drawDirectToClip) { | |
| draw_path(fContext, fSDC, pr, fClip, fClip.fixedClip().scissorRect(), | |
| *pass, matrix, shape, aa); | |
| } else { | |
| draw_stencil_rect(fSDC, fClip, *pass, SkMatrix::I(), | |
| SkRect::Make(fClip.fixedClip().scissorRect()), aa); | |
| } | |
| } | |
| return true; | |
| } | |
| bool StencilMaskHelper::drawShape(const GrShape& shape, | |
| const SkMatrix& matrix, | |
| SkRegion::Op op, | |
| GrAA aa) { | |
| if (shape.isRect() && !shape.inverted()) { | |
| this->drawRect(shape.rect(), matrix, op, aa); | |
| return true; | |
| } else { | |
| return this->drawPath(shape.asPath(), matrix, op, aa); | |
| } | |
| } | |
| void StencilMaskHelper::clear(bool insideStencil) { | |
| if (fClip.fixedClip().hasWindowRectangles()) { | |
| // Use a draw to benefit from window rectangles when resetting the stencil buffer; for | |
| // large buffers with MSAA this can be significant. | |
| draw_stencil_rect(fSDC, fClip.fixedClip(), | |
| GrStencilSettings::SetClipBitSettings(insideStencil), SkMatrix::I(), | |
| SkRect::Make(fClip.fixedClip().scissorRect()), GrAA::kNo); | |
| } else { | |
| fSDC->clearStencilClip(fClip.fixedClip().scissorRect(), insideStencil); | |
| } | |
| } | |
| void StencilMaskHelper::finish() { | |
| fSDC->setLastClip(fClip.stencilStackID(), fClip.fixedClip().scissorRect(), fNumFPs); | |
| } | |
| } // namespace skgpu::ganesh |