GrClipStackClip.cpp revision 93f1633abca95e302fdd31ece0f4d602b0b26708
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 "effects/GrConvexPolyEffect.h"
20#include "effects/GrRRectEffect.h"
21#include "effects/GrTextureDomain.h"
22
23typedef SkClipStack::Element Element;
24typedef GrReducedClip::InitialState InitialState;
25typedef GrReducedClip::ElementList ElementList;
26
27static const int kMaxAnalyticElements = 4;
28
29bool GrClipStackClip::quickContains(const SkRect& rect) const {
30    if (!fStack || fStack->isWideOpen()) {
31        return true;
32    }
33    return fStack->quickContains(rect.makeOffset(SkIntToScalar(fOrigin.x()),
34                                                 SkIntToScalar(fOrigin.y())));
35}
36
37bool GrClipStackClip::quickContains(const SkRRect& rrect) const {
38    if (!fStack || fStack->isWideOpen()) {
39        return true;
40    }
41    return fStack->quickContains(rrect.makeOffset(SkIntToScalar(fOrigin.fX),
42                                                  SkIntToScalar(fOrigin.fY)));
43}
44
45bool GrClipStackClip::isRRect(const SkRect& origRTBounds, SkRRect* rr, bool* aa) const {
46    if (!fStack) {
47        return false;
48    }
49    const SkRect* rtBounds = &origRTBounds;
50    SkRect tempRTBounds;
51    bool origin = fOrigin.fX || fOrigin.fY;
52    if (origin) {
53        tempRTBounds = origRTBounds;
54        tempRTBounds.offset(SkIntToScalar(fOrigin.fX), SkIntToScalar(fOrigin.fY));
55        rtBounds = &tempRTBounds;
56    }
57    if (fStack->isRRect(*rtBounds, rr, aa)) {
58        if (origin) {
59            rr->offset(-SkIntToScalar(fOrigin.fX), -SkIntToScalar(fOrigin.fY));
60        }
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(-fOrigin.x(), -fOrigin.y(), width, height, &devBounds,
77                                  isIntersectionOfRects);
78    devBounds.roundOut(devResult);
79}
80
81////////////////////////////////////////////////////////////////////////////////
82// set up the draw state to enable the aa clipping mask.
83static sk_sp<GrFragmentProcessor> create_fp_for_mask(GrTexture* result,
84                                                     const SkIRect &devBound) {
85    SkIRect domainTexels = SkIRect::MakeWH(devBound.width(), devBound.height());
86    return GrDeviceSpaceTextureDecalFragmentProcessor::Make(result, domainTexels,
87                                                            {devBound.fLeft, devBound.fTop});
88}
89
90// Does the path in 'element' require SW rendering? If so, return true (and,
91// optionally, set 'prOut' to NULL. If not, return false (and, optionally, set
92// 'prOut' to the non-SW path renderer that will do the job).
93bool GrClipStackClip::PathNeedsSWRenderer(GrContext* context,
94                                          bool hasUserStencilSettings,
95                                          const GrRenderTargetContext* renderTargetContext,
96                                          const SkMatrix& viewMatrix,
97                                          const Element* element,
98                                          GrPathRenderer** prOut,
99                                          bool needsStencil) {
100    if (Element::kRect_Type == element->getType()) {
101        // rects can always be drawn directly w/o using the software path
102        // TODO: skip rrects once we're drawing them directly.
103        if (prOut) {
104            *prOut = nullptr;
105        }
106        return false;
107    } else {
108        // We shouldn't get here with an empty clip element.
109        SkASSERT(Element::kEmpty_Type != element->getType());
110
111        // the gpu alpha mask will draw the inverse paths as non-inverse to a temp buffer
112        SkPath path;
113        element->asPath(&path);
114        if (path.isInverseFillType()) {
115            path.toggleInverseFillType();
116        }
117
118        GrPathRendererChain::DrawType type;
119
120        if (needsStencil) {
121            type = element->isAA()
122                            ? GrPathRendererChain::kStencilAndColorAntiAlias_DrawType
123                            : GrPathRendererChain::kStencilAndColor_DrawType;
124        } else {
125            type = element->isAA()
126                            ? GrPathRendererChain::kColorAntiAlias_DrawType
127                            : GrPathRendererChain::kColor_DrawType;
128        }
129
130        GrShape shape(path, GrStyle::SimpleFill());
131        GrPathRenderer::CanDrawPathArgs canDrawArgs;
132        canDrawArgs.fShaderCaps = context->caps()->shaderCaps();
133        canDrawArgs.fViewMatrix = &viewMatrix;
134        canDrawArgs.fShape = &shape;
135        canDrawArgs.fAntiAlias = element->isAA();
136        canDrawArgs.fHasUserStencilSettings = hasUserStencilSettings;
137        canDrawArgs.fIsStencilBufferMSAA = renderTargetContext->isStencilBufferMultisampled();
138
139        // the 'false' parameter disallows use of the SW path renderer
140        GrPathRenderer* pr =
141            context->contextPriv().drawingManager()->getPathRenderer(canDrawArgs, false, type);
142        if (prOut) {
143            *prOut = pr;
144        }
145        return SkToBool(!pr);
146    }
147}
148
149/*
150 * This method traverses the clip stack to see if the GrSoftwarePathRenderer
151 * will be used on any element. If so, it returns true to indicate that the
152 * entire clip should be rendered in SW and then uploaded en masse to the gpu.
153 */
154bool GrClipStackClip::UseSWOnlyPath(GrContext* context,
155                                    bool hasUserStencilSettings,
156                                    const GrRenderTargetContext* renderTargetContext,
157                                    const GrReducedClip& reducedClip) {
158    // TODO: generalize this function so that when
159    // a clip gets complex enough it can just be done in SW regardless
160    // of whether it would invoke the GrSoftwarePathRenderer.
161
162    // Set the matrix so that rendered clip elements are transformed to mask space from clip
163    // space.
164    SkMatrix translate;
165    translate.setTranslate(SkIntToScalar(-reducedClip.left()), SkIntToScalar(-reducedClip.top()));
166
167    for (ElementList::Iter iter(reducedClip.elements()); iter.get(); iter.next()) {
168        const Element* element = iter.get();
169
170        SkCanvas::ClipOp op = element->getOp();
171        bool invert = element->isInverseFilled();
172        bool needsStencil = invert ||
173                            SkCanvas::kIntersect_Op == op || SkCanvas::kReverseDifference_Op == op;
174
175        if (PathNeedsSWRenderer(context, hasUserStencilSettings,
176                                renderTargetContext, translate, element, nullptr, needsStencil)) {
177            return true;
178        }
179    }
180    return false;
181}
182
183static bool get_analytic_clip_processor(const ElementList& elements,
184                                        bool abortIfAA,
185                                        const SkVector& clipToRTOffset,
186                                        const SkRect& drawBounds,
187                                        sk_sp<GrFragmentProcessor>* resultFP) {
188    SkRect boundsInClipSpace;
189    boundsInClipSpace = drawBounds.makeOffset(-clipToRTOffset.fX, -clipToRTOffset.fY);
190    SkASSERT(elements.count() <= kMaxAnalyticElements);
191    SkSTArray<kMaxAnalyticElements, sk_sp<GrFragmentProcessor>> fps;
192    ElementList::Iter iter(elements);
193    while (iter.get()) {
194        SkCanvas::ClipOp op = iter.get()->getOp();
195        bool invert;
196        bool skip = false;
197        switch (op) {
198            case SkRegion::kReplace_Op:
199                SkASSERT(iter.get() == elements.head());
200                // Fallthrough, handled same as intersect.
201            case SkRegion::kIntersect_Op:
202                invert = false;
203                if (iter.get()->contains(boundsInClipSpace)) {
204                    skip = true;
205                }
206                break;
207            case SkRegion::kDifference_Op:
208                invert = true;
209                // We don't currently have a cheap test for whether a rect is fully outside an
210                // element's primitive, so don't attempt to set skip.
211                break;
212            default:
213                return false;
214        }
215        if (!skip) {
216            GrPrimitiveEdgeType edgeType;
217            if (iter.get()->isAA()) {
218                if (abortIfAA) {
219                    return false;
220                }
221                edgeType =
222                    invert ? kInverseFillAA_GrProcessorEdgeType : kFillAA_GrProcessorEdgeType;
223            } else {
224                edgeType =
225                    invert ? kInverseFillBW_GrProcessorEdgeType : kFillBW_GrProcessorEdgeType;
226            }
227
228            switch (iter.get()->getType()) {
229                case SkClipStack::Element::kPath_Type:
230                    fps.emplace_back(GrConvexPolyEffect::Make(edgeType, iter.get()->getPath(),
231                                                              &clipToRTOffset));
232                    break;
233                case SkClipStack::Element::kRRect_Type: {
234                    SkRRect rrect = iter.get()->getRRect();
235                    rrect.offset(clipToRTOffset.fX, clipToRTOffset.fY);
236                    fps.emplace_back(GrRRectEffect::Make(edgeType, rrect));
237                    break;
238                }
239                case SkClipStack::Element::kRect_Type: {
240                    SkRect rect = iter.get()->getRect();
241                    rect.offset(clipToRTOffset.fX, clipToRTOffset.fY);
242                    fps.emplace_back(GrConvexPolyEffect::Make(edgeType, rect));
243                    break;
244                }
245                default:
246                    break;
247            }
248            if (!fps.back()) {
249                return false;
250            }
251        }
252        iter.next();
253    }
254
255    *resultFP = nullptr;
256    if (fps.count()) {
257        *resultFP = GrFragmentProcessor::RunInSeries(fps.begin(), fps.count());
258    }
259    return true;
260}
261
262////////////////////////////////////////////////////////////////////////////////
263// sort out what kind of clip mask needs to be created: alpha, stencil,
264// scissor, or entirely software
265bool GrClipStackClip::apply(GrContext* context, GrRenderTargetContext* renderTargetContext,
266                            bool useHWAA, bool hasUserStencilSettings, GrAppliedClip* out) const {
267    if (!fStack || fStack->isWideOpen()) {
268        return true;
269    }
270
271    SkRect devBounds = SkRect::MakeIWH(renderTargetContext->worstCaseWidth(),
272                                       renderTargetContext->worstCaseHeight());
273    if (!devBounds.intersect(out->clippedDrawBounds())) {
274        return false;
275    }
276
277    const SkScalar clipX = SkIntToScalar(fOrigin.x()),
278                   clipY = SkIntToScalar(fOrigin.y());
279
280    SkRect clipSpaceDevBounds = devBounds.makeOffset(clipX, clipY);
281    const GrReducedClip reducedClip(*fStack, clipSpaceDevBounds,
282                                    renderTargetContext->priv().maxWindowRectangles());
283
284    if (reducedClip.hasIBounds() &&
285        !GrClip::IsInsideClip(reducedClip.ibounds(), clipSpaceDevBounds)) {
286        SkIRect scissorSpaceIBounds(reducedClip.ibounds());
287        scissorSpaceIBounds.offset(-fOrigin);
288        out->addScissor(scissorSpaceIBounds);
289    }
290
291    if (!reducedClip.windowRectangles().empty()) {
292        out->addWindowRectangles(reducedClip.windowRectangles(), fOrigin,
293                                 GrWindowRectsState::Mode::kExclusive);
294    }
295
296    if (reducedClip.elements().isEmpty()) {
297        return InitialState::kAllIn == reducedClip.initialState();
298    }
299
300#ifdef SK_DEBUG
301    SkASSERT(reducedClip.hasIBounds());
302    SkIRect rtIBounds = SkIRect::MakeWH(renderTargetContext->worstCaseWidth(),
303                                        renderTargetContext->worstCaseHeight());
304    SkIRect clipIBounds = reducedClip.ibounds().makeOffset(-fOrigin.x(), -fOrigin.y());
305    SkASSERT(rtIBounds.contains(clipIBounds)); // Mask shouldn't be larger than the RT.
306#endif
307
308    // An element count of 4 was chosen because of the common pattern in Blink of:
309    //   isect RR
310    //   diff  RR
311    //   isect convex_poly
312    //   isect convex_poly
313    // when drawing rounded div borders. This could probably be tuned based on a
314    // configuration's relative costs of switching RTs to generate a mask vs
315    // longer shaders.
316    if (reducedClip.elements().count() <= kMaxAnalyticElements) {
317        // When there are multiple samples we want to do per-sample clipping, not compute a
318        // fractional pixel coverage.
319        bool disallowAnalyticAA = renderTargetContext->isStencilBufferMultisampled();
320        if (disallowAnalyticAA && !renderTargetContext->numColorSamples()) {
321            // With a single color sample, any coverage info is lost from color once it hits the
322            // color buffer anyway, so we may as well use coverage AA if nothing else in the pipe
323            // is multisampled.
324            disallowAnalyticAA = useHWAA || hasUserStencilSettings;
325        }
326        sk_sp<GrFragmentProcessor> clipFP;
327        if (reducedClip.requiresAA() &&
328            get_analytic_clip_processor(reducedClip.elements(), disallowAnalyticAA,
329                                        {-clipX, -clipY}, devBounds, &clipFP)) {
330            out->addCoverageFP(std::move(clipFP));
331            return true;
332        }
333    }
334
335    // If the stencil buffer is multisampled we can use it to do everything.
336    if (!renderTargetContext->isStencilBufferMultisampled() && reducedClip.requiresAA()) {
337        sk_sp<GrTexture> result;
338        if (UseSWOnlyPath(context, hasUserStencilSettings, renderTargetContext, reducedClip)) {
339            // The clip geometry is complex enough that it will be more efficient to create it
340            // entirely in software
341            result = CreateSoftwareClipMask(context->textureProvider(), reducedClip);
342        } else {
343            result = CreateAlphaClipMask(context, reducedClip);
344        }
345
346        if (result) {
347            // The mask's top left coord should be pinned to the rounded-out top left corner of
348            // clipSpace bounds. We determine the mask's position WRT to the render target here.
349            SkIRect rtSpaceMaskBounds = reducedClip.ibounds();
350            rtSpaceMaskBounds.offset(-fOrigin);
351            out->addCoverageFP(create_fp_for_mask(result.get(), rtSpaceMaskBounds));
352            return true;
353        }
354        // if alpha clip mask creation fails fall through to the non-AA code paths
355    }
356
357    GrRenderTarget* rt = renderTargetContext->accessRenderTarget();
358    if (!rt) {
359        return true;
360    }
361
362    // use the stencil clip if we can't represent the clip as a rectangle.
363    if (!context->resourceProvider()->attachStencilAttachment(rt)) {
364        SkDebugf("WARNING: failed to attach stencil buffer for clip mask. Clip will be ignored.\n");
365        return true;
366    }
367
368    // This relies on the property that a reduced sub-rect of the last clip will contain all the
369    // relevant window rectangles that were in the last clip. This subtle requirement will go away
370    // after clipping is overhauled.
371    if (renderTargetContext->priv().mustRenderClip(reducedClip.elementsGenID(),
372                                                   reducedClip.ibounds(), fOrigin)) {
373        reducedClip.drawStencilClipMask(context, renderTargetContext, fOrigin);
374        renderTargetContext->priv().setLastClip(reducedClip.elementsGenID(), reducedClip.ibounds(),
375                                                fOrigin);
376    }
377    out->addStencilClip();
378    return true;
379}
380
381////////////////////////////////////////////////////////////////////////////////
382// Create a 8-bit clip mask in alpha
383
384static void GetClipMaskKey(int32_t clipGenID, const SkIRect& bounds, GrUniqueKey* key) {
385    static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
386    GrUniqueKey::Builder builder(key, kDomain, 3);
387    builder[0] = clipGenID;
388    // SkToS16 because image filters outset layers to a size indicated by the filter, which can
389    // sometimes result in negative coordinates from clip space.
390    builder[1] = SkToS16(bounds.fLeft) | (SkToS16(bounds.fRight) << 16);
391    builder[2] = SkToS16(bounds.fTop) | (SkToS16(bounds.fBottom) << 16);
392}
393
394sk_sp<GrTexture> GrClipStackClip::CreateAlphaClipMask(GrContext* context,
395                                                      const GrReducedClip& reducedClip) {
396    GrResourceProvider* resourceProvider = context->resourceProvider();
397    GrUniqueKey key;
398    GetClipMaskKey(reducedClip.elementsGenID(), reducedClip.ibounds(), &key);
399    if (GrTexture* texture = resourceProvider->findAndRefTextureByUniqueKey(key)) {
400        return sk_sp<GrTexture>(texture);
401    }
402
403    sk_sp<GrRenderTargetContext> rtc(context->makeRenderTargetContextWithFallback(
404                                                                             SkBackingFit::kApprox,
405                                                                             reducedClip.width(),
406                                                                             reducedClip.height(),
407                                                                             kAlpha_8_GrPixelConfig,
408                                                                             nullptr));
409    if (!rtc) {
410        return nullptr;
411    }
412
413    if (!reducedClip.drawAlphaClipMask(rtc.get())) {
414        return nullptr;
415    }
416
417    sk_sp<GrTexture> texture(rtc->asTexture());
418    if (!texture) {
419        return nullptr;
420    }
421
422    texture->resourcePriv().setUniqueKey(key);
423    return texture;
424}
425
426sk_sp<GrTexture> GrClipStackClip::CreateSoftwareClipMask(GrTextureProvider* texProvider,
427                                                         const GrReducedClip& reducedClip) {
428    GrUniqueKey key;
429    GetClipMaskKey(reducedClip.elementsGenID(), reducedClip.ibounds(), &key);
430    if (GrTexture* texture = texProvider->findAndRefTextureByUniqueKey(key)) {
431        return sk_sp<GrTexture>(texture);
432    }
433
434    // The mask texture may be larger than necessary. We round out the clip space bounds and pin
435    // the top left corner of the resulting rect to the top left of the texture.
436    SkIRect maskSpaceIBounds = SkIRect::MakeWH(reducedClip.width(), reducedClip.height());
437
438    GrSWMaskHelper helper(texProvider);
439
440    // Set the matrix so that rendered clip elements are transformed to mask space from clip
441    // space.
442    SkMatrix translate;
443    translate.setTranslate(SkIntToScalar(-reducedClip.left()), SkIntToScalar(-reducedClip.top()));
444
445    if (!helper.init(maskSpaceIBounds, &translate)) {
446        return nullptr;
447    }
448    helper.clear(InitialState::kAllIn == reducedClip.initialState() ? 0xFF : 0x00);
449
450    for (ElementList::Iter iter(reducedClip.elements()); iter.get(); iter.next()) {
451        const Element* element = iter.get();
452        SkCanvas::ClipOp op = element->getOp();
453
454        if (SkCanvas::kIntersect_Op == op || SkCanvas::kReverseDifference_Op == op) {
455            // Intersect and reverse difference require modifying pixels outside of the geometry
456            // that is being "drawn". In both cases we erase all the pixels outside of the geometry
457            // but leave the pixels inside the geometry alone. For reverse difference we invert all
458            // the pixels before clearing the ones outside the geometry.
459            if (SkCanvas::kReverseDifference_Op == op) {
460                SkRect temp = SkRect::Make(reducedClip.ibounds());
461                // invert the entire scene
462                helper.drawRect(temp, SkRegion::kXOR_Op, false, 0xFF);
463            }
464            SkPath clipPath;
465            element->asPath(&clipPath);
466            clipPath.toggleInverseFillType();
467            GrShape shape(clipPath, GrStyle::SimpleFill());
468            helper.drawShape(shape, SkRegion::kReplace_Op, element->isAA(), 0x00);
469            continue;
470        }
471
472        // The other ops (union, xor, diff) only affect pixels inside
473        // the geometry so they can just be drawn normally
474        if (Element::kRect_Type == element->getType()) {
475            helper.drawRect(element->getRect(), (SkRegion::Op)op, element->isAA(), 0xFF);
476        } else {
477            SkPath path;
478            element->asPath(&path);
479            GrShape shape(path, GrStyle::SimpleFill());
480            helper.drawShape(shape, (SkRegion::Op)op, element->isAA(), 0xFF);
481        }
482    }
483
484    // Allocate clip mask texture
485    GrSurfaceDesc desc;
486    desc.fWidth = reducedClip.width();
487    desc.fHeight = reducedClip.height();
488    desc.fConfig = kAlpha_8_GrPixelConfig;
489
490    sk_sp<GrTexture> result(texProvider->createApproxTexture(desc));
491    if (!result) {
492        return nullptr;
493    }
494    result->resourcePriv().setUniqueKey(key);
495
496    helper.toTexture(result.get());
497
498    return result;
499}
500