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