1 /*
2 * Copyright 2024 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 "tests/Test.h"
9
10 #if defined(SK_GRAPHITE)
11
12 #include "include/core/SkCanvas.h"
13 #include "include/core/SkPaint.h"
14 #include "include/core/SkTextBlob.h"
15 #include "include/effects/SkGradientShader.h"
16 #include "include/gpu/graphite/PrecompileContext.h"
17 #include "include/gpu/graphite/Surface.h"
18 #include "include/gpu/graphite/precompile/PaintOptions.h"
19 #include "include/gpu/graphite/precompile/Precompile.h"
20 #include "include/gpu/graphite/precompile/PrecompileShader.h"
21 #include "src/gpu/graphite/ContextPriv.h"
22 #include "src/gpu/graphite/GraphicsPipelineDesc.h"
23 #include "src/gpu/graphite/RenderPassDesc.h"
24 #include "src/gpu/graphite/ResourceProvider.h"
25 #include "tools/fonts/FontToolUtils.h"
26 #include "tools/graphite/UniqueKeyUtils.h"
27
28 #include <thread>
29
30 using namespace::skgpu::graphite;
31
32 namespace {
33
34 static constexpr int kMaxNumStops = 9;
35 static constexpr SkColor gColors[kMaxNumStops] = {
36 SK_ColorRED,
37 SK_ColorGREEN,
38 SK_ColorBLUE,
39 SK_ColorCYAN,
40 SK_ColorMAGENTA,
41 SK_ColorYELLOW,
42 SK_ColorBLACK,
43 SK_ColorDKGRAY,
44 SK_ColorLTGRAY,
45 };
46 static constexpr SkPoint gPts[kMaxNumStops] = {
47 { -100.0f, -100.0f },
48 { -50.0f, -50.0f },
49 { -25.0f, -25.0f },
50 { -12.5f, -12.5f },
51 { 0.0f, 0.0f },
52 { 12.5f, 12.5f },
53 { 25.0f, 25.0f },
54 { 50.0f, 50.0f },
55 { 100.0f, 100.0f }
56 };
57 static constexpr float gOffsets[kMaxNumStops] =
58 { 0.0f, 0.125f, 0.25f, 0.375f, 0.5f, 0.625f, 0.75f, 0.875f, 1.0f };
59
linear(int numStops)60 std::pair<SkPaint, PaintOptions> linear(int numStops) {
61 SkASSERT(numStops <= kMaxNumStops);
62
63 PaintOptions paintOptions;
64 paintOptions.setShaders({ PrecompileShaders::LinearGradient() });
65 paintOptions.setBlendModes({ SkBlendMode::kSrcOver });
66
67 SkPaint paint;
68 paint.setShader(SkGradientShader::MakeLinear(gPts,
69 gColors, gOffsets, numStops,
70 SkTileMode::kClamp));
71 paint.setBlendMode(SkBlendMode::kSrcOver);
72
73 return { paint, paintOptions };
74 }
75
radial(int numStops)76 std::pair<SkPaint, PaintOptions> radial(int numStops) {
77 SkASSERT(numStops <= kMaxNumStops);
78
79 PaintOptions paintOptions;
80 paintOptions.setShaders({ PrecompileShaders::RadialGradient() });
81 paintOptions.setBlendModes({ SkBlendMode::kSrcOver });
82
83 SkPaint paint;
84 paint.setShader(SkGradientShader::MakeRadial(/* center= */ {0, 0}, /* radius= */ 100,
85 gColors, gOffsets, numStops,
86 SkTileMode::kClamp));
87 paint.setBlendMode(SkBlendMode::kSrcOver);
88
89 return { paint, paintOptions };
90 }
91
sweep(int numStops)92 std::pair<SkPaint, PaintOptions> sweep(int numStops) {
93 SkASSERT(numStops <= kMaxNumStops);
94
95 PaintOptions paintOptions;
96 paintOptions.setShaders({ PrecompileShaders::SweepGradient() });
97 paintOptions.setBlendModes({ SkBlendMode::kSrcOver });
98
99 SkPaint paint;
100 paint.setShader(SkGradientShader::MakeSweep(/* cx= */ 0, /* cy= */ 0,
101 gColors, gOffsets, numStops,
102 SkTileMode::kClamp,
103 /* startAngle= */ 0, /* endAngle= */ 359,
104 /* flags= */ 0, /* localMatrix= */ nullptr));
105 paint.setBlendMode(SkBlendMode::kSrcOver);
106
107 return { paint, paintOptions };
108 }
109
conical(int numStops)110 std::pair<SkPaint, PaintOptions> conical(int numStops) {
111 SkASSERT(numStops <= kMaxNumStops);
112
113 PaintOptions paintOptions;
114 paintOptions.setShaders({ PrecompileShaders::TwoPointConicalGradient() });
115 paintOptions.setBlendModes({ SkBlendMode::kSrcOver });
116
117 SkPaint paint;
118 paint.setShader(SkGradientShader::MakeTwoPointConical(/* start= */ {100, 100},
119 /* startRadius= */ 100,
120 /* end= */ {-100, -100},
121 /* endRadius= */ 100,
122 gColors, gOffsets, numStops,
123 SkTileMode::kClamp));
124 paint.setBlendMode(SkBlendMode::kSrcOver);
125
126 return { paint, paintOptions };
127 }
128
precompile_gradients(std::unique_ptr<PrecompileContext> precompileContext,skiatest::Reporter *,int)129 void precompile_gradients(std::unique_ptr<PrecompileContext> precompileContext,
130 skiatest::Reporter* /* reporter */,
131 int /* threadID */) {
132 constexpr RenderPassProperties kProps = { DepthStencilFlags::kDepth,
133 kBGRA_8888_SkColorType,
134 /* requiresMSAA= */ false };
135
136 for (auto createOptionsMtd : { linear, radial, sweep, conical }) {
137 // numStops doesn't influence the paintOptions
138 auto [_, paintOptions] = createOptionsMtd(/* numStops= */ 2);
139 Precompile(precompileContext.get(),
140 paintOptions,
141 DrawTypeFlags::kBitmapText_Mask,
142 { &kProps, 1 });
143 }
144
145 precompileContext.reset();
146 }
147
148 // A simple helper to call Context::insertRecording on the Recordings generated on the
149 // recorder threads. It collects (and keeps ownership) of all the generated Recordings.
150 class Listener : public SkRefCnt {
151 public:
Listener(int numSenders)152 Listener(int numSenders) : fNumActiveSenders(numSenders) {}
153
addRecording(std::unique_ptr<Recording> recording)154 void addRecording(std::unique_ptr<Recording> recording) SK_EXCLUDES(fLock) {
155 {
156 SkAutoMutexExclusive lock(fLock);
157 fRecordings.push_back(std::move(recording));
158 }
159
160 fWorkAvailable.signal(1);
161 }
162
deregister()163 void deregister() SK_EXCLUDES(fLock) {
164 {
165 SkAutoMutexExclusive lock(fLock);
166 fNumActiveSenders--;
167 }
168
169 fWorkAvailable.signal(1);
170 }
171
insertRecordings(Context * context)172 void insertRecordings(Context* context) {
173 do {
174 fWorkAvailable.wait();
175 } while (this->insertRecording(context));
176 }
177
178 private:
179 // This entry point is run in a loop waiting on the 'fWorkAvailable' semaphore until there
180 // are no senders remaining (at which point it returns false) c.f. 'insertRecordings'.
insertRecording(Context * context)181 bool insertRecording(Context* context) SK_EXCLUDES(fLock) {
182 Recording* recording = nullptr;
183 int numSendersLeft;
184
185 {
186 SkAutoMutexExclusive lock(fLock);
187
188 numSendersLeft = fNumActiveSenders;
189
190 SkASSERT(fRecordings.size() >= fCurHandled);
191 if (fRecordings.size() > fCurHandled) {
192 recording = fRecordings[fCurHandled++].get();
193 }
194 }
195
196 if (recording) {
197 context->insertRecording({recording});
198 return true; // continue looping
199 }
200
201 return SkToBool(numSendersLeft); // continue looping if there are still active senders
202 }
203
204 SkMutex fLock;
205 SkSemaphore fWorkAvailable;
206
207 skia_private::TArray<std::unique_ptr<Recording>> fRecordings SK_GUARDED_BY(fLock);
208 int fCurHandled SK_GUARDED_BY(fLock) = 0;
209 int fNumActiveSenders SK_GUARDED_BY(fLock);
210 };
211
compile_gradients(std::unique_ptr<Recorder> recorder,sk_sp<Listener> listener,skiatest::Reporter *,int)212 void compile_gradients(std::unique_ptr<Recorder> recorder,
213 sk_sp<Listener> listener,
214 skiatest::Reporter* /* reporter */,
215 int /* threadID */) {
216 SkFont font(ToolUtils::DefaultPortableTypeface(), /* size= */ 16);
217
218 const char text[] = "hambur1";
219 sk_sp<SkTextBlob> blob = SkTextBlob::MakeFromText(text, strlen(text), font);
220
221 SkImageInfo ii = SkImageInfo::Make(16, 16,
222 kBGRA_8888_SkColorType,
223 kPremul_SkAlphaType);
224
225 sk_sp<SkSurface> surf = SkSurfaces::RenderTarget(recorder.get(), ii,
226 skgpu::Mipmapped::kNo,
227 /* surfaceProps= */ nullptr);
228 SkCanvas* canvas = surf->getCanvas();
229
230 for (auto createOptionsMtd : { linear, radial, sweep, conical }) {
231 for (int numStops : { 2, 7, kMaxNumStops }) {
232 auto [paint, _] = createOptionsMtd(numStops);
233
234 canvas->drawTextBlob(blob, 0, 16, paint);
235
236 // This will trigger pipeline creation via TaskList::prepareResources
237 std::unique_ptr<skgpu::graphite::Recording> recording = recorder->snap();
238
239 listener->addRecording(std::move(recording));
240 }
241 }
242
243 listener->deregister();
244 }
245
246 } // anonymous namespace
247
248 // This test precompiles all four flavors of gradient sequentially but on multiple
249 // threads with the goal of creating cache races.
DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(ThreadedPrecompileTest,reporter,context,CtsEnforcement::kNever)250 DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(ThreadedPrecompileTest,
251 reporter,
252 context,
253 CtsEnforcement::kNever) {
254 constexpr int kNumThreads = 4;
255
256
257 std::thread threads[kNumThreads];
258 for (int i = 0; i < kNumThreads; ++i) {
259 std::unique_ptr<PrecompileContext> precompileContext = context->makePrecompileContext();
260
261 threads[i] = std::thread(precompile_gradients, std::move(precompileContext), reporter, i);
262 }
263
264 for (auto& thread : threads) {
265 thread.join();
266 }
267
268 const GlobalCache::PipelineStats stats = context->priv().globalCache()->getStats();
269
270 // Four types of gradient times three combinations (i.e., 4,8,N) for each one.
271 REPORTER_ASSERT(reporter, stats.fGraphicsCacheAdditions == 12);
272 REPORTER_ASSERT(reporter, stats.fGraphicsRaces > 0);
273 REPORTER_ASSERT(reporter, stats.fGraphicsCacheMisses ==
274 stats.fGraphicsCacheAdditions + stats.fGraphicsRaces);
275 }
276
277 // This test runs two threads compiling the gradient flavours and two threads
278 // pre-compiling the gradient flavors. This is to exercise the tracking of the
279 // various race combinations (i.e., Normal vs Precompile, Normal vs. Normal, etc.).
DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(ThreadedCompilePrecompileTest,reporter,context,CtsEnforcement::kNever)280 DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(ThreadedCompilePrecompileTest,
281 reporter,
282 context,
283 CtsEnforcement::kNever) {
284 constexpr int kNumRecordingThreads = 2;
285 constexpr int kNumPrecompileThreads = 2;
286 constexpr int kTotNumThreads = kNumRecordingThreads + kNumPrecompileThreads;
287
288 sk_sp<Listener> listener = sk_make_sp<Listener>(kNumRecordingThreads);
289
290 std::thread threads[kTotNumThreads];
291
292 for (int i = 0; i < kNumRecordingThreads; ++i) {
293 std::unique_ptr<Recorder> recorder = context->makeRecorder();
294
295 threads[i] = std::thread(compile_gradients,
296 std::move(recorder),
297 listener,
298 reporter,
299 i);
300 }
301 for (int i = 0; i < kNumPrecompileThreads; ++i) {
302 std::unique_ptr<PrecompileContext> precompileContext = context->makePrecompileContext();
303
304 int threadID = kNumRecordingThreads+i;
305 threads[kNumRecordingThreads+i] = std::thread(precompile_gradients,
306 std::move(precompileContext),
307 reporter,
308 threadID);
309 }
310
311 // Process the work generated by the recording threads
312 listener->insertRecordings(context);
313
314 for (auto& thread : threads) {
315 if (thread.joinable()) {
316 thread.join();
317 }
318 }
319
320 context->submit(SyncToCpu::kYes);
321
322 const GlobalCache::PipelineStats stats = context->priv().globalCache()->getStats();
323
324 // Four types of gradient times three combinations (i.e., 4,8,N) for each one.
325 REPORTER_ASSERT(reporter, stats.fGraphicsCacheAdditions == 12);
326 REPORTER_ASSERT(reporter, stats.fGraphicsRaces > 0);
327 REPORTER_ASSERT(reporter, stats.fGraphicsCacheMisses ==
328 stats.fGraphicsCacheAdditions + stats.fGraphicsRaces);
329
330 // The 48 comes from:
331 // 4 gradient flavors (linear, radial, ...) *
332 // 3 types of each flavor (4, 8, N) *
333 // 4 threads (2 normal-compile + 2 pre-compile)
334 REPORTER_ASSERT(reporter, stats.fGraphicsCacheHits + stats.fGraphicsCacheMisses == 48);
335 }
336
337 #endif // SK_GRAPHITE
338