1/*
2 * Copyright 2012 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 "BenchLogger.h"
9#include "Timer.h"
10#include "CopyTilesRenderer.h"
11#include "CrashHandler.h"
12#include "LazyDecodeBitmap.h"
13#include "PictureBenchmark.h"
14#include "PictureRenderingFlags.h"
15#include "PictureResultsWriter.h"
16#include "SkCommandLineFlags.h"
17#include "SkData.h"
18#include "SkDiscardableMemoryPool.h"
19#include "SkGraphics.h"
20#include "SkImageDecoder.h"
21#include "SkMath.h"
22#include "SkOSFile.h"
23#include "SkPicture.h"
24#include "SkStream.h"
25#include "picture_utils.h"
26
27BenchLogger gLogger;
28PictureResultsLoggerWriter gLogWriter(&gLogger);
29PictureResultsMultiWriter gWriter;
30
31// Flags used by this file, in alphabetical order.
32DEFINE_bool(countRAM, false, "Count the RAM used for bitmap pixels in each skp file");
33DECLARE_bool(deferImageDecoding);
34DEFINE_string(filter, "",
35        "type:flag : Enable canvas filtering to disable a paint flag, "
36        "use no blur or low quality blur, or use no hinting or "
37        "slight hinting. For all flags except AAClip, specify the "
38        "type of primitive to effect, or choose all. for AAClip "
39        "alone, the filter affects all clips independent of type. "
40        "Specific flags are listed above.");
41DEFINE_string(logFile, "", "Destination for writing log output, in addition to stdout.");
42DEFINE_bool(logPerIter, false, "Log each repeat timer instead of mean.");
43DEFINE_string(jsonLog, "", "Destination for writing JSON data.");
44DEFINE_bool(min, false, "Print the minimum times (instead of average).");
45DECLARE_int32(multi);
46DECLARE_string(readPath);
47DEFINE_int32(repeat, 1, "Set the number of times to repeat each test.");
48DEFINE_bool(timeIndividualTiles, false, "Report times for drawing individual tiles, rather than "
49            "times for drawing the whole page. Requires tiled rendering.");
50DEFINE_bool(purgeDecodedTex, false, "Purge decoded and GPU-uploaded textures "
51            "after each iteration.");
52DEFINE_string(timers, "c", "[wcgWC]*: Display wall, cpu, gpu, truncated wall or truncated cpu time"
53              " for each picture.");
54DEFINE_bool(trackDeferredCaching, false, "Only meaningful with --deferImageDecoding and "
55            "SK_LAZY_CACHE_STATS set to true. Report percentage of cache hits when using "
56            "deferred image decoding.");
57
58DEFINE_bool(preprocess, false, "If true, perform device specific preprocessing before timing.");
59
60static char const * const gFilterTypes[] = {
61    "paint",
62    "point",
63    "line",
64    "bitmap",
65    "rect",
66    "oval",
67    "path",
68    "text",
69    "all",
70};
71
72static const size_t kFilterTypesCount = sizeof(gFilterTypes) / sizeof(gFilterTypes[0]);
73
74static char const * const gFilterFlags[] = {
75    "antiAlias",
76    "filterBitmap",
77    "dither",
78    "underlineText",
79    "strikeThruText",
80    "fakeBoldText",
81    "linearText",
82    "subpixelText",
83    "devKernText",
84    "LCDRenderText",
85    "embeddedBitmapText",
86    "autoHinting",
87    "verticalText",
88    "genA8FromLCD",
89    "blur",
90    "hinting",
91    "slightHinting",
92    "AAClip",
93};
94
95static const size_t kFilterFlagsCount = sizeof(gFilterFlags) / sizeof(gFilterFlags[0]);
96
97static SkString filtersName(sk_tools::PictureRenderer::DrawFilterFlags* drawFilters) {
98    int all = drawFilters[0];
99    size_t tIndex;
100    for (tIndex = 1; tIndex < SkDrawFilter::kTypeCount; ++tIndex) {
101        all &= drawFilters[tIndex];
102    }
103    SkString result;
104    for (size_t fIndex = 0; fIndex < kFilterFlagsCount; ++fIndex) {
105        SkString types;
106        if (all & (1 << fIndex)) {
107            types = gFilterTypes[SkDrawFilter::kTypeCount];
108        } else {
109            for (tIndex = 0; tIndex < SkDrawFilter::kTypeCount; ++tIndex) {
110                if (drawFilters[tIndex] & (1 << fIndex)) {
111                    types += gFilterTypes[tIndex];
112                }
113            }
114        }
115        if (!types.size()) {
116            continue;
117        }
118        result += "_";
119        result += types;
120        result += ".";
121        result += gFilterFlags[fIndex];
122    }
123    return result;
124}
125
126static SkString filterTypesUsage() {
127    SkString result;
128    for (size_t index = 0; index < kFilterTypesCount; ++index) {
129        result += gFilterTypes[index];
130        if (index < kFilterTypesCount - 1) {
131            result += " | ";
132        }
133    }
134    return result;
135}
136
137static SkString filterFlagsUsage() {
138    SkString result;
139    size_t len = 0;
140    for (size_t index = 0; index < kFilterFlagsCount; ++index) {
141        result += gFilterFlags[index];
142        if (result.size() - len >= 72) {
143            result += "\n\t\t";
144            len = result.size();
145        }
146        if (index < kFilterFlagsCount - 1) {
147            result += " | ";
148        }
149    }
150    return result;
151}
152
153#if SK_LAZY_CACHE_STATS
154static int32_t gTotalCacheHits;
155static int32_t gTotalCacheMisses;
156#endif
157
158static bool run_single_benchmark(const SkString& inputPath,
159                                 sk_tools::PictureBenchmark& benchmark) {
160    SkFILEStream inputStream;
161
162    inputStream.setPath(inputPath.c_str());
163    if (!inputStream.isValid()) {
164        SkString err;
165        err.printf("Could not open file %s\n", inputPath.c_str());
166        gLogger.logError(err);
167        return false;
168    }
169
170    SkDiscardableMemoryPool* pool = SkGetGlobalDiscardableMemoryPool();
171    // Since the old picture has been deleted, all pixels should be cleared.
172    SkASSERT(pool->getRAMUsed() == 0);
173    if (FLAGS_countRAM) {
174        pool->setRAMBudget(SK_MaxU32);
175        // Set the limit to max, so all pixels will be kept
176    }
177
178    SkPicture::InstallPixelRefProc proc;
179    if (FLAGS_deferImageDecoding) {
180        proc = &sk_tools::LazyDecodeBitmap;
181    } else {
182        proc = &SkImageDecoder::DecodeMemory;
183    }
184    SkAutoTUnref<SkPicture> picture(SkPicture::CreateFromStream(&inputStream, proc));
185
186    if (NULL == picture.get()) {
187        SkString err;
188        err.printf("Could not read an SkPicture from %s\n", inputPath.c_str());
189        gLogger.logError(err);
190        return false;
191    }
192
193    SkString filename = SkOSPath::SkBasename(inputPath.c_str());
194
195    gWriter.bench(filename.c_str(), picture->width(), picture->height());
196
197    benchmark.run(picture);
198
199#if SK_LAZY_CACHE_STATS
200    if (FLAGS_trackDeferredCaching) {
201        int cacheHits = pool->getCacheHits();
202        int cacheMisses = pool->getCacheMisses();
203        pool->resetCacheHitsAndMisses();
204        SkString hitString;
205        hitString.printf("Cache hit rate: %f\n", (double) cacheHits / (cacheHits + cacheMisses));
206        gLogger.logProgress(hitString);
207        gTotalCacheHits += cacheHits;
208        gTotalCacheMisses += cacheMisses;
209    }
210#endif
211    if (FLAGS_countRAM) {
212        SkString ramCount("RAM used for bitmaps: ");
213        size_t bytes = pool->getRAMUsed();
214        if (bytes > 1024) {
215            size_t kb = bytes / 1024;
216            if (kb > 1024) {
217                size_t mb = kb / 1024;
218                ramCount.appendf("%zi MB\n", mb);
219            } else {
220                ramCount.appendf("%zi KB\n", kb);
221            }
222        } else {
223            ramCount.appendf("%zi bytes\n", bytes);
224        }
225        gLogger.logProgress(ramCount);
226    }
227
228    return true;
229}
230
231static void setup_benchmark(sk_tools::PictureBenchmark* benchmark) {
232    sk_tools::PictureRenderer::DrawFilterFlags drawFilters[SkDrawFilter::kTypeCount];
233    sk_bzero(drawFilters, sizeof(drawFilters));
234
235    if (FLAGS_filter.count() > 0) {
236        const char* filters = FLAGS_filter[0];
237        const char* colon = strchr(filters, ':');
238        if (colon) {
239            int32_t type = -1;
240            size_t typeLen = colon - filters;
241            for (size_t tIndex = 0; tIndex < kFilterTypesCount; ++tIndex) {
242                if (typeLen == strlen(gFilterTypes[tIndex])
243                        && !strncmp(filters, gFilterTypes[tIndex], typeLen)) {
244                    type = SkToS32(tIndex);
245                    break;
246                }
247            }
248            if (type < 0) {
249                SkString err;
250                err.printf("Unknown type for --filter %s\n", filters);
251                gLogger.logError(err);
252                exit(-1);
253            }
254            int flag = -1;
255            size_t flagLen = strlen(filters) - typeLen - 1;
256            for (size_t fIndex = 0; fIndex < kFilterFlagsCount; ++fIndex) {
257                if (flagLen == strlen(gFilterFlags[fIndex])
258                        && !strncmp(colon + 1, gFilterFlags[fIndex], flagLen)) {
259                    flag = 1 << fIndex;
260                    break;
261                }
262            }
263            if (flag < 0) {
264                SkString err;
265                err.printf("Unknown flag for --filter %s\n", filters);
266                gLogger.logError(err);
267                exit(-1);
268            }
269            for (int index = 0; index < SkDrawFilter::kTypeCount; ++index) {
270                if (type != SkDrawFilter::kTypeCount && index != type) {
271                    continue;
272                }
273                drawFilters[index] = (sk_tools::PictureRenderer::DrawFilterFlags)
274                        (drawFilters[index] | flag);
275            }
276        } else {
277            SkString err;
278            err.printf("Unknown arg for --filter %s : missing colon\n", filters);
279            gLogger.logError(err);
280            exit(-1);
281        }
282    }
283
284    if (FLAGS_timers.count() > 0) {
285        size_t index = 0;
286        bool timerWall = false;
287        bool truncatedTimerWall = false;
288        bool timerCpu = false;
289        bool truncatedTimerCpu = false;
290        bool timerGpu = false;
291        while (index < strlen(FLAGS_timers[0])) {
292            switch (FLAGS_timers[0][index]) {
293                case 'w':
294                    timerWall = true;
295                    break;
296                case 'c':
297                    timerCpu = true;
298                    break;
299                case 'W':
300                    truncatedTimerWall = true;
301                    break;
302                case 'C':
303                    truncatedTimerCpu = true;
304                    break;
305                case 'g':
306                    timerGpu = true;
307                    break;
308                default:
309                    SkDebugf("mystery character\n");
310                    break;
311            }
312            index++;
313        }
314        benchmark->setTimersToShow(timerWall, truncatedTimerWall, timerCpu, truncatedTimerCpu,
315                                  timerGpu);
316    }
317
318    SkString errorString;
319    SkAutoTUnref<sk_tools::PictureRenderer> renderer(parseRenderer(errorString,
320                                                                   kBench_PictureTool));
321
322    if (errorString.size() > 0) {
323        gLogger.logError(errorString);
324    }
325
326    if (NULL == renderer.get()) {
327        exit(-1);
328    }
329
330    if (FLAGS_timeIndividualTiles) {
331        if (FLAGS_multi > 1) {
332            gLogger.logError("Cannot time individual tiles with more than one thread.\n");
333            exit(-1);
334        }
335        sk_tools::TiledPictureRenderer* tiledRenderer = renderer->getTiledRenderer();
336        if (NULL == tiledRenderer) {
337            gLogger.logError("--timeIndividualTiles requires tiled rendering.\n");
338            exit(-1);
339        }
340        if (!tiledRenderer->supportsTimingIndividualTiles()) {
341            gLogger.logError("This renderer does not support --timeIndividualTiles.\n");
342            exit(-1);
343        }
344        benchmark->setTimeIndividualTiles(true);
345    }
346
347    benchmark->setPurgeDecodedTex(FLAGS_purgeDecodedTex);
348    benchmark->setPreprocess(FLAGS_preprocess);
349
350    if (FLAGS_readPath.count() < 1) {
351        gLogger.logError(".skp files or directories are required.\n");
352        exit(-1);
353    }
354
355    renderer->setDrawFilters(drawFilters, filtersName(drawFilters));
356    if (FLAGS_logPerIter) {
357        benchmark->setTimerResultType(TimerData::kPerIter_Result);
358    } else if (FLAGS_min) {
359        benchmark->setTimerResultType(TimerData::kMin_Result);
360    } else {
361        benchmark->setTimerResultType(TimerData::kAvg_Result);
362    }
363    benchmark->setRenderer(renderer);
364    benchmark->setRepeats(FLAGS_repeat);
365    benchmark->setWriter(&gWriter);
366}
367
368static int process_input(const char* input,
369                         sk_tools::PictureBenchmark& benchmark) {
370    SkString inputAsSkString(input);
371    SkOSFile::Iter iter(input, "skp");
372    SkString inputFilename;
373    int failures = 0;
374    if (iter.next(&inputFilename)) {
375        do {
376            SkString inputPath = SkOSPath::SkPathJoin(input, inputFilename.c_str());
377            if (!run_single_benchmark(inputPath, benchmark)) {
378                ++failures;
379            }
380        } while(iter.next(&inputFilename));
381    } else if (SkStrEndsWith(input, ".skp")) {
382        if (!run_single_benchmark(inputAsSkString, benchmark)) {
383            ++failures;
384        }
385    } else {
386        SkString warning;
387        warning.printf("Warning: skipping %s\n", input);
388        gLogger.logError(warning);
389    }
390    return failures;
391}
392
393int tool_main(int argc, char** argv);
394int tool_main(int argc, char** argv) {
395    SetupCrashHandler();
396    SkString usage;
397    usage.printf("Time drawing .skp files.\n"
398                 "\tPossible arguments for --filter: [%s]\n\t\t[%s]",
399                 filterTypesUsage().c_str(), filterFlagsUsage().c_str());
400    SkCommandLineFlags::SetUsage(usage.c_str());
401    SkCommandLineFlags::Parse(argc, argv);
402
403    if (FLAGS_repeat < 1) {
404        SkString error;
405        error.printf("--repeats must be >= 1. Was %i\n", FLAGS_repeat);
406        gLogger.logError(error);
407        exit(-1);
408    }
409
410    if (FLAGS_logFile.count() == 1) {
411        if (!gLogger.SetLogFile(FLAGS_logFile[0])) {
412            SkString str;
413            str.printf("Could not open %s for writing.\n", FLAGS_logFile[0]);
414            gLogger.logError(str);
415            // TODO(borenet): We're disabling this for now, due to
416            // write-protected Android devices.  The very short-term
417            // solution is to ignore the fact that we have no log file.
418            //exit(-1);
419        }
420    }
421
422    SkAutoTDelete<PictureJSONResultsWriter> jsonWriter;
423    if (FLAGS_jsonLog.count() == 1) {
424        jsonWriter.reset(SkNEW(PictureJSONResultsWriter(FLAGS_jsonLog[0])));
425        gWriter.add(jsonWriter.get());
426    }
427
428    gWriter.add(&gLogWriter);
429
430
431#if SK_ENABLE_INST_COUNT
432    gPrintInstCount = true;
433#endif
434    SkAutoGraphics ag;
435
436    sk_tools::PictureBenchmark benchmark;
437
438    setup_benchmark(&benchmark);
439
440    int failures = 0;
441    for (int i = 0; i < FLAGS_readPath.count(); ++i) {
442        failures += process_input(FLAGS_readPath[i], benchmark);
443    }
444
445    if (failures != 0) {
446        SkString err;
447        err.printf("Failed to run %i benchmarks.\n", failures);
448        gLogger.logError(err);
449        return 1;
450    }
451#if SK_LAZY_CACHE_STATS
452    if (FLAGS_trackDeferredCaching) {
453        SkDebugf("Total cache hit rate: %f\n",
454                 (double) gTotalCacheHits / (gTotalCacheHits + gTotalCacheMisses));
455    }
456#endif
457    gWriter.end();
458    return 0;
459}
460
461#if !defined SK_BUILD_FOR_IOS
462int main(int argc, char * const argv[]) {
463    return tool_main(argc, (char**) argv);
464}
465#endif
466