fm.cpp 22 KB

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