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