• 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 "experimental/svg/model/SkSVGDOM.h"
5 #include "gm/gm.h"
6 #include "include/codec/SkCodec.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/private/SkTHash.h"
14 #include "src/core/SkColorSpacePriv.h"
15 #include "src/core/SkMD5.h"
16 #include "src/core/SkOSFile.h"
17 #include "src/gpu/GrContextPriv.h"
18 #include "src/gpu/GrGpu.h"
19 #include "src/utils/SkOSPath.h"
20 #include "tools/AutoreleasePool.h"
21 #include "tools/CrashHandler.h"
22 #include "tools/HashAndEncode.h"
23 #include "tools/ToolUtils.h"
24 #include "tools/flags/CommandLineFlags.h"
25 #include "tools/flags/CommonFlags.h"
26 #include "tools/gpu/GrContextFactory.h"
27 #include "tools/gpu/MemoryCache.h"
28 #include "tools/trace/EventTracingPriv.h"
29 #include <chrono>
30 #include <functional>
31 #include <stdio.h>
32 #include <stdlib.h>
33 
34 #if defined(SK_ENABLE_SKOTTIE)
35     #include "modules/skottie/include/Skottie.h"
36     #include "modules/skottie/utils/SkottieUtils.h"
37 #endif
38 
39 using sk_gpu_test::GrContextFactory;
40 
41 static DEFINE_string2(sources, s, "", "Which GMs, .skps, or images to draw.");
42 static DEFINE_string2(backend, b, "", "Backend used to create a canvas to draw into.");
43 
44 static DEFINE_string(ct    ,   "8888", "The color type for any raster backend.");
45 static DEFINE_string(at    , "premul", "The alpha type for any raster backend.");
46 static DEFINE_string(gamut ,   "srgb", "The color gamut for any raster backend.");
47 static DEFINE_string(tf    ,   "srgb", "The transfer function for any raster backend.");
48 static DEFINE_bool  (legacy,    false, "Use a null SkColorSpace instead of --gamut and --tf?");
49 
50 static DEFINE_int   (samples ,         0, "Samples per pixel in GPU backends.");
51 static DEFINE_bool  (stencils,      true, "If false, avoid stencil buffers in GPU backends.");
52 static DEFINE_bool  (dit     ,     false, "Use device-independent text in GPU backends.");
53 static DEFINE_string(surf    , "default", "Backing store for GPU backend surfaces.");
54 
55 static DEFINE_bool(       preAbandonGpuContext, false, "Abandon the GrContext before drawing.");
56 static DEFINE_bool(          abandonGpuContext, false, "Abandon the GrContext after drawing.");
57 static DEFINE_bool(releaseAndAbandonGpuContext, false,
58                    "Release all GPU resources and abandon the GrContext after drawing.");
59 
60 static DEFINE_bool(decodeToDst, false,
61                    "Decode images to destination format rather than suggested natural format.");
62 
63 static DEFINE_double(rasterDPI, SK_ScalarDefaultRasterDPI,
64                      "DPI for rasterized content in vector backends like --backend pdf.");
65 static DEFINE_bool(PDFA, false, "Create PDF/A with --backend pdf?");
66 
67 static DEFINE_bool   (cpuDetect, true, "Detect CPU features for runtime optimizations?");
68 static DEFINE_string2(writePath, w, "", "Write .pngs to this directory if set.");
69 
70 static DEFINE_string(writeShaders, "", "Write GLSL shaders to this directory if set.");
71 
72 static DEFINE_string(key,        "", "Metadata passed through to .png encoder and .json output.");
73 static DEFINE_string(properties, "", "Metadata passed through to .png encoder and .json output.");
74 
75 template <typename T>
76 struct FlagOption {
77     const char* label;
78     T           value;
79 };
80 
81 template <typename T, int N>
parse_flag(const CommandLineFlags::StringArray & flag,const char * flag_name,const FlagOption<T> (& array)[N],T * value)82 static bool parse_flag(const CommandLineFlags::StringArray& flag,
83                        const char* flag_name,
84                        const FlagOption<T> (&array)[N],
85                        T* value) {
86     for (auto entry : array) {
87         if (flag.contains(entry.label)) {
88             *value = entry.value;
89             return true;
90         }
91     }
92     fprintf(stderr, "Known values for --%s:\n", flag_name);
93     for (auto entry : array) {
94         fprintf(stderr, "    --%s %s\n", flag_name, entry.label);
95     }
96     return false;
97 }
98 
99 struct Result {
100     enum { Ok, Skip, Fail} status;
101     SkString               failure;
102 };
103 static const Result ok = {Result::Ok,   {}},
104                   skip = {Result::Skip, {}};
105 
106 template <typename... Args>
fail(const char * why,Args...args)107 static Result fail(const char* why, Args... args) {
108     return { Result::Fail, SkStringPrintf(why, args...) };
109 }
110 
111 
112 struct Source {
113     SkString                               name;
114     SkISize                                size;
115     std::function<Result(SkCanvas*)>       draw;
__anon684cdf960202Source116     std::function<void(GrContextOptions*)> tweak = [](GrContextOptions*){};
117 };
118 
init(Source * source,std::shared_ptr<skiagm::GM> gm)119 static void init(Source* source, std::shared_ptr<skiagm::GM> gm) {
120     source->size  = gm->getISize();
121     source->tweak = [gm](GrContextOptions* options) { gm->modifyGrContextOptions(options); };
122     source->draw  = [gm](SkCanvas* canvas) {
123         SkString err;
124         switch (gm->draw(canvas, &err)) {
125             case skiagm::DrawResult::kOk:   break;
126             case skiagm::DrawResult::kSkip: return skip;
127             case skiagm::DrawResult::kFail: return fail(err.c_str());
128         }
129         return ok;
130     };
131 }
132 
init(Source * source,sk_sp<SkPicture> pic)133 static void init(Source* source, sk_sp<SkPicture> pic) {
134     source->size = pic->cullRect().roundOut().size();
135     source->draw = [pic](SkCanvas* canvas) {
136         canvas->drawPicture(pic);
137         return ok;
138     };
139 }
140 
init(Source * source,std::shared_ptr<SkCodec> codec)141 static void init(Source* source, std::shared_ptr<SkCodec> codec) {
142     source->size = codec->dimensions();
143     source->draw = [codec](SkCanvas* canvas) {
144         SkImageInfo info = codec->getInfo();
145         if (FLAGS_decodeToDst) {
146             info = canvas->imageInfo().makeWH(info.width(),
147                                               info.height());
148         }
149 
150         SkBitmap bm;
151         bm.allocPixels(info);
152         switch (SkCodec::Result result = codec->getPixels(info, bm.getPixels(), bm.rowBytes())) {
153             case SkCodec::kSuccess:
154             case SkCodec::kErrorInInput:
155             case SkCodec::kIncompleteInput: canvas->drawBitmap(bm, 0,0);
156                                             break;
157             default: return fail("codec->getPixels() failed: %d\n", result);
158         }
159         return ok;
160     };
161 }
162 
init(Source * source,sk_sp<SkSVGDOM> svg)163 static void init(Source* source, sk_sp<SkSVGDOM> svg) {
164     source->size = svg->containerSize().isEmpty() ? SkISize{1000,1000}
165                                                   : svg->containerSize().toCeil();
166     source->draw = [svg](SkCanvas* canvas) {
167         svg->render(canvas);
168         return ok;
169     };
170 }
171 
172 #if defined(SK_ENABLE_SKOTTIE)
init(Source * source,sk_sp<skottie::Animation> animation)173 static void init(Source* source, sk_sp<skottie::Animation> animation) {
174     source->size = {1000,1000};
175     source->draw = [animation](SkCanvas* canvas) {
176         canvas->clear(SK_ColorWHITE);
177 
178         // Draw frames in a shuffled order to exercise nonlinear frame progression.
179         // The film strip will still be in time order, just drawn out of order.
180         const int order[] = { 4, 0, 3, 1, 2 };
181         const int tiles = SK_ARRAY_COUNT(order);
182         const float dim = 1000.0f / tiles;
183 
184         const float dt = 1.0f / (tiles*tiles - 1);
185 
186         for (int y : order)
187         for (int x : order) {
188             SkRect dst = {x*dim, y*dim, (x+1)*dim, (y+1)*dim};
189 
190             SkAutoCanvasRestore _(canvas, true/*save now*/);
191             canvas->clipRect(dst, /*aa=*/true);
192             canvas->concat(SkMatrix::MakeRectToRect(SkRect::MakeSize(animation->size()),
193                                                     dst,
194                                                     SkMatrix::kCenter_ScaleToFit));
195             float t = (y*tiles + x) * dt;
196             animation->seek(t);
197             animation->render(canvas);
198         }
199         return ok;
200     };
201 }
202 #endif
203 
draw_with_cpu(std::function<bool (SkCanvas *)> draw,SkImageInfo info)204 static sk_sp<SkImage> draw_with_cpu(std::function<bool(SkCanvas*)> draw,
205                                     SkImageInfo info) {
206     if (sk_sp<SkSurface> surface = SkSurface::MakeRaster(info)) {
207         if (draw(surface->getCanvas())) {
208             return surface->makeImageSnapshot();
209         }
210     }
211     return nullptr;
212 }
213 
draw_as_skp(std::function<bool (SkCanvas *)> draw,SkImageInfo info)214 static sk_sp<SkData> draw_as_skp(std::function<bool(SkCanvas*)> draw,
215                                  SkImageInfo info) {
216     SkPictureRecorder recorder;
217     if (draw(recorder.beginRecording(info.width(), info.height()))) {
218         return recorder.finishRecordingAsPicture()->serialize();
219     }
220     return nullptr;
221 }
222 
draw_as_pdf(std::function<bool (SkCanvas *)> draw,SkImageInfo info,SkString name)223 static sk_sp<SkData> draw_as_pdf(std::function<bool(SkCanvas*)> draw,
224                                  SkImageInfo info,
225                                  SkString name) {
226     SkPDF::Metadata metadata;
227     metadata.fTitle     = name;
228     metadata.fCreator   = "Skia/FM";
229     metadata.fRasterDPI = FLAGS_rasterDPI;
230     metadata.fPDFA      = FLAGS_PDFA;
231 
232     SkDynamicMemoryWStream stream;
233     if (sk_sp<SkDocument> doc = SkPDF::MakeDocument(&stream, metadata)) {
234         if (draw(doc->beginPage(info.width(), info.height()))) {
235             doc->endPage();
236             doc->close();
237             return stream.detachAsData();
238         }
239     }
240     return nullptr;
241 }
242 
draw_with_gpu(std::function<bool (SkCanvas *)> draw,SkImageInfo info,GrContextFactory::ContextType api,GrContextFactory * factory)243 static sk_sp<SkImage> draw_with_gpu(std::function<bool(SkCanvas*)> draw,
244                                     SkImageInfo info,
245                                     GrContextFactory::ContextType api,
246                                     GrContextFactory* factory) {
247     enum class SurfaceType { kDefault, kBackendTexture, kBackendRenderTarget };
248     const FlagOption<SurfaceType> kSurfaceTypes[] = {
249         { "default", SurfaceType::kDefault },
250         { "betex"  , SurfaceType::kBackendTexture },
251         { "bert"   , SurfaceType::kBackendRenderTarget },
252     };
253     SurfaceType surfaceType;
254     if (!parse_flag(FLAGS_surf, "surf", kSurfaceTypes, &surfaceType)) {
255         return nullptr;
256     }
257 
258     auto overrides = GrContextFactory::ContextOverrides::kNone;
259     if (!FLAGS_stencils) { overrides |= GrContextFactory::ContextOverrides::kAvoidStencilBuffers; }
260 
261     GrContext* context = factory->getContextInfo(api, overrides)
262                                  .grContext();
263 
264     uint32_t flags = FLAGS_dit ? SkSurfaceProps::kUseDeviceIndependentFonts_Flag
265                                : 0;
266     SkSurfaceProps props(flags, SkSurfaceProps::kLegacyFontHost_InitType);
267 
268     sk_sp<SkSurface> surface;
269     GrBackendTexture backendTexture;
270     GrBackendRenderTarget backendRT;
271 
272     switch (surfaceType) {
273         case SurfaceType::kDefault:
274             surface = SkSurface::MakeRenderTarget(context,
275                                                   SkBudgeted::kNo,
276                                                   info,
277                                                   FLAGS_samples,
278                                                   &props);
279             break;
280 
281         case SurfaceType::kBackendTexture:
282             backendTexture = context->createBackendTexture(info.width(),
283                                                            info.height(),
284                                                            info.colorType(),
285                                                            GrMipMapped::kNo,
286                                                            GrRenderable::kYes,
287                                                            GrProtected::kNo);
288             surface = SkSurface::MakeFromBackendTexture(context,
289                                                         backendTexture,
290                                                         kTopLeft_GrSurfaceOrigin,
291                                                         FLAGS_samples,
292                                                         info.colorType(),
293                                                         info.refColorSpace(),
294                                                         &props);
295             break;
296 
297         case SurfaceType::kBackendRenderTarget:
298             backendRT = context->priv().getGpu()
299                 ->createTestingOnlyBackendRenderTarget(info.width(),
300                                                        info.height(),
301                                                        SkColorTypeToGrColorType(info.colorType()));
302             surface = SkSurface::MakeFromBackendRenderTarget(context,
303                                                              backendRT,
304                                                              kBottomLeft_GrSurfaceOrigin,
305                                                              info.colorType(),
306                                                              info.refColorSpace(),
307                                                              &props);
308             break;
309     }
310 
311     if (!surface) {
312         fprintf(stderr, "Could not create GPU surface.\n");
313         return nullptr;
314     }
315 
316     if (FLAGS_preAbandonGpuContext) {
317         factory->abandonContexts();
318     }
319 
320     sk_sp<SkImage> image;
321     if (draw(surface->getCanvas())) {
322         image = surface->makeImageSnapshot();
323     }
324 
325     if (FLAGS_abandonGpuContext) {
326         factory->abandonContexts();
327     } else if (FLAGS_releaseAndAbandonGpuContext) {
328         factory->releaseResourcesAndAbandonContexts();
329     }
330 
331     if (!context->abandoned()) {
332         surface.reset();
333         if (backendTexture.isValid()) {
334             context->deleteBackendTexture(backendTexture);
335         }
336         if (backendRT.isValid()) {
337             context->priv().getGpu()->deleteTestingOnlyBackendRenderTarget(backendRT);
338         }
339     }
340 
341     return image;
342 }
343 
main(int argc,char ** argv)344 int main(int argc, char** argv) {
345     CommandLineFlags::Parse(argc, argv);
346     SetupCrashHandler();
347 
348     if (FLAGS_cpuDetect) {
349         SkGraphics::Init();
350     }
351     initializeEventTracingForTools();
352     ToolUtils::SetDefaultFontMgr();
353     SetAnalyticAAFromCommonFlags();
354 
355     GrContextOptions baseOptions;
356     SetCtxOptionsFromCommonFlags(&baseOptions);
357 
358     sk_gpu_test::MemoryCache memoryCache;
359     if (!FLAGS_writeShaders.isEmpty()) {
360         baseOptions.fPersistentCache = &memoryCache;
361         baseOptions.fDisallowGLSLBinaryCaching = true;
362     }
363 
364     SkTHashMap<SkString, skiagm::GMFactory> gm_factories;
365     for (skiagm::GMFactory factory : skiagm::GMRegistry::Range()) {
366         std::unique_ptr<skiagm::GM> gm{factory()};
367         if (FLAGS_sources.isEmpty()) {
368             fprintf(stdout, "%s\n", gm->getName());
369         } else {
370             gm_factories.set(SkString{gm->getName()}, factory);
371         }
372     }
373     if (FLAGS_sources.isEmpty()) {
374         return 0;
375     }
376 
377     SkTArray<Source> sources;
378     for (const SkString& name : FLAGS_sources) {
379         Source* source = &sources.push_back();
380 
381         if (skiagm::GMFactory* factory = gm_factories.find(name)) {
382             std::shared_ptr<skiagm::GM> gm{(*factory)()};
383             source->name = name;
384             init(source, std::move(gm));
385             continue;
386         }
387 
388         if (sk_sp<SkData> blob = SkData::MakeFromFileName(name.c_str())) {
389             source->name = SkOSPath::Basename(name.c_str());
390 
391             if (name.endsWith(".skp")) {
392                 if (sk_sp<SkPicture> pic = SkPicture::MakeFromData(blob.get())) {
393                     init(source, pic);
394                     continue;
395                 }
396             } else if (name.endsWith(".svg")) {
397                 SkMemoryStream stream{blob};
398                 if (sk_sp<SkSVGDOM> svg = SkSVGDOM::MakeFromStream(stream)) {
399                     init(source, svg);
400                     continue;
401                 }
402             }
403 #if defined(SK_ENABLE_SKOTTIE)
404             else if (name.endsWith(".json")) {
405                 const SkString dir  = SkOSPath::Dirname(name.c_str());
406                 if (sk_sp<skottie::Animation> animation = skottie::Animation::Builder()
407                         .setResourceProvider(skottie_utils::FileResourceProvider::Make(dir))
408                         .make((const char*)blob->data(), blob->size())) {
409                     init(source, animation);
410                     continue;
411                 }
412             }
413 #endif
414             else if (std::shared_ptr<SkCodec> codec = SkCodec::MakeFromData(blob)) {
415                 init(source, codec);
416                 continue;
417             }
418         }
419 
420         fprintf(stderr, "Don't understand source '%s'... bailing out.\n", name.c_str());
421         return 1;
422     }
423 
424     enum NonGpuBackends {
425         kCPU_Backend = -1,
426         kSKP_Backend = -2,
427         kPDF_Backend = -3,
428     };
429     const FlagOption<int> kBackends[] = {
430         { "cpu"            , kCPU_Backend },
431         { "skp"            , kSKP_Backend },
432         { "pdf"            , kPDF_Backend },
433         { "gl"             , GrContextFactory::kGL_ContextType },
434         { "gles"           , GrContextFactory::kGLES_ContextType },
435         { "angle_d3d9_es2" , GrContextFactory::kANGLE_D3D9_ES2_ContextType },
436         { "angle_d3d11_es2", GrContextFactory::kANGLE_D3D11_ES2_ContextType },
437         { "angle_d3d11_es3", GrContextFactory::kANGLE_D3D11_ES3_ContextType },
438         { "angle_gl_es2"   , GrContextFactory::kANGLE_GL_ES2_ContextType },
439         { "angle_gl_es3"   , GrContextFactory::kANGLE_GL_ES3_ContextType },
440         { "commandbuffer"  , GrContextFactory::kCommandBuffer_ContextType },
441         { "vk"             , GrContextFactory::kVulkan_ContextType },
442         { "mtl"            , GrContextFactory::kMetal_ContextType },
443         { "mock"           , GrContextFactory::kMock_ContextType },
444     };
445     const FlagOption<SkColorType> kColorTypes[] = {
446         { "a8",           kAlpha_8_SkColorType },
447         { "g8",            kGray_8_SkColorType },
448         { "565",          kRGB_565_SkColorType },
449         { "4444",       kARGB_4444_SkColorType },
450         { "8888",             kN32_SkColorType },
451         { "888x",        kRGB_888x_SkColorType },
452         { "1010102", kRGBA_1010102_SkColorType },
453         { "101010x",  kRGB_101010x_SkColorType },
454         { "f16norm", kRGBA_F16Norm_SkColorType },
455         { "f16",         kRGBA_F16_SkColorType },
456         { "f32",         kRGBA_F32_SkColorType },
457         { "rgba",       kRGBA_8888_SkColorType },
458         { "bgra",       kBGRA_8888_SkColorType },
459     };
460     const FlagOption<SkAlphaType> kAlphaTypes[] = {
461         {   "premul",   kPremul_SkAlphaType },
462         { "unpremul", kUnpremul_SkAlphaType },
463     };
464     const FlagOption<skcms_Matrix3x3> kGamuts[] = {
465         { "srgb",    SkNamedGamut::kSRGB },
466         { "p3",      SkNamedGamut::kDCIP3 },
467         { "rec2020", SkNamedGamut::kRec2020 },
468         { "adobe",   SkNamedGamut::kAdobeRGB },
469         { "narrow",  gNarrow_toXYZD50},
470     };
471     const FlagOption<skcms_TransferFunction> kTransferFunctions[] = {
472         { "srgb"   , SkNamedTransferFn::kSRGB },
473         { "rec2020", SkNamedTransferFn::kRec2020 },
474         { "2.2"    , SkNamedTransferFn::k2Dot2 },
475         { "linear" , SkNamedTransferFn::kLinear },
476     };
477 
478 
479     int                      backend;
480     SkColorType              ct;
481     SkAlphaType              at;
482     skcms_Matrix3x3          gamut;
483     skcms_TransferFunction   tf;
484 
485     if (!parse_flag(FLAGS_backend, "backend", kBackends         , &backend) ||
486         !parse_flag(FLAGS_ct     , "ct"     , kColorTypes       , &ct)      ||
487         !parse_flag(FLAGS_at     , "at"     , kAlphaTypes       , &at)      ||
488         !parse_flag(FLAGS_gamut  , "gamut"  , kGamuts           , &gamut)   ||
489         !parse_flag(FLAGS_tf     , "tf"     , kTransferFunctions, &tf)) {
490         return 1;
491     }
492 
493     sk_sp<SkColorSpace> cs = FLAGS_legacy ? nullptr
494                                           : SkColorSpace::MakeRGB(tf,gamut);
495     const SkImageInfo unsized_info = SkImageInfo::Make(0,0, ct,at,cs);
496 
497     AutoreleasePool pool;
498     for (auto source : sources) {
499         const auto start = std::chrono::steady_clock::now();
500         fprintf(stdout, "%50s", source.name.c_str());
501         fflush(stdout);
502 
503         const SkImageInfo info = unsized_info.makeWH(source.size.width(),
504                                                      source.size.height());
505 
506         auto draw = [&source](SkCanvas* canvas) {
507             Result result = source.draw(canvas);
508             switch (result.status) {
509                 case Result::Ok:   break;
510                 case Result::Skip: return false;
511                 case Result::Fail:
512                     SK_ABORT(result.failure.c_str());
513             }
514             return true;
515         };
516 
517         GrContextOptions options = baseOptions;
518         source.tweak(&options);
519         GrContextFactory factory(options);  // N.B. factory must outlive image
520 
521         sk_sp<SkImage> image;
522         sk_sp<SkData>  blob;
523         const char*    ext = ".png";
524         switch (backend) {
525             case kCPU_Backend:
526                 image = draw_with_cpu(draw, info);
527                 break;
528             case kSKP_Backend:
529                 blob = draw_as_skp(draw, info);
530                 ext  = ".skp";
531                 break;
532             case kPDF_Backend:
533                 blob = draw_as_pdf(draw, info, source.name);
534                 ext  = ".pdf";
535                 break;
536             default:
537                 image = draw_with_gpu(draw, info, (GrContextFactory::ContextType)backend, &factory);
538                 break;
539         }
540 
541         if (!image && !blob) {
542             fprintf(stdout, "\tskipped\n");
543             continue;
544         }
545 
546         SkBitmap bitmap;
547         if (image && !image->asLegacyBitmap(&bitmap)) {
548             SK_ABORT("SkImage::asLegacyBitmap() failed.");
549         }
550 
551         HashAndEncode hashAndEncode{bitmap};
552         SkString md5;
553         {
554             SkMD5 hash;
555             if (image) {
556                 hashAndEncode.write(&hash);
557             } else {
558                 hash.write(blob->data(), blob->size());
559             }
560 
561             SkMD5::Digest digest = hash.finish();
562             for (int i = 0; i < 16; i++) {
563                 md5.appendf("%02x", digest.data[i]);
564             }
565         }
566 
567         if (!FLAGS_writePath.isEmpty()) {
568             sk_mkdir(FLAGS_writePath[0]);
569             SkString path = SkStringPrintf("%s/%s%s", FLAGS_writePath[0], source.name.c_str(), ext);
570 
571             if (image) {
572                 if (!hashAndEncode.writePngTo(path.c_str(), md5.c_str(),
573                                               FLAGS_key, FLAGS_properties)) {
574                     SK_ABORT("Could not write .png.");
575                 }
576             } else {
577                 SkFILEWStream file(path.c_str());
578                 file.write(blob->data(), blob->size());
579             }
580         }
581 
582         const auto elapsed = std::chrono::steady_clock::now() - start;
583         fprintf(stdout, "\t%s\t%7dms\n",
584                 md5.c_str(),
585                 (int)std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count());
586         pool.drain();
587     }
588 
589     if (!FLAGS_writeShaders.isEmpty()) {
590         sk_mkdir(FLAGS_writeShaders[0]);
591         GrBackendApi api =
592                 GrContextFactory::ContextTypeBackend((GrContextFactory::ContextType)backend);
593         memoryCache.writeShadersToDisk(FLAGS_writeShaders[0], api);
594 
595     }
596 
597     return 0;
598 }
599