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 "src/gpu/GrClipStackClip.h"
9
10#include "include/private/SkTo.h"
11#include "src/core/SkClipOpPriv.h"
12#include "src/core/SkTaskGroup.h"
13#include "src/core/SkTraceEvent.h"
14#include "src/gpu/GrAppliedClip.h"
15#include "src/gpu/GrContextPriv.h"
16#include "src/gpu/GrDeferredProxyUploader.h"
17#include "src/gpu/GrDrawingManager.h"
18#include "src/gpu/GrFixedClip.h"
19#include "src/gpu/GrGpuResourcePriv.h"
20#include "src/gpu/GrProxyProvider.h"
21#include "src/gpu/GrRecordingContextPriv.h"
22#include "src/gpu/GrRenderTargetContextPriv.h"
23#include "src/gpu/GrSWMaskHelper.h"
24#include "src/gpu/GrStencilAttachment.h"
25#include "src/gpu/GrStyle.h"
26#include "src/gpu/GrTextureProxy.h"
27#include "src/gpu/effects/GrConvexPolyEffect.h"
28#include "src/gpu/effects/GrRRectEffect.h"
29#include "src/gpu/effects/GrTextureDomain.h"
30#include "src/gpu/effects/generated/GrDeviceSpaceEffect.h"
31#include "src/gpu/geometry/GrShape.h"
32
33typedef SkClipStack::Element Element;
34typedef GrReducedClip::InitialState InitialState;
35typedef GrReducedClip::ElementList ElementList;
36
37const char GrClipStackClip::kMaskTestTag[] = "clip_mask";
38
39bool GrClipStackClip::quickContains(const SkRect& rect) const {
40 if (!fStack || fStack->isWideOpen()) {
41 return true;
42 }
43 return fStack->quickContains(rect);
44}
45
46bool GrClipStackClip::quickContains(const SkRRect& rrect) const {
47 if (!fStack || fStack->isWideOpen()) {
48 return true;
49 }
50 return fStack->quickContains(rrect);
51}
52
53bool GrClipStackClip::isRRect(const SkRect& origRTBounds, SkRRect* rr, GrAA* aa) const {
54 if (!fStack) {
55 return false;
56 }
57 const SkRect* rtBounds = &origRTBounds;
58 bool isAA;
59 if (fStack->isRRect(*rtBounds, rr, &isAA)) {
60 *aa = GrAA(isAA);
61 return true;
62 }
63 return false;
64}
65
66void GrClipStackClip::getConservativeBounds(int width, int height, SkIRect* devResult,
67 bool* isIntersectionOfRects) const {
68 if (!fStack) {
69 devResult->setXYWH(0, 0, width, height);
70 if (isIntersectionOfRects) {
71 *isIntersectionOfRects = true;
72 }
73 return;
74 }
75 SkRect devBounds;
76 fStack->getConservativeBounds(0, 0, width, height, &devBounds, isIntersectionOfRects);
77 devBounds.roundOut(devResult);
78}
79
80////////////////////////////////////////////////////////////////////////////////
81// set up the draw state to enable the aa clipping mask.
82static std::unique_ptr<GrFragmentProcessor> create_fp_for_mask(GrSurfaceProxyView mask,
83 const SkIRect& devBound,
84 const GrCaps& caps) {
85 GrSamplerState samplerState(GrSamplerState::WrapMode::kClampToBorder,
86 GrSamplerState::Filter::kNearest);
87 auto m = SkMatrix::MakeTrans(-devBound.fLeft, -devBound.fTop);
88 auto subset = SkRect::Make(devBound.size());
89 // We scissor to devBounds. The mask's texel centers are aligned to device space
90 // pixel centers. Hence this domain of texture coordinates.
91 auto domain = subset.makeInset(0.5, 0.5);
92 auto fp = GrTextureEffect::MakeSubset(std::move(mask), kPremul_SkAlphaType, m, samplerState,
93 subset, domain, caps);
94 return GrDeviceSpaceEffect::Make(std::move(fp));
95}
96
97// Does the path in 'element' require SW rendering? If so, return true (and,
98// optionally, set 'prOut' to NULL. If not, return false (and, optionally, set
99// 'prOut' to the non-SW path renderer that will do the job).
100bool GrClipStackClip::PathNeedsSWRenderer(GrRecordingContext* context,
101 const SkIRect& scissorRect,
102 bool hasUserStencilSettings,
103 const GrRenderTargetContext* renderTargetContext,
104 const SkMatrix& viewMatrix,
105 const Element* element,
106 GrPathRenderer** prOut,
107 bool needsStencil) {
108 if (Element::DeviceSpaceType::kRect == element->getDeviceSpaceType()) {
109 // rects can always be drawn directly w/o using the software path
110 // TODO: skip rrects once we're drawing them directly.
111 if (prOut) {
112 *prOut = nullptr;
113 }
114 return false;
115 } else {
116 // We shouldn't get here with an empty clip element.
117 SkASSERT(Element::DeviceSpaceType::kEmpty != element->getDeviceSpaceType());
118
119 // the gpu alpha mask will draw the inverse paths as non-inverse to a temp buffer
120 SkPath path;
121 element->asDeviceSpacePath(&path);
122 if (path.isInverseFillType()) {
123 path.toggleInverseFillType();
124 }
125
126 // We only use this method when rendering coverage clip masks.
127 SkASSERT(renderTargetContext->numSamples() <= 1);
128 auto aaType = (element->isAA()) ? GrAAType::kCoverage : GrAAType::kNone;
129
130 GrPathRendererChain::DrawType type =
131 needsStencil ? GrPathRendererChain::DrawType::kStencilAndColor
132 : GrPathRendererChain::DrawType::kColor;
133
134 GrShape shape(path, GrStyle::SimpleFill());
135 GrPathRenderer::CanDrawPathArgs canDrawArgs;
136 canDrawArgs.fCaps = context->priv().caps();
137 canDrawArgs.fProxy = renderTargetContext->asRenderTargetProxy();
138 canDrawArgs.fClipConservativeBounds = &scissorRect;
139 canDrawArgs.fViewMatrix = &viewMatrix;
140 canDrawArgs.fShape = &shape;
141 canDrawArgs.fAAType = aaType;
142 SkASSERT(!renderTargetContext->wrapsVkSecondaryCB());
143 canDrawArgs.fTargetIsWrappedVkSecondaryCB = false;
144 canDrawArgs.fHasUserStencilSettings = hasUserStencilSettings;
145
146 // the 'false' parameter disallows use of the SW path renderer
147 GrPathRenderer* pr =
148 context->priv().drawingManager()->getPathRenderer(canDrawArgs, false, type);
149 if (prOut) {
150 *prOut = pr;
151 }
152 return SkToBool(!pr);
153 }
154}
155
156/*
157 * This method traverses the clip stack to see if the GrSoftwarePathRenderer
158 * will be used on any element. If so, it returns true to indicate that the
159 * entire clip should be rendered in SW and then uploaded en masse to the gpu.
160 */
161bool GrClipStackClip::UseSWOnlyPath(GrRecordingContext* context,
162 bool hasUserStencilSettings,
163 const GrRenderTargetContext* renderTargetContext,
164 const GrReducedClip& reducedClip) {
165 // TODO: right now it appears that GPU clip masks are strictly slower than software. We may
166 // want to revisit this assumption once we can test with render target sorting.
167 return true;
168
169 // TODO: generalize this function so that when
170 // a clip gets complex enough it can just be done in SW regardless
171 // of whether it would invoke the GrSoftwarePathRenderer.
172
173 // If we're avoiding stencils, always use SW. This includes drawing into a wrapped vulkan
174 // secondary command buffer which can't handle stencils.
175 if (context->priv().caps()->avoidStencilBuffers() ||
176 renderTargetContext->wrapsVkSecondaryCB()) {
177 return true;
178 }
179
180 // Set the matrix so that rendered clip elements are transformed to mask space from clip
181 // space.
182 SkMatrix translate;
183 translate.setTranslate(SkIntToScalar(-reducedClip.left()), SkIntToScalar(-reducedClip.top()));
184
185 for (ElementList::Iter iter(reducedClip.maskElements()); iter.get(); iter.next()) {
186 const Element* element = iter.get();
187
188 SkClipOp op = element->getOp();
189 bool invert = element->isInverseFilled();
190 bool needsStencil = invert ||
191 kIntersect_SkClipOp == op || kReverseDifference_SkClipOp == op;
192
193 if (PathNeedsSWRenderer(context, reducedClip.scissor(), hasUserStencilSettings,
194 renderTargetContext, translate, element, nullptr, needsStencil)) {
195 return true;
196 }
197 }
198 return false;
199}
200
201////////////////////////////////////////////////////////////////////////////////
202// sort out what kind of clip mask needs to be created: alpha, stencil,
203// scissor, or entirely software
204bool GrClipStackClip::apply(GrRecordingContext* context, GrRenderTargetContext* renderTargetContext,
205 bool useHWAA, bool hasUserStencilSettings, GrAppliedClip* out,
206 SkRect* bounds) const {
207 SkRect devBounds = SkRect::MakeIWH(renderTargetContext->width(), renderTargetContext->height());
208 if (!devBounds.intersect(*bounds)) {
209 return false;
210 }
211
212 if (!fStack || fStack->isWideOpen()) {
213 return true;
214 }
215
216 // An default count of 4 was chosen because of the common pattern in Blink of:
217 // isect RR
218 // diff RR
219 // isect convex_poly
220 // isect convex_poly
221 // when drawing rounded div borders.
222 constexpr int kMaxAnalyticFPs = 4;
223
224 int maxWindowRectangles = renderTargetContext->priv().maxWindowRectangles();
225 int maxAnalyticFPs = kMaxAnalyticFPs;
226 if (renderTargetContext->numSamples() > 1 || useHWAA || hasUserStencilSettings) {
227 // Disable analytic clips when we have MSAA. In MSAA we never conflate coverage and opacity.
228 maxAnalyticFPs = 0;
229 // We disable MSAA when avoiding stencil.
230 SkASSERT(!context->priv().caps()->avoidStencilBuffers());
231 }
232 auto* ccpr = context->priv().drawingManager()->getCoverageCountingPathRenderer();
233
234 GrReducedClip reducedClip(*fStack, devBounds, context->priv().caps(),
235 maxWindowRectangles, maxAnalyticFPs, ccpr ? maxAnalyticFPs : 0);
236 if (InitialState::kAllOut == reducedClip.initialState() &&
237 reducedClip.maskElements().isEmpty()) {
238 return false;
239 }
240
241 if (reducedClip.hasScissor() && !GrClip::IsInsideClip(reducedClip.scissor(), devBounds)) {
242 out->hardClip().addScissor(reducedClip.scissor(), bounds);
243 }
244
245 if (!reducedClip.windowRectangles().empty()) {
246 out->hardClip().addWindowRectangles(reducedClip.windowRectangles(),
247 GrWindowRectsState::Mode::kExclusive);
248 }
249
250 if (!reducedClip.maskElements().isEmpty()) {
251 if (!this->applyClipMask(context, renderTargetContext, reducedClip, hasUserStencilSettings,
252 out)) {
253 return false;
254 }
255 }
256
257 // The opsTask ID must not be looked up until AFTER producing the clip mask (if any). That step
258 // can cause a flush or otherwise change which opstask our draw is going into.
259 uint32_t opsTaskID = renderTargetContext->getOpsTask()->uniqueID();
260 if (auto clipFPs = reducedClip.finishAndDetachAnalyticFPs(ccpr, opsTaskID)) {
261 out->addCoverageFP(std::move(clipFPs));
262 }
263
264 return true;
265}
266
267bool GrClipStackClip::applyClipMask(GrRecordingContext* context,
268 GrRenderTargetContext* renderTargetContext,
269 const GrReducedClip& reducedClip, bool hasUserStencilSettings,
270 GrAppliedClip* out) const {
271#ifdef SK_DEBUG
272 SkASSERT(reducedClip.hasScissor());
273 SkIRect rtIBounds = SkIRect::MakeWH(renderTargetContext->width(),
274 renderTargetContext->height());
275 const SkIRect& scissor = reducedClip.scissor();
276 SkASSERT(rtIBounds.contains(scissor)); // Mask shouldn't be larger than the RT.
277#endif
278
279 // MIXED SAMPLES TODO: We may want to explore using the stencil buffer for AA clipping.
280 if ((renderTargetContext->numSamples() <= 1 && reducedClip.maskRequiresAA()) ||
281 context->priv().caps()->avoidStencilBuffers() ||
282 renderTargetContext->wrapsVkSecondaryCB()) {
283 GrSurfaceProxyView result;
284 if (UseSWOnlyPath(context, hasUserStencilSettings, renderTargetContext, reducedClip)) {
285 // The clip geometry is complex enough that it will be more efficient to create it
286 // entirely in software
287 result = this->createSoftwareClipMask(context, reducedClip, renderTargetContext);
288 } else {
289 result = this->createAlphaClipMask(context, reducedClip);
290 }
291
292 if (result) {
293 // The mask's top left coord should be pinned to the rounded-out top left corner of
294 // the clip's device space bounds.
295 out->addCoverageFP(create_fp_for_mask(std::move(result), reducedClip.scissor(),
296 *context->priv().caps()));
297 return true;
298 }
299
300 // If alpha or software clip mask creation fails, fall through to the stencil code paths,
301 // unless stencils are disallowed.
302 if (context->priv().caps()->avoidStencilBuffers() ||
303 renderTargetContext->wrapsVkSecondaryCB()) {
304 SkDebugf("WARNING: Clip mask requires stencil, but stencil unavailable. "
305 "Clip will be ignored.\n");
306 return false;
307 }
308 }
309
310 // This relies on the property that a reduced sub-rect of the last clip will contain all the
311 // relevant window rectangles that were in the last clip. This subtle requirement will go away
312 // after clipping is overhauled.
313 if (renderTargetContext->priv().mustRenderClip(reducedClip.maskGenID(), reducedClip.scissor(),
314 reducedClip.numAnalyticFPs())) {
315 reducedClip.drawStencilClipMask(context, renderTargetContext);
316 renderTargetContext->priv().setLastClip(reducedClip.maskGenID(), reducedClip.scissor(),
317 reducedClip.numAnalyticFPs());
318 }
319 // GrAppliedClip doesn't need to figure numAnalyticFPs into its key (used by operator==) because
320 // it verifies the FPs are also equal.
321 out->hardClip().addStencilClip(reducedClip.maskGenID());
322 return true;
323}
324
325////////////////////////////////////////////////////////////////////////////////
326// Create a 8-bit clip mask in alpha
327
328static void create_clip_mask_key(uint32_t clipGenID, const SkIRect& bounds, int numAnalyticFPs,
329 GrUniqueKey* key) {
330 static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
331 GrUniqueKey::Builder builder(key, kDomain, 4, GrClipStackClip::kMaskTestTag);
332 builder[0] = clipGenID;
333 // SkToS16 because image filters outset layers to a size indicated by the filter, which can
334 // sometimes result in negative coordinates from device space.
335 builder[1] = SkToS16(bounds.fLeft) | (SkToS16(bounds.fRight) << 16);
336 builder[2] = SkToS16(bounds.fTop) | (SkToS16(bounds.fBottom) << 16);
337 builder[3] = numAnalyticFPs;
338}
339
340static void add_invalidate_on_pop_message(GrRecordingContext* context,
341 const SkClipStack& stack, uint32_t clipGenID,
342 const GrUniqueKey& clipMaskKey) {
343 GrProxyProvider* proxyProvider = context->priv().proxyProvider();
344
345 SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
346 while (const Element* element = iter.prev()) {
347 if (element->getGenID() == clipGenID) {
348 element->addResourceInvalidationMessage(proxyProvider, clipMaskKey);
349 return;
350 }
351 }
352 SkDEBUGFAIL("Gen ID was not found in stack.");
353}
354
355static constexpr auto kMaskOrigin = kTopLeft_GrSurfaceOrigin;
356
357static GrSurfaceProxyView find_mask(GrProxyProvider* provider, const GrUniqueKey& key) {
358 return provider->findCachedProxyWithColorTypeFallback(key, kMaskOrigin, GrColorType::kAlpha_8,
359 1);
360}
361
362GrSurfaceProxyView GrClipStackClip::createAlphaClipMask(GrRecordingContext* context,
363 const GrReducedClip& reducedClip) const {
364 GrProxyProvider* proxyProvider = context->priv().proxyProvider();
365 GrUniqueKey key;
366 create_clip_mask_key(reducedClip.maskGenID(), reducedClip.scissor(),
367 reducedClip.numAnalyticFPs(), &key);
368
369 if (auto cachedView = find_mask(context->priv().proxyProvider(), key)) {
370 return cachedView;
371 }
372
373 auto rtc = GrRenderTargetContext::MakeWithFallback(
374 context, GrColorType::kAlpha_8, nullptr, SkBackingFit::kApprox,
375 {reducedClip.width(), reducedClip.height()}, 1, GrMipMapped::kNo, GrProtected::kNo,
376 kMaskOrigin);
377 if (!rtc) {
378 return {};
379 }
380
381 if (!reducedClip.drawAlphaClipMask(rtc.get())) {
382 return {};
383 }
384
385 GrSurfaceProxyView result = rtc->readSurfaceView();
386 if (!result || !result.asTextureProxy()) {
387 return {};
388 }
389
390 SkASSERT(result.origin() == kMaskOrigin);
391 proxyProvider->assignUniqueKeyToProxy(key, result.asTextureProxy());
392 add_invalidate_on_pop_message(context, *fStack, reducedClip.maskGenID(), key);
393
394 return result;
395}
396
397namespace {
398
399/**
400 * Payload class for use with GrTDeferredProxyUploader. The clip mask code renders multiple
401 * elements, each storing their own AA setting (and already transformed into device space). This
402 * stores all of the information needed by the worker thread to draw all clip elements (see below,
403 * in createSoftwareClipMask).
404 */
405class ClipMaskData {
406public:
407 ClipMaskData(const GrReducedClip& reducedClip)
408 : fScissor(reducedClip.scissor())
409 , fInitialState(reducedClip.initialState()) {
410 for (ElementList::Iter iter(reducedClip.maskElements()); iter.get(); iter.next()) {
411 fElements.addToTail(*iter.get());
412 }
413 }
414
415 const SkIRect& scissor() const { return fScissor; }
416 InitialState initialState() const { return fInitialState; }
417 const ElementList& elements() const { return fElements; }
418
419private:
420 SkIRect fScissor;
421 InitialState fInitialState;
422 ElementList fElements;
423};
424
425}
426
427static void draw_clip_elements_to_mask_helper(GrSWMaskHelper& helper, const ElementList& elements,
428 const SkIRect& scissor, InitialState initialState) {
429 // Set the matrix so that rendered clip elements are transformed to mask space from clip space.
430 SkMatrix translate;
431 translate.setTranslate(SkIntToScalar(-scissor.left()), SkIntToScalar(-scissor.top()));
432
433 helper.clear(InitialState::kAllIn == initialState ? 0xFF : 0x00);
434
435 for (ElementList::Iter iter(elements); iter.get(); iter.next()) {
436 const Element* element = iter.get();
437 SkClipOp op = element->getOp();
438 GrAA aa = GrAA(element->isAA());
439
440 if (kIntersect_SkClipOp == op || kReverseDifference_SkClipOp == op) {
441 // Intersect and reverse difference require modifying pixels outside of the geometry
442 // that is being "drawn". In both cases we erase all the pixels outside of the geometry
443 // but leave the pixels inside the geometry alone. For reverse difference we invert all
444 // the pixels before clearing the ones outside the geometry.
445 if (kReverseDifference_SkClipOp == op) {
446 SkRect temp = SkRect::Make(scissor);
447 // invert the entire scene
448 helper.drawRect(temp, translate, SkRegion::kXOR_Op, GrAA::kNo, 0xFF);
449 }
450 SkPath clipPath;
451 element->asDeviceSpacePath(&clipPath);
452 clipPath.toggleInverseFillType();
453 GrShape shape(clipPath, GrStyle::SimpleFill());
454 helper.drawShape(shape, translate, SkRegion::kReplace_Op, aa, 0x00);
455 continue;
456 }
457
458 // The other ops (union, xor, diff) only affect pixels inside
459 // the geometry so they can just be drawn normally
460 if (Element::DeviceSpaceType::kRect == element->getDeviceSpaceType()) {
461 helper.drawRect(element->getDeviceSpaceRect(), translate, (SkRegion::Op)op, aa, 0xFF);
462 } else {
463 SkPath path;
464 element->asDeviceSpacePath(&path);
465 GrShape shape(path, GrStyle::SimpleFill());
466 helper.drawShape(shape, translate, (SkRegion::Op)op, aa, 0xFF);
467 }
468 }
469}
470
471GrSurfaceProxyView GrClipStackClip::createSoftwareClipMask(
472 GrRecordingContext* context, const GrReducedClip& reducedClip,
473 GrRenderTargetContext* renderTargetContext) const {
474 GrUniqueKey key;
475 create_clip_mask_key(reducedClip.maskGenID(), reducedClip.scissor(),
476 reducedClip.numAnalyticFPs(), &key);
477
478 GrProxyProvider* proxyProvider = context->priv().proxyProvider();
479
480 if (auto cachedView = find_mask(proxyProvider, key)) {
481 return cachedView;
482 }
483
484 // The mask texture may be larger than necessary. We round out the clip bounds and pin the top
485 // left corner of the resulting rect to the top left of the texture.
486 SkIRect maskSpaceIBounds = SkIRect::MakeWH(reducedClip.width(), reducedClip.height());
487
488 SkTaskGroup* taskGroup = nullptr;
489 if (auto direct = context->priv().asDirectContext()) {
490 taskGroup = direct->priv().getTaskGroup();
491 }
492
493 GrSurfaceProxyView view;
494 if (taskGroup && renderTargetContext) {
495 const GrCaps* caps = context->priv().caps();
496 // Create our texture proxy
497 GrBackendFormat format = caps->getDefaultBackendFormat(GrColorType::kAlpha_8,
498 GrRenderable::kNo);
499
500 GrSwizzle swizzle = context->priv().caps()->getReadSwizzle(format, GrColorType::kAlpha_8);
501
502 // MDB TODO: We're going to fill this proxy with an ASAP upload (which is out of order wrt
503 // to ops), so it can't have any pending IO.
504 auto proxy = proxyProvider->createProxy(format,
505 maskSpaceIBounds.size(),
506 GrRenderable::kNo,
507 1,
508 GrMipMapped::kNo,
509 SkBackingFit::kApprox,
510 SkBudgeted::kYes,
511 GrProtected::kNo);
512
513 auto uploader = std::make_unique<GrTDeferredProxyUploader<ClipMaskData>>(reducedClip);
514 GrTDeferredProxyUploader<ClipMaskData>* uploaderRaw = uploader.get();
515 auto drawAndUploadMask = [uploaderRaw, maskSpaceIBounds] {
516 TRACE_EVENT0("skia.gpu", "Threaded SW Clip Mask Render");
517 GrSWMaskHelper helper(uploaderRaw->getPixels());
518 if (helper.init(maskSpaceIBounds)) {
519 draw_clip_elements_to_mask_helper(helper, uploaderRaw->data().elements(),
520 uploaderRaw->data().scissor(),
521 uploaderRaw->data().initialState());
522 } else {
523 SkDEBUGFAIL("Unable to allocate SW clip mask.");
524 }
525 uploaderRaw->signalAndFreeData();
526 };
527
528 taskGroup->add(std::move(drawAndUploadMask));
529 proxy->texPriv().setDeferredUploader(std::move(uploader));
530
531 view = {std::move(proxy), kMaskOrigin, swizzle};
532 } else {
533 GrSWMaskHelper helper;
534 if (!helper.init(maskSpaceIBounds)) {
535 return {};
536 }
537
538 draw_clip_elements_to_mask_helper(helper, reducedClip.maskElements(), reducedClip.scissor(),
539 reducedClip.initialState());
540
541 view = helper.toTextureView(context, SkBackingFit::kApprox);
542 }
543
544 SkASSERT(view);
545 SkASSERT(view.origin() == kMaskOrigin);
546 proxyProvider->assignUniqueKeyToProxy(key, view.asTextureProxy());
547 add_invalidate_on_pop_message(context, *fStack, reducedClip.maskGenID(), key);
548 return view;
549}
550