1 /*
2 * Copyright 2023 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 "fuzz/Fuzz.h"
9
10 #include "include/core/SkCanvas.h"
11 #include "include/core/SkColorFilter.h"
12 #include "include/core/SkColorSpace.h"
13 #include "include/core/SkFont.h"
14 #include "include/core/SkImageInfo.h"
15 #include "include/core/SkPaint.h"
16 #include "include/core/SkPathBuilder.h"
17 #include "include/core/SkRefCnt.h"
18 #include "include/effects/SkColorMatrix.h"
19 #include "include/gpu/graphite/Context.h"
20 #include "include/gpu/graphite/Surface.h"
21 #include "include/gpu/graphite/precompile/Precompile.h"
22 #include "modules/skcms/skcms.h"
23 #include "src/core/SkBlenderBase.h"
24 #include "src/gpu/graphite/ContextPriv.h"
25 #include "src/gpu/graphite/ContextUtils.h"
26 #include "src/gpu/graphite/FactoryFunctions.h"
27 #include "src/gpu/graphite/KeyContext.h"
28 #include "src/gpu/graphite/PaintParams.h"
29 #include "src/gpu/graphite/PaintParamsKey.h"
30 #include "src/gpu/graphite/PipelineData.h"
31 #include "src/gpu/graphite/PrecompileInternal.h"
32 #include "src/gpu/graphite/PublicPrecompile.h"
33 #include "src/gpu/graphite/RecorderPriv.h"
34 #include "src/gpu/graphite/Renderer.h"
35 #include "src/gpu/graphite/RuntimeEffectDictionary.h"
36 #include "src/gpu/graphite/geom/Geometry.h"
37 #include "src/gpu/graphite/precompile/PaintOptionsPriv.h"
38 #include "tools/ToolUtils.h"
39 #include "tools/gpu/GrContextFactory.h"
40 #include "tools/graphite/ContextFactory.h"
41
42 using namespace skgpu::graphite;
43
44 namespace {
45
random_blend_mode(Fuzz * fuzz)46 SkBlendMode random_blend_mode(Fuzz* fuzz) {
47 uint32_t temp;
48 fuzz->next(&temp);
49 return (SkBlendMode) (temp % kSkBlendModeCount);
50 }
51
random_opaque_skcolor(Fuzz * fuzz)52 SkColor random_opaque_skcolor(Fuzz* fuzz) {
53 SkColor color;
54 fuzz->next(&color);
55 return 0xff000000 | color;
56 }
57
random_color4f(Fuzz * fuzz)58 SkColor4f random_color4f(Fuzz* fuzz) {
59 bool makeOpaque;
60 fuzz->next(&makeOpaque);
61
62 SkColor4f color;
63 fuzz->nextRange(&color.fR, 0, 1);
64 fuzz->nextRange(&color.fG, 0, 1);
65 fuzz->nextRange(&color.fB, 0, 1);
66 if (makeOpaque) {
67 color.fA = 1.0;
68 } else {
69 fuzz->nextRange(&color.fA, 0, 1);
70 }
71
72 return color;
73 }
74
make_path()75 SkPath make_path() {
76 SkPathBuilder path;
77 path.moveTo(0, 0);
78 path.lineTo(8, 2);
79 path.lineTo(16, 0);
80 path.lineTo(14, 8);
81 path.lineTo(16, 16);
82 path.lineTo(8, 14);
83 path.lineTo(0, 16);
84 path.lineTo(2, 8);
85 path.close();
86 return path.detach();
87 }
88
89 //--------------------------------------------------------------------------------------------------
90 // color spaces
91
random_transfer_function(Fuzz * fuzz)92 const skcms_TransferFunction& random_transfer_function(Fuzz* fuzz) {
93 static constexpr skcms_TransferFunction gTransferFunctions[] = {
94 SkNamedTransferFn::kSRGB,
95 SkNamedTransferFn::k2Dot2,
96 SkNamedTransferFn::kLinear,
97 SkNamedTransferFn::kRec2020,
98 SkNamedTransferFn::kPQ,
99 SkNamedTransferFn::kHLG,
100 };
101
102 uint32_t xferFunction;
103 fuzz->next(&xferFunction);
104 xferFunction %= std::size(gTransferFunctions);
105 return gTransferFunctions[xferFunction];
106 }
107
random_gamut(Fuzz * fuzz)108 const skcms_Matrix3x3& random_gamut(Fuzz* fuzz) {
109 static constexpr skcms_Matrix3x3 gGamuts[] = {
110 SkNamedGamut::kSRGB,
111 SkNamedGamut::kAdobeRGB,
112 SkNamedGamut::kDisplayP3,
113 SkNamedGamut::kRec2020,
114 SkNamedGamut::kXYZ,
115 };
116
117 uint32_t gamut;
118 fuzz->next(&gamut);
119 gamut %= std::size(gGamuts);
120 return gGamuts[gamut];
121 }
122
123 enum class ColorSpaceType {
124 kNone,
125 kSRGB,
126 kSRGBLinear,
127 kRGB,
128
129 kLast = kRGB
130 };
131
132 static constexpr int kColorSpaceTypeCount = static_cast<int>(ColorSpaceType::kLast) + 1;
133
create_colorspace(Fuzz * fuzz,ColorSpaceType csType)134 sk_sp<SkColorSpace> create_colorspace(Fuzz* fuzz, ColorSpaceType csType) {
135 switch (csType) {
136 case ColorSpaceType::kNone:
137 return nullptr;
138 case ColorSpaceType::kSRGB:
139 return SkColorSpace::MakeSRGB();
140 case ColorSpaceType::kSRGBLinear:
141 return SkColorSpace::MakeSRGBLinear();
142 case ColorSpaceType::kRGB:
143 return SkColorSpace::MakeRGB(random_transfer_function(fuzz), random_gamut(fuzz));
144 }
145
146 SkUNREACHABLE;
147 }
148
create_random_colorspace(Fuzz * fuzz)149 sk_sp<SkColorSpace> create_random_colorspace(Fuzz* fuzz) {
150 uint32_t temp;
151 fuzz->next(&temp);
152 ColorSpaceType csType = (ColorSpaceType) (temp % kColorSpaceTypeCount);
153
154 return create_colorspace(fuzz, csType);
155 }
156
157 //--------------------------------------------------------------------------------------------------
158 // color filters
159
160 enum class ColorFilterType {
161 kNone,
162 kBlend,
163 kMatrix,
164 kHSLAMatrix,
165 // TODO: add more color filters
166
167 kLast = kHSLAMatrix
168 };
169
170 static constexpr int kColorFilterTypeCount = static_cast<int>(ColorFilterType::kLast) + 1;
171
create_blend_colorfilter(Fuzz * fuzz)172 std::pair<sk_sp<SkColorFilter>, sk_sp<PrecompileColorFilter>> create_blend_colorfilter(
173 Fuzz* fuzz) {
174
175 sk_sp<SkColorFilter> cf;
176
177 // SkColorFilters::Blend is clever and can weed out noop color filters. Loop until we get
178 // a valid color filter.
179 while (!cf && !fuzz->exhausted()) {
180 cf = SkColorFilters::Blend(random_color4f(fuzz),
181 create_random_colorspace(fuzz),
182 random_blend_mode(fuzz));
183 }
184
185 sk_sp<PrecompileColorFilter> o = cf ? PrecompileColorFilters::Blend() : nullptr;
186
187 return { cf, o };
188 }
189
create_matrix_colorfilter()190 std::pair<sk_sp<SkColorFilter>, sk_sp<PrecompileColorFilter>> create_matrix_colorfilter() {
191 sk_sp<SkColorFilter> cf = SkColorFilters::Matrix(
192 SkColorMatrix::RGBtoYUV(SkYUVColorSpace::kJPEG_Full_SkYUVColorSpace));
193 sk_sp<PrecompileColorFilter> o = PrecompileColorFilters::Matrix();
194
195 return { cf, o };
196 }
197
create_hsla_matrix_colorfilter()198 std::pair<sk_sp<SkColorFilter>, sk_sp<PrecompileColorFilter>> create_hsla_matrix_colorfilter() {
199 sk_sp<SkColorFilter> cf = SkColorFilters::HSLAMatrix(
200 SkColorMatrix::RGBtoYUV(SkYUVColorSpace::kJPEG_Full_SkYUVColorSpace));
201 sk_sp<PrecompileColorFilter> o = PrecompileColorFilters::HSLAMatrix();
202
203 return { cf, o };
204 }
205
create_colorfilter(Fuzz * fuzz,ColorFilterType type,int depth)206 std::pair<sk_sp<SkColorFilter>, sk_sp<PrecompileColorFilter>> create_colorfilter(
207 Fuzz* fuzz,
208 ColorFilterType type,
209 int depth) {
210 if (depth <= 0) {
211 return {};
212 }
213
214 switch (type) {
215 case ColorFilterType::kNone:
216 return { nullptr, nullptr };
217 case ColorFilterType::kBlend:
218 return create_blend_colorfilter(fuzz);
219 case ColorFilterType::kMatrix:
220 return create_matrix_colorfilter();
221 case ColorFilterType::kHSLAMatrix:
222 return create_hsla_matrix_colorfilter();
223 }
224
225 SkUNREACHABLE;
226 }
227
create_random_colorfilter(Fuzz * fuzz,int depth)228 std::pair<sk_sp<SkColorFilter>, sk_sp<PrecompileColorFilter>> create_random_colorfilter(
229 Fuzz* fuzz,
230 int depth) {
231
232 uint32_t temp;
233 fuzz->next(&temp);
234 ColorFilterType cf = (ColorFilterType) (temp % kColorFilterTypeCount);
235
236 return create_colorfilter(fuzz, cf, depth);
237 }
238
239 //--------------------------------------------------------------------------------------------------
create_random_paint(Fuzz * fuzz,int depth)240 std::pair<SkPaint, PaintOptions> create_random_paint(Fuzz* fuzz, int depth) {
241 if (depth <= 0) {
242 return {};
243 }
244
245 SkPaint paint;
246 paint.setColor(random_opaque_skcolor(fuzz));
247
248 PaintOptions paintOptions;
249
250 {
251 auto [cf, o] = create_random_colorfilter(fuzz, depth - 1);
252 SkASSERT_RELEASE(!cf == !o);
253
254 if (cf) {
255 paint.setColorFilter(std::move(cf));
256 paintOptions.setColorFilters({o});
257 }
258 }
259
260 return { paint, paintOptions };
261 }
262
263 //--------------------------------------------------------------------------------------------------
check_draw(Context * context,Recorder * recorder,const SkPaint & paint,DrawTypeFlags dt,const SkPath & path)264 void check_draw(Context* context,
265 Recorder* recorder,
266 const SkPaint& paint,
267 DrawTypeFlags dt,
268 const SkPath& path) {
269 int before = context->priv().globalCache()->numGraphicsPipelines();
270
271 {
272 // TODO: vary the colorType of the target surface too
273 SkImageInfo ii = SkImageInfo::Make(16, 16,
274 kRGBA_8888_SkColorType,
275 kPremul_SkAlphaType);
276
277 sk_sp<SkSurface> surf = SkSurfaces::RenderTarget(recorder, ii);
278 SkCanvas* canvas = surf->getCanvas();
279
280 switch (dt) {
281 case DrawTypeFlags::kShape:
282 canvas->drawRect(SkRect::MakeWH(16, 16), paint);
283 canvas->drawPath(path, paint);
284 break;
285 default:
286 SkASSERT_RELEASE(false);
287 break;
288 }
289
290 std::unique_ptr<skgpu::graphite::Recording> recording = recorder->snap();
291 context->insertRecording({ recording.get() });
292 context->submit(SyncToCpu::kYes);
293 }
294
295 int after = context->priv().globalCache()->numGraphicsPipelines();
296
297 // Actually using the SkPaint with the specified type of draw shouldn't have caused
298 // any additional compilation
299 SkASSERT_RELEASE(before == after);
300 }
301
fuzz_graphite(Fuzz * fuzz,Context * context,int depth=9)302 void fuzz_graphite(Fuzz* fuzz, Context* context, int depth = 9) {
303 auto recorder = context->makeRecorder();
304 ShaderCodeDictionary* dict = context->priv().shaderCodeDictionary();
305
306 SkColorInfo ci = SkColorInfo(kRGBA_8888_SkColorType, kPremul_SkAlphaType,
307 SkColorSpace::MakeSRGB());
308
309 std::unique_ptr<RuntimeEffectDictionary> rtDict = std::make_unique<RuntimeEffectDictionary>();
310 KeyContext precompileKeyContext(recorder->priv().caps(),
311 dict,
312 rtDict.get(),
313 ci,
314 /* dstTexture= */ nullptr,
315 /* dstOffset= */ {0, 0});
316
317 auto dstTexInfo = recorder->priv().caps()->getDefaultSampledTextureInfo(kRGBA_8888_SkColorType,
318 skgpu::Mipmapped::kNo,
319 skgpu::Protected::kNo,
320 skgpu::Renderable::kNo);
321 // Use Budgeted::kYes to avoid immediately instantiating the TextureProxy. This test doesn't
322 // require full resources.
323 sk_sp<TextureProxy> fakeDstTexture = TextureProxy::Make(recorder->priv().caps(),
324 recorder->priv().resourceProvider(),
325 SkISize::Make(1, 1),
326 dstTexInfo,
327 "FuzzPrecompileFakeDstTexture",
328 skgpu::Budgeted::kYes);
329 constexpr SkIPoint fakeDstOffset = SkIPoint::Make(0, 0);
330
331 DrawTypeFlags kDrawType = DrawTypeFlags::kShape;
332 SkPath path = make_path();
333
334 Layout layout = context->backend() == skgpu::BackendApi::kMetal ? Layout::kMetal
335 : Layout::kStd140;
336
337 PaintParamsKeyBuilder builder(dict);
338 PipelineDataGatherer gatherer(recorder->priv().caps(), layout);
339
340
341 auto [paint, paintOptions] = create_random_paint(fuzz, depth);
342
343 constexpr Coverage coverageOptions[3] = {
344 Coverage::kNone, Coverage::kSingleChannel, Coverage::kLCD};
345 uint32_t temp;
346 fuzz->next(&temp);
347 Coverage coverage = coverageOptions[temp % 3];
348
349 DstReadRequirement dstReadReq = DstReadRequirement::kNone;
350 const SkBlenderBase* blender = as_BB(paint.getBlender());
351 if (blender) {
352 dstReadReq = GetDstReadRequirement(recorder->priv().caps(),
353 blender->asBlendMode(),
354 coverage);
355 }
356 bool needsDstSample = dstReadReq == DstReadRequirement::kTextureCopy ||
357 dstReadReq == DstReadRequirement::kTextureSample;
358 sk_sp<TextureProxy> curDst = needsDstSample ? fakeDstTexture : nullptr;
359
360 auto [paintID, uData, tData] = ExtractPaintData(recorder.get(),
361 &gatherer,
362 &builder,
363 layout,
364 {},
365 PaintParams(paint,
366 /* primitiveBlender= */ nullptr,
367 /* clipShader= */ nullptr,
368 dstReadReq,
369 /* skipColorXform= */ false),
370 {},
371 curDst,
372 fakeDstOffset,
373 ci);
374
375 std::vector<UniquePaintParamsID> precompileIDs;
376 paintOptions.priv().buildCombinations(precompileKeyContext,
377 &gatherer,
378 DrawTypeFlags::kNone,
379 /* withPrimitiveBlender= */ false,
380 coverage,
381 [&](UniquePaintParamsID id,
382 DrawTypeFlags,
383 bool /* withPrimitiveBlender */,
384 Coverage) {
385 precompileIDs.push_back(id);
386 });
387
388 // The specific key generated by ExtractPaintData should be one of the
389 // combinations generated by the combination system.
390 auto result = std::find(precompileIDs.begin(), precompileIDs.end(), paintID);
391
392 #ifdef SK_DEBUG
393 if (result == precompileIDs.end()) {
394 SkDebugf("From paint: ");
395 dict->dump(paintID);
396
397 SkDebugf("From combination builder:");
398 for (auto iter : precompileIDs) {
399 dict->dump(iter);
400 }
401 }
402 #endif
403
404 SkASSERT_RELEASE(result != precompileIDs.end());
405
406 {
407 context->priv().globalCache()->resetGraphicsPipelines();
408
409 int before = context->priv().globalCache()->numGraphicsPipelines();
410 Precompile(context, paintOptions, kDrawType);
411 int after = context->priv().globalCache()->numGraphicsPipelines();
412
413 SkASSERT_RELEASE(before == 0);
414 SkASSERT_RELEASE(after > before);
415
416 check_draw(context, recorder.get(), paint, kDrawType, path);
417 }
418 }
419
420 } // anonymous namespace
421
DEF_FUZZ(Precompile,fuzz)422 DEF_FUZZ(Precompile, fuzz) {
423 skiatest::graphite::ContextFactory factory;
424
425 skgpu::ContextType contextType;
426 #if defined(SK_METAL)
427 contextType = skgpu::ContextType::kMetal;
428 #elif defined(SK_VULKAN)
429 contextType = skgpu::ContextType::kVulkan;
430 #else
431 contextType = skgpu::ContextType::kMock;
432 #endif
433
434 skiatest::graphite::ContextInfo ctxInfo = factory.getContextInfo(contextType);
435 skgpu::graphite::Context* context = ctxInfo.fContext;
436 if (!context) {
437 return;
438 }
439
440 fuzz_graphite(fuzz, context);
441 }
442