• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2016 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "GrClipStackClip.h"
9 
10 #include "GrAppliedClip.h"
11 #include "GrContextPriv.h"
12 #include "GrDrawingManager.h"
13 #include "GrRenderTargetContextPriv.h"
14 #include "GrFixedClip.h"
15 #include "GrGpuResourcePriv.h"
16 #include "GrRenderTargetPriv.h"
17 #include "GrResourceProvider.h"
18 #include "GrStencilAttachment.h"
19 #include "GrSWMaskHelper.h"
20 #include "GrTextureProxy.h"
21 #include "effects/GrConvexPolyEffect.h"
22 #include "effects/GrRRectEffect.h"
23 #include "effects/GrTextureDomain.h"
24 #include "SkClipOpPriv.h"
25 
26 typedef SkClipStack::Element Element;
27 typedef GrReducedClip::InitialState InitialState;
28 typedef GrReducedClip::ElementList ElementList;
29 
30 static const int kMaxAnalyticElements = 4;
31 const char GrClipStackClip::kMaskTestTag[] = "clip_mask";
32 
quickContains(const SkRect & rect) const33 bool GrClipStackClip::quickContains(const SkRect& rect) const {
34     if (!fStack || fStack->isWideOpen()) {
35         return true;
36     }
37     return fStack->quickContains(rect);
38 }
39 
quickContains(const SkRRect & rrect) const40 bool GrClipStackClip::quickContains(const SkRRect& rrect) const {
41     if (!fStack || fStack->isWideOpen()) {
42         return true;
43     }
44     return fStack->quickContains(rrect);
45 }
46 
isRRect(const SkRect & origRTBounds,SkRRect * rr,GrAA * aa) const47 bool GrClipStackClip::isRRect(const SkRect& origRTBounds, SkRRect* rr, GrAA* aa) const {
48     if (!fStack) {
49         return false;
50     }
51     const SkRect* rtBounds = &origRTBounds;
52     bool isAA;
53     if (fStack->isRRect(*rtBounds, rr, &isAA)) {
54         *aa = GrBoolToAA(isAA);
55         return true;
56     }
57     return false;
58 }
59 
getConservativeBounds(int width,int height,SkIRect * devResult,bool * isIntersectionOfRects) const60 void GrClipStackClip::getConservativeBounds(int width, int height, SkIRect* devResult,
61                                             bool* isIntersectionOfRects) const {
62     if (!fStack) {
63         devResult->setXYWH(0, 0, width, height);
64         if (isIntersectionOfRects) {
65             *isIntersectionOfRects = true;
66         }
67         return;
68     }
69     SkRect devBounds;
70     fStack->getConservativeBounds(0, 0, width, height, &devBounds, isIntersectionOfRects);
71     devBounds.roundOut(devResult);
72 }
73 
74 ////////////////////////////////////////////////////////////////////////////////
75 // set up the draw state to enable the aa clipping mask.
create_fp_for_mask(sk_sp<GrTextureProxy> mask,const SkIRect & devBound)76 static sk_sp<GrFragmentProcessor> create_fp_for_mask(sk_sp<GrTextureProxy> mask,
77                                                      const SkIRect &devBound) {
78     SkIRect domainTexels = SkIRect::MakeWH(devBound.width(), devBound.height());
79     return GrDeviceSpaceTextureDecalFragmentProcessor::Make(std::move(mask), domainTexels,
80                                                             {devBound.fLeft, devBound.fTop});
81 }
82 
83 // Does the path in 'element' require SW rendering? If so, return true (and,
84 // optionally, set 'prOut' to NULL. If not, return false (and, optionally, set
85 // 'prOut' to the non-SW path renderer that will do the job).
PathNeedsSWRenderer(GrContext * context,bool hasUserStencilSettings,const GrRenderTargetContext * renderTargetContext,const SkMatrix & viewMatrix,const Element * element,GrPathRenderer ** prOut,bool needsStencil)86 bool GrClipStackClip::PathNeedsSWRenderer(GrContext* context,
87                                           bool hasUserStencilSettings,
88                                           const GrRenderTargetContext* renderTargetContext,
89                                           const SkMatrix& viewMatrix,
90                                           const Element* element,
91                                           GrPathRenderer** prOut,
92                                           bool needsStencil) {
93     if (Element::kRect_Type == element->getType()) {
94         // rects can always be drawn directly w/o using the software path
95         // TODO: skip rrects once we're drawing them directly.
96         if (prOut) {
97             *prOut = nullptr;
98         }
99         return false;
100     } else {
101         // We shouldn't get here with an empty clip element.
102         SkASSERT(Element::kEmpty_Type != element->getType());
103 
104         // the gpu alpha mask will draw the inverse paths as non-inverse to a temp buffer
105         SkPath path;
106         element->asPath(&path);
107         if (path.isInverseFillType()) {
108             path.toggleInverseFillType();
109         }
110 
111         GrPathRendererChain::DrawType type =
112                 needsStencil ? GrPathRendererChain::DrawType::kStencilAndColor
113                              : GrPathRendererChain::DrawType::kColor;
114 
115         GrShape shape(path, GrStyle::SimpleFill());
116         GrPathRenderer::CanDrawPathArgs canDrawArgs;
117         canDrawArgs.fCaps = context->caps();
118         canDrawArgs.fViewMatrix = &viewMatrix;
119         canDrawArgs.fShape = &shape;
120         canDrawArgs.fAAType = GrChooseAAType(GrBoolToAA(element->isAA()),
121                                              renderTargetContext->fsaaType(),
122                                              GrAllowMixedSamples::kYes,
123                                              *context->caps());
124         canDrawArgs.fHasUserStencilSettings = hasUserStencilSettings;
125 
126         // the 'false' parameter disallows use of the SW path renderer
127         GrPathRenderer* pr =
128             context->contextPriv().drawingManager()->getPathRenderer(canDrawArgs, false, type);
129         if (prOut) {
130             *prOut = pr;
131         }
132         return SkToBool(!pr);
133     }
134 }
135 
136 /*
137  * This method traverses the clip stack to see if the GrSoftwarePathRenderer
138  * will be used on any element. If so, it returns true to indicate that the
139  * entire clip should be rendered in SW and then uploaded en masse to the gpu.
140  */
UseSWOnlyPath(GrContext * context,bool hasUserStencilSettings,const GrRenderTargetContext * renderTargetContext,const GrReducedClip & reducedClip)141 bool GrClipStackClip::UseSWOnlyPath(GrContext* context,
142                                     bool hasUserStencilSettings,
143                                     const GrRenderTargetContext* renderTargetContext,
144                                     const GrReducedClip& reducedClip) {
145     // TODO: generalize this function so that when
146     // a clip gets complex enough it can just be done in SW regardless
147     // of whether it would invoke the GrSoftwarePathRenderer.
148 
149     // If we're avoiding stencils, always use SW:
150     if (context->caps()->avoidStencilBuffers())
151         return true;
152 
153     // Set the matrix so that rendered clip elements are transformed to mask space from clip
154     // space.
155     SkMatrix translate;
156     translate.setTranslate(SkIntToScalar(-reducedClip.left()), SkIntToScalar(-reducedClip.top()));
157 
158     for (ElementList::Iter iter(reducedClip.elements()); iter.get(); iter.next()) {
159         const Element* element = iter.get();
160 
161         SkClipOp op = element->getOp();
162         bool invert = element->isInverseFilled();
163         bool needsStencil = invert ||
164                             kIntersect_SkClipOp == op || kReverseDifference_SkClipOp == op;
165 
166         if (PathNeedsSWRenderer(context, hasUserStencilSettings,
167                                 renderTargetContext, translate, element, nullptr, needsStencil)) {
168             return true;
169         }
170     }
171     return false;
172 }
173 
get_analytic_clip_processor(const ElementList & elements,bool abortIfAA,const SkRect & drawDevBounds,sk_sp<GrFragmentProcessor> * resultFP)174 static bool get_analytic_clip_processor(const ElementList& elements,
175                                         bool abortIfAA,
176                                         const SkRect& drawDevBounds,
177                                         sk_sp<GrFragmentProcessor>* resultFP) {
178     SkASSERT(elements.count() <= kMaxAnalyticElements);
179     SkSTArray<kMaxAnalyticElements, sk_sp<GrFragmentProcessor>> fps;
180     ElementList::Iter iter(elements);
181     while (iter.get()) {
182         SkClipOp op = iter.get()->getOp();
183         bool invert;
184         bool skip = false;
185         switch (op) {
186             case kReplace_SkClipOp:
187                 SkASSERT(iter.get() == elements.head());
188                 // Fallthrough, handled same as intersect.
189             case kIntersect_SkClipOp:
190                 invert = false;
191                 if (iter.get()->contains(drawDevBounds)) {
192                     skip = true;
193                 }
194                 break;
195             case kDifference_SkClipOp:
196                 invert = true;
197                 // We don't currently have a cheap test for whether a rect is fully outside an
198                 // element's primitive, so don't attempt to set skip.
199                 break;
200             default:
201                 return false;
202         }
203         if (!skip) {
204             GrPrimitiveEdgeType edgeType;
205             if (iter.get()->isAA()) {
206                 if (abortIfAA) {
207                     return false;
208                 }
209                 edgeType =
210                     invert ? kInverseFillAA_GrProcessorEdgeType : kFillAA_GrProcessorEdgeType;
211             } else {
212                 edgeType =
213                     invert ? kInverseFillBW_GrProcessorEdgeType : kFillBW_GrProcessorEdgeType;
214             }
215 
216             switch (iter.get()->getType()) {
217                 case SkClipStack::Element::kPath_Type:
218                     fps.emplace_back(GrConvexPolyEffect::Make(edgeType, iter.get()->getPath()));
219                     break;
220                 case SkClipStack::Element::kRRect_Type: {
221                     fps.emplace_back(GrRRectEffect::Make(edgeType, iter.get()->getRRect()));
222                     break;
223                 }
224                 case SkClipStack::Element::kRect_Type: {
225                     fps.emplace_back(GrConvexPolyEffect::Make(edgeType, iter.get()->getRect()));
226                     break;
227                 }
228                 default:
229                     break;
230             }
231             if (!fps.back()) {
232                 return false;
233             }
234         }
235         iter.next();
236     }
237 
238     *resultFP = nullptr;
239     if (fps.count()) {
240         *resultFP = GrFragmentProcessor::RunInSeries(fps.begin(), fps.count());
241     }
242     return true;
243 }
244 
245 ////////////////////////////////////////////////////////////////////////////////
246 // sort out what kind of clip mask needs to be created: alpha, stencil,
247 // scissor, or entirely software
apply(GrContext * context,GrRenderTargetContext * renderTargetContext,bool useHWAA,bool hasUserStencilSettings,GrAppliedClip * out,SkRect * bounds) const248 bool GrClipStackClip::apply(GrContext* context, GrRenderTargetContext* renderTargetContext,
249                             bool useHWAA, bool hasUserStencilSettings, GrAppliedClip* out,
250                             SkRect* bounds) const {
251     SkRect devBounds = SkRect::MakeIWH(renderTargetContext->width(), renderTargetContext->height());
252     if (!devBounds.intersect(*bounds)) {
253         return false;
254     }
255 
256     if (!fStack || fStack->isWideOpen()) {
257         return true;
258     }
259 
260     const GrReducedClip reducedClip(*fStack, devBounds,
261                                     renderTargetContext->priv().maxWindowRectangles());
262 
263     if (reducedClip.hasIBounds() && !GrClip::IsInsideClip(reducedClip.ibounds(), devBounds)) {
264         out->addScissor(reducedClip.ibounds(), bounds);
265     }
266 
267     if (!reducedClip.windowRectangles().empty()) {
268         out->addWindowRectangles(reducedClip.windowRectangles(),
269                                  GrWindowRectsState::Mode::kExclusive);
270     }
271 
272     if (reducedClip.elements().isEmpty()) {
273         return InitialState::kAllIn == reducedClip.initialState();
274     }
275 
276 #ifdef SK_DEBUG
277     SkASSERT(reducedClip.hasIBounds());
278     SkIRect rtIBounds = SkIRect::MakeWH(renderTargetContext->width(),
279                                         renderTargetContext->height());
280     const SkIRect& clipIBounds = reducedClip.ibounds();
281     SkASSERT(rtIBounds.contains(clipIBounds)); // Mask shouldn't be larger than the RT.
282 #endif
283 
284     bool avoidStencilBuffers = context->caps()->avoidStencilBuffers();
285 
286     // An element count of 4 was chosen because of the common pattern in Blink of:
287     //   isect RR
288     //   diff  RR
289     //   isect convex_poly
290     //   isect convex_poly
291     // when drawing rounded div borders. This could probably be tuned based on a
292     // configuration's relative costs of switching RTs to generate a mask vs
293     // longer shaders.
294     if (reducedClip.elements().count() <= kMaxAnalyticElements) {
295         // When there are multiple samples we want to do per-sample clipping, not compute a
296         // fractional pixel coverage.
297         bool disallowAnalyticAA =
298                 GrFSAAType::kNone != renderTargetContext->fsaaType() && !avoidStencilBuffers;
299         if (disallowAnalyticAA && !renderTargetContext->numColorSamples()) {
300             // With a single color sample, any coverage info is lost from color once it hits the
301             // color buffer anyway, so we may as well use coverage AA if nothing else in the pipe
302             // is multisampled.
303             disallowAnalyticAA = useHWAA || hasUserStencilSettings;
304         }
305         sk_sp<GrFragmentProcessor> clipFP;
306         if ((reducedClip.requiresAA() || avoidStencilBuffers) &&
307             get_analytic_clip_processor(reducedClip.elements(), disallowAnalyticAA, devBounds,
308                                         &clipFP)) {
309             out->addCoverageFP(std::move(clipFP));
310             return true;
311         }
312     }
313 
314     // If the stencil buffer is multisampled we can use it to do everything.
315     if ((GrFSAAType::kNone == renderTargetContext->fsaaType() && reducedClip.requiresAA()) ||
316         avoidStencilBuffers) {
317         sk_sp<GrTextureProxy> result;
318         if (UseSWOnlyPath(context, hasUserStencilSettings, renderTargetContext, reducedClip)) {
319             // The clip geometry is complex enough that it will be more efficient to create it
320             // entirely in software
321             result = this->createSoftwareClipMask(context, reducedClip);
322         } else {
323             result = this->createAlphaClipMask(context, reducedClip);
324         }
325 
326         if (result) {
327             // The mask's top left coord should be pinned to the rounded-out top left corner of
328             // the clip's device space bounds.
329             out->addCoverageFP(create_fp_for_mask(std::move(result), reducedClip.ibounds()));
330             return true;
331         }
332 
333         // If alpha or software clip mask creation fails, fall through to the stencil code paths,
334         // unless stencils are disallowed.
335         if (context->caps()->avoidStencilBuffers()) {
336             SkDebugf("WARNING: Clip mask requires stencil, but stencil unavailable. Clip will be ignored.\n");
337             return false;
338         }
339     }
340 
341     GrRenderTarget* rt = renderTargetContext->accessRenderTarget();
342     if (!rt) {
343         return true;
344     }
345 
346     // use the stencil clip if we can't represent the clip as a rectangle.
347     if (!context->resourceProvider()->attachStencilAttachment(rt)) {
348         SkDebugf("WARNING: failed to attach stencil buffer for clip mask. Clip will be ignored.\n");
349         return true;
350     }
351 
352     // This relies on the property that a reduced sub-rect of the last clip will contain all the
353     // relevant window rectangles that were in the last clip. This subtle requirement will go away
354     // after clipping is overhauled.
355     if (renderTargetContext->priv().mustRenderClip(reducedClip.elementsGenID(),
356                                                    reducedClip.ibounds())) {
357         reducedClip.drawStencilClipMask(context, renderTargetContext);
358         renderTargetContext->priv().setLastClip(reducedClip.elementsGenID(), reducedClip.ibounds());
359     }
360     out->addStencilClip(reducedClip.elementsGenID());
361     return true;
362 }
363 
364 ////////////////////////////////////////////////////////////////////////////////
365 // Create a 8-bit clip mask in alpha
366 
create_clip_mask_key(uint32_t clipGenID,const SkIRect & bounds,GrUniqueKey * key)367 static void create_clip_mask_key(uint32_t clipGenID, const SkIRect& bounds, GrUniqueKey* key) {
368     static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
369     GrUniqueKey::Builder builder(key, kDomain, 3, GrClipStackClip::kMaskTestTag);
370     builder[0] = clipGenID;
371     // SkToS16 because image filters outset layers to a size indicated by the filter, which can
372     // sometimes result in negative coordinates from device space.
373     builder[1] = SkToS16(bounds.fLeft) | (SkToS16(bounds.fRight) << 16);
374     builder[2] = SkToS16(bounds.fTop) | (SkToS16(bounds.fBottom) << 16);
375 }
376 
add_invalidate_on_pop_message(const SkClipStack & stack,uint32_t clipGenID,const GrUniqueKey & clipMaskKey)377 static void add_invalidate_on_pop_message(const SkClipStack& stack, uint32_t clipGenID,
378                                           const GrUniqueKey& clipMaskKey) {
379     SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
380     while (const Element* element = iter.prev()) {
381         if (element->getGenID() == clipGenID) {
382             std::unique_ptr<GrUniqueKeyInvalidatedMessage> msg(
383                     new GrUniqueKeyInvalidatedMessage(clipMaskKey));
384             element->addResourceInvalidationMessage(std::move(msg));
385             return;
386         }
387     }
388     SkDEBUGFAIL("Gen ID was not found in stack.");
389 }
390 
createAlphaClipMask(GrContext * context,const GrReducedClip & reducedClip) const391 sk_sp<GrTextureProxy> GrClipStackClip::createAlphaClipMask(GrContext* context,
392                                                            const GrReducedClip& reducedClip) const {
393     GrResourceProvider* resourceProvider = context->resourceProvider();
394     GrUniqueKey key;
395     create_clip_mask_key(reducedClip.elementsGenID(), reducedClip.ibounds(), &key);
396 
397     sk_sp<GrTextureProxy> proxy(resourceProvider->findProxyByUniqueKey(key));
398     if (proxy) {
399         return proxy;
400     }
401 
402     sk_sp<GrRenderTargetContext> rtc(context->makeDeferredRenderTargetContextWithFallback(
403                                                                              SkBackingFit::kApprox,
404                                                                              reducedClip.width(),
405                                                                              reducedClip.height(),
406                                                                              kAlpha_8_GrPixelConfig,
407                                                                              nullptr));
408     if (!rtc) {
409         return nullptr;
410     }
411 
412     if (!reducedClip.drawAlphaClipMask(rtc.get())) {
413         return nullptr;
414     }
415 
416     sk_sp<GrTextureProxy> result(rtc->asTextureProxyRef());
417     if (!result) {
418         return nullptr;
419     }
420 
421     resourceProvider->assignUniqueKeyToProxy(key, result.get());
422     // MDB TODO (caching): this has to play nice with the GrSurfaceProxy's caching
423     add_invalidate_on_pop_message(*fStack, reducedClip.elementsGenID(), key);
424 
425     return result;
426 }
427 
createSoftwareClipMask(GrContext * context,const GrReducedClip & reducedClip) const428 sk_sp<GrTextureProxy> GrClipStackClip::createSoftwareClipMask(
429                                                           GrContext* context,
430                                                           const GrReducedClip& reducedClip) const {
431     GrUniqueKey key;
432     create_clip_mask_key(reducedClip.elementsGenID(), reducedClip.ibounds(), &key);
433 
434     sk_sp<GrTextureProxy> proxy(context->resourceProvider()->findProxyByUniqueKey(key));
435     if (proxy) {
436         return proxy;
437     }
438 
439     // The mask texture may be larger than necessary. We round out the clip bounds and pin the top
440     // left corner of the resulting rect to the top left of the texture.
441     SkIRect maskSpaceIBounds = SkIRect::MakeWH(reducedClip.width(), reducedClip.height());
442 
443     GrSWMaskHelper helper;
444 
445     // Set the matrix so that rendered clip elements are transformed to mask space from clip
446     // space.
447     SkMatrix translate;
448     translate.setTranslate(SkIntToScalar(-reducedClip.left()), SkIntToScalar(-reducedClip.top()));
449 
450     if (!helper.init(maskSpaceIBounds, &translate)) {
451         return nullptr;
452     }
453     helper.clear(InitialState::kAllIn == reducedClip.initialState() ? 0xFF : 0x00);
454 
455     for (ElementList::Iter iter(reducedClip.elements()); iter.get(); iter.next()) {
456         const Element* element = iter.get();
457         SkClipOp op = element->getOp();
458         GrAA aa = GrBoolToAA(element->isAA());
459 
460         if (kIntersect_SkClipOp == op || kReverseDifference_SkClipOp == op) {
461             // Intersect and reverse difference require modifying pixels outside of the geometry
462             // that is being "drawn". In both cases we erase all the pixels outside of the geometry
463             // but leave the pixels inside the geometry alone. For reverse difference we invert all
464             // the pixels before clearing the ones outside the geometry.
465             if (kReverseDifference_SkClipOp == op) {
466                 SkRect temp = SkRect::Make(reducedClip.ibounds());
467                 // invert the entire scene
468                 helper.drawRect(temp, SkRegion::kXOR_Op, GrAA::kNo, 0xFF);
469             }
470             SkPath clipPath;
471             element->asPath(&clipPath);
472             clipPath.toggleInverseFillType();
473             GrShape shape(clipPath, GrStyle::SimpleFill());
474             helper.drawShape(shape, SkRegion::kReplace_Op, aa, 0x00);
475             continue;
476         }
477 
478         // The other ops (union, xor, diff) only affect pixels inside
479         // the geometry so they can just be drawn normally
480         if (Element::kRect_Type == element->getType()) {
481             helper.drawRect(element->getRect(), (SkRegion::Op)op, aa, 0xFF);
482         } else {
483             SkPath path;
484             element->asPath(&path);
485             GrShape shape(path, GrStyle::SimpleFill());
486             helper.drawShape(shape, (SkRegion::Op)op, aa, 0xFF);
487         }
488     }
489 
490     sk_sp<GrTextureProxy> result(helper.toTextureProxy(context, SkBackingFit::kApprox));
491 
492     context->resourceProvider()->assignUniqueKeyToProxy(key, result.get());
493     // MDB TODO (caching): this has to play nice with the GrSurfaceProxy's caching
494     add_invalidate_on_pop_message(*fStack, reducedClip.elementsGenID(), key);
495     return result;
496 }
497