xref: /aosp_15_r20/external/skia/tests/graphite/precompile/ThreadedPrecompileTest.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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