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