Line data Source code
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 :
32 0 : bool GrClipStackClip::quickContains(const SkRect& rect) const {
33 0 : if (!fStack || fStack->isWideOpen()) {
34 0 : return true;
35 : }
36 0 : return fStack->quickContains(rect);
37 : }
38 :
39 0 : bool GrClipStackClip::quickContains(const SkRRect& rrect) const {
40 0 : if (!fStack || fStack->isWideOpen()) {
41 0 : return true;
42 : }
43 0 : return fStack->quickContains(rrect);
44 : }
45 :
46 0 : bool GrClipStackClip::isRRect(const SkRect& origRTBounds, SkRRect* rr, GrAA* aa) const {
47 0 : if (!fStack) {
48 0 : return false;
49 : }
50 0 : const SkRect* rtBounds = &origRTBounds;
51 : bool isAA;
52 0 : if (fStack->isRRect(*rtBounds, rr, &isAA)) {
53 0 : *aa = GrBoolToAA(isAA);
54 0 : return true;
55 : }
56 0 : return false;
57 : }
58 :
59 0 : void GrClipStackClip::getConservativeBounds(int width, int height, SkIRect* devResult,
60 : bool* isIntersectionOfRects) const {
61 0 : if (!fStack) {
62 0 : devResult->setXYWH(0, 0, width, height);
63 0 : if (isIntersectionOfRects) {
64 0 : *isIntersectionOfRects = true;
65 : }
66 0 : return;
67 : }
68 : SkRect devBounds;
69 0 : fStack->getConservativeBounds(0, 0, width, height, &devBounds, isIntersectionOfRects);
70 0 : devBounds.roundOut(devResult);
71 : }
72 :
73 : ////////////////////////////////////////////////////////////////////////////////
74 : // set up the draw state to enable the aa clipping mask.
75 0 : static sk_sp<GrFragmentProcessor> create_fp_for_mask(GrResourceProvider* resourceProvider,
76 : sk_sp<GrTextureProxy> mask,
77 : const SkIRect &devBound) {
78 0 : SkIRect domainTexels = SkIRect::MakeWH(devBound.width(), devBound.height());
79 : return GrDeviceSpaceTextureDecalFragmentProcessor::Make(resourceProvider,
80 0 : std::move(mask), domainTexels,
81 0 : {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).
87 0 : 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 0 : 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 0 : if (prOut) {
98 0 : *prOut = nullptr;
99 : }
100 0 : return false;
101 : } else {
102 : // We shouldn't get here with an empty clip element.
103 0 : 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 0 : SkPath path;
107 0 : element->asPath(&path);
108 0 : if (path.isInverseFillType()) {
109 0 : path.toggleInverseFillType();
110 : }
111 :
112 : GrPathRendererChain::DrawType type =
113 0 : needsStencil ? GrPathRendererChain::DrawType::kStencilAndColor
114 0 : : GrPathRendererChain::DrawType::kColor;
115 :
116 0 : GrShape shape(path, GrStyle::SimpleFill());
117 : GrPathRenderer::CanDrawPathArgs canDrawArgs;
118 0 : canDrawArgs.fShaderCaps = context->caps()->shaderCaps();
119 0 : canDrawArgs.fViewMatrix = &viewMatrix;
120 0 : canDrawArgs.fShape = &shape;
121 0 : if (!element->isAA()) {
122 0 : canDrawArgs.fAAType = GrAAType::kNone;
123 0 : } else if (renderTargetContext->isUnifiedMultisampled()) {
124 0 : canDrawArgs.fAAType = GrAAType::kMSAA;
125 0 : } else if (renderTargetContext->isStencilBufferMultisampled()){
126 0 : canDrawArgs.fAAType = GrAAType::kMixedSamples;
127 : } else {
128 0 : canDrawArgs.fAAType = GrAAType::kCoverage;
129 : }
130 0 : canDrawArgs.fHasUserStencilSettings = hasUserStencilSettings;
131 :
132 : // the 'false' parameter disallows use of the SW path renderer
133 : GrPathRenderer* pr =
134 0 : context->contextPriv().drawingManager()->getPathRenderer(canDrawArgs, false, type);
135 0 : if (prOut) {
136 0 : *prOut = pr;
137 : }
138 0 : 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 : */
147 0 : 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 0 : translate.setTranslate(SkIntToScalar(-reducedClip.left()), SkIntToScalar(-reducedClip.top()));
159 :
160 0 : for (ElementList::Iter iter(reducedClip.elements()); iter.get(); iter.next()) {
161 0 : const Element* element = iter.get();
162 :
163 0 : SkClipOp op = element->getOp();
164 0 : bool invert = element->isInverseFilled();
165 0 : bool needsStencil = invert ||
166 0 : kIntersect_SkClipOp == op || kReverseDifference_SkClipOp == op;
167 :
168 0 : if (PathNeedsSWRenderer(context, hasUserStencilSettings,
169 : renderTargetContext, translate, element, nullptr, needsStencil)) {
170 0 : return true;
171 : }
172 : }
173 0 : return false;
174 : }
175 :
176 0 : static bool get_analytic_clip_processor(const ElementList& elements,
177 : bool abortIfAA,
178 : const SkRect& drawDevBounds,
179 : sk_sp<GrFragmentProcessor>* resultFP) {
180 0 : SkASSERT(elements.count() <= kMaxAnalyticElements);
181 0 : SkSTArray<kMaxAnalyticElements, sk_sp<GrFragmentProcessor>> fps;
182 0 : ElementList::Iter iter(elements);
183 0 : while (iter.get()) {
184 0 : SkClipOp op = iter.get()->getOp();
185 : bool invert;
186 0 : bool skip = false;
187 0 : switch (op) {
188 : case kReplace_SkClipOp:
189 0 : SkASSERT(iter.get() == elements.head());
190 : // Fallthrough, handled same as intersect.
191 : case kIntersect_SkClipOp:
192 0 : invert = false;
193 0 : if (iter.get()->contains(drawDevBounds)) {
194 0 : skip = true;
195 : }
196 0 : break;
197 : case kDifference_SkClipOp:
198 0 : 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 0 : break;
202 : default:
203 0 : return false;
204 : }
205 0 : if (!skip) {
206 : GrPrimitiveEdgeType edgeType;
207 0 : if (iter.get()->isAA()) {
208 0 : if (abortIfAA) {
209 0 : return false;
210 : }
211 0 : edgeType =
212 0 : invert ? kInverseFillAA_GrProcessorEdgeType : kFillAA_GrProcessorEdgeType;
213 : } else {
214 0 : edgeType =
215 0 : invert ? kInverseFillBW_GrProcessorEdgeType : kFillBW_GrProcessorEdgeType;
216 : }
217 :
218 0 : switch (iter.get()->getType()) {
219 : case SkClipStack::Element::kPath_Type:
220 0 : fps.emplace_back(GrConvexPolyEffect::Make(edgeType, iter.get()->getPath()));
221 0 : break;
222 : case SkClipStack::Element::kRRect_Type: {
223 0 : fps.emplace_back(GrRRectEffect::Make(edgeType, iter.get()->getRRect()));
224 0 : break;
225 : }
226 : case SkClipStack::Element::kRect_Type: {
227 0 : fps.emplace_back(GrConvexPolyEffect::Make(edgeType, iter.get()->getRect()));
228 0 : break;
229 : }
230 : default:
231 0 : break;
232 : }
233 0 : if (!fps.back()) {
234 0 : return false;
235 : }
236 : }
237 0 : iter.next();
238 : }
239 :
240 0 : *resultFP = nullptr;
241 0 : if (fps.count()) {
242 0 : *resultFP = GrFragmentProcessor::RunInSeries(fps.begin(), fps.count());
243 : }
244 0 : return true;
245 : }
246 :
247 : ////////////////////////////////////////////////////////////////////////////////
248 : // sort out what kind of clip mask needs to be created: alpha, stencil,
249 : // scissor, or entirely software
250 0 : bool GrClipStackClip::apply(GrContext* context, GrRenderTargetContext* renderTargetContext,
251 : bool useHWAA, bool hasUserStencilSettings, GrAppliedClip* out,
252 : SkRect* bounds) const {
253 0 : SkRect devBounds = SkRect::MakeIWH(renderTargetContext->width(), renderTargetContext->height());
254 0 : if (!devBounds.intersect(*bounds)) {
255 0 : return false;
256 : }
257 :
258 0 : if (!fStack || fStack->isWideOpen()) {
259 0 : return true;
260 : }
261 :
262 0 : const GrReducedClip reducedClip(*fStack, devBounds,
263 0 : renderTargetContext->priv().maxWindowRectangles());
264 :
265 0 : if (reducedClip.hasIBounds() && !GrClip::IsInsideClip(reducedClip.ibounds(), devBounds)) {
266 0 : out->addScissor(reducedClip.ibounds(), bounds);
267 : }
268 :
269 0 : if (!reducedClip.windowRectangles().empty()) {
270 0 : out->addWindowRectangles(reducedClip.windowRectangles(),
271 0 : GrWindowRectsState::Mode::kExclusive);
272 : }
273 :
274 0 : if (reducedClip.elements().isEmpty()) {
275 0 : return InitialState::kAllIn == reducedClip.initialState();
276 : }
277 :
278 : #ifdef SK_DEBUG
279 0 : SkASSERT(reducedClip.hasIBounds());
280 : SkIRect rtIBounds = SkIRect::MakeWH(renderTargetContext->width(),
281 0 : renderTargetContext->height());
282 0 : const SkIRect& clipIBounds = reducedClip.ibounds();
283 0 : 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 0 : 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 0 : bool disallowAnalyticAA = renderTargetContext->isStencilBufferMultisampled();
298 0 : 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 0 : disallowAnalyticAA = useHWAA || hasUserStencilSettings;
303 : }
304 0 : sk_sp<GrFragmentProcessor> clipFP;
305 0 : if (reducedClip.requiresAA() &&
306 0 : get_analytic_clip_processor(reducedClip.elements(), disallowAnalyticAA, devBounds,
307 : &clipFP)) {
308 0 : out->addCoverageFP(std::move(clipFP));
309 0 : return true;
310 : }
311 : }
312 :
313 : // If the stencil buffer is multisampled we can use it to do everything.
314 0 : if (!renderTargetContext->isStencilBufferMultisampled() && reducedClip.requiresAA()) {
315 0 : sk_sp<GrTextureProxy> result;
316 0 : 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 0 : result = this->createSoftwareClipMask(context, reducedClip);
320 : } else {
321 0 : result = this->createAlphaClipMask(context, reducedClip);
322 : }
323 :
324 0 : 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 0 : out->addCoverageFP(create_fp_for_mask(context->resourceProvider(), std::move(result),
328 0 : reducedClip.ibounds()));
329 0 : return true;
330 : }
331 : // if alpha clip mask creation fails fall through to the non-AA code paths
332 : }
333 :
334 0 : GrRenderTarget* rt = renderTargetContext->accessRenderTarget();
335 0 : if (!rt) {
336 0 : return true;
337 : }
338 :
339 : // use the stencil clip if we can't represent the clip as a rectangle.
340 0 : if (!context->resourceProvider()->attachStencilAttachment(rt)) {
341 0 : SkDebugf("WARNING: failed to attach stencil buffer for clip mask. Clip will be ignored.\n");
342 0 : 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 0 : if (renderTargetContext->priv().mustRenderClip(reducedClip.elementsGenID(),
349 : reducedClip.ibounds())) {
350 0 : reducedClip.drawStencilClipMask(context, renderTargetContext);
351 0 : renderTargetContext->priv().setLastClip(reducedClip.elementsGenID(), reducedClip.ibounds());
352 : }
353 0 : out->addStencilClip();
354 0 : return true;
355 : }
356 :
357 : ////////////////////////////////////////////////////////////////////////////////
358 : // Create a 8-bit clip mask in alpha
359 :
360 0 : static void create_clip_mask_key(int32_t clipGenID, const SkIRect& bounds, GrUniqueKey* key) {
361 0 : static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
362 0 : GrUniqueKey::Builder builder(key, kDomain, 3, GrClipStackClip::kMaskTestTag);
363 0 : 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 0 : builder[1] = SkToS16(bounds.fLeft) | (SkToS16(bounds.fRight) << 16);
367 0 : builder[2] = SkToS16(bounds.fTop) | (SkToS16(bounds.fBottom) << 16);
368 0 : }
369 :
370 0 : static void add_invalidate_on_pop_message(const SkClipStack& stack, int32_t clipGenID,
371 : const GrUniqueKey& clipMaskKey) {
372 0 : SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
373 0 : while (const Element* element = iter.prev()) {
374 0 : if (element->getGenID() == clipGenID) {
375 : std::unique_ptr<GrUniqueKeyInvalidatedMessage> msg(
376 0 : new GrUniqueKeyInvalidatedMessage(clipMaskKey));
377 0 : element->addResourceInvalidationMessage(std::move(msg));
378 0 : return;
379 : }
380 0 : }
381 0 : SkDEBUGFAIL("Gen ID was not found in stack.");
382 : }
383 :
384 0 : sk_sp<GrTextureProxy> GrClipStackClip::createAlphaClipMask(GrContext* context,
385 : const GrReducedClip& reducedClip) const {
386 0 : GrResourceProvider* resourceProvider = context->resourceProvider();
387 0 : GrUniqueKey key;
388 0 : create_clip_mask_key(reducedClip.elementsGenID(), reducedClip.ibounds(), &key);
389 :
390 0 : sk_sp<GrTextureProxy> proxy(resourceProvider->findProxyByUniqueKey(key));
391 0 : if (proxy) {
392 0 : 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 0 : nullptr));
401 0 : if (!rtc) {
402 0 : return nullptr;
403 : }
404 :
405 0 : if (!reducedClip.drawAlphaClipMask(rtc.get())) {
406 0 : return nullptr;
407 : }
408 :
409 0 : sk_sp<GrTextureProxy> result(rtc->asTextureProxyRef());
410 0 : if (!result) {
411 0 : return nullptr;
412 : }
413 :
414 0 : resourceProvider->assignUniqueKeyToProxy(key, result.get());
415 : // MDB TODO (caching): this has to play nice with the GrSurfaceProxy's caching
416 0 : add_invalidate_on_pop_message(*fStack, reducedClip.elementsGenID(), key);
417 :
418 0 : return result;
419 : }
420 :
421 0 : sk_sp<GrTextureProxy> GrClipStackClip::createSoftwareClipMask(
422 : GrContext* context,
423 : const GrReducedClip& reducedClip) const {
424 0 : GrUniqueKey key;
425 0 : create_clip_mask_key(reducedClip.elementsGenID(), reducedClip.ibounds(), &key);
426 :
427 0 : sk_sp<GrTextureProxy> proxy(context->resourceProvider()->findProxyByUniqueKey(key));
428 0 : if (proxy) {
429 0 : 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 0 : SkIRect maskSpaceIBounds = SkIRect::MakeWH(reducedClip.width(), reducedClip.height());
435 :
436 0 : 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 0 : translate.setTranslate(SkIntToScalar(-reducedClip.left()), SkIntToScalar(-reducedClip.top()));
442 :
443 0 : if (!helper.init(maskSpaceIBounds, &translate)) {
444 0 : return nullptr;
445 : }
446 0 : helper.clear(InitialState::kAllIn == reducedClip.initialState() ? 0xFF : 0x00);
447 :
448 0 : for (ElementList::Iter iter(reducedClip.elements()); iter.get(); iter.next()) {
449 0 : const Element* element = iter.get();
450 0 : SkClipOp op = element->getOp();
451 0 : GrAA aa = GrBoolToAA(element->isAA());
452 :
453 0 : 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 0 : if (kReverseDifference_SkClipOp == op) {
459 0 : SkRect temp = SkRect::Make(reducedClip.ibounds());
460 : // invert the entire scene
461 0 : helper.drawRect(temp, SkRegion::kXOR_Op, GrAA::kNo, 0xFF);
462 : }
463 0 : SkPath clipPath;
464 0 : element->asPath(&clipPath);
465 0 : clipPath.toggleInverseFillType();
466 0 : GrShape shape(clipPath, GrStyle::SimpleFill());
467 0 : helper.drawShape(shape, SkRegion::kReplace_Op, aa, 0x00);
468 0 : 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 0 : if (Element::kRect_Type == element->getType()) {
474 0 : helper.drawRect(element->getRect(), (SkRegion::Op)op, aa, 0xFF);
475 : } else {
476 0 : SkPath path;
477 0 : element->asPath(&path);
478 0 : GrShape shape(path, GrStyle::SimpleFill());
479 0 : helper.drawShape(shape, (SkRegion::Op)op, aa, 0xFF);
480 : }
481 : }
482 :
483 0 : sk_sp<GrTextureProxy> result(helper.toTextureProxy(context, SkBackingFit::kApprox));
484 :
485 0 : context->resourceProvider()->assignUniqueKeyToProxy(key, result.get());
486 : // MDB TODO (caching): this has to play nice with the GrSurfaceProxy's caching
487 0 : add_invalidate_on_pop_message(*fStack, reducedClip.elementsGenID(), key);
488 0 : return result;
489 : }
|