1 /*
2 * Copyright 2013 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "dm/DMJsonWriter.h"
9 #include "dm/DMSrcSink.h"
10 #include "gm/verifiers/gmverifier.h"
11 #include "include/codec/SkCodec.h"
12 #include "include/core/SkBBHFactory.h"
13 #include "include/core/SkColorPriv.h"
14 #include "include/core/SkColorSpace.h"
15 #include "include/core/SkData.h"
16 #include "include/core/SkDocument.h"
17 #include "include/core/SkGraphics.h"
18 #include "include/private/SkChecksum.h"
19 #include "include/private/SkSpinlock.h"
20 #include "src/base/SkHalf.h"
21 #include "src/base/SkLeanWindows.h"
22 #include "src/core/SkColorSpacePriv.h"
23 #include "src/core/SkMD5.h"
24 #include "src/core/SkOSFile.h"
25 #include "src/core/SkTHash.h"
26 #include "src/core/SkTaskGroup.h"
27 #include "src/utils/SkOSPath.h"
28 #include "tests/Test.h"
29 #include "tests/TestHarness.h"
30 #include "tools/AutoreleasePool.h"
31 #include "tools/HashAndEncode.h"
32 #include "tools/ProcStats.h"
33 #include "tools/Resources.h"
34 #include "tools/ToolUtils.h"
35 #include "tools/flags/CommonFlags.h"
36 #include "tools/flags/CommonFlagsConfig.h"
37 #include "tools/ios_utils.h"
38 #include "tools/trace/ChromeTracingTracer.h"
39 #include "tools/trace/EventTracingPriv.h"
40 #include "tools/trace/SkDebugfTracer.h"
41
42 #include <memory>
43 #include <vector>
44
45 #include <stdlib.h>
46
47 #ifndef SK_BUILD_FOR_WIN
48 #include <unistd.h>
49 #endif
50
51 #if defined(SK_BUILD_FOR_ANDROID_FRAMEWORK) && defined(SK_HAS_HEIF_LIBRARY)
52 #include <binder/IPCThreadState.h>
53 #endif
54
55 #if defined(SK_BUILD_FOR_MAC)
56 #include "include/utils/mac/SkCGUtils.h"
57 #include "src/utils/mac/SkUniqueCFRef.h"
58 #endif
59
60 #if defined(SK_ENABLE_SVG)
61 #include "modules/svg/include/SkSVGOpenTypeSVGDecoder.h"
62 #endif
63
64 extern bool gSkForceRasterPipelineBlitter;
65 extern bool gForceHighPrecisionRasterPipeline;
66 extern bool gUseSkVMBlitter;
67 extern bool gSkVMAllowJIT;
68 extern bool gSkVMJITViaDylib;
69 extern bool gSkBlobAsSlugTesting;
70
71 static DEFINE_string(src, "tests gm skp mskp lottie rive svg image colorImage",
72 "Source types to test.");
73 static DEFINE_bool(nameByHash, false,
74 "If true, write to FLAGS_writePath[0]/<hash>.png instead of "
75 "to FLAGS_writePath[0]/<config>/<sourceType>/<sourceOptions>/<name>.png");
76 static DEFINE_bool2(pathOpsExtended, x, false, "Run extended pathOps tests.");
77 static DEFINE_string(matrix, "1 0 0 1",
78 "2x2 scale+skew matrix to apply or upright when using "
79 "'matrix' or 'upright' in config.");
80
81 static DEFINE_string(skip, "",
82 "Space-separated config/src/srcOptions/name quadruples to skip. "
83 "'_' matches anything. '~' negates the match. E.g. \n"
84 "'--skip gpu skp _ _' will skip all SKPs drawn into the gpu config.\n"
85 "'--skip gpu skp _ _ 8888 gm _ aarects' will also skip the aarects GM on 8888.\n"
86 "'--skip ~8888 svg _ svgparse_' blocks non-8888 SVGs that contain \"svgparse_\" in "
87 "the name.");
88
89 static DEFINE_string2(readPath, r, "",
90 "If set check for equality with golden results in this directory.");
91 DEFINE_string2(writePath, w, "", "If set, write bitmaps here as .pngs.");
92
93
94 static DEFINE_string(uninterestingHashesFile, "",
95 "File containing a list of uninteresting hashes. If a result hashes to something in "
96 "this list, no image is written for that result.");
97
98 static DEFINE_int(shards, 1, "We're splitting source data into this many shards.");
99 static DEFINE_int(shard, 0, "Which shard do I run?");
100
101 static DEFINE_string(mskps, "", "Directory to read mskps from, or a single mskp file.");
102 static DEFINE_bool(forceRasterPipeline, false, "sets gSkForceRasterPipelineBlitter");
103 static DEFINE_bool(forceRasterPipelineHP, false, "sets gSkForceRasterPipelineBlitter and gForceHighPrecisionRasterPipeline");
104 static DEFINE_bool(skvm, false, "sets gUseSkVMBlitter");
105 static DEFINE_bool(jit, true, "sets gSkVMAllowJIT");
106 static DEFINE_bool(dylib, false, "JIT via dylib (much slower compile but easier to debug/profile)");
107 static DEFINE_bool(blobAsSlugTesting, false, "sets gSkBlobAsSlugTesting");
108
109 static DEFINE_string(bisect, "",
110 "Pair of: SKP file to bisect, followed by an l/r bisect trail string (e.g., 'lrll'). The "
111 "l/r trail specifies which half to keep at each step of a binary search through the SKP's "
112 "paths. An empty string performs no bisect. Only the SkPaths are bisected; all other draws "
113 "are thrown out. This is useful for finding a reduced repo case for path drawing bugs.");
114
115 static DEFINE_bool(ignoreSigInt, false, "ignore SIGINT signals during test execution");
116
117 static DEFINE_bool(checkF16, false, "Ensure that F16Norm pixels are clamped.");
118
119 static DEFINE_string(colorImages, "",
120 "List of images and/or directories to decode with color correction. "
121 "A directory with no images is treated as a fatal error.");
122
123 static DEFINE_bool2(veryVerbose, V, false, "tell individual tests to be verbose.");
124
125 static DEFINE_bool(cpu, true, "Run CPU-bound work?");
126 static DEFINE_bool(gpu, true, "Run GPU-bound work?");
127 static DEFINE_bool(graphite, true, "Run Graphite work?");
128
129 static DEFINE_bool(dryRun, false,
130 "just print the tests that would be run, without actually running them.");
131
132 static DEFINE_string(images, "",
133 "List of images and/or directories to decode. A directory with no images"
134 " is treated as a fatal error.");
135
136 static DEFINE_bool(simpleCodec, false,
137 "Runs of a subset of the codec tests, "
138 "with no scaling or subsetting, always using the canvas color type.");
139
140 static DEFINE_string2(match, m, nullptr,
141 "[~][^]substring[$] [...] of name to run.\n"
142 "Multiple matches may be separated by spaces.\n"
143 "~ causes a matching name to always be skipped\n"
144 "^ requires the start of the name to match\n"
145 "$ requires the end of the name to match\n"
146 "^ and $ requires an exact match\n"
147 "If a name does not match any list entry,\n"
148 "it is skipped unless some list entry starts with ~");
149
150 static DEFINE_bool2(quiet, q, false, "if true, don't print status updates.");
151 static DEFINE_bool2(verbose, v, false, "enable verbose output from the test driver.");
152
153 static DEFINE_string(skps, "skps", "Directory to read skps from.");
154 static DEFINE_string(lotties, "lotties", "Directory to read (Bodymovin) jsons from.");
155 static DEFINE_string(svgs, "", "Directory to read SVGs from, or a single SVG file.");
156
157 static DEFINE_int_2(threads, j, -1,
158 "Run threadsafe tests on a threadpool with this many extra threads, "
159 "defaulting to one extra thread per core.");
160
161 static DEFINE_string(key, "",
162 "Space-separated key/value pairs to add to JSON identifying this builder.");
163 static DEFINE_string(properties, "",
164 "Space-separated key/value pairs to add to JSON identifying this run.");
165
166 static DEFINE_bool(rasterize_pdf, false, "Rasterize PDFs when possible.");
167
168 static DEFINE_bool(runVerifiers, false,
169 "if true, run SkQP-style verification of GM-produced images.");
170
171 #if defined(__MSVC_RUNTIME_CHECKS)
172 #include <rtcapi.h>
RuntimeCheckErrorFunc(int errorType,const char * filename,int linenumber,const char * moduleName,const char * fmt,...)173 int RuntimeCheckErrorFunc(int errorType, const char* filename, int linenumber,
174 const char* moduleName, const char* fmt, ...) {
175 va_list args;
176 va_start(args, fmt);
177 vfprintf(stderr, fmt, args);
178 va_end(args);
179
180 SkDebugf("Line #%d\nFile: %s\nModule: %s\n",
181 linenumber, filename ? filename : "Unknown", moduleName ? moduleName : "Unknwon");
182 return 1;
183 }
184 #endif
185
186 using namespace DM;
187 using sk_gpu_test::GrContextFactory;
188 using sk_gpu_test::ContextInfo;
189 using skiatest::TestType;
190 #ifdef SK_GL
191 using sk_gpu_test::GLTestContext;
192 #endif
193
194 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
195
196 static FILE* gVLog;
197
198 static void vlog(const char* fmt, ...) SK_PRINTF_LIKE(1, 2);
199
vlog(const char * fmt,...)200 static void vlog(const char* fmt, ...) {
201 if (gVLog) {
202 va_list args;
203 va_start(args, fmt);
204 vfprintf(gVLog, fmt, args);
205 fflush(gVLog);
206 va_end(args);
207 }
208 }
209
210 static void info(const char* fmt, ...) SK_PRINTF_LIKE(1, 2);
211
info(const char * fmt,...)212 static void info(const char* fmt, ...) {
213 va_list args;
214 va_start(args, fmt);
215
216 if (gVLog) {
217 va_list vlogArgs;
218 va_copy(vlogArgs, args);
219 vfprintf(gVLog, fmt, vlogArgs);
220 fflush(gVLog);
221 va_end(vlogArgs);
222 }
223
224 if (!FLAGS_quiet) {
225 vprintf(fmt, args);
226 }
227
228 va_end(args);
229 }
230
231 static SkTArray<SkString>* gFailures = new SkTArray<SkString>;
232
fail(const SkString & err)233 static void fail(const SkString& err) {
234 static SkSpinlock mutex;
235 SkAutoSpinlock lock(mutex);
236 SkDebugf("\n\nFAILURE: %s\n\n", err.c_str());
237 gFailures->push_back(err);
238 }
239
240 struct Running {
241 SkString id;
242 SkThreadID thread;
243
dumpRunning244 void dump() const {
245 info("\t%s\n", id.c_str());
246 }
247 };
248
dump_json()249 static void dump_json() {
250 if (!FLAGS_writePath.isEmpty()) {
251 JsonWriter::DumpJson(FLAGS_writePath[0], FLAGS_key, FLAGS_properties);
252 }
253 }
254
255 // We use a spinlock to make locking this in a signal handler _somewhat_ safe.
256 static SkSpinlock* gMutex = new SkSpinlock;
257 static int gPending;
258 static SkTArray<Running>* gRunning = new SkTArray<Running>;
259
done(const char * config,const char * src,const char * srcOptions,const char * name)260 static void done(const char* config, const char* src, const char* srcOptions, const char* name) {
261 SkString id = SkStringPrintf("%s %s %s %s", config, src, srcOptions, name);
262 vlog("done %s\n", id.c_str());
263 int pending;
264 {
265 SkAutoSpinlock lock(*gMutex);
266 for (int i = 0; i < gRunning->size(); i++) {
267 if (gRunning->at(i).id == id) {
268 gRunning->removeShuffle(i);
269 break;
270 }
271 }
272 pending = --gPending;
273 }
274
275 // We write out dm.json file and print out a progress update every once in a while.
276 // Notice this also handles the final dm.json and progress update when pending == 0.
277 if (pending % 500 == 0) {
278 dump_json();
279
280 int curr = sk_tools::getCurrResidentSetSizeMB(),
281 peak = sk_tools::getMaxResidentSetSizeMB();
282
283 SkAutoSpinlock lock(*gMutex);
284 info("\n%dMB RAM, %dMB peak, %d queued, %d active:\n",
285 curr, peak, gPending - gRunning->size(), gRunning->size());
286 for (auto& task : *gRunning) {
287 task.dump();
288 }
289 }
290 }
291
start(const char * config,const char * src,const char * srcOptions,const char * name)292 static void start(const char* config, const char* src, const char* srcOptions, const char* name) {
293 SkString id = SkStringPrintf("%s %s %s %s", config, src, srcOptions, name);
294 vlog("start %s\n", id.c_str());
295 SkAutoSpinlock lock(*gMutex);
296 gRunning->push_back({id,SkGetThreadID()});
297 }
298
find_culprit()299 static void find_culprit() {
300 // Assumes gMutex is locked.
301 SkThreadID thisThread = SkGetThreadID();
302 for (auto& task : *gRunning) {
303 if (task.thread == thisThread) {
304 info("Likely culprit:\n");
305 task.dump();
306 }
307 }
308 }
309
310 #if defined(SK_BUILD_FOR_WIN)
crash_handler(EXCEPTION_POINTERS * e)311 static LONG WINAPI crash_handler(EXCEPTION_POINTERS* e) {
312 static const struct {
313 const char* name;
314 DWORD code;
315 } kExceptions[] = {
316 #define _(E) {#E, E}
317 _(EXCEPTION_ACCESS_VIOLATION),
318 _(EXCEPTION_BREAKPOINT),
319 _(EXCEPTION_INT_DIVIDE_BY_ZERO),
320 _(EXCEPTION_STACK_OVERFLOW),
321 // TODO: more?
322 #undef _
323 };
324
325 SkAutoSpinlock lock(*gMutex);
326
327 const DWORD code = e->ExceptionRecord->ExceptionCode;
328 info("\nCaught exception %lu", code);
329 for (const auto& exception : kExceptions) {
330 if (exception.code == code) {
331 info(" %s", exception.name);
332 }
333 }
334 info(", was running:\n");
335 for (auto& task : *gRunning) {
336 task.dump();
337 }
338 find_culprit();
339 fflush(stdout);
340
341 // Execute default exception handler... hopefully, exit.
342 return EXCEPTION_EXECUTE_HANDLER;
343 }
344
setup_crash_handler()345 static void setup_crash_handler() {
346 SetUnhandledExceptionFilter(crash_handler);
347 }
348 #else
349 #include <signal.h>
350 #if !defined(SK_BUILD_FOR_ANDROID)
351 #include <execinfo.h>
352
353 #endif
354
max_of()355 static constexpr int max_of() { return 0; }
356 template <typename... Rest>
max_of(int x,Rest...rest)357 static constexpr int max_of(int x, Rest... rest) {
358 return x > max_of(rest...) ? x : max_of(rest...);
359 }
360
361 static void (*previous_handler[max_of(SIGABRT,SIGBUS,SIGFPE,SIGILL,SIGSEGV,SIGTERM)+1])(int);
362
crash_handler(int sig)363 static void crash_handler(int sig) {
364 SkAutoSpinlock lock(*gMutex);
365
366 info("\nCaught signal %d [%s] (%dMB RAM, peak %dMB), was running:\n",
367 sig, strsignal(sig),
368 sk_tools::getCurrResidentSetSizeMB(), sk_tools::getMaxResidentSetSizeMB());
369
370 for (auto& task : *gRunning) {
371 task.dump();
372 }
373 find_culprit();
374
375 #if !defined(SK_BUILD_FOR_ANDROID)
376 void* stack[128];
377 int count = backtrace(stack, std::size(stack));
378 char** symbols = backtrace_symbols(stack, count);
379 info("\nStack trace:\n");
380 for (int i = 0; i < count; i++) {
381 info(" %s\n", symbols[i]);
382 }
383 #endif
384 fflush(stdout);
385
386 if (sig == SIGINT && FLAGS_ignoreSigInt) {
387 info("Ignoring signal %d because of --ignoreSigInt.\n"
388 "This is probably a sign the bot is overloaded with work.\n", sig);
389 } else {
390 signal(sig, previous_handler[sig]);
391 raise(sig);
392 }
393 }
394
setup_crash_handler()395 static void setup_crash_handler() {
396 const int kSignals[] = { SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM };
397 for (int sig : kSignals) {
398 previous_handler[sig] = signal(sig, crash_handler);
399 }
400 }
401 #endif
402
403 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
404
405 struct Gold : public SkString {
GoldGold406 Gold() : SkString("") {}
GoldGold407 Gold(const SkString& sink, const SkString& src,
408 const SkString& srcOptions, const SkString& name,
409 const SkString& md5)
410 : SkString("") {
411 this->append(sink);
412 this->append(src);
413 this->append(srcOptions);
414 this->append(name);
415 this->append(md5);
416 }
417 struct Hash {
operator ()Gold::Hash418 uint32_t operator()(const Gold& g) const {
419 return SkGoodHash()((const SkString&)g);
420 }
421 };
422 };
423 static SkTHashSet<Gold, Gold::Hash>* gGold = new SkTHashSet<Gold, Gold::Hash>;
424
add_gold(JsonWriter::BitmapResult r)425 static void add_gold(JsonWriter::BitmapResult r) {
426 gGold->add(Gold(r.config, r.sourceType, r.sourceOptions, r.name, r.md5));
427 }
428
gather_gold()429 static void gather_gold() {
430 if (!FLAGS_readPath.isEmpty()) {
431 SkString path(FLAGS_readPath[0]);
432 path.append("/dm.json");
433 if (!JsonWriter::ReadJson(path.c_str(), add_gold)) {
434 fail(SkStringPrintf("Couldn't read %s for golden results.", path.c_str()));
435 }
436 }
437 }
438
439 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
440
441 #if defined(SK_BUILD_FOR_WIN)
442 static constexpr char kNewline[] = "\r\n";
443 #else
444 static constexpr char kNewline[] = "\n";
445 #endif
446
447 static SkTHashSet<SkString>* gUninterestingHashes = new SkTHashSet<SkString>;
448
gather_uninteresting_hashes()449 static void gather_uninteresting_hashes() {
450 if (!FLAGS_uninterestingHashesFile.isEmpty()) {
451 sk_sp<SkData> data(SkData::MakeFromFileName(FLAGS_uninterestingHashesFile[0]));
452 if (!data) {
453 info("WARNING: unable to read uninteresting hashes from %s\n",
454 FLAGS_uninterestingHashesFile[0]);
455 return;
456 }
457
458 // Copy to a string to make sure SkStrSplit has a terminating \0 to find.
459 SkString contents((const char*)data->data(), data->size());
460
461 SkTArray<SkString> hashes;
462 SkStrSplit(contents.c_str(), kNewline, &hashes);
463 for (const SkString& hash : hashes) {
464 gUninterestingHashes->add(hash);
465 }
466 info("FYI: loaded %d distinct uninteresting hashes from %d lines\n",
467 gUninterestingHashes->count(), hashes.size());
468 }
469 }
470
471 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
472
473 struct TaggedSrc : public std::unique_ptr<Src> {
474 SkString tag;
475 SkString options;
476 };
477
478 struct TaggedSink : public std::unique_ptr<Sink> {
479 SkString tag;
480 };
481
482 static constexpr bool kMemcpyOK = true;
483
484 static SkTArray<TaggedSrc, kMemcpyOK>* gSrcs = new SkTArray<TaggedSrc, kMemcpyOK>;
485 static SkTArray<TaggedSink, kMemcpyOK>* gSinks = new SkTArray<TaggedSink, kMemcpyOK>;
486
in_shard()487 static bool in_shard() {
488 static int N = 0;
489 return N++ % FLAGS_shards == FLAGS_shard;
490 }
491
push_src(const char * tag,ImplicitString options,Src * inSrc)492 static void push_src(const char* tag, ImplicitString options, Src* inSrc) {
493 std::unique_ptr<Src> src(inSrc);
494 if (in_shard() && FLAGS_src.contains(tag) &&
495 !CommandLineFlags::ShouldSkip(FLAGS_match, src->name().c_str())) {
496 TaggedSrc& s = gSrcs->push_back();
497 s.reset(src.release());
498 s.tag = tag;
499 s.options = options;
500 }
501 }
502
push_codec_src(Path path,CodecSrc::Mode mode,CodecSrc::DstColorType dstColorType,SkAlphaType dstAlphaType,float scale)503 static void push_codec_src(Path path, CodecSrc::Mode mode, CodecSrc::DstColorType dstColorType,
504 SkAlphaType dstAlphaType, float scale) {
505 if (FLAGS_simpleCodec) {
506 const bool simple = CodecSrc::kCodec_Mode == mode || CodecSrc::kAnimated_Mode == mode;
507 if (!simple || dstColorType != CodecSrc::kGetFromCanvas_DstColorType || scale != 1.0f) {
508 // Only decode in the simple case.
509 return;
510 }
511 }
512 SkString folder;
513 switch (mode) {
514 case CodecSrc::kCodec_Mode:
515 folder.append("codec");
516 break;
517 case CodecSrc::kCodecZeroInit_Mode:
518 folder.append("codec_zero_init");
519 break;
520 case CodecSrc::kScanline_Mode:
521 folder.append("scanline");
522 break;
523 case CodecSrc::kStripe_Mode:
524 folder.append("stripe");
525 break;
526 case CodecSrc::kCroppedScanline_Mode:
527 folder.append("crop");
528 break;
529 case CodecSrc::kSubset_Mode:
530 folder.append("codec_subset");
531 break;
532 case CodecSrc::kAnimated_Mode:
533 folder.append("codec_animated");
534 break;
535 }
536
537 switch (dstColorType) {
538 case CodecSrc::kGrayscale_Always_DstColorType:
539 folder.append("_kGray8");
540 break;
541 case CodecSrc::kNonNative8888_Always_DstColorType:
542 folder.append("_kNonNative");
543 break;
544 default:
545 break;
546 }
547
548 switch (dstAlphaType) {
549 case kPremul_SkAlphaType:
550 folder.append("_premul");
551 break;
552 case kUnpremul_SkAlphaType:
553 folder.append("_unpremul");
554 break;
555 default:
556 break;
557 }
558
559 if (1.0f != scale) {
560 folder.appendf("_%.3f", scale);
561 }
562
563 CodecSrc* src = new CodecSrc(path, mode, dstColorType, dstAlphaType, scale);
564 push_src("image", folder, src);
565 }
566
push_android_codec_src(Path path,CodecSrc::DstColorType dstColorType,SkAlphaType dstAlphaType,int sampleSize)567 static void push_android_codec_src(Path path, CodecSrc::DstColorType dstColorType,
568 SkAlphaType dstAlphaType, int sampleSize) {
569 SkString folder;
570 folder.append("scaled_codec");
571
572 switch (dstColorType) {
573 case CodecSrc::kGrayscale_Always_DstColorType:
574 folder.append("_kGray8");
575 break;
576 case CodecSrc::kNonNative8888_Always_DstColorType:
577 folder.append("_kNonNative");
578 break;
579 default:
580 break;
581 }
582
583 switch (dstAlphaType) {
584 case kPremul_SkAlphaType:
585 folder.append("_premul");
586 break;
587 case kUnpremul_SkAlphaType:
588 folder.append("_unpremul");
589 break;
590 default:
591 break;
592 }
593
594 if (1 != sampleSize) {
595 folder.appendf("_%.3f", 1.0f / (float) sampleSize);
596 }
597
598 AndroidCodecSrc* src = new AndroidCodecSrc(path, dstColorType, dstAlphaType, sampleSize);
599 push_src("image", folder, src);
600 }
601
push_image_gen_src(Path path,ImageGenSrc::Mode mode,SkAlphaType alphaType,bool isGpu)602 static void push_image_gen_src(Path path, ImageGenSrc::Mode mode, SkAlphaType alphaType, bool isGpu)
603 {
604 SkString folder;
605 switch (mode) {
606 case ImageGenSrc::kCodec_Mode:
607 folder.append("gen_codec");
608 break;
609 case ImageGenSrc::kPlatform_Mode:
610 folder.append("gen_platform");
611 break;
612 }
613
614 if (isGpu) {
615 folder.append("_gpu");
616 } else {
617 switch (alphaType) {
618 case kOpaque_SkAlphaType:
619 folder.append("_opaque");
620 break;
621 case kPremul_SkAlphaType:
622 folder.append("_premul");
623 break;
624 case kUnpremul_SkAlphaType:
625 folder.append("_unpremul");
626 break;
627 default:
628 break;
629 }
630 }
631
632 ImageGenSrc* src = new ImageGenSrc(path, mode, alphaType, isGpu);
633 push_src("image", folder, src);
634 }
635
636 #ifdef SK_ENABLE_ANDROID_UTILS
push_brd_src(Path path,CodecSrc::DstColorType dstColorType,BRDSrc::Mode mode,uint32_t sampleSize)637 static void push_brd_src(Path path, CodecSrc::DstColorType dstColorType, BRDSrc::Mode mode,
638 uint32_t sampleSize) {
639 SkString folder("brd_android_codec");
640 switch (mode) {
641 case BRDSrc::kFullImage_Mode:
642 break;
643 case BRDSrc::kDivisor_Mode:
644 folder.append("_divisor");
645 break;
646 default:
647 SkASSERT(false);
648 return;
649 }
650
651 switch (dstColorType) {
652 case CodecSrc::kGetFromCanvas_DstColorType:
653 break;
654 case CodecSrc::kGrayscale_Always_DstColorType:
655 folder.append("_kGray");
656 break;
657 default:
658 SkASSERT(false);
659 return;
660 }
661
662 if (1 != sampleSize) {
663 folder.appendf("_%.3f", 1.0f / (float) sampleSize);
664 }
665
666 BRDSrc* src = new BRDSrc(path, mode, dstColorType, sampleSize);
667 push_src("image", folder, src);
668 }
669
push_brd_srcs(Path path,bool gray)670 static void push_brd_srcs(Path path, bool gray) {
671 if (gray) {
672 // Only run grayscale to one sampleSize and Mode. Though interesting
673 // to test grayscale, it should not reveal anything across various
674 // sampleSizes and Modes
675 // Arbitrarily choose Mode and sampleSize.
676 push_brd_src(path, CodecSrc::kGrayscale_Always_DstColorType,
677 BRDSrc::kFullImage_Mode, 2);
678 }
679
680 // Test on a variety of sampleSizes, making sure to include:
681 // - 2, 4, and 8, which are natively supported by jpeg
682 // - multiples of 2 which are not divisible by 4 (analogous for 4)
683 // - larger powers of two, since BRD clients generally use powers of 2
684 // We will only produce output for the larger sizes on large images.
685 const uint32_t sampleSizes[] = { 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 24, 32, 64 };
686
687 const BRDSrc::Mode modes[] = { BRDSrc::kFullImage_Mode, BRDSrc::kDivisor_Mode, };
688
689 for (uint32_t sampleSize : sampleSizes) {
690 for (BRDSrc::Mode mode : modes) {
691 push_brd_src(path, CodecSrc::kGetFromCanvas_DstColorType, mode, sampleSize);
692 }
693 }
694 }
695 #endif // SK_ENABLE_ANDROID_UTILS
696
push_codec_srcs(Path path)697 static void push_codec_srcs(Path path) {
698 sk_sp<SkData> encoded(SkData::MakeFromFileName(path.c_str()));
699 if (!encoded) {
700 info("Couldn't read %s.", path.c_str());
701 return;
702 }
703 std::unique_ptr<SkCodec> codec = SkCodec::MakeFromData(encoded);
704 if (nullptr == codec) {
705 info("Couldn't create codec for %s.", path.c_str());
706 return;
707 }
708
709 // native scaling is only supported by WEBP and JPEG
710 bool supportsNativeScaling = false;
711
712 SkTArray<CodecSrc::Mode> nativeModes;
713 nativeModes.push_back(CodecSrc::kCodec_Mode);
714 nativeModes.push_back(CodecSrc::kCodecZeroInit_Mode);
715 switch (codec->getEncodedFormat()) {
716 case SkEncodedImageFormat::kJPEG:
717 nativeModes.push_back(CodecSrc::kScanline_Mode);
718 nativeModes.push_back(CodecSrc::kStripe_Mode);
719 nativeModes.push_back(CodecSrc::kCroppedScanline_Mode);
720 supportsNativeScaling = true;
721 break;
722 case SkEncodedImageFormat::kWEBP:
723 nativeModes.push_back(CodecSrc::kSubset_Mode);
724 supportsNativeScaling = true;
725 break;
726 case SkEncodedImageFormat::kDNG:
727 break;
728 default:
729 nativeModes.push_back(CodecSrc::kScanline_Mode);
730 break;
731 }
732
733 SkTArray<CodecSrc::DstColorType> colorTypes;
734 colorTypes.push_back(CodecSrc::kGetFromCanvas_DstColorType);
735 colorTypes.push_back(CodecSrc::kNonNative8888_Always_DstColorType);
736 switch (codec->getInfo().colorType()) {
737 case kGray_8_SkColorType:
738 colorTypes.push_back(CodecSrc::kGrayscale_Always_DstColorType);
739 break;
740 default:
741 break;
742 }
743
744 SkTArray<SkAlphaType> alphaModes;
745 alphaModes.push_back(kPremul_SkAlphaType);
746 if (codec->getInfo().alphaType() != kOpaque_SkAlphaType) {
747 alphaModes.push_back(kUnpremul_SkAlphaType);
748 }
749
750 for (CodecSrc::Mode mode : nativeModes) {
751 for (CodecSrc::DstColorType colorType : colorTypes) {
752 for (SkAlphaType alphaType : alphaModes) {
753 // Only test kCroppedScanline_Mode when the alpha type is premul. The test is
754 // slow and won't be interestingly different with different alpha types.
755 if (CodecSrc::kCroppedScanline_Mode == mode &&
756 kPremul_SkAlphaType != alphaType) {
757 continue;
758 }
759
760 push_codec_src(path, mode, colorType, alphaType, 1.0f);
761
762 // Skip kNonNative on different native scales. It won't be interestingly
763 // different.
764 if (supportsNativeScaling &&
765 CodecSrc::kNonNative8888_Always_DstColorType == colorType) {
766 // Native Scales
767 // SkJpegCodec natively supports scaling to the following:
768 for (auto scale : { 0.125f, 0.25f, 0.375f, 0.5f, 0.625f, 0.750f, 0.875f }) {
769 push_codec_src(path, mode, colorType, alphaType, scale);
770 }
771 }
772 }
773 }
774 }
775
776 {
777 std::vector<SkCodec::FrameInfo> frameInfos = codec->getFrameInfo();
778 if (frameInfos.size() > 1) {
779 for (auto dstCT : { CodecSrc::kNonNative8888_Always_DstColorType,
780 CodecSrc::kGetFromCanvas_DstColorType }) {
781 for (auto at : { kUnpremul_SkAlphaType, kPremul_SkAlphaType }) {
782 push_codec_src(path, CodecSrc::kAnimated_Mode, dstCT, at, 1.0f);
783 }
784 }
785 for (float scale : { .5f, .33f }) {
786 push_codec_src(path, CodecSrc::kAnimated_Mode, CodecSrc::kGetFromCanvas_DstColorType,
787 kPremul_SkAlphaType, scale);
788 }
789 }
790
791 }
792
793 if (FLAGS_simpleCodec) {
794 return;
795 }
796
797 const int sampleSizes[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
798
799 for (int sampleSize : sampleSizes) {
800 for (CodecSrc::DstColorType colorType : colorTypes) {
801 for (SkAlphaType alphaType : alphaModes) {
802 // We can exercise all of the kNonNative support code in the swizzler with just a
803 // few sample sizes. Skip the rest.
804 if (CodecSrc::kNonNative8888_Always_DstColorType == colorType && sampleSize > 3) {
805 continue;
806 }
807
808 push_android_codec_src(path, colorType, alphaType, sampleSize);
809 }
810 }
811 }
812
813 const char* ext = strrchr(path.c_str(), '.');
814 if (ext) {
815 ext++;
816
817 static const char* const rawExts[] = {
818 "arw", "cr2", "dng", "nef", "nrw", "orf", "raf", "rw2", "pef", "srw",
819 "ARW", "CR2", "DNG", "NEF", "NRW", "ORF", "RAF", "RW2", "PEF", "SRW",
820 };
821 for (const char* rawExt : rawExts) {
822 if (0 == strcmp(rawExt, ext)) {
823 // RAW is not supported by image generator (skbug.com/5079) or BRD.
824 return;
825 }
826 }
827
828 #ifdef SK_ENABLE_ANDROID_UTILS
829 static const char* const brdExts[] = {
830 "jpg", "jpeg", "png", "webp",
831 "JPG", "JPEG", "PNG", "WEBP",
832 };
833 for (const char* brdExt : brdExts) {
834 if (0 == strcmp(brdExt, ext)) {
835 bool gray = codec->getInfo().colorType() == kGray_8_SkColorType;
836 push_brd_srcs(path, gray);
837 break;
838 }
839 }
840 #endif
841 }
842
843 // Push image generator GPU test.
844 push_image_gen_src(path, ImageGenSrc::kCodec_Mode, codec->getInfo().alphaType(), true);
845
846 // Push image generator CPU tests.
847 for (SkAlphaType alphaType : alphaModes) {
848 push_image_gen_src(path, ImageGenSrc::kCodec_Mode, alphaType, false);
849
850 #if defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS)
851 if (SkEncodedImageFormat::kWEBP != codec->getEncodedFormat() &&
852 SkEncodedImageFormat::kWBMP != codec->getEncodedFormat() &&
853 kUnpremul_SkAlphaType != alphaType)
854 {
855 push_image_gen_src(path, ImageGenSrc::kPlatform_Mode, alphaType, false);
856 }
857 #elif defined(SK_BUILD_FOR_WIN)
858 if (SkEncodedImageFormat::kWEBP != codec->getEncodedFormat() &&
859 SkEncodedImageFormat::kWBMP != codec->getEncodedFormat())
860 {
861 push_image_gen_src(path, ImageGenSrc::kPlatform_Mode, alphaType, false);
862 }
863 #elif defined(SK_ENABLE_NDK_IMAGES)
864 push_image_gen_src(path, ImageGenSrc::kPlatform_Mode, alphaType, false);
865 #endif
866 }
867 }
868
869 template <typename T>
gather_file_srcs(const CommandLineFlags::StringArray & flags,const char * ext,const char * src_name=nullptr)870 void gather_file_srcs(const CommandLineFlags::StringArray& flags,
871 const char* ext,
872 const char* src_name = nullptr) {
873 if (!src_name) {
874 // With the exception of Lottie files, the source name is the extension.
875 src_name = ext;
876 }
877
878 for (int i = 0; i < flags.size(); i++) {
879 const char* path = flags[i];
880 if (sk_isdir(path)) {
881 SkOSFile::Iter it(path, ext);
882 for (SkString file; it.next(&file); ) {
883 push_src(src_name, "", new T(SkOSPath::Join(path, file.c_str())));
884 }
885 } else {
886 push_src(src_name, "", new T(path));
887 }
888 }
889 }
890
gather_srcs()891 static bool gather_srcs() {
892 for (skiagm::GMFactory f : skiagm::GMRegistry::Range()) {
893 push_src("gm", "", new GMSrc(f));
894 }
895
896 gather_file_srcs<SKPSrc>(FLAGS_skps, "skp");
897 gather_file_srcs<MSKPSrc>(FLAGS_mskps, "mskp");
898 #if defined(SK_ENABLE_SKOTTIE)
899 gather_file_srcs<SkottieSrc>(FLAGS_lotties, "json", "lottie");
900 #endif
901 #if defined(SK_ENABLE_SVG)
902 gather_file_srcs<SVGSrc>(FLAGS_svgs, "svg");
903 #endif
904 if (!FLAGS_bisect.isEmpty()) {
905 // An empty l/r trail string will draw all the paths.
906 push_src("bisect", "",
907 new BisectSrc(FLAGS_bisect[0], FLAGS_bisect.size() > 1 ? FLAGS_bisect[1] : ""));
908 }
909
910 SkTArray<SkString> images;
911 if (!CommonFlags::CollectImages(FLAGS_images, &images)) {
912 return false;
913 }
914
915 for (const SkString& image : images) {
916 push_codec_srcs(image);
917 }
918
919 SkTArray<SkString> colorImages;
920 if (!CommonFlags::CollectImages(FLAGS_colorImages, &colorImages)) {
921 return false;
922 }
923
924 for (const SkString& colorImage : colorImages) {
925 push_src("colorImage", "decode_native", new ColorCodecSrc(colorImage, false));
926 push_src("colorImage", "decode_to_dst", new ColorCodecSrc(colorImage, true));
927 }
928
929 return true;
930 }
931
push_sink(const SkCommandLineConfig & config,Sink * s)932 static void push_sink(const SkCommandLineConfig& config, Sink* s) {
933 std::unique_ptr<Sink> sink(s);
934
935 // Try a simple Src as a canary. If it fails, skip this sink.
936 struct : public Src {
937 Result draw(SkCanvas* c) const override {
938 c->drawRect(SkRect::MakeWH(1,1), SkPaint());
939 return Result::Ok();
940 }
941 SkISize size() const override { return SkISize::Make(16, 16); }
942 Name name() const override { return "justOneRect"; }
943 } justOneRect;
944
945 SkBitmap bitmap;
946 SkDynamicMemoryWStream stream;
947 SkString log;
948 Result result = sink->draw(justOneRect, &bitmap, &stream, &log);
949 if (result.isFatal()) {
950 info("Could not run %s: %s\n", config.getTag().c_str(), result.c_str());
951 exit(1);
952 }
953
954 TaggedSink& ts = gSinks->push_back();
955 ts.reset(sink.release());
956 ts.tag = config.getTag();
957 }
958
create_sink(const GrContextOptions & grCtxOptions,const SkCommandLineConfig * config)959 static Sink* create_sink(const GrContextOptions& grCtxOptions, const SkCommandLineConfig* config) {
960 if (FLAGS_gpu) {
961 if (const SkCommandLineConfigGpu* gpuConfig = config->asConfigGpu()) {
962 GrContextFactory testFactory(grCtxOptions);
963 if (!testFactory.get(gpuConfig->getContextType(), gpuConfig->getContextOverrides())) {
964 info("WARNING: can not create GPU context for config '%s'. "
965 "GM tests will be skipped.\n", gpuConfig->getTag().c_str());
966 return nullptr;
967 }
968 if (gpuConfig->getTestThreading()) {
969 SkASSERT(!gpuConfig->getTestPersistentCache());
970 return new GPUThreadTestingSink(gpuConfig, grCtxOptions);
971 } else if (gpuConfig->getTestPersistentCache()) {
972 return new GPUPersistentCacheTestingSink(gpuConfig, grCtxOptions);
973 } else if (gpuConfig->getTestPrecompile()) {
974 return new GPUPrecompileTestingSink(gpuConfig, grCtxOptions);
975 } else if (gpuConfig->getUseDDLSink()) {
976 return new GPUDDLSink(gpuConfig, grCtxOptions);
977 } else if (gpuConfig->getSlug()) {
978 return new GPUSlugSink(gpuConfig, grCtxOptions);
979 } else if (gpuConfig->getSerializedSlug()) {
980 return new GPUSerializeSlugSink(gpuConfig, grCtxOptions);
981 } else if (gpuConfig->getRemoteSlug()) {
982 return new GPURemoteSlugSink(gpuConfig, grCtxOptions);
983 } else {
984 return new GPUSink(gpuConfig, grCtxOptions);
985 }
986 }
987 }
988 #if defined(SK_GRAPHITE)
989 if (FLAGS_graphite) {
990 if (const SkCommandLineConfigGraphite *graphiteConfig = config->asConfigGraphite()) {
991 return new GraphiteSink(graphiteConfig);
992 }
993 }
994 #endif
995 if (const SkCommandLineConfigSvg* svgConfig = config->asConfigSvg()) {
996 int pageIndex = svgConfig->getPageIndex();
997 return new SVGSink(pageIndex);
998 }
999
1000 #define SINK(t, sink, ...) if (config->getBackend().equals(t)) return new sink(__VA_ARGS__)
1001
1002 if (FLAGS_cpu) {
1003 SINK("r8", RasterSink, kR8_unorm_SkColorType);
1004 SINK("565", RasterSink, kRGB_565_SkColorType);
1005 SINK("4444", RasterSink, kARGB_4444_SkColorType);
1006 SINK("8888", RasterSink, kN32_SkColorType);
1007 SINK("rgba", RasterSink, kRGBA_8888_SkColorType);
1008 SINK("bgra", RasterSink, kBGRA_8888_SkColorType);
1009 SINK("rgbx", RasterSink, kRGB_888x_SkColorType);
1010 SINK("1010102", RasterSink, kRGBA_1010102_SkColorType);
1011 SINK("101010x", RasterSink, kRGB_101010x_SkColorType);
1012 SINK("bgra1010102", RasterSink, kBGRA_1010102_SkColorType);
1013 SINK("bgr101010x", RasterSink, kBGR_101010x_SkColorType);
1014 SINK("f16", RasterSink, kRGBA_F16_SkColorType);
1015 SINK("f16norm", RasterSink, kRGBA_F16Norm_SkColorType);
1016 SINK("f32", RasterSink, kRGBA_F32_SkColorType);
1017 SINK("srgba", RasterSink, kSRGBA_8888_SkColorType);
1018
1019 SINK("pdf", PDFSink, false, SK_ScalarDefaultRasterDPI);
1020 SINK("skp", SKPSink);
1021 SINK("svg", SVGSink);
1022 SINK("null", NullSink);
1023 SINK("xps", XPSSink);
1024 SINK("pdfa", PDFSink, true, SK_ScalarDefaultRasterDPI);
1025 SINK("pdf300", PDFSink, false, 300);
1026 SINK("jsdebug", DebugSink);
1027 }
1028 #undef SINK
1029 return nullptr;
1030 }
1031
create_via(const SkString & tag,Sink * wrapped)1032 static Sink* create_via(const SkString& tag, Sink* wrapped) {
1033 #define VIA(t, via, ...) if (tag.equals(t)) return new via(__VA_ARGS__)
1034 #ifdef TEST_VIA_SVG
1035 VIA("svg", ViaSVG, wrapped);
1036 #endif
1037 VIA("serialize", ViaSerialization, wrapped);
1038 VIA("pic", ViaPicture, wrapped);
1039 VIA("rtblend", ViaRuntimeBlend, wrapped);
1040
1041 if (FLAGS_matrix.size() == 4) {
1042 SkMatrix m;
1043 m.reset();
1044 m.setScaleX((SkScalar)atof(FLAGS_matrix[0]));
1045 m.setSkewX ((SkScalar)atof(FLAGS_matrix[1]));
1046 m.setSkewY ((SkScalar)atof(FLAGS_matrix[2]));
1047 m.setScaleY((SkScalar)atof(FLAGS_matrix[3]));
1048 VIA("matrix", ViaMatrix, m, wrapped);
1049 VIA("upright", ViaUpright, m, wrapped);
1050 }
1051
1052 #undef VIA
1053
1054 return nullptr;
1055 }
1056
gather_sinks(const GrContextOptions & grCtxOptions,bool defaultConfigs)1057 static bool gather_sinks(const GrContextOptions& grCtxOptions, bool defaultConfigs) {
1058 SkCommandLineConfigArray configs;
1059 ParseConfigs(FLAGS_config, &configs);
1060 AutoreleasePool pool;
1061 for (int i = 0; i < configs.size(); i++) {
1062 const SkCommandLineConfig& config = *configs[i];
1063 Sink* sink = create_sink(grCtxOptions, &config);
1064 if (sink == nullptr) {
1065 info("Skipping config %s: Don't understand '%s'.\n", config.getTag().c_str(),
1066 config.getTag().c_str());
1067 continue;
1068 }
1069
1070 // The command line config already parsed out the via-style color space. Apply it here.
1071 sink->setColorSpace(config.refColorSpace());
1072
1073 const SkTArray<SkString>& parts = config.getViaParts();
1074 for (int j = parts.size(); j-- > 0;) {
1075 const SkString& part = parts[j];
1076 Sink* next = create_via(part, sink);
1077 if (next == nullptr) {
1078 info("Skipping config %s: Don't understand '%s'.\n", config.getTag().c_str(),
1079 part.c_str());
1080 delete sink;
1081 sink = nullptr;
1082 break;
1083 }
1084 sink = next;
1085 }
1086 if (sink) {
1087 push_sink(config, sink);
1088 }
1089 }
1090
1091 // If no configs were requested (just running tests, perhaps?), then we're okay.
1092 if (configs.size() == 0 ||
1093 // If we're using the default configs, we're okay.
1094 defaultConfigs ||
1095 // Otherwise, make sure that all specified configs have become sinks.
1096 configs.size() == gSinks->size()) {
1097 return true;
1098 }
1099 return false;
1100 }
1101
match(const char * needle,const char * haystack)1102 static bool match(const char* needle, const char* haystack) {
1103 if ('~' == needle[0]) {
1104 return !match(needle + 1, haystack);
1105 }
1106 if (0 == strcmp("_", needle)) {
1107 return true;
1108 }
1109 return nullptr != strstr(haystack, needle);
1110 }
1111
should_skip(const char * sink,const char * src,const char * srcOptions,const char * name)1112 static bool should_skip(const char* sink, const char* src,
1113 const char* srcOptions, const char* name) {
1114 for (int i = 0; i < FLAGS_skip.size() - 3; i += 4) {
1115 if (match(FLAGS_skip[i+0], sink) &&
1116 match(FLAGS_skip[i+1], src) &&
1117 match(FLAGS_skip[i+2], srcOptions) &&
1118 match(FLAGS_skip[i+3], name)) {
1119 return true;
1120 }
1121 }
1122 return false;
1123 }
1124
1125 // Even when a Task Sink reports to be non-threadsafe (e.g. GPU), we know things like
1126 // .png encoding are definitely thread safe. This lets us offload that work to CPU threads.
1127 static SkTaskGroup* gDefinitelyThreadSafeWork = new SkTaskGroup;
1128
1129 // The finest-grained unit of work we can run: draw a single Src into a single Sink,
1130 // report any errors, and perhaps write out the output: a .png of the bitmap, or a raw stream.
1131 struct Task {
TaskTask1132 Task(const TaggedSrc& src, const TaggedSink& sink) : src(src), sink(sink) {}
1133 const TaggedSrc& src;
1134 const TaggedSink& sink;
1135
RunTask1136 static void Run(const Task& task) {
1137 AutoreleasePool pool;
1138 SkString name = task.src->name();
1139
1140 SkString log;
1141 if (!FLAGS_dryRun) {
1142 SkBitmap bitmap;
1143 SkDynamicMemoryWStream stream;
1144 start(task.sink.tag.c_str(), task.src.tag.c_str(),
1145 task.src.options.c_str(), name.c_str());
1146 Result result = task.sink->draw(*task.src, &bitmap, &stream, &log);
1147 if (!log.isEmpty()) {
1148 info("%s %s %s %s:\n%s\n", task.sink.tag.c_str()
1149 , task.src.tag.c_str()
1150 , task.src.options.c_str()
1151 , name.c_str()
1152 , log.c_str());
1153 }
1154 if (result.isSkip()) {
1155 done(task.sink.tag.c_str(), task.src.tag.c_str(),
1156 task.src.options.c_str(), name.c_str());
1157 return;
1158 }
1159 if (result.isFatal()) {
1160 fail(SkStringPrintf("%s %s %s %s: %s",
1161 task.sink.tag.c_str(),
1162 task.src.tag.c_str(),
1163 task.src.options.c_str(),
1164 name.c_str(),
1165 result.c_str()));
1166 }
1167
1168 // Run verifiers if specified
1169 if (FLAGS_runVerifiers) {
1170 RunGMVerifiers(task, bitmap);
1171 }
1172
1173 // We're likely switching threads here, so we must capture by value, [=] or [foo,bar].
1174 SkStreamAsset* data = stream.detachAsStream().release();
1175 gDefinitelyThreadSafeWork->add([task,name,bitmap,data]{
1176 std::unique_ptr<SkStreamAsset> ownedData(data);
1177
1178 std::unique_ptr<HashAndEncode> hashAndEncode;
1179
1180 SkString md5;
1181 if (!FLAGS_writePath.isEmpty() || !FLAGS_readPath.isEmpty()) {
1182 SkMD5 hash;
1183 if (data->getLength()) {
1184 hash.writeStream(data, data->getLength());
1185 data->rewind();
1186 } else {
1187 hashAndEncode = std::make_unique<HashAndEncode>(bitmap);
1188 hashAndEncode->feedHash(&hash);
1189 }
1190 SkMD5::Digest digest = hash.finish();
1191 for (int i = 0; i < 16; i++) {
1192 md5.appendf("%02x", digest.data[i]);
1193 }
1194 }
1195
1196 if (!FLAGS_readPath.isEmpty() &&
1197 !gGold->contains(Gold(task.sink.tag, task.src.tag,
1198 task.src.options, name, md5))) {
1199 fail(SkStringPrintf("%s not found for %s %s %s %s in %s",
1200 md5.c_str(),
1201 task.sink.tag.c_str(),
1202 task.src.tag.c_str(),
1203 task.src.options.c_str(),
1204 name.c_str(),
1205 FLAGS_readPath[0]));
1206 }
1207
1208 // Tests sometimes use a nullptr ext to indicate no image should be uploaded.
1209 const char* ext = task.sink->fileExtension();
1210 if (ext && !FLAGS_writePath.isEmpty()) {
1211 #if defined(SK_BUILD_FOR_MAC)
1212 if (FLAGS_rasterize_pdf && SkString("pdf").equals(ext)) {
1213 SkASSERT(data->getLength() > 0);
1214
1215 sk_sp<SkData> blob = SkData::MakeFromStream(data, data->getLength());
1216
1217 SkUniqueCFRef<CGDataProviderRef> provider{
1218 CGDataProviderCreateWithData(nullptr,
1219 blob->data(),
1220 blob->size(),
1221 nullptr)};
1222
1223 SkUniqueCFRef<CGPDFDocumentRef> pdf{
1224 CGPDFDocumentCreateWithProvider(provider.get())};
1225
1226 CGPDFPageRef page = CGPDFDocumentGetPage(pdf.get(), 1);
1227
1228 CGRect bounds = CGPDFPageGetBoxRect(page, kCGPDFMediaBox);
1229 const int w = (int)CGRectGetWidth (bounds),
1230 h = (int)CGRectGetHeight(bounds);
1231
1232 SkBitmap rasterized;
1233 rasterized.allocPixels(SkImageInfo::Make(
1234 w, h, kRGBA_8888_SkColorType, kPremul_SkAlphaType));
1235 rasterized.eraseColor(SK_ColorWHITE);
1236
1237 SkUniqueCFRef<CGColorSpaceRef> cs{CGColorSpaceCreateDeviceRGB()};
1238 CGBitmapInfo info = kCGBitmapByteOrder32Big |
1239 (CGBitmapInfo)kCGImageAlphaPremultipliedLast;
1240
1241 SkUniqueCFRef<CGContextRef> ctx{CGBitmapContextCreate(
1242 rasterized.getPixels(), w,h,8, rasterized.rowBytes(), cs.get(), info)};
1243 CGContextDrawPDFPage(ctx.get(), page);
1244
1245 // Skip calling hashAndEncode->feedHash(SkMD5*)... we want the .pdf's hash.
1246 hashAndEncode = std::make_unique<HashAndEncode>(rasterized);
1247 WriteToDisk(task, md5, "png", nullptr,0, &rasterized, hashAndEncode.get());
1248 } else
1249 #endif
1250 if (data->getLength()) {
1251 WriteToDisk(task, md5, ext, data, data->getLength(), nullptr, nullptr);
1252 SkASSERT(bitmap.drawsNothing());
1253 } else if (!bitmap.drawsNothing()) {
1254 WriteToDisk(task, md5, ext, nullptr, 0, &bitmap, hashAndEncode.get());
1255 }
1256 }
1257
1258 SkPixmap pm;
1259 if (FLAGS_checkF16 && bitmap.colorType() == kRGBA_F16Norm_SkColorType &&
1260 bitmap.peekPixels(&pm)) {
1261 bool unclamped = false;
1262 for (int y = 0; y < pm.height() && !unclamped; ++y)
1263 for (int x = 0; x < pm.width() && !unclamped; ++x) {
1264 skvx::float4 rgba = SkHalfToFloat_finite_ftz(*pm.addr64(x, y));
1265 float a = rgba[3];
1266 if (a > 1.0f || any(rgba < 0.0f) || any(rgba > a)) {
1267 SkDebugf("[%s] F16Norm pixel [%d, %d] unclamped: (%g, %g, %g, %g)\n",
1268 name.c_str(), x, y, rgba[0], rgba[1], rgba[2], rgba[3]);
1269 unclamped = true;
1270 }
1271 }
1272 }
1273 });
1274 }
1275 done(task.sink.tag.c_str(), task.src.tag.c_str(), task.src.options.c_str(), name.c_str());
1276 }
1277
identify_gamutTask1278 static SkString identify_gamut(SkColorSpace* cs) {
1279 if (!cs) {
1280 return SkString("untagged");
1281 }
1282
1283 skcms_Matrix3x3 gamut;
1284 if (cs->toXYZD50(&gamut)) {
1285 auto eq = [](skcms_Matrix3x3 x, skcms_Matrix3x3 y) {
1286 for (int i = 0; i < 3; i++)
1287 for (int j = 0; j < 3; j++) {
1288 if (x.vals[i][j] != y.vals[i][j]) { return false; }
1289 }
1290 return true;
1291 };
1292
1293 if (eq(gamut, SkNamedGamut::kSRGB )) { return SkString("sRGB"); }
1294 if (eq(gamut, SkNamedGamut::kAdobeRGB )) { return SkString("Adobe"); }
1295 if (eq(gamut, SkNamedGamut::kDisplayP3)) { return SkString("P3"); }
1296 if (eq(gamut, SkNamedGamut::kRec2020 )) { return SkString("2020"); }
1297 if (eq(gamut, SkNamedGamut::kXYZ )) { return SkString("XYZ"); }
1298 if (eq(gamut, gNarrow_toXYZD50 )) { return SkString("narrow"); }
1299 return SkString("other");
1300 }
1301 return SkString("non-XYZ");
1302 }
1303
identify_transfer_fnTask1304 static SkString identify_transfer_fn(SkColorSpace* cs) {
1305 if (!cs) {
1306 return SkString("untagged");
1307 }
1308
1309 auto eq = [](skcms_TransferFunction x, skcms_TransferFunction y) {
1310 return x.g == y.g
1311 && x.a == y.a
1312 && x.b == y.b
1313 && x.c == y.c
1314 && x.d == y.d
1315 && x.e == y.e
1316 && x.f == y.f;
1317 };
1318
1319 skcms_TransferFunction tf;
1320 cs->transferFn(&tf);
1321 switch (skcms_TransferFunction_getType(&tf)) {
1322 case skcms_TFType_sRGBish:
1323 if (tf.a == 1 && tf.b == 0 && tf.c == 0 && tf.d == 0 && tf.e == 0 && tf.f == 0) {
1324 return SkStringPrintf("gamma %.3g", tf.g);
1325 }
1326 if (eq(tf, SkNamedTransferFn::kSRGB)) { return SkString("sRGB"); }
1327 if (eq(tf, SkNamedTransferFn::kRec2020)) { return SkString("2020"); }
1328 return SkStringPrintf("%.3g %.3g %.3g %.3g %.3g %.3g %.3g",
1329 tf.g, tf.a, tf.b, tf.c, tf.d, tf.e, tf.f);
1330
1331 case skcms_TFType_PQish:
1332 if (eq(tf, SkNamedTransferFn::kPQ)) { return SkString("PQ"); }
1333 return SkStringPrintf("PQish %.3g %.3g %.3g %.3g %.3g %.3g",
1334 tf.a, tf.b, tf.c, tf.d, tf.e, tf.f);
1335
1336 case skcms_TFType_HLGish:
1337 if (eq(tf, SkNamedTransferFn::kHLG)) { return SkString("HLG"); }
1338 return SkStringPrintf("HLGish %.3g %.3g %.3g %.3g %.3g (%.3g)",
1339 tf.a, tf.b, tf.c, tf.d, tf.e, tf.f+1);
1340
1341 case skcms_TFType_HLGinvish: break;
1342 case skcms_TFType_Invalid: break;
1343 }
1344 return SkString("non-numeric");
1345 }
1346
WriteToDiskTask1347 static void WriteToDisk(const Task& task,
1348 SkString md5,
1349 const char* ext,
1350 SkStream* data, size_t len,
1351 const SkBitmap* bitmap,
1352 const HashAndEncode* hashAndEncode) {
1353
1354 // Determine whether or not the OldestSupportedSkpVersion extra_config is provided.
1355 bool isOldestSupportedSkp = false;
1356 for (int i = 1; i < FLAGS_key.size(); i += 2) {
1357 if (0 == strcmp(FLAGS_key[i-1], "extra_config") &&
1358 0 == strcmp(FLAGS_key[i], "OldestSupportedSkpVersion")) {
1359 isOldestSupportedSkp = true;
1360 break;
1361 }
1362 }
1363
1364 JsonWriter::BitmapResult result;
1365 result.name = task.src->name();
1366 result.config = task.sink.tag;
1367 result.sourceType = task.src.tag;
1368 // If the OldestSupportedSkpVersion extra_config is provided, override the "skp"
1369 // source_type with "old-skp". This has the effect of grouping the oldest supported SKPs in
1370 // a separate Gold corpus for easier triaging.
1371 if (isOldestSupportedSkp && 0 == strcmp(result.sourceType.c_str(), "skp")) {
1372 result.sourceType = "old-skp";
1373 }
1374 result.sourceOptions = task.src.options;
1375 result.ext = ext;
1376 result.md5 = md5;
1377 if (bitmap) {
1378 result.gamut = identify_gamut (bitmap->colorSpace());
1379 result.transferFn = identify_transfer_fn (bitmap->colorSpace());
1380 result.colorType = ToolUtils::colortype_name (bitmap->colorType());
1381 result.alphaType = ToolUtils::alphatype_name (bitmap->alphaType());
1382 result.colorDepth = ToolUtils::colortype_depth(bitmap->colorType());
1383 }
1384 JsonWriter::AddBitmapResult(result);
1385
1386 // If an MD5 is uninteresting, we want it noted in the JSON file,
1387 // but don't want to dump it out as a .png (or whatever ext is).
1388 if (gUninterestingHashes->contains(md5)) {
1389 return;
1390 }
1391
1392 const char* dir = FLAGS_writePath[0];
1393 SkString resources = GetResourcePath();
1394 if (0 == strcmp(dir, "@")) { // Needed for iOS.
1395 dir = resources.c_str();
1396 }
1397 sk_mkdir(dir);
1398
1399 SkString path;
1400 if (FLAGS_nameByHash) {
1401 path = SkOSPath::Join(dir, result.md5.c_str());
1402 path.append(".");
1403 path.append(ext);
1404 if (sk_exists(path.c_str())) {
1405 return; // Content-addressed. If it exists already, we're done.
1406 }
1407 } else {
1408 path = SkOSPath::Join(dir, task.sink.tag.c_str());
1409 sk_mkdir(path.c_str());
1410 path = SkOSPath::Join(path.c_str(), task.src.tag.c_str());
1411 sk_mkdir(path.c_str());
1412 if (0 != strcmp(task.src.options.c_str(), "")) {
1413 path = SkOSPath::Join(path.c_str(), task.src.options.c_str());
1414 sk_mkdir(path.c_str());
1415 }
1416 path = SkOSPath::Join(path.c_str(), task.src->name().c_str());
1417 path.append(".");
1418 path.append(ext);
1419 }
1420
1421 SkFILEWStream file(path.c_str());
1422 if (!file.isValid()) {
1423 fail(SkStringPrintf("Can't open %s for writing.\n", path.c_str()));
1424 return;
1425 }
1426 if (bitmap) {
1427 SkASSERT(hashAndEncode);
1428 if (!hashAndEncode->encodePNG(&file,
1429 result.md5.c_str(),
1430 FLAGS_key,
1431 FLAGS_properties)) {
1432 fail(SkStringPrintf("Can't encode PNG to %s.\n", path.c_str()));
1433 return;
1434 }
1435 } else {
1436 if (!file.writeStream(data, len)) {
1437 fail(SkStringPrintf("Can't write to %s.\n", path.c_str()));
1438 return;
1439 }
1440 }
1441 }
1442
RunGMVerifiersTask1443 static void RunGMVerifiers(const Task& task, const SkBitmap& actualBitmap) {
1444 const SkString name = task.src->name();
1445 auto verifierList = task.src->getVerifiers();
1446 if (verifierList == nullptr) {
1447 return;
1448 }
1449
1450 skiagm::verifiers::VerifierResult
1451 res = verifierList->verifyAll(task.sink->colorInfo(), actualBitmap);
1452 if (!res.ok()) {
1453 fail(
1454 SkStringPrintf(
1455 "%s %s %s %s: verifier failed: %s", task.sink.tag.c_str(), task.src.tag.c_str(),
1456 task.src.options.c_str(), name.c_str(), res.message().c_str()));
1457 }
1458 }
1459 };
1460
1461 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
1462
1463 // Unit tests don't fit so well into the Src/Sink model, so we give them special treatment.
1464
1465 static SkTDArray<skiatest::Test>* gCPUTests = new SkTDArray<skiatest::Test>;
1466 static SkTDArray<skiatest::Test>* gGaneshTests = new SkTDArray<skiatest::Test>;
1467 static SkTDArray<skiatest::Test>* gGraphiteTests = new SkTDArray<skiatest::Test>;
1468
gather_tests()1469 static void gather_tests() {
1470 if (!FLAGS_src.contains("tests")) {
1471 return;
1472 }
1473 for (const skiatest::Test& test : skiatest::TestRegistry::Range()) {
1474 if (!in_shard()) {
1475 continue;
1476 }
1477 if (CommandLineFlags::ShouldSkip(FLAGS_match, test.fName)) {
1478 continue;
1479 }
1480 if (test.fTestType == TestType::kGanesh && FLAGS_gpu) {
1481 gGaneshTests->push_back(test);
1482 } else if (test.fTestType == TestType::kGraphite && FLAGS_graphite) {
1483 gGraphiteTests->push_back(test);
1484 } else if (test.fTestType == TestType::kCPU && FLAGS_cpu) {
1485 gCPUTests->push_back(test);
1486 }
1487 }
1488 }
1489
1490 struct DMReporter : public skiatest::Reporter {
reportFailedDMReporter1491 void reportFailed(const skiatest::Failure& failure) override {
1492 fail(failure.toString());
1493 }
allowExtendedTestDMReporter1494 bool allowExtendedTest() const override {
1495 return FLAGS_pathOpsExtended;
1496 }
verboseDMReporter1497 bool verbose() const override { return FLAGS_veryVerbose; }
1498 };
1499
run_cpu_test(skiatest::Test test)1500 static void run_cpu_test(skiatest::Test test) {
1501 DMReporter reporter;
1502 if (!FLAGS_dryRun && !should_skip("_", "tests", "_", test.fName)) {
1503 skiatest::ReporterContext ctx(&reporter, SkString(test.fName));
1504 start("unit", "test", "", test.fName);
1505 test.cpu(&reporter);
1506 }
1507 done("unit", "test", "", test.fName);
1508 }
1509
run_ganesh_test(skiatest::Test test,const GrContextOptions & grCtxOptions)1510 static void run_ganesh_test(skiatest::Test test, const GrContextOptions& grCtxOptions) {
1511 DMReporter reporter;
1512 if (!FLAGS_dryRun && !should_skip("_", "tests", "_", test.fName)) {
1513 AutoreleasePool pool;
1514 GrContextOptions options = grCtxOptions;
1515 test.modifyGrContextOptions(&options);
1516
1517 skiatest::ReporterContext ctx(&reporter, SkString(test.fName));
1518 start("unit", "test", "", test.fName);
1519 test.ganesh(&reporter, options);
1520 }
1521 done("unit", "test", "", test.fName);
1522 }
1523
run_graphite_test(skiatest::Test test)1524 static void run_graphite_test(skiatest::Test test) {
1525 DMReporter reporter;
1526 if (!FLAGS_dryRun && !should_skip("_", "tests", "_", test.fName)) {
1527 AutoreleasePool pool;
1528 skiatest::ReporterContext ctx(&reporter, SkString(test.fName));
1529 start("unit", "test", "", test.fName);
1530 test.graphite(&reporter);
1531 }
1532 done("unit", "test", "", test.fName);
1533 }
1534
1535 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
1536
CurrentTestHarness()1537 TestHarness CurrentTestHarness() {
1538 return TestHarness::kDM;
1539 }
1540
1541 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
1542
main(int argc,char ** argv)1543 int main(int argc, char** argv) {
1544 #if defined(__MSVC_RUNTIME_CHECKS)
1545 _RTC_SetErrorFunc(RuntimeCheckErrorFunc);
1546 #endif
1547 #if defined(SK_BUILD_FOR_ANDROID_FRAMEWORK) && defined(SK_HAS_HEIF_LIBRARY)
1548 android::ProcessState::self()->startThreadPool();
1549 #endif
1550 CommandLineFlags::Parse(argc, argv);
1551
1552 initializeEventTracingForTools();
1553
1554 #if !defined(SK_BUILD_FOR_GOOGLE3) && defined(SK_BUILD_FOR_IOS)
1555 cd_Documents();
1556 #endif
1557 setbuf(stdout, nullptr);
1558 setup_crash_handler();
1559
1560 CommonFlags::SetDefaultFontMgr();
1561 CommonFlags::SetAnalyticAA();
1562
1563 gSkForceRasterPipelineBlitter = FLAGS_forceRasterPipelineHP || FLAGS_forceRasterPipeline;
1564 gForceHighPrecisionRasterPipeline = FLAGS_forceRasterPipelineHP;
1565 gUseSkVMBlitter = FLAGS_skvm;
1566 gSkVMAllowJIT = FLAGS_jit;
1567 gSkVMJITViaDylib = FLAGS_dylib;
1568 gSkBlobAsSlugTesting = FLAGS_blobAsSlugTesting;
1569
1570 // The bots like having a verbose.log to upload, so always touch the file even if --verbose.
1571 if (!FLAGS_writePath.isEmpty()) {
1572 sk_mkdir(FLAGS_writePath[0]);
1573 gVLog = fopen(SkOSPath::Join(FLAGS_writePath[0], "verbose.log").c_str(), "w");
1574 }
1575 if (FLAGS_verbose) {
1576 gVLog = stderr;
1577 }
1578
1579 GrContextOptions grCtxOptions;
1580 CommonFlags::SetCtxOptions(&grCtxOptions);
1581
1582 dump_json(); // It's handy for the bots to assume this is ~never missing.
1583
1584 SkAutoGraphics ag;
1585 #if defined(SK_ENABLE_SVG)
1586 SkGraphics::SetOpenTypeSVGDecoderFactory(SkSVGOpenTypeSVGDecoder::Make);
1587 #endif
1588 SkTaskGroup::Enabler enabled(FLAGS_threads);
1589
1590 if (nullptr == GetResourceAsData("images/color_wheel.png")) {
1591 info("Some resources are missing. Do you need to set --resourcePath?\n");
1592 }
1593 gather_gold();
1594 gather_uninteresting_hashes();
1595
1596 if (!gather_srcs()) {
1597 return 1;
1598 }
1599 // TODO(dogben): This is a bit ugly. Find a cleaner way to do this.
1600 bool defaultConfigs = true;
1601 for (int i = 0; i < argc; i++) {
1602 if (strcmp(argv[i], "--config") == 0) {
1603 defaultConfigs = false;
1604 break;
1605 }
1606 }
1607 if (!gather_sinks(grCtxOptions, defaultConfigs)) {
1608 return 1;
1609 }
1610 gather_tests();
1611 int testCount = gCPUTests->size() + gGaneshTests->size() + gGraphiteTests->size();
1612 gPending = gSrcs->size() * gSinks->size() + testCount;
1613 info("%d srcs * %d sinks + %d tests == %d tasks\n",
1614 gSrcs->size(), gSinks->size(), testCount,
1615 gPending);
1616
1617 // Kick off as much parallel work as we can, making note of any serial work we'll need to do.
1618 SkTaskGroup parallel;
1619 SkTArray<Task> serial;
1620
1621 for (TaggedSink& sink : *gSinks) {
1622 for (TaggedSrc& src : *gSrcs) {
1623 if (src->veto(sink->flags()) ||
1624 should_skip(sink.tag.c_str(), src.tag.c_str(),
1625 src.options.c_str(), src->name().c_str())) {
1626 SkAutoSpinlock lock(*gMutex);
1627 gPending--;
1628 continue;
1629 }
1630
1631 Task task(src, sink);
1632 if (src->serial() || sink->serial()) {
1633 serial.push_back(task);
1634 } else {
1635 parallel.add([task] { Task::Run(task); });
1636 }
1637 }
1638 }
1639 for (skiatest::Test& test : *gCPUTests) {
1640 parallel.add([test] { run_cpu_test(test); });
1641 }
1642
1643 // With the parallel work running, run serial tasks and tests here on main thread.
1644 for (Task& task : serial) { Task::Run(task); }
1645 for (skiatest::Test& test : *gGaneshTests) { run_ganesh_test(test, grCtxOptions); }
1646 for (skiatest::Test& test : *gGraphiteTests) { run_graphite_test(test); }
1647
1648 // Wait for any remaining parallel work to complete (including any spun off of serial tasks).
1649 parallel.wait();
1650 gDefinitelyThreadSafeWork->wait();
1651
1652 // At this point we're back in single-threaded land.
1653
1654 // We'd better have run everything.
1655 SkASSERT(gPending == 0);
1656 // Make sure we've flushed all our results to disk.
1657 dump_json();
1658
1659 if (!gFailures->empty()) {
1660 info("Failures:\n");
1661 for (const SkString& fail : *gFailures) {
1662 info("\t%s\n", fail.c_str());
1663 }
1664 info("%d failures\n", gFailures->size());
1665 return 1;
1666 }
1667
1668 SkGraphics::PurgeAllCaches();
1669 info("Finished!\n");
1670
1671 return 0;
1672 }
1673