diff options
-rw-r--r-- | gm/windowrectangles.cpp | 45 | ||||
-rw-r--r-- | src/gpu/GrClipStackClip.cpp | 168 | ||||
-rw-r--r-- | src/gpu/GrClipStackClip.h | 21 | ||||
-rw-r--r-- | src/gpu/GrReducedClip.cpp | 128 | ||||
-rw-r--r-- | src/gpu/GrReducedClip.h | 1 |
5 files changed, 347 insertions, 16 deletions
diff --git a/gm/windowrectangles.cpp b/gm/windowrectangles.cpp index 8b85554af8..931a154df4 100644 --- a/gm/windowrectangles.cpp +++ b/gm/windowrectangles.cpp @@ -107,8 +107,8 @@ DEF_GM( return new WindowRectanglesGM(); ) constexpr static int kNumWindows = 8; /** - * Visualizes the stencil mask for a clip with several window rectangles. The purpose of this test - * is to verify that window rectangles are being used during clip mask generation, and to + * Visualizes the mask (alpha or stencil) for a clip with several window rectangles. The purpose of + * this test is to verify that window rectangles are being used during clip mask generation, and to * visualize where the window rectangles are placed. * * We use window rectangles when generating the clip mask because there is no need to invest time @@ -123,6 +123,7 @@ private: constexpr static int kMaskCheckerSize = 5; SkString onShortName() final { return SkString("windowrectangles_mask"); } void onCoverClipStack(const SkClipStack&, SkCanvas*) final; + void visualizeAlphaMask(GrContext*, GrRenderTargetContext*, const GrReducedClip&, GrPaint&&); void visualizeStencilMask(GrContext*, GrRenderTargetContext*, const GrReducedClip&, GrPaint&&); void stencilCheckerboard(GrRenderTargetContext*, bool flip); void fail(SkCanvas*); @@ -183,14 +184,48 @@ void WindowRectanglesMaskGM::onCoverClipStack(const SkClipStack& stack, SkCanvas const GrReducedClip reducedClip(stack, SkRect::Make(kCoverRect), rtc->caps()->shaderCaps(), kNumWindows); - GrPaint paint; + GrPaint paint; if (GrFSAAType::kNone == rtc->fsaaType()) { - // Use different colors so we don't confuse masks that don't have AA with ones that should. paint.setColor4f(GrColor4f(0, 0.25f, 1, 1)); + this->visualizeAlphaMask(ctx, rtc, reducedClip, std::move(paint)); } else { paint.setColor4f(GrColor4f(1, 0.25f, 0.25f, 1)); + this->visualizeStencilMask(ctx, rtc, reducedClip, std::move(paint)); } - this->visualizeStencilMask(ctx, rtc, reducedClip, std::move(paint)); +} + +void WindowRectanglesMaskGM::visualizeAlphaMask(GrContext* ctx, GrRenderTargetContext* rtc, + const GrReducedClip& reducedClip, GrPaint&& paint) { + const int padRight = (kDeviceRect.right() - kCoverRect.right()) / 2; + const int padBottom = (kDeviceRect.bottom() - kCoverRect.bottom()) / 2; + sk_sp<GrRenderTargetContext> maskRTC( + ctx->contextPriv().makeDeferredRenderTargetContextWithFallback( + SkBackingFit::kExact, + kCoverRect.width() + padRight, + kCoverRect.height() + padBottom, + kAlpha_8_GrPixelConfig, nullptr)); + if (!maskRTC) { + return; + } + + // Draw a checker pattern into the alpha mask so we can visualize the regions left untouched by + // the clip mask generation. + this->stencilCheckerboard(maskRTC.get(), true); + maskRTC->clear(nullptr, GrColorPackA4(0xff), GrRenderTargetContext::CanClearFullscreen::kYes); + maskRTC->priv().drawAndStencilRect(make_stencil_only_clip(), &GrUserStencilSettings::kUnused, + SkRegion::kDifference_Op, false, GrAA::kNo, SkMatrix::I(), + SkRect::MakeIWH(maskRTC->width(), maskRTC->height())); + reducedClip.drawAlphaClipMask(maskRTC.get()); + + int x = kCoverRect.x() - kDeviceRect.x(), + y = kCoverRect.y() - kDeviceRect.y(); + + // Now visualize the alpha mask by drawing a rect over the area where it is defined. The regions + // inside window rectangles or outside the scissor should still have the initial checkerboard + // intact. (This verifies we didn't spend any time modifying those pixels in the mask.) + AlphaOnlyClip clip(maskRTC->asTextureProxyRef(), x, y); + rtc->drawRect(clip, std::move(paint), GrAA::kYes, SkMatrix::I(), + SkRect::Make(SkIRect::MakeXYWH(x, y, maskRTC->width(), maskRTC->height()))); } void WindowRectanglesMaskGM::visualizeStencilMask(GrContext* ctx, GrRenderTargetContext* rtc, diff --git a/src/gpu/GrClipStackClip.cpp b/src/gpu/GrClipStackClip.cpp index ed4a098c8d..fe8d10095c 100644 --- a/src/gpu/GrClipStackClip.cpp +++ b/src/gpu/GrClipStackClip.cpp @@ -73,6 +73,111 @@ void GrClipStackClip::getConservativeBounds(int width, int height, SkIRect* devR devBounds.roundOut(devResult); } +//////////////////////////////////////////////////////////////////////////////// +// set up the draw state to enable the aa clipping mask. +static std::unique_ptr<GrFragmentProcessor> create_fp_for_mask(sk_sp<GrTextureProxy> mask, + const SkIRect& devBound) { + SkIRect domainTexels = SkIRect::MakeWH(devBound.width(), devBound.height()); + return GrDeviceSpaceTextureDecalFragmentProcessor::Make(std::move(mask), domainTexels, + {devBound.fLeft, devBound.fTop}); +} + +// Does the path in 'element' require SW rendering? If so, return true (and, +// optionally, set 'prOut' to NULL. If not, return false (and, optionally, set +// 'prOut' to the non-SW path renderer that will do the job). +bool GrClipStackClip::PathNeedsSWRenderer(GrContext* context, + const SkIRect& scissorRect, + bool hasUserStencilSettings, + const GrRenderTargetContext* renderTargetContext, + const SkMatrix& viewMatrix, + const Element* element, + GrPathRenderer** prOut, + bool needsStencil) { + if (Element::DeviceSpaceType::kRect == element->getDeviceSpaceType()) { + // rects can always be drawn directly w/o using the software path + // TODO: skip rrects once we're drawing them directly. + if (prOut) { + *prOut = nullptr; + } + return false; + } else { + // We shouldn't get here with an empty clip element. + SkASSERT(Element::DeviceSpaceType::kEmpty != element->getDeviceSpaceType()); + + // the gpu alpha mask will draw the inverse paths as non-inverse to a temp buffer + SkPath path; + element->asDeviceSpacePath(&path); + if (path.isInverseFillType()) { + path.toggleInverseFillType(); + } + + GrPathRendererChain::DrawType type = + needsStencil ? GrPathRendererChain::DrawType::kStencilAndColor + : GrPathRendererChain::DrawType::kColor; + + GrShape shape(path, GrStyle::SimpleFill()); + GrPathRenderer::CanDrawPathArgs canDrawArgs; + canDrawArgs.fCaps = context->caps(); + canDrawArgs.fClipConservativeBounds = &scissorRect; + canDrawArgs.fViewMatrix = &viewMatrix; + canDrawArgs.fShape = &shape; + canDrawArgs.fAAType = GrChooseAAType(GrAA(element->isAA()), + renderTargetContext->fsaaType(), + GrAllowMixedSamples::kYes, + *context->caps()); + canDrawArgs.fHasUserStencilSettings = hasUserStencilSettings; + + // the 'false' parameter disallows use of the SW path renderer + GrPathRenderer* pr = + context->contextPriv().drawingManager()->getPathRenderer(canDrawArgs, false, type); + if (prOut) { + *prOut = pr; + } + return SkToBool(!pr); + } +} + +/* + * This method traverses the clip stack to see if the GrSoftwarePathRenderer + * will be used on any element. If so, it returns true to indicate that the + * entire clip should be rendered in SW and then uploaded en masse to the gpu. + */ +bool GrClipStackClip::UseSWOnlyPath(GrContext* context, + bool hasUserStencilSettings, + const GrRenderTargetContext* renderTargetContext, + const GrReducedClip& reducedClip) { + // TODO: generalize this function so that when + // a clip gets complex enough it can just be done in SW regardless + // of whether it would invoke the GrSoftwarePathRenderer. + + // If we're avoiding stencils, always use SW: + if (context->caps()->avoidStencilBuffers()) + return true; + + // Set the matrix so that rendered clip elements are transformed to mask space from clip + // space. + SkMatrix translate; + translate.setTranslate(SkIntToScalar(-reducedClip.left()), SkIntToScalar(-reducedClip.top())); + + for (ElementList::Iter iter(reducedClip.maskElements()); iter.get(); iter.next()) { + const Element* element = iter.get(); + + SkClipOp op = element->getOp(); + bool invert = element->isInverseFilled(); + bool needsStencil = invert || + kIntersect_SkClipOp == op || kReverseDifference_SkClipOp == op; + + if (PathNeedsSWRenderer(context, reducedClip.scissor(), hasUserStencilSettings, + renderTargetContext, translate, element, nullptr, needsStencil)) { + return true; + } + } + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// sort out what kind of clip mask needs to be created: alpha, stencil, +// scissor, or entirely software bool GrClipStackClip::apply(GrContext* context, GrRenderTargetContext* renderTargetContext, bool useHWAA, bool hasUserStencilSettings, GrAppliedClip* out, SkRect* bounds) const { @@ -148,18 +253,24 @@ bool GrClipStackClip::applyClipMask(GrContext* context, GrRenderTargetContext* r // If the stencil buffer is multisampled we can use it to do everything. if ((GrFSAAType::kNone == renderTargetContext->fsaaType() && reducedClip.maskRequiresAA()) || context->caps()->avoidStencilBuffers()) { - if (auto mask = this->createSoftwareClipMask(context, reducedClip, renderTargetContext)) { - // The mask should fill the clip's scissor. - SkIRect domainTexels = SkIRect::MakeWH(reducedClip.width(), reducedClip.height()); - SkIPoint maskOffset = SkIPoint::Make(reducedClip.left(), reducedClip.top()); - out->addCoverageFP(GrDeviceSpaceTextureDecalFragmentProcessor::Make(std::move(mask), - domainTexels, - maskOffset)); + sk_sp<GrTextureProxy> result; + if (UseSWOnlyPath(context, hasUserStencilSettings, renderTargetContext, reducedClip)) { + // The clip geometry is complex enough that it will be more efficient to create it + // entirely in software + result = this->createSoftwareClipMask(context, reducedClip, renderTargetContext); + } else { + result = this->createAlphaClipMask(context, reducedClip); + } + + if (result) { + // The mask's top left coord should be pinned to the rounded-out top left corner of + // the clip's device space bounds. + out->addCoverageFP(create_fp_for_mask(std::move(result), reducedClip.scissor())); return true; } - // If software clip mask creation fails, fall through to the stencil code paths, unless - // stencils are disallowed. + // If alpha or software clip mask creation fails, fall through to the stencil code paths, + // unless stencils are disallowed. if (context->caps()->avoidStencilBuffers()) { SkDebugf("WARNING: Clip mask requires stencil, but stencil unavailable. " "Clip will be ignored.\n"); @@ -213,6 +324,45 @@ static void add_invalidate_on_pop_message(const SkClipStack& stack, uint32_t cli SkDEBUGFAIL("Gen ID was not found in stack."); } +sk_sp<GrTextureProxy> GrClipStackClip::createAlphaClipMask(GrContext* context, + const GrReducedClip& reducedClip) const { + GrProxyProvider* proxyProvider = context->contextPriv().proxyProvider(); + GrUniqueKey key; + create_clip_mask_key(reducedClip.maskGenID(), reducedClip.scissor(), + reducedClip.numAnalyticFPs(), &key); + + sk_sp<GrTextureProxy> proxy(proxyProvider->findOrCreateProxyByUniqueKey( + key, kBottomLeft_GrSurfaceOrigin)); + if (proxy) { + return proxy; + } + + sk_sp<GrRenderTargetContext> rtc( + context->contextPriv().makeDeferredRenderTargetContextWithFallback(SkBackingFit::kApprox, + reducedClip.width(), + reducedClip.height(), + kAlpha_8_GrPixelConfig, + nullptr)); + if (!rtc) { + return nullptr; + } + + if (!reducedClip.drawAlphaClipMask(rtc.get())) { + return nullptr; + } + + sk_sp<GrTextureProxy> result(rtc->asTextureProxyRef()); + if (!result) { + return nullptr; + } + + SkASSERT(result->origin() == kBottomLeft_GrSurfaceOrigin); + proxyProvider->assignUniqueKeyToProxy(key, result.get()); + add_invalidate_on_pop_message(*fStack, reducedClip.maskGenID(), key); + + return result; +} + namespace { /** diff --git a/src/gpu/GrClipStackClip.h b/src/gpu/GrClipStackClip.h index 05d9f18c88..aeb983468c 100644 --- a/src/gpu/GrClipStackClip.h +++ b/src/gpu/GrClipStackClip.h @@ -37,14 +37,31 @@ public: static const char kMaskTestTag[]; private: + static bool PathNeedsSWRenderer(GrContext* context, + const SkIRect& scissorRect, + bool hasUserStencilSettings, + const GrRenderTargetContext*, + const SkMatrix& viewMatrix, + const SkClipStack::Element* element, + GrPathRenderer** prOut, + bool needsStencil); + bool applyClipMask(GrContext*, GrRenderTargetContext*, const GrReducedClip&, bool hasUserStencilSettings, GrAppliedClip*) const; - // Creates an alpha mask of the remaining reduced clip elements that could not be handled - // analytically on the GPU. The mask fills the reduced clip's scissor rect. + // Creates an alpha mask of the clip. The mask is a rasterization of elements through the + // rect specified by clipSpaceIBounds. + sk_sp<GrTextureProxy> createAlphaClipMask(GrContext*, const GrReducedClip&) const; + + // Similar to createAlphaClipMask but it rasterizes in SW and uploads to the result texture. sk_sp<GrTextureProxy> createSoftwareClipMask(GrContext*, const GrReducedClip&, GrRenderTargetContext*) const; + static bool UseSWOnlyPath(GrContext*, + bool hasUserStencilSettings, + const GrRenderTargetContext*, + const GrReducedClip&); + const SkClipStack* fStack; }; diff --git a/src/gpu/GrReducedClip.cpp b/src/gpu/GrReducedClip.cpp index 2f60dad28f..9d27f7696e 100644 --- a/src/gpu/GrReducedClip.cpp +++ b/src/gpu/GrReducedClip.cpp @@ -673,6 +673,134 @@ void GrReducedClip::makeEmpty() { } //////////////////////////////////////////////////////////////////////////////// +// Create a 8-bit clip mask in alpha + +static bool stencil_element(GrRenderTargetContext* rtc, + const GrFixedClip& clip, + const GrUserStencilSettings* ss, + const SkMatrix& viewMatrix, + const SkClipStack::Element* element) { + GrAA aa = GrAA(element->isAA()); + switch (element->getDeviceSpaceType()) { + case SkClipStack::Element::DeviceSpaceType::kEmpty: + SkDEBUGFAIL("Should never get here with an empty element."); + break; + case SkClipStack::Element::DeviceSpaceType::kRect: + return rtc->priv().drawAndStencilRect(clip, ss, (SkRegion::Op)element->getOp(), + element->isInverseFilled(), aa, viewMatrix, + element->getDeviceSpaceRect()); + break; + default: { + SkPath path; + element->asDeviceSpacePath(&path); + if (path.isInverseFillType()) { + path.toggleInverseFillType(); + } + + return rtc->priv().drawAndStencilPath(clip, ss, (SkRegion::Op)element->getOp(), + element->isInverseFilled(), aa, viewMatrix, path); + break; + } + } + + return false; +} + +static void draw_element(GrRenderTargetContext* rtc, + const GrClip& clip, // TODO: can this just always be WideOpen? + GrPaint&& paint, + GrAA aa, + const SkMatrix& viewMatrix, + const SkClipStack::Element* element) { + // TODO: Draw rrects directly here. + switch (element->getDeviceSpaceType()) { + case SkClipStack::Element::DeviceSpaceType::kEmpty: + SkDEBUGFAIL("Should never get here with an empty element."); + break; + case SkClipStack::Element::DeviceSpaceType::kRect: + rtc->drawRect(clip, std::move(paint), aa, viewMatrix, element->getDeviceSpaceRect()); + break; + default: { + SkPath path; + element->asDeviceSpacePath(&path); + if (path.isInverseFillType()) { + path.toggleInverseFillType(); + } + + rtc->drawPath(clip, std::move(paint), aa, viewMatrix, path, GrStyle::SimpleFill()); + break; + } + } +} + +bool GrReducedClip::drawAlphaClipMask(GrRenderTargetContext* rtc) const { + // The texture may be larger than necessary, this rect represents the part of the texture + // we populate with a rasterization of the clip. + GrFixedClip clip(SkIRect::MakeWH(fScissor.width(), fScissor.height())); + + if (!fWindowRects.empty()) { + clip.setWindowRectangles(fWindowRects.makeOffset(-fScissor.left(), -fScissor.top()), + GrWindowRectsState::Mode::kExclusive); + } + + // The scratch texture that we are drawing into can be substantially larger than the mask. Only + // clear the part that we care about. + GrColor initialCoverage = InitialState::kAllIn == this->initialState() ? -1 : 0; + rtc->priv().clear(clip, initialCoverage, GrRenderTargetContext::CanClearFullscreen::kYes); + + // Set the matrix so that rendered clip elements are transformed to mask space from clip space. + SkMatrix translate; + translate.setTranslate(SkIntToScalar(-fScissor.left()), SkIntToScalar(-fScissor.top())); + + // walk through each clip element and perform its set op + for (ElementList::Iter iter(fMaskElements); iter.get(); iter.next()) { + const Element* element = iter.get(); + SkRegion::Op op = (SkRegion::Op)element->getOp(); + GrAA aa = GrAA(element->isAA()); + bool invert = element->isInverseFilled(); + if (invert || SkRegion::kIntersect_Op == op || SkRegion::kReverseDifference_Op == op) { + // draw directly into the result with the stencil set to make the pixels affected + // by the clip shape be non-zero. + static constexpr GrUserStencilSettings kStencilInElement( + GrUserStencilSettings::StaticInit< + 0xffff, + GrUserStencilTest::kAlways, + 0xffff, + GrUserStencilOp::kReplace, + GrUserStencilOp::kReplace, + 0xffff>() + ); + if (!stencil_element(rtc, clip, &kStencilInElement, translate, element)) { + return false; + } + + // Draw to the exterior pixels (those with a zero stencil value). + static constexpr GrUserStencilSettings kDrawOutsideElement( + GrUserStencilSettings::StaticInit< + 0x0000, + GrUserStencilTest::kEqual, + 0xffff, + GrUserStencilOp::kZero, + GrUserStencilOp::kZero, + 0xffff>() + ); + if (!rtc->priv().drawAndStencilRect(clip, &kDrawOutsideElement, op, !invert, GrAA::kNo, + translate, SkRect::Make(fScissor))) { + return false; + } + } else { + // all the remaining ops can just be directly draw into the accumulation buffer + GrPaint paint; + paint.setCoverageSetOpXPFactory(op, false); + + draw_element(rtc, clip, std::move(paint), aa, translate, element); + } + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// // Create a 1-bit clip mask in the stencil buffer. bool GrReducedClip::drawStencilClipMask(GrContext* context, diff --git a/src/gpu/GrReducedClip.h b/src/gpu/GrReducedClip.h index 5447652137..c3838d8802 100644 --- a/src/gpu/GrReducedClip.h +++ b/src/gpu/GrReducedClip.h @@ -83,6 +83,7 @@ public: */ bool maskRequiresAA() const { SkASSERT(!fMaskElements.isEmpty()); return fMaskRequiresAA; } + bool drawAlphaClipMask(GrRenderTargetContext*) const; bool drawStencilClipMask(GrContext*, GrRenderTargetContext*) const; int numAnalyticFPs() const { return fAnalyticFPs.count() + fCCPRClipPaths.count(); } |