1/*
2 * Copyright 2015 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
9#include <VisualBench/VisualBenchmarkStream.h>
10#include <VisualBench/WrappedBenchmark.h>
11#include "GMBench.h"
12#include "SkOSFile.h"
13#include "SkPath.h"
14#include "SkPictureRecorder.h"
15#include "SkStream.h"
16#include "sk_tool_utils.h"
17#include "VisualFlags.h"
18#include "VisualSKPBench.h"
19
20#if SK_SUPPORT_GPU
21#include "GrContext.h"
22#endif
23
24DEFINE_string2(match, m, nullptr,
25               "[~][^]substring[$] [...] of bench name to run.\n"
26               "Multiple matches may be separated by spaces.\n"
27               "~ causes a matching bench to always be skipped\n"
28               "^ requires the start of the bench to match\n"
29               "$ requires the end of the bench to match\n"
30               "^ and $ requires an exact match\n"
31               "If a bench does not match any list entry,\n"
32               "it is skipped unless some list entry starts with ~");
33DEFINE_string(skps, "skps", "Directory to read skps from.");
34DEFINE_bool(warmup, true, "Include a warmup bench? (Excluding the warmup may compromise results)");
35
36// We draw a big nonAA path to warmup the gpu / cpu
37#include "SkPerlinNoiseShader.h"
38class WarmupBench : public Benchmark {
39public:
40    WarmupBench() {
41        sk_tool_utils::make_big_path(fPath);
42        fPerlinRect = SkRect::MakeLTRB(0., 0., 400., 400.);
43    }
44private:
45    const char* onGetName() override { return "warmupbench"; }
46    SkIPoint onGetSize() override {
47        int w = SkScalarCeilToInt(SkTMax(fPath.getBounds().right(), fPerlinRect.right()));
48        int h = SkScalarCeilToInt(SkTMax(fPath.getBounds().bottom(), fPerlinRect.bottom()));
49        return SkIPoint::Make(w, h);
50    }
51    void onDraw(int loops, SkCanvas* canvas) override {
52        // We draw a big path to warm up the cpu, and then use perlin noise shader to warm up the
53        // gpu
54        SkPaint paint;
55        paint.setStyle(SkPaint::kStroke_Style);
56        paint.setStrokeWidth(2);
57
58        SkPaint perlinPaint;
59        perlinPaint.setShader(SkPerlinNoiseShader::CreateTurbulence(0.1f, 0.1f, 1, 0,
60                                                                    nullptr))->unref();
61        for (int i = 0; i < loops; i++) {
62            canvas->drawPath(fPath, paint);
63            canvas->drawRect(fPerlinRect, perlinPaint);
64#if SK_SUPPORT_GPU
65            // Ensure the GrContext doesn't batch across draw loops.
66            if (GrContext* context = canvas->getGrContext()) {
67                context->flush();
68            }
69#endif
70        }
71    }
72    SkPath fPath;
73    SkRect fPerlinRect;
74};
75
76VisualBenchmarkStream::VisualBenchmarkStream(const SkSurfaceProps& surfaceProps, bool justSKP)
77    : fSurfaceProps(surfaceProps)
78    , fBenches(BenchRegistry::Head())
79    , fGMs(skiagm::GMRegistry::Head())
80    , fSourceType(nullptr)
81    , fBenchType(nullptr)
82    , fCurrentSKP(0)
83    , fIsWarmedUp(false) {
84    for (int i = 0; i < FLAGS_skps.count(); i++) {
85        if (SkStrEndsWith(FLAGS_skps[i], ".skp")) {
86            fSKPs.push_back() = FLAGS_skps[i];
87        } else {
88            SkOSFile::Iter it(FLAGS_skps[i], ".skp");
89            SkString path;
90            while (it.next(&path)) {
91                fSKPs.push_back() = SkOSPath::Join(FLAGS_skps[0], path.c_str());
92            }
93        }
94    }
95
96    if (justSKP) {
97       fGMs = nullptr;
98       fBenches = nullptr;
99    }
100
101    // seed with an initial benchmark
102    // NOTE the initial benchmark will not have preTimingHooks called, but that is okay because
103    // it is the warmupbench
104    this->next();
105}
106
107bool VisualBenchmarkStream::ReadPicture(const char* path, SkAutoTUnref<SkPicture>* pic) {
108    // Not strictly necessary, as it will be checked again later,
109    // but helps to avoid a lot of pointless work if we're going to skip it.
110    if (SkCommandLineFlags::ShouldSkip(FLAGS_match, path)) {
111        return false;
112    }
113
114    SkAutoTDelete<SkStream> stream(SkStream::NewFromFile(path));
115    if (stream.get() == nullptr) {
116        SkDebugf("Could not read %s.\n", path);
117        return false;
118    }
119
120    pic->reset(SkPicture::CreateFromStream(stream.get()));
121    if (pic->get() == nullptr) {
122        SkDebugf("Could not read %s as an SkPicture.\n", path);
123        return false;
124    }
125    return true;
126}
127
128Benchmark* VisualBenchmarkStream::next() {
129    Benchmark* bench;
130    if (FLAGS_warmup && !fIsWarmedUp) {
131        fIsWarmedUp = true;
132        bench = new WarmupBench;
133    } else {
134        // skips non matching benches
135        while ((bench = this->innerNext()) &&
136               (SkCommandLineFlags::ShouldSkip(FLAGS_match, bench->getUniqueName()) ||
137                !bench->isSuitableFor(Benchmark::kGPU_Backend))) {
138            bench->unref();
139        }
140    }
141
142    // TODO move this all to --config
143    if (bench && FLAGS_cpu) {
144        bench = new CpuWrappedBenchmark(fSurfaceProps, bench);
145    } else if (bench && FLAGS_offscreen) {
146        bench = new GpuWrappedBenchmark(fSurfaceProps, bench, FLAGS_msaa);
147    }
148
149    fBenchmark.reset(bench);
150    return fBenchmark;
151}
152
153Benchmark* VisualBenchmarkStream::innerNext() {
154    while (fBenches) {
155        Benchmark* bench = fBenches->factory()(nullptr);
156        fBenches = fBenches->next();
157        if (bench->isVisual()) {
158            fSourceType = "bench";
159            fBenchType  = "micro";
160            return bench;
161        }
162        bench->unref();
163    }
164
165    while (fGMs) {
166        SkAutoTDelete<skiagm::GM> gm(fGMs->factory()(nullptr));
167        fGMs = fGMs->next();
168        if (gm->runAsBench()) {
169            fSourceType = "gm";
170            fBenchType  = "micro";
171            return new GMBench(gm.detach());
172        }
173    }
174
175    // Render skps
176    while (fCurrentSKP < fSKPs.count()) {
177        const SkString& path = fSKPs[fCurrentSKP++];
178        SkAutoTUnref<SkPicture> pic;
179        if (!ReadPicture(path.c_str(), &pic)) {
180            continue;
181        }
182
183        SkString name = SkOSPath::Basename(path.c_str());
184        fSourceType = "skp";
185        fBenchType = "playback";
186        return new VisualSKPBench(name.c_str(), pic.get());
187    }
188
189    return nullptr;
190}
191