1 /*
2  * Copyright 2019 Google LLC.
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 "gm/gm.h"
9 
10 #include "include/core/SkPath.h"
11 #include "include/gpu/GrContextOptions.h"
12 #include "include/gpu/GrRecordingContext.h"
13 #include "src/gpu/GrDirectContextPriv.h"
14 #include "src/gpu/GrDrawingManager.h"
15 #include "src/gpu/GrRecordingContextPriv.h"
16 #include "src/gpu/GrRenderTargetContext.h"
17 #include "src/gpu/ccpr/GrCCPathCache.h"
18 #include "src/gpu/ccpr/GrCoverageCountingPathRenderer.h"
19 #include "tools/ToolUtils.h"
20 
21 namespace skiagm {
22 
23 #define ERR_MSG_ASSERT(COND) \
24     do { \
25         if (!(COND)) { \
26             errorMsg->printf("preservefillrule.cpp(%i): assert(%s)", \
27                              __LINE__, #COND); \
28             return DrawResult::kFail; \
29         } \
30     } while (false)
31 
32 
33 /**
34  * This test ensures that the ccpr path cache preserves fill rules properly, both in the case where
35  * we copy paths into a8 literal coverage atlases, as well as in the case where we just reuse a
36  * stashed fp16 coverage count atlas.
37  */
38 class PreserveFillRuleGM : public GpuGM {
39 public:
40     // fStarSize affects whether ccpr copies the paths to an a8 literal coverage atlas, or just
41     // leaves them stashed in an fp16 coverage count atlas. The threshold for copying to a8 is
42     // currently 256x256 total pixels copied. If this ever changes, there is code in onDraw that
43     // will detect the unexpected behavior and draw a failure message.
PreserveFillRuleGM(bool literalCoverageAtlas)44     PreserveFillRuleGM(bool literalCoverageAtlas)
45             : fLiteralCoverageAtlas(literalCoverageAtlas)
46             , fStarSize((fLiteralCoverageAtlas) ? 200 : 20) {
47     }
48 
49 private:
onShortName()50     SkString onShortName() override {
51         SkString name("preservefillrule");
52         name += (fLiteralCoverageAtlas) ? "_big" : "_little";
53         return name;
54     }
onISize()55     SkISize onISize() override { return SkISize::Make(fStarSize * 2, fStarSize * 2); }
56 
modifyGrContextOptions(GrContextOptions * ctxOptions)57     void modifyGrContextOptions(GrContextOptions* ctxOptions) override {
58         ctxOptions->fGpuPathRenderers = GpuPathRenderers::kCoverageCounting;
59         ctxOptions->fAllowPathMaskCaching = true;
60     }
61 
onDraw(GrRecordingContext * rContext,GrRenderTargetContext * rtc,SkCanvas * canvas,SkString * errorMsg)62     DrawResult onDraw(GrRecordingContext* rContext, GrRenderTargetContext* rtc, SkCanvas* canvas,
63                       SkString* errorMsg) override {
64         using CoverageType = GrCCAtlas::CoverageType;
65 
66         if (rtc->numSamples() > 1) {
67             errorMsg->set("ccpr is currently only used for coverage AA");
68             return DrawResult::kSkip;
69         }
70 
71         auto* ccpr = rContext->priv().drawingManager()->getCoverageCountingPathRenderer();
72         if (!ccpr) {
73             errorMsg->set("ccpr only");
74             return DrawResult::kSkip;
75         }
76 
77         auto pathCache = ccpr->testingOnly_getPathCache();
78         if (!pathCache) {
79             errorMsg->set("ccpr is not in caching mode. "
80                           "Are you using viewer? Launch with \"--cachePathMasks true\".");
81             return DrawResult::kFail;
82         }
83 
84         auto dContext = GrAsDirectContext(rContext);
85         if (!dContext) {
86             *errorMsg = "Requires a direct context.";
87             return skiagm::DrawResult::kSkip;
88         }
89 
90         auto starRect = SkRect::MakeWH(fStarSize, fStarSize);
91         SkPath star7_winding = ToolUtils::make_star(starRect, 7);
92         star7_winding.setFillType(SkPathFillType::kWinding);
93 
94         SkPath star7_evenOdd = star7_winding;
95         star7_evenOdd.transform(SkMatrix::Translate(0, fStarSize));
96         star7_evenOdd.setFillType(SkPathFillType::kEvenOdd);
97 
98         SkPath star5_winding = ToolUtils::make_star(starRect, 5);
99         star5_winding.transform(SkMatrix::Translate(fStarSize, 0));
100         star5_winding.setFillType(SkPathFillType::kWinding);
101 
102         SkPath star5_evenOdd = star5_winding;
103         star5_evenOdd.transform(SkMatrix::Translate(0, fStarSize));
104         star5_evenOdd.setFillType(SkPathFillType::kEvenOdd);
105 
106         SkPaint paint;
107         paint.setColor(SK_ColorGREEN);
108         paint.setAntiAlias(true);
109 
110         for (int i = 0; i < 3; ++i) {
111             canvas->clear(SK_ColorWHITE);
112             canvas->drawPath(star7_winding, paint);
113             canvas->drawPath(star7_evenOdd, paint);
114             canvas->drawPath(star5_winding, paint);
115             canvas->drawPath(star5_evenOdd, paint);
116             rtc->flush(SkSurface::BackendSurfaceAccess::kNoAccess, GrFlushInfo(), nullptr);
117 
118             // Ensure the path cache is behaving in such a way that we are actually testing what we
119             // think we are.
120             int numCachedPaths = 0;
121             for (GrCCPathCacheEntry* entry : pathCache->testingOnly_getLRU()) {
122                 if (0 == i) {
123                     // We don't cache an atlas on the first hit.
124                     ERR_MSG_ASSERT(!entry->cachedAtlas());
125                 } else {
126                     // The stars should be cached in an atlas now.
127                     ERR_MSG_ASSERT(entry->cachedAtlas());
128 
129                     CoverageType atlasCoverageType = entry->cachedAtlas()->coverageType();
130                     if (i < 2) {
131                         // We never copy to an a8 atlas before the second hit.
132                         ERR_MSG_ASSERT(ccpr->coverageType() == atlasCoverageType);
133                     } else if (fLiteralCoverageAtlas) {
134                         // Verify fStarSize is large enough that the paths got copied to an a8
135                         // atlas.
136                         ERR_MSG_ASSERT(CoverageType::kA8_LiteralCoverage == atlasCoverageType);
137                     } else {
138                         // Verify fStarSize is small enough that the paths did *NOT* get copied to
139                         // an a8 atlas.
140                         ERR_MSG_ASSERT(ccpr->coverageType() == atlasCoverageType);
141                     }
142                 }
143                 ++numCachedPaths;
144             }
145 
146             if (dContext) {
147                 // Verify all 4 paths are tracked by the path cache.
148                 ERR_MSG_ASSERT(4 == numCachedPaths);
149             }
150         }
151 
152         return DrawResult::kOk;
153     }
154 
155 private:
156     const bool fLiteralCoverageAtlas;
157     const int fStarSize;
158 };
159 
160 DEF_GM( return new PreserveFillRuleGM(true); )
161 DEF_GM( return new PreserveFillRuleGM(false); )
162 
163 }  // namespace skiagm
164