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
25typedef SkClipStack::Element Element;
26typedef GrReducedClip::InitialState InitialState;
27typedef GrReducedClip::ElementList ElementList;
28
29static const int kMaxAnalyticElements = 4;
30const char GrClipStackClip::kMaskTestTag[] = "clip_mask";
31
32bool GrClipStackClip::quickContains(const SkRect& rect) const {
33    if (!fStack || fStack->isWideOpen()) {
34        return true;
35    }
36    return fStack->quickContains(rect);
37}
38
39bool GrClipStackClip::quickContains(const SkRRect& rrect) const {
40    if (!fStack || fStack->isWideOpen()) {
41        return true;
42    }
43    return fStack->quickContains(rrect);
44}
45
46bool 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
59void 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.
75static 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).
87bool 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 */
147bool 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
176static 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
250bool 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
360static 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
370static 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
384sk_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
421sk_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