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