• 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 "tests/TestHarness.h"
24 #include "tools/AutoreleasePool.h"
25 #include "tools/CrashHandler.h"
26 #include "tools/HashAndEncode.h"
27 #include "tools/ToolUtils.h"
28 #include "tools/flags/CommandLineFlags.h"
29 #include "tools/flags/CommonFlags.h"
30 #include "tools/gpu/BackendSurfaceFactory.h"
31 #include "tools/gpu/GrContextFactory.h"
32 #include "tools/gpu/MemoryCache.h"
33 #include "tools/trace/EventTracingPriv.h"
34 
35 #include <chrono>
36 #include <functional>
37 #include <limits.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 
41 #if defined(SK_ENABLE_SVG)
42 #include "modules/svg/include/SkSVGDOM.h"
43 #include "modules/svg/include/SkSVGNode.h"
44 #endif
45 
46 #if defined(SK_ENABLE_SKOTTIE)
47     #include "modules/skottie/include/Skottie.h"
48     #include "modules/skresources/include/SkResources.h"
49 #endif
50 
51 using sk_gpu_test::GrContextFactory;
52 
53 static DEFINE_bool(listGMs  , false, "Print GM names and exit.");
54 static DEFINE_bool(listTests, false, "Print unit test names and exit.");
55 
56 static DEFINE_string2(sources, s, "", "Which GMs, .skps, or images to draw.");
57 static DEFINE_string2(backend, b, "", "Backend used to create a canvas to draw into.");
58 
59 static DEFINE_string(ct    ,   "8888", "The color type for any raster backend.");
60 static DEFINE_string(at    , "premul", "The alpha type for any raster backend.");
61 static DEFINE_string(gamut ,   "srgb", "The color gamut for any raster backend.");
62 static DEFINE_string(tf    ,   "srgb", "The transfer function for any raster backend.");
63 static DEFINE_bool  (legacy,    false, "Use a null SkColorSpace instead of --gamut and --tf?");
64 
65 static DEFINE_bool  (skvm ,    false, "Use SkVMBlitter when supported?");
66 static DEFINE_bool  (jit  ,     true, "JIT SkVM?");
67 static DEFINE_bool  (dylib,    false, "JIT SkVM via dylib?");
68 
69 static DEFINE_bool  (reducedshaders,    false, "Use reduced shader set for any GPU backend.");
70 static DEFINE_int   (samples       ,         0, "Samples per pixel in GPU backends.");
71 static DEFINE_bool  (stencils      ,      true, "If false, avoid stencil buffers in GPU backends.");
72 static DEFINE_bool  (dit           ,     false, "Use device-independent text in GPU backends.");
73 static DEFINE_string(surf          , "default", "Backing store for GPU backend surfaces.");
74 
75 static DEFINE_bool(       preAbandonGpuContext, false, "Abandon the GrContext before drawing.");
76 static DEFINE_bool(          abandonGpuContext, false, "Abandon the GrContext after drawing.");
77 static DEFINE_bool(releaseAndAbandonGpuContext, false,
78                    "Release all GPU resources and abandon the GrContext after drawing.");
79 
80 static DEFINE_bool(decodeToDst, false,
81                    "Decode images to destination format rather than suggested natural format.");
82 
83 static DEFINE_double(rasterDPI, SK_ScalarDefaultRasterDPI,
84                      "DPI for rasterized content in vector backends like --backend pdf.");
85 static DEFINE_bool(PDFA, false, "Create PDF/A with --backend pdf?");
86 
87 static DEFINE_int(clipW, INT_MAX, "Limit source width.");
88 static DEFINE_int(clipH, INT_MAX, "Limit source height.");
89 
90 static DEFINE_bool   (cpuDetect, true, "Detect CPU features for runtime optimizations?");
91 static DEFINE_string2(writePath, w, "", "Write .pngs to this directory if set.");
92 static DEFINE_bool   (quick, false, "Skip image hashing and encoding?");
93 static DEFINE_int    (race, 0, "If >0, use threads to induce race conditions?");
94 
95 static DEFINE_string(writeShaders, "", "Write GLSL shaders to this directory if set.");
96 
97 static DEFINE_string(key,        "", "Metadata passed through to .png encoder and .json output.");
98 static DEFINE_string(properties, "", "Metadata passed through to .png encoder and .json output.");
99 
100 template <typename T>
101 struct FlagOption {
102     const char* label;
103     T           value;
104 };
105 
106 template <typename T, int N>
parse_flag(const CommandLineFlags::StringArray & flag,const char * flag_name,const FlagOption<T> (& array)[N],T * value)107 static bool parse_flag(const CommandLineFlags::StringArray& flag,
108                        const char* flag_name,
109                        const FlagOption<T> (&array)[N],
110                        T* value) {
111     for (auto entry : array) {
112         if (flag.contains(entry.label)) {
113             *value = entry.value;
114             return true;
115         }
116     }
117     fprintf(stderr, "Known values for --%s:\n", flag_name);
118     for (auto entry : array) {
119         fprintf(stderr, "    --%s %s\n", flag_name, entry.label);
120     }
121     return false;
122 }
123 
124 struct Result {
125     enum { Ok, Skip, Fail } status;
126     SkString                failure;
127 };
128 static const Result ok   = {Result::Ok,   {}},
129                     skip = {Result::Skip, {}};
130 
fail(SkString why)131 static Result fail(SkString why) {
132     return {Result::Fail, why};
133 }
134 
135 struct Source {
136     SkString                               name;
137     SkISize                                size;
138     std::function<Result(SkCanvas*)>       draw;
__anonc7e89a7a0202Source139     std::function<void(GrContextOptions*)> tweak = [](GrContextOptions*){};
140 };
141 
init(Source * source,std::shared_ptr<skiagm::GM> gm)142 static void init(Source* source, std::shared_ptr<skiagm::GM> gm) {
143     source->size  = gm->getISize();
144     source->tweak = [gm](GrContextOptions* options) { gm->modifyGrContextOptions(options); };
145     source->draw  = [gm](SkCanvas* canvas) {
146         auto direct = GrAsDirectContext(canvas->recordingContext());
147 
148         SkString err;
149         switch (gm->gpuSetup(direct, canvas, &err)) {
150             case skiagm::DrawResult::kOk  : break;
151             case skiagm::DrawResult::kSkip: return skip;
152             case skiagm::DrawResult::kFail: return fail(err);
153         }
154 
155         switch (gm->draw(canvas, &err)) {
156             case skiagm::DrawResult::kOk:   break;
157             case skiagm::DrawResult::kSkip: return skip;
158             case skiagm::DrawResult::kFail: return fail(err);
159         }
160         return ok;
161     };
162 }
163 
init(Source * source,sk_sp<SkPicture> pic)164 static void init(Source* source, sk_sp<SkPicture> pic) {
165     source->size = pic->cullRect().roundOut().size();
166     source->draw = [pic](SkCanvas* canvas) {
167         canvas->drawPicture(pic);
168         return ok;
169     };
170 }
171 
init(Source * source,std::shared_ptr<SkCodec> codec)172 static void init(Source* source, std::shared_ptr<SkCodec> codec) {
173     source->size = codec->dimensions();
174     source->draw = [codec](SkCanvas* canvas) {
175         SkImageInfo info = codec->getInfo();
176         if (FLAGS_decodeToDst) {
177             info = canvas->imageInfo().makeDimensions(info.dimensions());
178         }
179 
180         auto [image, result] = codec->getImage(info);
181         if (image) {
182             canvas->drawImage(image, 0,0);
183             return ok;
184         }
185         return fail(SkStringPrintf("codec->getPixels() failed: %d\n", result));
186     };
187 }
188 
189 #if defined(SK_ENABLE_SVG)
init(Source * source,sk_sp<SkSVGDOM> svg)190 static void init(Source* source, sk_sp<SkSVGDOM> svg) {
191     if (svg->containerSize().isEmpty()) {
192         svg->setContainerSize({1000,1000});
193     }
194     source->size = svg->containerSize().toCeil();
195     source->draw = [svg](SkCanvas* canvas) {
196         svg->render(canvas);
197         return ok;
198     };
199 }
200 #endif
201 
202 #if defined(SK_ENABLE_SKOTTIE)
init(Source * source,sk_sp<skottie::Animation> animation)203 static void init(Source* source, sk_sp<skottie::Animation> animation) {
204     source->size = {1000,1000};
205     source->draw = [animation](SkCanvas* canvas) {
206         canvas->clear(SK_ColorWHITE);
207 
208         // Draw frames in a shuffled order to exercise nonlinear frame progression.
209         // The film strip will still be in time order, just drawn out of order.
210         const int order[] = { 4, 0, 3, 1, 2 };
211         const int tiles = SK_ARRAY_COUNT(order);
212         const float dim = 1000.0f / tiles;
213 
214         const float dt = 1.0f / (tiles*tiles - 1);
215 
216         for (int y : order)
217         for (int x : order) {
218             SkRect dst = {x*dim, y*dim, (x+1)*dim, (y+1)*dim};
219 
220             SkAutoCanvasRestore _(canvas, /*doSave=*/true);
221             canvas->clipRect(dst, /*doAntiAlias=*/true);
222             canvas->concat(SkMatrix::RectToRect(SkRect::MakeSize(animation->size()), dst,
223                                                 SkMatrix::kCenter_ScaleToFit));
224             float t = (y*tiles + x) * dt;
225             animation->seek(t);
226             animation->render(canvas);
227         }
228         return ok;
229     };
230 }
231 #endif
232 
init(Source * source,const skiatest::Test & test)233 static void init(Source* source, const skiatest::Test& test) {
234     source->size  = {1,1};
235     source->draw  = [test](SkCanvas* canvas) {
236         struct Reporter : public skiatest::Reporter {
237             SkString msg;
238 
239             void reportFailed(const skiatest::Failure& failure) override {
240                 msg += failure.toString();
241                 msg += "\n";
242             }
243         } reporter;
244 
245         test.run(&reporter, GrContextOptions{});
246 
247         if (reporter.msg.isEmpty()) {
248             canvas->clear(SK_ColorGREEN);
249             return ok;
250         }
251 
252         canvas->clear(SK_ColorRED);
253         return fail(reporter.msg);
254     };
255 }
256 
draw_with_cpu(std::function<bool (SkCanvas *)> draw,SkImageInfo info)257 static sk_sp<SkImage> draw_with_cpu(std::function<bool(SkCanvas*)> draw,
258                                     SkImageInfo info) {
259     if (sk_sp<SkSurface> surface = SkSurface::MakeRaster(info)) {
260         if (draw(surface->getCanvas())) {
261             return surface->makeImageSnapshot();
262         }
263     }
264     return nullptr;
265 }
266 
draw_as_skp(std::function<bool (SkCanvas *)> draw,SkImageInfo info)267 static sk_sp<SkData> draw_as_skp(std::function<bool(SkCanvas*)> draw,
268                                  SkImageInfo info) {
269     SkPictureRecorder recorder;
270     if (draw(recorder.beginRecording(info.width(), info.height()))) {
271         return recorder.finishRecordingAsPicture()->serialize();
272     }
273     return nullptr;
274 }
275 
draw_as_pdf(std::function<bool (SkCanvas *)> draw,SkImageInfo info,SkString name)276 static sk_sp<SkData> draw_as_pdf(std::function<bool(SkCanvas*)> draw,
277                                  SkImageInfo info,
278                                  SkString name) {
279     SkPDF::Metadata metadata;
280     metadata.fTitle     = name;
281     metadata.fCreator   = "Skia/FM";
282     metadata.fRasterDPI = FLAGS_rasterDPI;
283     metadata.fPDFA      = FLAGS_PDFA;
284 
285     SkDynamicMemoryWStream stream;
286     if (sk_sp<SkDocument> doc = SkPDF::MakeDocument(&stream, metadata)) {
287         if (draw(doc->beginPage(info.width(), info.height()))) {
288             doc->endPage();
289             doc->close();
290             return stream.detachAsData();
291         }
292     }
293     return nullptr;
294 }
295 
draw_with_gpu(std::function<bool (SkCanvas *)> draw,SkImageInfo info,GrContextFactory::ContextType api,GrContextFactory * factory)296 static sk_sp<SkImage> draw_with_gpu(std::function<bool(SkCanvas*)> draw,
297                                     SkImageInfo info,
298                                     GrContextFactory::ContextType api,
299                                     GrContextFactory* factory) {
300     enum class SurfaceType { kDefault, kBackendTexture, kBackendRenderTarget };
301     const FlagOption<SurfaceType> kSurfaceTypes[] = {
302         { "default", SurfaceType::kDefault },
303         { "betex"  , SurfaceType::kBackendTexture },
304         { "bert"   , SurfaceType::kBackendRenderTarget },
305     };
306     SurfaceType surfaceType;
307     if (!parse_flag(FLAGS_surf, "surf", kSurfaceTypes, &surfaceType)) {
308         return nullptr;
309     }
310 
311     auto overrides = GrContextFactory::ContextOverrides::kNone;
312     if (!FLAGS_stencils) { overrides |= GrContextFactory::ContextOverrides::kAvoidStencilBuffers; }
313 
314     auto context = factory->getContextInfo(api, overrides).directContext();
315 
316     uint32_t flags = FLAGS_dit ? SkSurfaceProps::kUseDeviceIndependentFonts_Flag
317                                : 0;
318     SkSurfaceProps props(flags, kRGB_H_SkPixelGeometry);
319 
320     sk_sp<SkSurface> surface;
321 
322     switch (surfaceType) {
323         case SurfaceType::kDefault:
324             surface = SkSurface::MakeRenderTarget(context,
325                                                   SkBudgeted::kNo,
326                                                   info,
327                                                   FLAGS_samples,
328                                                   &props);
329             break;
330 
331         case SurfaceType::kBackendTexture:
332             surface = sk_gpu_test::MakeBackendTextureSurface(context,
333                                                              info,
334                                                              kTopLeft_GrSurfaceOrigin,
335                                                              FLAGS_samples,
336                                                              GrMipmapped::kNo,
337                                                              GrProtected::kNo,
338                                                              &props);
339             break;
340 
341         case SurfaceType::kBackendRenderTarget:
342             surface = sk_gpu_test::MakeBackendRenderTargetSurface(context,
343                                                                   info,
344                                                                   kBottomLeft_GrSurfaceOrigin,
345                                                                   FLAGS_samples,
346                                                                   GrProtected::kNo,
347                                                                   &props);
348             break;
349     }
350 
351     if (!surface) {
352         fprintf(stderr, "Could not create GPU surface.\n");
353         return nullptr;
354     }
355 
356     if (FLAGS_preAbandonGpuContext) {
357         factory->abandonContexts();
358     }
359 
360     sk_sp<SkImage> image;
361     if (draw(surface->getCanvas())) {
362         image = surface->makeImageSnapshot();
363     }
364 
365     if (FLAGS_abandonGpuContext) {
366         factory->abandonContexts();
367     } else if (FLAGS_releaseAndAbandonGpuContext) {
368         factory->releaseResourcesAndAbandonContexts();
369     }
370 
371     return image;
372 }
373 
CurrentTestHarness()374 TestHarness CurrentTestHarness() {
375     return TestHarness::kFM;
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         { "r8",                 kR8_unorm_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