1// Main binary for DM.
2// For a high-level overview, please see dm/README.
3
4#include "Benchmark.h"
5#include "CrashHandler.h"
6#include "SkCommandLineFlags.h"
7#include "SkForceLinking.h"
8#include "SkGraphics.h"
9#include "SkPicture.h"
10#include "SkString.h"
11#include "Test.h"
12#include "gm.h"
13
14#include "DMBenchTask.h"
15#include "DMCpuGMTask.h"
16#include "DMGpuGMTask.h"
17#include "DMGpuSupport.h"
18#include "DMPDFTask.h"
19#include "DMReporter.h"
20#include "DMSKPTask.h"
21#include "DMTask.h"
22#include "DMTaskRunner.h"
23#include "DMTestTask.h"
24#include "DMWriteTask.h"
25
26#ifdef SK_BUILD_POPPLER
27#  include "SkPDFRasterizer.h"
28#  define RASTERIZE_PDF_PROC SkPopplerRasterizePDF
29#else
30#  define RASTERIZE_PDF_PROC NULL
31#endif
32
33#include <ctype.h>
34
35using skiagm::GM;
36using skiagm::GMRegistry;
37using skiatest::Test;
38using skiatest::TestRegistry;
39
40DEFINE_int32(threads, -1, "Threads for CPU work. Default NUM_CPUS.");
41DEFINE_int32(gpuThreads, 1, "Threads for GPU work.");
42DEFINE_string2(expectations, r, "",
43               "If a directory, compare generated images against images under this path. "
44               "If a file, compare generated images against JSON expectations at this path."
45);
46DEFINE_string2(resources, i, "resources", "Path to resources directory.");
47DEFINE_string(match, "",  "[~][^]substring[$] [...] of GM name to run.\n"
48                          "Multiple matches may be separated by spaces.\n"
49                          "~ causes a matching GM to always be skipped\n"
50                          "^ requires the start of the GM to match\n"
51                          "$ requires the end of the GM to match\n"
52                          "^ and $ requires an exact match\n"
53                          "If a GM does not match any list entry,\n"
54                          "it is skipped unless some list entry starts with ~");
55DEFINE_string(config, "565 8888 pdf gpu nonrendering",
56              "Options: 565 8888 pdf gpu nonrendering msaa4 msaa16 nvprmsaa4 nvprmsaa16 "
57              "gpunull gpudebug angle mesa");
58DEFINE_bool(dryRun, false,
59            "Just print the tests that would be run, without actually running them.");
60DEFINE_bool(leaks, false, "Print leaked instance-counted objects at exit?");
61DEFINE_string(skps, "", "Directory to read skps from.");
62
63DEFINE_bool(gms, true, "Run GMs?");
64DEFINE_bool(benches, true, "Run benches?  Does not run GMs-as-benches.");
65DEFINE_bool(tests, true, "Run tests?");
66
67DECLARE_bool(verbose);
68
69__SK_FORCE_IMAGE_DECODER_LINKING;
70
71// "FooBar" -> "foobar".  Obviously, ASCII only.
72static SkString lowercase(SkString s) {
73    for (size_t i = 0; i < s.size(); i++) {
74        s[i] = tolower(s[i]);
75    }
76    return s;
77}
78
79static const GrContextFactory::GLContextType native = GrContextFactory::kNative_GLContextType;
80static const GrContextFactory::GLContextType nvpr = GrContextFactory::kNVPR_GLContextType;
81static const GrContextFactory::GLContextType null   = GrContextFactory::kNull_GLContextType;
82static const GrContextFactory::GLContextType debug  = GrContextFactory::kDebug_GLContextType;
83static const GrContextFactory::GLContextType angle  =
84#if SK_ANGLE
85GrContextFactory::kANGLE_GLContextType;
86#else
87native;
88#endif
89static const GrContextFactory::GLContextType mesa   =
90#if SK_MESA
91GrContextFactory::kMESA_GLContextType;
92#else
93native;
94#endif
95
96static void kick_off_gms(const SkTDArray<GMRegistry::Factory>& gms,
97                         const SkTArray<SkString>& configs,
98                         const DM::Expectations& expectations,
99                         DM::Reporter* reporter,
100                         DM::TaskRunner* tasks) {
101#define START(name, type, ...)                                                              \
102    if (lowercase(configs[j]).equals(name)) {                                               \
103        tasks->add(SkNEW_ARGS(DM::type, (name, reporter, tasks, gms[i], ## __VA_ARGS__)));  \
104    }
105    for (int i = 0; i < gms.count(); i++) {
106        for (int j = 0; j < configs.count(); j++) {
107            START("565",        CpuGMTask, expectations, kRGB_565_SkColorType);
108            START("8888",       CpuGMTask, expectations, kN32_SkColorType);
109            START("gpu",        GpuGMTask, expectations, native, 0);
110            START("msaa4",      GpuGMTask, expectations, native, 4);
111            START("msaa16",     GpuGMTask, expectations, native, 16);
112            START("nvprmsaa4",  GpuGMTask, expectations, nvpr,   4);
113            START("nvprmsaa16", GpuGMTask, expectations, nvpr,   16);
114            START("gpunull",    GpuGMTask, expectations, null,   0);
115            START("gpudebug",   GpuGMTask, expectations, debug,  0);
116            START("angle",      GpuGMTask, expectations, angle,  0);
117            START("mesa",       GpuGMTask, expectations, mesa,   0);
118            START("pdf",        PDFTask,   RASTERIZE_PDF_PROC);
119        }
120    }
121#undef START
122}
123
124static void kick_off_benches(const SkTDArray<BenchRegistry::Factory>& benches,
125                             const SkTArray<SkString>& configs,
126                             DM::Reporter* reporter,
127                             DM::TaskRunner* tasks) {
128#define START(name, type, ...)                                                                 \
129    if (lowercase(configs[j]).equals(name)) {                                                  \
130        tasks->add(SkNEW_ARGS(DM::type, (name, reporter, tasks, benches[i], ## __VA_ARGS__))); \
131    }
132    for (int i = 0; i < benches.count(); i++) {
133        for (int j = 0; j < configs.count(); j++) {
134            START("nonrendering", NonRenderingBenchTask);
135            START("565",          CpuBenchTask, kRGB_565_SkColorType);
136            START("8888",         CpuBenchTask, kN32_SkColorType);
137            START("gpu",          GpuBenchTask, native, 0);
138            START("msaa4",        GpuBenchTask, native, 4);
139            START("msaa16",       GpuBenchTask, native, 16);
140            START("nvprmsaa4",    GpuBenchTask, nvpr,   4);
141            START("nvprmsaa16",   GpuBenchTask, nvpr,   16);
142            START("gpunull",      GpuBenchTask, null,   0);
143            START("gpudebug",     GpuBenchTask, debug,  0);
144            START("angle",        GpuBenchTask, angle,  0);
145            START("mesa",         GpuBenchTask, mesa,   0);
146        }
147    }
148#undef START
149}
150
151static void kick_off_tests(const SkTDArray<TestRegistry::Factory>& tests,
152                           DM::Reporter* reporter,
153                           DM::TaskRunner* tasks) {
154    for (int i = 0; i < tests.count(); i++) {
155        SkAutoTDelete<Test> test(tests[i](NULL));
156        if (test->isGPUTest()) {
157            tasks->add(SkNEW_ARGS(DM::GpuTestTask, (reporter, tasks, tests[i])));
158        } else {
159            tasks->add(SkNEW_ARGS(DM::CpuTestTask, (reporter, tasks, tests[i])));
160        }
161    }
162}
163
164static void kick_off_skps(DM::Reporter* reporter, DM::TaskRunner* tasks) {
165    if (FLAGS_skps.isEmpty()) {
166        return;
167    }
168
169    SkOSFile::Iter it(FLAGS_skps[0], ".skp");
170    SkString filename;
171    while (it.next(&filename)) {
172        if (SkCommandLineFlags::ShouldSkip(FLAGS_match, filename.c_str())) {
173            continue;
174        }
175
176        const SkString path = SkOSPath::SkPathJoin(FLAGS_skps[0], filename.c_str());
177
178        SkAutoTDelete<SkStream> stream(SkStream::NewFromFile(path.c_str()));
179        if (stream.get() == NULL) {
180            SkDebugf("Could not read %s.\n", path.c_str());
181            exit(1);
182        }
183        SkAutoTUnref<SkPicture> pic(SkPicture::CreateFromStream(stream.get()));
184        if (pic.get() == NULL) {
185            SkDebugf("Could not read %s as an SkPicture.\n", path.c_str());
186            exit(1);
187        }
188
189        tasks->add(SkNEW_ARGS(DM::SKPTask, (reporter, tasks, pic->clone(), filename)));
190        tasks->add(SkNEW_ARGS(DM::PDFTask, (reporter, tasks, pic->clone(), filename,
191                                            RASTERIZE_PDF_PROC)));
192    }
193}
194
195static void report_failures(const SkTArray<SkString>& failures) {
196    if (failures.count() == 0) {
197        return;
198    }
199
200    SkDebugf("Failures:\n");
201    for (int i = 0; i < failures.count(); i++) {
202        SkDebugf("  %s\n", failures[i].c_str());
203    }
204    SkDebugf("%d failures.\n", failures.count());
205}
206
207template <typename T, typename Registry>
208static void append_matching_factories(Registry* head, SkTDArray<typename Registry::Factory>* out) {
209    for (const Registry* reg = head; reg != NULL; reg = reg->next()) {
210        SkAutoTDelete<T> forName(reg->factory()(NULL));
211        if (!SkCommandLineFlags::ShouldSkip(FLAGS_match, forName->getName())) {
212            *out->append() = reg->factory();
213        }
214    }
215}
216
217int tool_main(int argc, char** argv);
218int tool_main(int argc, char** argv) {
219    SetupCrashHandler();
220    SkAutoGraphics ag;
221    SkCommandLineFlags::Parse(argc, argv);
222
223    if (FLAGS_dryRun) {
224        FLAGS_verbose = true;
225    }
226#if SK_ENABLE_INST_COUNT
227    gPrintInstCount = FLAGS_leaks;
228#endif
229
230    SkTArray<SkString> configs;
231    for (int i = 0; i < FLAGS_config.count(); i++) {
232        SkStrSplit(FLAGS_config[i], ", ", &configs);
233    }
234
235    SkTDArray<GMRegistry::Factory> gms;
236    SkAutoTDelete<DM::Expectations> expectations(SkNEW(DM::NoExpectations));
237    if (FLAGS_gms) {
238        append_matching_factories<GM>(GMRegistry::Head(), &gms);
239
240        if (FLAGS_expectations.count() > 0) {
241            const char* path = FLAGS_expectations[0];
242            if (sk_isdir(path)) {
243                expectations.reset(SkNEW_ARGS(DM::WriteTask::Expectations, (path)));
244            } else {
245                expectations.reset(SkNEW_ARGS(DM::JsonExpectations, (path)));
246            }
247        }
248    }
249
250    SkTDArray<BenchRegistry::Factory> benches;
251    if (FLAGS_benches) {
252        append_matching_factories<Benchmark>(BenchRegistry::Head(), &benches);
253    }
254
255    SkTDArray<TestRegistry::Factory> tests;
256    if (FLAGS_tests) {
257        append_matching_factories<Test>(TestRegistry::Head(), &tests);
258    }
259
260    SkDebugf("(%d GMs, %d benches) x %d configs, %d tests\n",
261             gms.count(), benches.count(), configs.count(), tests.count());
262    DM::Reporter reporter;
263    DM::TaskRunner tasks(FLAGS_threads, FLAGS_gpuThreads);
264    kick_off_gms(gms, configs, *expectations, &reporter, &tasks);
265    kick_off_benches(benches, configs, &reporter, &tasks);
266    kick_off_tests(tests, &reporter, &tasks);
267    kick_off_skps(&reporter, &tasks);
268    tasks.wait();
269
270    SkDebugf("\n");
271
272    SkTArray<SkString> failures;
273    reporter.getFailures(&failures);
274    report_failures(failures);
275    return failures.count() > 0;
276}
277
278#if !defined(SK_BUILD_FOR_IOS) && !defined(SK_BUILD_FOR_NACL)
279int main(int argc, char** argv) {
280    return tool_main(argc, argv);
281}
282#endif
283