1 /*
2 * Copyright 2017 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 "include/core/SkBlendMode.h"
9 #include "include/core/SkColorSpace.h"
10 #include "include/core/SkMatrix.h"
11 #include "include/core/SkPaint.h"
12 #include "include/core/SkPath.h"
13 #include "include/core/SkRect.h"
14 #include "include/core/SkRefCnt.h"
15 #include "include/core/SkStrokeRec.h"
16 #include "include/core/SkSurfaceProps.h"
17 #include "include/core/SkTypes.h"
18 #include "include/gpu/GpuTypes.h"
19 #include "include/gpu/ganesh/GrDirectContext.h"
20 #include "include/gpu/ganesh/GrRecordingContext.h"
21 #include "include/gpu/ganesh/GrTypes.h"
22 #include "include/private/gpu/ganesh/GrTypesPriv.h"
23 #include "src/core/SkPathPriv.h"
24 #include "src/gpu/SkBackingFit.h"
25 #include "src/gpu/ganesh/GrDirectContextPriv.h"
26 #include "src/gpu/ganesh/GrPaint.h"
27 #include "src/gpu/ganesh/GrRecordingContextPriv.h"
28 #include "src/gpu/ganesh/GrResourceCache.h"
29 #include "src/gpu/ganesh/GrStyle.h"
30 #include "src/gpu/ganesh/GrUserStencilSettings.h"
31 #include "src/gpu/ganesh/PathRenderer.h"
32 #include "src/gpu/ganesh/SurfaceDrawContext.h"
33 #include "src/gpu/ganesh/effects/GrPorterDuffXferProcessor.h"
34 #include "src/gpu/ganesh/geometry/GrStyledShape.h"
35 #include "src/gpu/ganesh/ops/SoftwarePathRenderer.h"
36 #include "src/gpu/ganesh/ops/TriangulatingPathRenderer.h"
37 #include "tests/CtsEnforcement.h"
38 #include "tests/Test.h"
39
40 #include <functional>
41 #include <memory>
42 #include <utility>
43
44 struct GrContextOptions;
45
create_concave_path()46 static SkPath create_concave_path() {
47 SkPath path;
48 path.moveTo(100, 0);
49 path.lineTo(200, 200);
50 path.lineTo(100, 150);
51 path.lineTo(0, 200);
52 path.close();
53 return path;
54 }
55
draw_path(GrRecordingContext * rContext,skgpu::ganesh::SurfaceDrawContext * sdc,const SkPath & path,skgpu::ganesh::PathRenderer * pr,GrAAType aaType,const GrStyle & style,float scaleX=1.f)56 static void draw_path(GrRecordingContext* rContext,
57 skgpu::ganesh::SurfaceDrawContext* sdc,
58 const SkPath& path,
59 skgpu::ganesh::PathRenderer* pr,
60 GrAAType aaType,
61 const GrStyle& style,
62 float scaleX = 1.f) {
63 GrPaint paint;
64 paint.setXPFactory(GrPorterDuffXPFactory::Get(SkBlendMode::kSrc));
65
66 SkIRect clipConservativeBounds = SkIRect::MakeWH(sdc->width(),
67 sdc->height());
68 GrStyledShape shape(path, style);
69 if (shape.style().applies()) {
70 shape = shape.applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, 1.0f);
71 }
72 SkMatrix matrix = SkMatrix::I();
73 matrix.setScaleX(scaleX);
74 skgpu::ganesh::PathRenderer::DrawPathArgs args{rContext,
75 std::move(paint),
76 &GrUserStencilSettings::kUnused,
77 sdc,
78 nullptr,
79 &clipConservativeBounds,
80 &matrix,
81 &shape,
82 aaType,
83 false};
84 pr->drawPath(args);
85 }
86
cache_non_scratch_resources_equals(GrResourceCache * cache,int expected)87 static bool cache_non_scratch_resources_equals(GrResourceCache* cache, int expected) {
88 #if GR_CACHE_STATS
89 GrResourceCache::Stats stats;
90 cache->getStats(&stats);
91 return (stats.fTotal - stats.fScratch) == expected;
92 #else
93 return true;
94 #endif
95 }
96
test_path(skiatest::Reporter * reporter,const std::function<SkPath (void)> & createPath,const std::function<skgpu::ganesh::PathRenderer * (GrRecordingContext *)> & makePathRenderer,int expected,bool checkListeners,GrAAType aaType=GrAAType::kNone,GrStyle style=GrStyle (SkStrokeRec::kFill_InitStyle))97 static void test_path(
98 skiatest::Reporter* reporter,
99 const std::function<SkPath(void)>& createPath,
100 const std::function<skgpu::ganesh::PathRenderer*(GrRecordingContext*)>& makePathRenderer,
101 int expected,
102 bool checkListeners,
103 GrAAType aaType = GrAAType::kNone,
104 GrStyle style = GrStyle(SkStrokeRec::kFill_InitStyle)) {
105 sk_sp<GrDirectContext> dContext = GrDirectContext::MakeMock(nullptr);
106 // The cache needs to be big enough that nothing gets flushed, or our expectations can be wrong
107 dContext->setResourceCacheLimit(8000000);
108 GrResourceCache* cache = dContext->priv().getResourceCache();
109
110 auto sdc = skgpu::ganesh::SurfaceDrawContext::Make(dContext.get(),
111 GrColorType::kRGBA_8888,
112 nullptr,
113 SkBackingFit::kApprox,
114 {800, 800},
115 SkSurfaceProps(),
116 /*label=*/{},
117 /* sampleCnt= */ 1,
118 skgpu::Mipmapped::kNo,
119 GrProtected::kNo,
120 kTopLeft_GrSurfaceOrigin);
121 if (!sdc) {
122 return;
123 }
124
125 sk_sp<skgpu::ganesh::PathRenderer> pathRenderer(makePathRenderer(dContext.get()));
126 SkPath path = createPath();
127
128 // Initially, cache only has the render target context
129 REPORTER_ASSERT(reporter, cache_non_scratch_resources_equals(cache, 0));
130
131 // Draw the path, check that new resource count matches expectations
132 draw_path(dContext.get(), sdc.get(), path, pathRenderer.get(), aaType, style);
133 dContext->flushAndSubmit();
134 REPORTER_ASSERT(reporter, cache_non_scratch_resources_equals(cache, expected));
135
136 // Nothing should be purgeable yet
137 cache->purgeAsNeeded();
138 REPORTER_ASSERT(reporter, cache_non_scratch_resources_equals(cache, expected));
139
140 // Reset the path to change the GenID, which should invalidate one resource in the cache.
141 // Some path renderers may leave other unique-keyed resources in the cache, though.
142 path.reset();
143 cache->purgeAsNeeded();
144 REPORTER_ASSERT(reporter, cache_non_scratch_resources_equals(cache, expected - 1));
145
146 if (!checkListeners) {
147 return;
148 }
149
150 // Test that purging the cache of masks also removes listeners from the path.
151 path = createPath();
152 REPORTER_ASSERT(reporter, SkPathPriv::GenIDChangeListenersCount(path) == 0);
153 for (int i = 0; i < 20; ++i) {
154 float scaleX = 1 + ((float)i + 1)/20.f;
155 draw_path(dContext.get(), sdc.get(), path, pathRenderer.get(), aaType, style, scaleX);
156 }
157 dContext->flushAndSubmit();
158 REPORTER_ASSERT(reporter, SkPathPriv::GenIDChangeListenersCount(path) == 20);
159 cache->purgeUnlockedResources(GrPurgeResourceOptions::kAllResources);
160 // The listeners don't actually purge until we try to add another one.
161 draw_path(dContext.get(), sdc.get(), path, pathRenderer.get(), aaType, style);
162 REPORTER_ASSERT(reporter, SkPathPriv::GenIDChangeListenersCount(path) == 1);
163 }
164
165 #if !defined(SK_ENABLE_OPTIMIZE_SIZE)
166 // Test that deleting the original path invalidates the VBs cached by the tessellating path renderer
167 DEF_GANESH_TEST(TriangulatingPathRendererCacheTest,
168 reporter,
169 /* options */,
170 CtsEnforcement::kNever) {
__anonff05443c0102(GrRecordingContext*) 171 auto createPR = [](GrRecordingContext*) {
172 return new skgpu::ganesh::TriangulatingPathRenderer();
173 };
174
175 // Triangulating path renderer creates a single vertex buffer for non-AA paths. No other
176 // resources should be created.
177 const int kExpectedResources = 1;
178
179 test_path(reporter, create_concave_path, createPR, kExpectedResources, false);
180
181 // Test with a style that alters the path geometry. This needs to attach the invalidation logic
182 // to the original path, not the modified path produced by the style.
183 SkPaint paint;
184 paint.setStyle(SkPaint::kStroke_Style);
185 paint.setStrokeWidth(1);
186 GrStyle style(paint);
187 test_path(reporter, create_concave_path, createPR, kExpectedResources, false, GrAAType::kNone,
188 std::move(style));
189 }
190 #endif
191
192 // Test that deleting the original path invalidates the textures cached by the SW path renderer
193 DEF_GANESH_TEST(SoftwarePathRendererCacheTest,
194 reporter,
195 /* options */,
196 CtsEnforcement::kApiLevel_T) {
__anonff05443c0202(GrRecordingContext* rContext) 197 auto createPR = [](GrRecordingContext* rContext) {
198 return new skgpu::ganesh::SoftwarePathRenderer(rContext->priv().proxyProvider(), true);
199 };
200
201 // Software path renderer creates a mask texture and renders with a non-AA rect, but the flush
202 // only contains a single quad so FillRectOp doesn't need to use the shared index buffer.
203 const int kExpectedResources = 1;
204
205 test_path(reporter, create_concave_path, createPR, kExpectedResources, true,
206 GrAAType::kCoverage);
207
208 // Test with a style that alters the path geometry. This needs to attach the invalidation logic
209 // to the original path, not the modified path produced by the style.
210 SkPaint paint;
211 paint.setStyle(SkPaint::kStroke_Style);
212 paint.setStrokeWidth(1);
213 GrStyle style(paint);
214 test_path(reporter, create_concave_path, createPR, kExpectedResources, true,
215 GrAAType::kCoverage, std::move(style));
216 }
217