• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2019 Google LLC.
2 // Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
3 
4 #include "gm/gm.h"
5 #include "include/codec/SkCodec.h"
6 #include "include/core/SkCanvas.h"
7 #include "include/core/SkColorSpace.h"
8 #include "include/core/SkGraphics.h"
9 #include "include/core/SkPicture.h"
10 #include "include/core/SkPictureRecorder.h"
11 #include "include/docs/SkPDFDocument.h"
12 #include "include/gpu/GrContextOptions.h"
13 #include "include/gpu/GrDirectContext.h"
14 #include "include/private/SkTHash.h"
15 #include "src/core/SkColorSpacePriv.h"
16 #include "src/core/SkMD5.h"
17 #include "src/core/SkOSFile.h"
18 #include "src/core/SkTaskGroup.h"
19 #include "src/gpu/GrDirectContextPriv.h"
20 #include "src/gpu/GrGpu.h"
21 #include "src/utils/SkOSPath.h"
22 #include "tests/Test.h"
23 #include "tools/AutoreleasePool.h"
24 #include "tools/CrashHandler.h"
25 #include "tools/HashAndEncode.h"
26 #include "tools/ToolUtils.h"
27 #include "tools/flags/CommandLineFlags.h"
28 #include "tools/flags/CommonFlags.h"
29 #include "tools/gpu/BackendSurfaceFactory.h"
30 #include "tools/gpu/GrContextFactory.h"
31 #include "tools/gpu/MemoryCache.h"
32 #include "tools/trace/EventTracingPriv.h"
33 
34 #include <chrono>
35 #include <functional>
36 #include <limits.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 
40 #if defined(SK_ENABLE_SVG)
41 #include "modules/svg/include/SkSVGDOM.h"
42 #include "modules/svg/include/SkSVGNode.h"
43 #endif
44 
45 #if defined(SK_ENABLE_SKOTTIE)
46     #include "modules/skottie/include/Skottie.h"
47     #include "modules/skresources/include/SkResources.h"
48 #endif
49 
50 using sk_gpu_test::GrContextFactory;
51 
52 static DEFINE_bool(listGMs  , false, "Print GM names and exit.");
53 static DEFINE_bool(listTests, false, "Print unit test names and exit.");
54 
55 static DEFINE_string2(sources, s, "", "Which GMs, .skps, or images to draw.");
56 static DEFINE_string2(backend, b, "", "Backend used to create a canvas to draw into.");
57 
58 static DEFINE_string(ct    ,   "8888", "The color type for any raster backend.");
59 static DEFINE_string(at    , "premul", "The alpha type for any raster backend.");
60 static DEFINE_string(gamut ,   "srgb", "The color gamut for any raster backend.");
61 static DEFINE_string(tf    ,   "srgb", "The transfer function for any raster backend.");
62 static DEFINE_bool  (legacy,    false, "Use a null SkColorSpace instead of --gamut and --tf?");
63 
64 static DEFINE_bool  (skvm ,    false, "Use SkVMBlitter when supported?");
65 static DEFINE_bool  (jit  ,     true, "JIT SkVM?");
66 static DEFINE_bool  (dylib,    false, "JIT SkVM via dylib?");
67 
68 static DEFINE_bool  (reducedshaders,    false, "Use reduced shader set for any GPU backend.");
69 static DEFINE_int   (samples       ,         0, "Samples per pixel in GPU backends.");
70 static DEFINE_bool  (stencils      ,      true, "If false, avoid stencil buffers in GPU backends.");
71 static DEFINE_bool  (dit           ,     false, "Use device-independent text in GPU backends.");
72 static DEFINE_string(surf          , "default", "Backing store for GPU backend surfaces.");
73 
74 static DEFINE_bool(       preAbandonGpuContext, false, "Abandon the GrContext before drawing.");
75 static DEFINE_bool(          abandonGpuContext, false, "Abandon the GrContext after drawing.");
76 static DEFINE_bool(releaseAndAbandonGpuContext, false,
77                    "Release all GPU resources and abandon the GrContext after drawing.");
78 
79 static DEFINE_bool(decodeToDst, false,
80                    "Decode images to destination format rather than suggested natural format.");
81 
82 static DEFINE_double(rasterDPI, SK_ScalarDefaultRasterDPI,
83                      "DPI for rasterized content in vector backends like --backend pdf.");
84 static DEFINE_bool(PDFA, false, "Create PDF/A with --backend pdf?");
85 
86 static DEFINE_int(clipW, INT_MAX, "Limit source width.");
87 static DEFINE_int(clipH, INT_MAX, "Limit source height.");
88 
89 static DEFINE_bool   (cpuDetect, true, "Detect CPU features for runtime optimizations?");
90 static DEFINE_string2(writePath, w, "", "Write .pngs to this directory if set.");
91 static DEFINE_bool   (quick, false, "Skip image hashing and encoding?");
92 static DEFINE_int    (race, 0, "If >0, use threads to induce race conditions?");
93 
94 static DEFINE_string(writeShaders, "", "Write GLSL shaders to this directory if set.");
95 
96 static DEFINE_string(key,        "", "Metadata passed through to .png encoder and .json output.");
97 static DEFINE_string(properties, "", "Metadata passed through to .png encoder and .json output.");
98 
99 template <typename T>
100 struct FlagOption {
101     const char* label;
102     T           value;
103 };
104 
105 template <typename T, int N>
parse_flag(const CommandLineFlags::StringArray & flag,const char * flag_name,const FlagOption<T> (& array)[N],T * value)106 static bool parse_flag(const CommandLineFlags::StringArray& flag,
107                        const char* flag_name,
108                        const FlagOption<T> (&array)[N],
109                        T* value) {
110     for (auto entry : array) {
111         if (flag.contains(entry.label)) {
112             *value = entry.value;
113             return true;
114         }
115     }
116     fprintf(stderr, "Known values for --%s:\n", flag_name);
117     for (auto entry : array) {
118         fprintf(stderr, "    --%s %s\n", flag_name, entry.label);
119     }
120     return false;
121 }
122 
123 struct Result {
124     enum { Ok, Skip, Fail} status;
125     SkString               failure;
126 };
127 static const Result ok = {Result::Ok,   {}},
128                   skip = {Result::Skip, {}};
129 
fail(const char * why)130 static Result fail(const char* why) {
131     return { Result::Fail, SkString(why) };
132 }
133 template <typename... Args>
fail(const char * whyFmt,Args...args)134 static Result fail(const char* whyFmt, Args... args) {
135     return { Result::Fail, SkStringPrintf(whyFmt, args...) };
136 }
137 
138 
139 struct Source {
140     SkString                               name;
141     SkISize                                size;
142     std::function<Result(SkCanvas*)>       draw;
__anonf615384c0202Source143     std::function<void(GrContextOptions*)> tweak = [](GrContextOptions*){};
144 };
145 
init(Source * source,std::shared_ptr<skiagm::GM> gm)146 static void init(Source* source, std::shared_ptr<skiagm::GM> gm) {
147     source->size  = gm->getISize();
148     source->tweak = [gm](GrContextOptions* options) { gm->modifyGrContextOptions(options); };
149     source->draw  = [gm](SkCanvas* canvas) {
150         auto direct = GrAsDirectContext(canvas->recordingContext());
151 
152         SkString err;
153         switch (gm->gpuSetup(direct, canvas, &err)) {
154             case skiagm::DrawResult::kOk  : break;
155             case skiagm::DrawResult::kSkip: return skip;
156             case skiagm::DrawResult::kFail: return fail(err.c_str());
157         }
158 
159         switch (gm->draw(canvas, &err)) {
160             case skiagm::DrawResult::kOk:   break;
161             case skiagm::DrawResult::kSkip: return skip;
162             case skiagm::DrawResult::kFail: return fail(err.c_str());
163         }
164         return ok;
165     };
166 }
167 
init(Source * source,sk_sp<SkPicture> pic)168 static void init(Source* source, sk_sp<SkPicture> pic) {
169     source->size = pic->cullRect().roundOut().size();
170     source->draw = [pic](SkCanvas* canvas) {
171         canvas->drawPicture(pic);
172         return ok;
173     };
174 }
175 
init(Source * source,std::shared_ptr<SkCodec> codec)176 static void init(Source* source, std::shared_ptr<SkCodec> codec) {
177     source->size = codec->dimensions();
178     source->draw = [codec](SkCanvas* canvas) {
179         SkImageInfo info = codec->getInfo();
180         if (FLAGS_decodeToDst) {
181             info = canvas->imageInfo().makeDimensions(info.dimensions());
182         }
183 
184         auto [image, result] = codec->getImage(info);
185         if (image) {
186             canvas->drawImage(image, 0,0);
187             return ok;
188         }
189         return fail("codec->getPixels() failed: %d\n", result);
190     };
191 }
192 
193 #if defined(SK_ENABLE_SVG)
init(Source * source,sk_sp<SkSVGDOM> svg)194 static void init(Source* source, sk_sp<SkSVGDOM> svg) {
195     if (svg->containerSize().isEmpty()) {
196         svg->setContainerSize({1000,1000});
197     }
198     source->size = svg->containerSize().toCeil();
199     source->draw = [svg](SkCanvas* canvas) {
200         svg->render(canvas);
201         return ok;
202     };
203 }
204 #endif
205 
206 #if defined(SK_ENABLE_SKOTTIE)
init(Source * source,sk_sp<skottie::Animation> animation)207 static void init(Source* source, sk_sp<skottie::Animation> animation) {
208     source->size = {1000,1000};
209     source->draw = [animation](SkCanvas* canvas) {
210         canvas->clear(SK_ColorWHITE);
211 
212         // Draw frames in a shuffled order to exercise nonlinear frame progression.
213         // The film strip will still be in time order, just drawn out of order.
214         const int order[] = { 4, 0, 3, 1, 2 };
215         const int tiles = SK_ARRAY_COUNT(order);
216         const float dim = 1000.0f / tiles;
217 
218         const float dt = 1.0f / (tiles*tiles - 1);
219 
220         for (int y : order)
221         for (int x : order) {
222             SkRect dst = {x*dim, y*dim, (x+1)*dim, (y+1)*dim};
223 
224             SkAutoCanvasRestore _(canvas, /*doSave=*/true);
225             canvas->clipRect(dst, /*doAntiAlias=*/true);
226             canvas->concat(SkMatrix::RectToRect(SkRect::MakeSize(animation->size()), dst,
227                                                 SkMatrix::kCenter_ScaleToFit));
228             float t = (y*tiles + x) * dt;
229             animation->seek(t);
230             animation->render(canvas);
231         }
232         return ok;
233     };
234 }
235 #endif
236 
init(Source * source,const skiatest::Test & test)237 static void init(Source* source, const skiatest::Test& test) {
238     source->size  = {1,1};
239     source->draw  = [test](SkCanvas* canvas) {
240         struct Reporter : public skiatest::Reporter {
241             SkString msg;
242 
243             void reportFailed(const skiatest::Failure& failure) override {
244                 msg += failure.toString();
245                 msg += "\n";
246             }
247         } reporter;
248 
249         test.run(&reporter, GrContextOptions{});
250 
251         if (reporter.msg.isEmpty()) {
252             canvas->clear(SK_ColorGREEN);
253             return ok;
254         }
255 
256         canvas->clear(SK_ColorRED);
257         return fail(reporter.msg.c_str());
258     };
259 }
260 
draw_with_cpu(std::function<bool (SkCanvas *)> draw,SkImageInfo info)261 static sk_sp<SkImage> draw_with_cpu(std::function<bool(SkCanvas*)> draw,
262                                     SkImageInfo info) {
263     if (sk_sp<SkSurface> surface = SkSurface::MakeRaster(info)) {
264         if (draw(surface->getCanvas())) {
265             return surface->makeImageSnapshot();
266         }
267     }
268     return nullptr;
269 }
270 
draw_as_skp(std::function<bool (SkCanvas *)> draw,SkImageInfo info)271 static sk_sp<SkData> draw_as_skp(std::function<bool(SkCanvas*)> draw,
272                                  SkImageInfo info) {
273     SkPictureRecorder recorder;
274     if (draw(recorder.beginRecording(info.width(), info.height()))) {
275         return recorder.finishRecordingAsPicture()->serialize();
276     }
277     return nullptr;
278 }
279 
draw_as_pdf(std::function<bool (SkCanvas *)> draw,SkImageInfo info,SkString name)280 static sk_sp<SkData> draw_as_pdf(std::function<bool(SkCanvas*)> draw,
281                                  SkImageInfo info,
282                                  SkString name) {
283     SkPDF::Metadata metadata;
284     metadata.fTitle     = name;
285     metadata.fCreator   = "Skia/FM";
286     metadata.fRasterDPI = FLAGS_rasterDPI;
287     metadata.fPDFA      = FLAGS_PDFA;
288 
289     SkDynamicMemoryWStream stream;
290     if (sk_sp<SkDocument> doc = SkPDF::MakeDocument(&stream, metadata)) {
291         if (draw(doc->beginPage(info.width(), info.height()))) {
292             doc->endPage();
293             doc->close();
294             return stream.detachAsData();
295         }
296     }
297     return nullptr;
298 }
299 
draw_with_gpu(std::function<bool (SkCanvas *)> draw,SkImageInfo info,GrContextFactory::ContextType api,GrContextFactory * factory)300 static sk_sp<SkImage> draw_with_gpu(std::function<bool(SkCanvas*)> draw,
301                                     SkImageInfo info,
302                                     GrContextFactory::ContextType api,
303                                     GrContextFactory* factory) {
304     enum class SurfaceType { kDefault, kBackendTexture, kBackendRenderTarget };
305     const FlagOption<SurfaceType> kSurfaceTypes[] = {
306         { "default", SurfaceType::kDefault },
307         { "betex"  , SurfaceType::kBackendTexture },
308         { "bert"   , SurfaceType::kBackendRenderTarget },
309     };
310     SurfaceType surfaceType;
311     if (!parse_flag(FLAGS_surf, "surf", kSurfaceTypes, &surfaceType)) {
312         return nullptr;
313     }
314 
315     auto overrides = GrContextFactory::ContextOverrides::kNone;
316     if (!FLAGS_stencils) { overrides |= GrContextFactory::ContextOverrides::kAvoidStencilBuffers; }
317 
318     auto context = factory->getContextInfo(api, overrides).directContext();
319 
320     uint32_t flags = FLAGS_dit ? SkSurfaceProps::kUseDeviceIndependentFonts_Flag
321                                : 0;
322     SkSurfaceProps props(flags, kRGB_H_SkPixelGeometry);
323 
324     sk_sp<SkSurface> surface;
325 
326     switch (surfaceType) {
327         case SurfaceType::kDefault:
328             surface = SkSurface::MakeRenderTarget(context,
329                                                   SkBudgeted::kNo,
330                                                   info,
331                                                   FLAGS_samples,
332                                                   &props);
333             break;
334 
335         case SurfaceType::kBackendTexture:
336             surface = sk_gpu_test::MakeBackendTextureSurface(context,
337                                                              info,
338                                                              kTopLeft_GrSurfaceOrigin,
339                                                              FLAGS_samples,
340                                                              GrMipmapped::kNo,
341                                                              GrProtected::kNo,
342                                                              &props);
343             break;
344 
345         case SurfaceType::kBackendRenderTarget:
346             surface = sk_gpu_test::MakeBackendRenderTargetSurface(context,
347                                                                   info,
348                                                                   kBottomLeft_GrSurfaceOrigin,
349                                                                   FLAGS_samples,
350                                                                   GrProtected::kNo,
351                                                                   &props);
352             break;
353     }
354 
355     if (!surface) {
356         fprintf(stderr, "Could not create GPU surface.\n");
357         return nullptr;
358     }
359 
360     if (FLAGS_preAbandonGpuContext) {
361         factory->abandonContexts();
362     }
363 
364     sk_sp<SkImage> image;
365     if (draw(surface->getCanvas())) {
366         image = surface->makeImageSnapshot();
367     }
368 
369     if (FLAGS_abandonGpuContext) {
370         factory->abandonContexts();
371     } else if (FLAGS_releaseAndAbandonGpuContext) {
372         factory->releaseResourcesAndAbandonContexts();
373     }
374 
375     return image;
376 }
377 
378 extern bool gUseSkVMBlitter;
379 extern bool gSkVMAllowJIT;
380 extern bool gSkVMJITViaDylib;
381 
main(int argc,char ** argv)382 int main(int argc, char** argv) {
383     CommandLineFlags::Parse(argc, argv);
384     SetupCrashHandler();
385     SkTaskGroup::Enabler enabled(FLAGS_race);
386 
387     if (FLAGS_cpuDetect) {
388         SkGraphics::Init();
389     }
390     gUseSkVMBlitter  = FLAGS_skvm;
391     gSkVMAllowJIT    = FLAGS_jit;
392     gSkVMJITViaDylib = FLAGS_dylib;
393 
394     initializeEventTracingForTools();
395     CommonFlags::SetDefaultFontMgr();
396     CommonFlags::SetAnalyticAA();
397 
398     GrContextOptions baseOptions;
399     CommonFlags::SetCtxOptions(&baseOptions);
400     baseOptions.fReducedShaderVariations = FLAGS_reducedshaders;
401 
402     sk_gpu_test::MemoryCache memoryCache;
403     if (!FLAGS_writeShaders.isEmpty()) {
404         baseOptions.fPersistentCache = &memoryCache;
405         baseOptions.fShaderCacheStrategy = GrContextOptions::ShaderCacheStrategy::kBackendSource;
406     }
407 
408     SkTHashMap<SkString, skiagm::GMFactory> gm_factories;
409     for (skiagm::GMFactory factory : skiagm::GMRegistry::Range()) {
410         std::unique_ptr<skiagm::GM> gm{factory()};
411         if (FLAGS_listGMs) {
412             fprintf(stdout, "%s\n", gm->getName());
413         } else {
414             gm_factories.set(SkString{gm->getName()}, factory);
415         }
416     }
417 
418     SkTHashMap<SkString, const skiatest::Test*> tests;
419     for (const skiatest::Test& test : skiatest::TestRegistry::Range()) {
420         if (test.fNeedsGpu || test.fNeedsGraphite) {
421             continue;  // TODO
422         }
423         if (FLAGS_listTests) {
424             fprintf(stdout, "%s\n", test.fName);
425         } else {
426             tests.set(SkString{test.fName}, &test);
427         }
428     }
429 
430     if (FLAGS_listGMs || FLAGS_listTests) {
431         return 0;
432     }
433     if (FLAGS_sources.isEmpty()) {
434         fprintf(stderr, "Please give me something to run using -s/--sources!\n");
435         return 1;
436     }
437 
438     const int replicas = std::max(1, FLAGS_race);
439 
440     SkTArray<Source> sources;
441     for (const SkString& name : FLAGS_sources)
442     for (int replica = 0; replica < replicas; replica++) {
443         Source* source = &sources.push_back();
444         source->name = name;
445 
446         if (skiagm::GMFactory* factory = gm_factories.find(name)) {
447             std::shared_ptr<skiagm::GM> gm{(*factory)()};
448             init(source, std::move(gm));
449             continue;
450         }
451 
452         if (const skiatest::Test** test = tests.find(name)) {
453             init(source, **test);
454             continue;
455         }
456 
457         if (sk_sp<SkData> blob = SkData::MakeFromFileName(name.c_str())) {
458             if (name.endsWith(".skp")) {
459                 if (sk_sp<SkPicture> pic = SkPicture::MakeFromData(blob.get())) {
460                     init(source, pic);
461                     continue;
462                 }
463             }
464 #if defined(SK_ENABLE_SVG)
465             else if (name.endsWith(".svg")) {
466                 SkMemoryStream stream{blob};
467                 if (sk_sp<SkSVGDOM> svg = SkSVGDOM::MakeFromStream(stream)) {
468                     init(source, svg);
469                     continue;
470                 }
471             }
472 #endif
473 #if defined(SK_ENABLE_SKOTTIE)
474             else if (name.endsWith(".json")) {
475                 const SkString dir  = SkOSPath::Dirname(name.c_str());
476                 if (sk_sp<skottie::Animation> animation = skottie::Animation::Builder()
477                         .setResourceProvider(skresources::FileResourceProvider::Make(dir))
478                         .make((const char*)blob->data(), blob->size())) {
479                     init(source, animation);
480                     continue;
481                 }
482             }
483 #endif
484             else if (std::shared_ptr<SkCodec> codec = SkCodec::MakeFromData(blob)) {
485                 init(source, codec);
486                 continue;
487             }
488         }
489 
490         fprintf(stderr, "Don't understand source '%s'... bailing out.\n", name.c_str());
491         return 1;
492     }
493 
494     enum NonGpuBackends {
495         kCPU_Backend = -1,
496         kSKP_Backend = -2,
497         kPDF_Backend = -3,
498     };
499     const FlagOption<int> kBackends[] = {
500         { "cpu"            , kCPU_Backend },
501         { "skp"            , kSKP_Backend },
502         { "pdf"            , kPDF_Backend },
503         { "gl"             , GrContextFactory::kGL_ContextType },
504         { "gles"           , GrContextFactory::kGLES_ContextType },
505         { "angle_d3d9_es2" , GrContextFactory::kANGLE_D3D9_ES2_ContextType },
506         { "angle_d3d11_es2", GrContextFactory::kANGLE_D3D11_ES2_ContextType },
507         { "angle_d3d11_es3", GrContextFactory::kANGLE_D3D11_ES3_ContextType },
508         { "angle_gl_es2"   , GrContextFactory::kANGLE_GL_ES2_ContextType },
509         { "angle_gl_es3"   , GrContextFactory::kANGLE_GL_ES3_ContextType },
510         { "cmdbuffer_es2"  , GrContextFactory::kCommandBuffer_ES2_ContextType },
511         { "cmdbuffer_es3"  , GrContextFactory::kCommandBuffer_ES3_ContextType },
512         { "vk"             , GrContextFactory::kVulkan_ContextType },
513         { "mtl"            , GrContextFactory::kMetal_ContextType },
514         { "mock"           , GrContextFactory::kMock_ContextType },
515     };
516     const FlagOption<SkColorType> kColorTypes[] = {
517         { "a8",                  kAlpha_8_SkColorType },
518         { "g8",                   kGray_8_SkColorType },
519         { "565",                 kRGB_565_SkColorType },
520         { "4444",              kARGB_4444_SkColorType },
521         { "8888",                    kN32_SkColorType },
522         { "888x",               kRGB_888x_SkColorType },
523         { "1010102",        kRGBA_1010102_SkColorType },
524         { "101010x",         kRGB_101010x_SkColorType },
525         { "bgra1010102",    kBGRA_1010102_SkColorType },
526         { "bgr101010x",      kBGR_101010x_SkColorType },
527         { "f16norm",        kRGBA_F16Norm_SkColorType },
528         { "f16",                kRGBA_F16_SkColorType },
529         { "f32",                kRGBA_F32_SkColorType },
530         { "rgba",              kRGBA_8888_SkColorType },
531         { "bgra",              kBGRA_8888_SkColorType },
532         { "srgba",            kSRGBA_8888_SkColorType },
533         { "16161616", kR16G16B16A16_unorm_SkColorType },
534     };
535     const FlagOption<SkAlphaType> kAlphaTypes[] = {
536         {   "premul",   kPremul_SkAlphaType },
537         { "unpremul", kUnpremul_SkAlphaType },
538     };
539     const FlagOption<skcms_Matrix3x3> kGamuts[] = {
540         { "srgb",    SkNamedGamut::kSRGB },
541         { "p3",      SkNamedGamut::kDisplayP3 },
542         { "rec2020", SkNamedGamut::kRec2020 },
543         { "adobe",   SkNamedGamut::kAdobeRGB },
544         { "narrow",  gNarrow_toXYZD50},
545     };
546     const FlagOption<skcms_TransferFunction> kTransferFunctions[] = {
547         { "srgb"   , SkNamedTransferFn::kSRGB },
548         { "rec2020", SkNamedTransferFn::kRec2020 },
549         { "2.2"    , SkNamedTransferFn::k2Dot2 },
550         { "linear" , SkNamedTransferFn::kLinear },
551     };
552 
553 
554     int                      backend;
555     SkColorType              ct;
556     SkAlphaType              at;
557     skcms_Matrix3x3          gamut;
558     skcms_TransferFunction   tf;
559 
560     if (!parse_flag(FLAGS_backend, "backend", kBackends         , &backend) ||
561         !parse_flag(FLAGS_ct     , "ct"     , kColorTypes       , &ct)      ||
562         !parse_flag(FLAGS_at     , "at"     , kAlphaTypes       , &at)      ||
563         !parse_flag(FLAGS_gamut  , "gamut"  , kGamuts           , &gamut)   ||
564         !parse_flag(FLAGS_tf     , "tf"     , kTransferFunctions, &tf)) {
565         return 1;
566     }
567 
568     sk_sp<SkColorSpace> cs = FLAGS_legacy ? nullptr
569                                           : SkColorSpace::MakeRGB(tf,gamut);
570     const SkColorInfo color_info{ct,at,cs};
571 
572     for (int i = 0; i < sources.count(); i += replicas)
573     SkTaskGroup{}.batch(replicas, [=](int replica) {
574         Source source = sources[i+replica];
575 
576         AutoreleasePool pool;
577         const auto start = std::chrono::steady_clock::now();
578 
579         auto [w,h] = source.size;
580         w = std::min(w, FLAGS_clipW);
581         h = std::min(h, FLAGS_clipH);
582         const SkImageInfo info = SkImageInfo::Make({w,h}, color_info);
583 
584         auto draw = [&source](SkCanvas* canvas) {
585             Result result = source.draw(canvas);
586             switch (result.status) {
587                 case Result::Ok:   break;
588                 case Result::Skip: return false;
589                 case Result::Fail:
590                     SK_ABORT("%s", result.failure.c_str());
591             }
592             return true;
593         };
594 
595         GrContextOptions options = baseOptions;
596         source.tweak(&options);
597         GrContextFactory factory(options);  // N.B. factory must outlive image
598 
599         sk_sp<SkImage> image;
600         sk_sp<SkData>  blob;
601         const char*    ext = ".png";
602         switch (backend) {
603             case kCPU_Backend:
604                 image = draw_with_cpu(draw, info);
605                 break;
606             case kSKP_Backend:
607                 blob = draw_as_skp(draw, info);
608                 ext  = ".skp";
609                 break;
610             case kPDF_Backend:
611                 blob = draw_as_pdf(draw, info, source.name);
612                 ext  = ".pdf";
613                 break;
614             default:
615                 image = draw_with_gpu(draw, info, (GrContextFactory::ContextType)backend, &factory);
616                 break;
617         }
618 
619         // We read back a bitmap even when --quick is set and we won't use it,
620         // to keep us honest about deferred work, flushing pipelines, etc.
621         SkBitmap bitmap;
622         if (image && !image->asLegacyBitmap(&bitmap)) {
623             SK_ABORT("SkImage::asLegacyBitmap() failed.");
624         }
625 
626         // Our --race replicas have done their job by now if they're going to catch anything.
627         if (replica != 0) {
628             return;
629         }
630 
631         if (!image && !blob) {
632             fprintf(stdout, "%50s  skipped\n", source.name.c_str());
633             fflush(stdout);
634             return;
635         }
636 
637         SkString md5;
638         if (!FLAGS_quick) {
639             HashAndEncode hashAndEncode{bitmap};
640             {
641                 SkMD5 hash;
642                 if (image) {
643                     hashAndEncode.feedHash(&hash);
644                 } else {
645                     hash.write(blob->data(), blob->size());
646                 }
647 
648                 SkMD5::Digest digest = hash.finish();
649                 for (int j = 0; j < 16; j++) {
650                     md5.appendf("%02x", digest.data[j]);
651                 }
652             }
653 
654             if (!FLAGS_writePath.isEmpty()) {
655                 SkString path = SkStringPrintf("%s/%s%s",
656                                                FLAGS_writePath[0], source.name.c_str(), ext);
657                 for (char* it = path.writable_str(); *it != '\0'; it++) {
658                     if (*it == '/' || *it == '\\') {
659                         char prev = std::exchange(*it, '\0');
660                         sk_mkdir(path.c_str());
661                         *it = prev;
662                     }
663                 }
664 
665                 SkFILEWStream file(path.c_str());
666                 if (image) {
667                     if (!hashAndEncode.encodePNG(&file, md5.c_str(),
668                                                  FLAGS_key, FLAGS_properties)) {
669                         SK_ABORT("Could not write .png.");
670                     }
671                 } else {
672                     file.write(blob->data(), blob->size());
673                 }
674             }
675         }
676 
677         const auto elapsed = std::chrono::steady_clock::now() - start;
678         fprintf(stdout, "%50s  %s  %7dms\n",
679                 source.name.c_str(),
680                 md5.c_str(),
681                 (int)std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count());
682         fflush(stdout);
683     });
684 
685 
686     if (!FLAGS_writeShaders.isEmpty()) {
687         sk_mkdir(FLAGS_writeShaders[0]);
688         GrBackendApi api =
689                 GrContextFactory::ContextTypeBackend((GrContextFactory::ContextType)backend);
690         memoryCache.writeShadersToDisk(FLAGS_writeShaders[0], api);
691 
692     }
693 
694     return 0;
695 }
696