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