Add viewer mode to VisualBench.
authorjvanverth <jvanverth@google.com>
Tue, 15 Sep 2015 14:40:56 +0000 (07:40 -0700)
committerCommit bot <commit-bot@chromium.org>
Tue, 15 Sep 2015 14:40:56 +0000 (07:40 -0700)
Displays each benchmark/skp with a graph showing a series of frame times.
Use the space bar to skip to the next benchmark.

Adds an option to hit ESC to quit VisualBench. Useful in fullscreen mode.

Review URL: https://codereview.chromium.org/1336043003

tools/VisualBench/VisualBench.cpp
tools/VisualBench/VisualBench.h
tools/VisualBench/VisualInteractiveModule.cpp [new file with mode: 0755]
tools/VisualBench/VisualInteractiveModule.h [new file with mode: 0755]
tools/VisualBench/VisualLightweightBenchModule.cpp
tools/VisualBench/VisualLightweightBenchModule.h
tools/VisualBench/VisualModule.h

index 3c41204..c4b7c24 100644 (file)
 #include "SkStream.h"
 #include "Stats.h"
 #include "VisualLightweightBenchModule.h"
+#include "VisualInteractiveModule.h"
 #include "gl/GrGLInterface.h"
 
 DEFINE_bool2(fullscreen, f, true, "Run fullscreen.");
+DEFINE_bool2(interactive, n, false, "Run in interactive mode.");
 
 VisualBench::VisualBench(void* hwnd, int argc, char** argv)
     : INHERITED(hwnd)
     , fModule(new VisualLightweightBenchModule(this)) {
     SkCommandLineFlags::Parse(argc, argv);
 
+    if (FLAGS_interactive) {
+        fModule.reset(new VisualInteractiveModule(this));
+    }
+
     this->setTitle();
     this->setupBackend();
 }
@@ -98,12 +104,27 @@ void VisualBench::draw(SkCanvas* canvas) {
     this->inval(nullptr);
 }
 
+void VisualBench::clear(SkCanvas* canvas, SkColor color, int frames) {
+    canvas->clear(color);
+    for (int i = 0; i < frames - 1; ++i) {
+        canvas->flush();
+        this->present();
+        canvas->clear(color);
+    }
+}
+
 void VisualBench::onSizeChange() {
     this->setupRenderTarget();
 }
 
 bool VisualBench::onHandleChar(SkUnichar unichar) {
-    return true;
+    static const auto kEscKey = 27;
+    if (kEscKey == unichar) {
+        this->closeWindow();
+        return true;
+    }
+
+    return fModule->onHandleChar(unichar);
 }
 
 // Externally declared entry points
index 75615f0..c1f61d9 100644 (file)
@@ -33,6 +33,8 @@ public:
 
     void reset() { this->resetContext(); }
 
+    void clear(SkCanvas* canvas, SkColor color, int frames);
+
 protected:
     SkSurface* createSurface() override;
 
diff --git a/tools/VisualBench/VisualInteractiveModule.cpp b/tools/VisualBench/VisualInteractiveModule.cpp
new file mode 100755 (executable)
index 0000000..538dda7
--- /dev/null
@@ -0,0 +1,230 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ */
+
+#include "VisualInteractiveModule.h"
+
+#include "ProcStats.h"
+#include "SkApplication.h"
+#include "SkCanvas.h"
+#include "SkCommandLineFlags.h"
+#include "SkForceLinking.h"
+#include "SkGraphics.h"
+#include "SkGr.h"
+#include "SkImageDecoder.h"
+#include "SkOSFile.h"
+#include "SkStream.h"
+#include "Stats.h"
+#include "gl/GrGLInterface.h"
+
+__SK_FORCE_IMAGE_DECODER_LINKING;
+
+static const int kGpuFrameLag = 5;
+static const int kFrames = 5;
+static const double kLoopMs = 5;
+
+VisualInteractiveModule::VisualInteractiveModule(VisualBench* owner)
+    : fCurrentMeasurement(0)
+    , fCurrentFrame(0)
+    , fLoops(1)
+    , fState(kPreWarmLoops_State)
+    , fBenchmark(nullptr)
+    , fOwner(SkRef(owner)) {
+    fBenchmarkStream.reset(new VisualBenchmarkStream);
+
+    memset(fMeasurements, 0, sizeof(fMeasurements));
+}
+
+inline void VisualInteractiveModule::renderFrame(SkCanvas* canvas) {
+    fBenchmark->draw(fLoops, canvas);
+    this->drawStats(canvas);
+    canvas->flush();
+    fOwner->present();
+}
+
+void VisualInteractiveModule::drawStats(SkCanvas* canvas) {
+    static const float kPixelPerMS = 2.0f;
+    static const int kDisplayWidth = 130;
+    static const int kDisplayHeight = 100;
+    static const int kDisplayPadding = 10;
+    static const int kGraphPadding = 3;
+    static const float kBaseMS = 1000.f / 60.f;  // ms/frame to hit 60 fps
+
+    SkISize canvasSize = canvas->getDeviceSize();
+    SkRect rect = SkRect::MakeXYWH(SkIntToScalar(canvasSize.fWidth-kDisplayWidth-kDisplayPadding),
+                                   SkIntToScalar(kDisplayPadding),
+                                   SkIntToScalar(kDisplayWidth), SkIntToScalar(kDisplayHeight));
+    SkPaint paint;
+    canvas->clipRect(rect);
+    paint.setColor(SK_ColorBLACK);
+    canvas->drawRect(rect, paint);
+    // draw the 16ms line
+    paint.setColor(SK_ColorLTGRAY);
+    canvas->drawLine(rect.fLeft, rect.fBottom - kBaseMS*kPixelPerMS, 
+                     rect.fRight, rect.fBottom - kBaseMS*kPixelPerMS, paint);
+    paint.setColor(SK_ColorRED);
+    paint.setStyle(SkPaint::kStroke_Style);
+    canvas->drawRect(rect, paint);
+
+    int x = SkScalarTruncToInt(rect.fLeft) + kGraphPadding;
+    const int xStep = 2;
+    const int startY = SkScalarTruncToInt(rect.fBottom);
+    int i = fCurrentMeasurement;
+    do {
+        int endY = startY - (int)(fMeasurements[i] * kPixelPerMS + 0.5);  // round to nearest value
+        canvas->drawLine(SkIntToScalar(x), SkIntToScalar(startY), 
+                         SkIntToScalar(x), SkIntToScalar(endY), paint);
+        i++;
+        i &= (kMeasurementCount - 1);  // fast mod
+        x += xStep;
+    } while (i != fCurrentMeasurement);
+
+}
+
+bool VisualInteractiveModule::advanceRecordIfNecessary(SkCanvas* canvas) {
+    if (fBenchmark) {
+        return true;
+    }
+
+    fBenchmark.reset(fBenchmarkStream->next());
+    if (!fBenchmark) {
+        return false;
+    }
+
+    // clear both buffers
+    fOwner->clear(canvas, SK_ColorWHITE, 2);
+
+    fBenchmark->preDraw();
+
+    return true;
+}
+
+void VisualInteractiveModule::draw(SkCanvas* canvas) {
+    if (!this->advanceRecordIfNecessary(canvas)) {
+        SkDebugf("Exiting VisualBench successfully\n");
+        fOwner->closeWindow();
+        return;
+    }
+    this->renderFrame(canvas);
+    switch (fState) {
+        case kPreWarmLoopsPerCanvasPreDraw_State: {
+            this->perCanvasPreDraw(canvas, kPreWarmLoops_State);
+            break;
+        }
+        case kPreWarmLoops_State: {
+            this->preWarm(kTuneLoops_State);
+            break;
+        }
+        case kTuneLoops_State: {
+            this->tuneLoops(canvas);
+            break;
+        }
+        case kPreTiming_State: {
+            fBenchmark->perCanvasPreDraw(canvas);
+            fCurrentFrame = 0;
+            fTimer.start();
+            fState = kTiming_State;
+            // fall to next state
+        }
+        case kTiming_State: {
+            this->timing(canvas);
+            break;
+        }
+        case kAdvance_State: {
+            this->postDraw(canvas);
+            this->nextState(kPreWarmLoopsPerCanvasPreDraw_State);
+            break;
+        }
+    }
+}
+
+inline void VisualInteractiveModule::nextState(State nextState) {
+    fState = nextState;
+}
+
+void VisualInteractiveModule::perCanvasPreDraw(SkCanvas* canvas, State nextState) {
+    fBenchmark->perCanvasPreDraw(canvas);
+    fCurrentFrame = 0;
+    this->nextState(nextState);
+}
+
+void VisualInteractiveModule::preWarm(State nextState) {
+    if (fCurrentFrame >= kGpuFrameLag) {
+        // we currently time across all frames to make sure we capture all GPU work
+        this->nextState(nextState);
+        fCurrentFrame = 0;
+        fTimer.start();
+    } else {
+        fCurrentFrame++;
+    }
+}
+
+inline double VisualInteractiveModule::elapsed() {
+    fTimer.end();
+    return fTimer.fWall;
+}
+
+void VisualInteractiveModule::resetTimingState() {
+    fCurrentFrame = 0;
+    fTimer = WallTimer();
+    fOwner->reset();
+}
+
+void VisualInteractiveModule::scaleLoops(double elapsedMs) {
+    // Scale back the number of loops
+    fLoops = (int)ceil(fLoops * kLoopMs / elapsedMs);
+}
+
+inline void VisualInteractiveModule::tuneLoops(SkCanvas* canvas) {
+    if (1 << 30 == fLoops) {
+        // We're about to wrap.  Something's wrong with the bench.
+        SkDebugf("InnerLoops wrapped\n");
+        fLoops = 0;
+    } else {
+        double elapsedMs = this->elapsed();
+        if (elapsedMs > kLoopMs) {
+            this->scaleLoops(elapsedMs);
+            fBenchmark->perCanvasPostDraw(canvas);
+            this->nextState(kPreTiming_State);
+        } else {
+            fLoops *= 2;
+            this->nextState(kPreWarmLoops_State);
+        }
+        this->resetTimingState();
+    }
+}
+
+void VisualInteractiveModule::recordMeasurement() {
+    double measurement = this->elapsed() / (kFrames * fLoops);
+    fMeasurements[fCurrentMeasurement++] = measurement;
+    fCurrentMeasurement &= (kMeasurementCount-1);  // fast mod
+    SkASSERT(fCurrentMeasurement < kMeasurementCount);
+}
+
+void VisualInteractiveModule::postDraw(SkCanvas* canvas) {
+    fBenchmark->perCanvasPostDraw(canvas);
+    fBenchmark.reset(nullptr);
+    fLoops = 1;
+}
+
+inline void VisualInteractiveModule::timing(SkCanvas* canvas) {
+    if (fCurrentFrame >= kFrames) {
+        this->recordMeasurement();
+        fTimer.start();
+        fCurrentFrame = 0;
+    } else {
+        fCurrentFrame++;
+    }
+}
+
+bool VisualInteractiveModule::onHandleChar(SkUnichar c) {
+    if (' ' == c) {
+        this->nextState(kAdvance_State);
+    }
+
+    return true;
+}
diff --git a/tools/VisualBench/VisualInteractiveModule.h b/tools/VisualBench/VisualInteractiveModule.h
new file mode 100755 (executable)
index 0000000..4ff6a36
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ */
+
+#ifndef VisualInteractiveModule_DEFINED
+#define VisualInteractiveModule_DEFINED
+
+#include "VisualModule.h"
+
+#include "ResultsWriter.h"
+#include "SkPicture.h"
+#include "Timer.h"
+#include "VisualBench.h"
+#include "VisualBenchmarkStream.h"
+
+class SkCanvas;
+
+/*
+ * This module for VisualBench is designed to display stats data dynamically
+ */
+class VisualInteractiveModule : public VisualModule {
+public:
+    // TODO get rid of backpointer
+    VisualInteractiveModule(VisualBench* owner);
+
+    void draw(SkCanvas* canvas) override;
+    bool onHandleChar(SkUnichar unichar) override;
+
+private:
+    /*
+     * The heart of visual bench is an event driven timing loop.
+     * kPreWarmLoopsPerCanvasPreDraw_State:  Before we begin timing, Benchmarks have a hook to
+     *                                       access the canvas.  Then we prewarm before the autotune
+     *                                       loops step.
+     * kPreWarmLoops_State:                  We prewarm the gpu before auto tuning to enter a steady
+     *                                       work state
+     * kTuneLoops_State:                     Then we tune the loops of the benchmark to ensure we
+     *                                       are doing a measurable amount of work
+     * kPreTiming_State:                     Because reset the context after tuning loops to ensure
+     *                                       coherent state, we need to restart before timing
+     * kTiming_State:                        Finally we time the benchmark.  In this case we
+     *                                       continue running and displaying benchmark data
+     *                                       until we quit or switch to another benchmark
+     * kAdvance_State:                       Advance to the next benchmark in the stream
+     */
+    enum State {
+        kPreWarmLoopsPerCanvasPreDraw_State,
+        kPreWarmLoops_State,
+        kTuneLoops_State,
+        kPreTiming_State,
+        kTiming_State,
+        kAdvance_State,
+    };
+    void setTitle();
+    bool setupBackend();
+    void setupRenderTarget();
+    void drawStats(SkCanvas*);
+    bool advanceRecordIfNecessary(SkCanvas*);
+    inline void renderFrame(SkCanvas*);
+    inline void nextState(State);
+    void perCanvasPreDraw(SkCanvas*, State);
+    void preWarm(State nextState);
+    void scaleLoops(double elapsedMs);
+    inline void tuneLoops(SkCanvas*);
+    inline void timing(SkCanvas*);
+    inline double elapsed();
+    void resetTimingState();
+    void postDraw(SkCanvas*);
+    void recordMeasurement();
+
+    static const int kMeasurementCount = 64;  // should be power of 2 for fast mod
+    double fMeasurements[kMeasurementCount];
+    int fCurrentMeasurement;
+
+    int fCurrentFrame;
+    int fLoops;
+    WallTimer fTimer;
+    State fState;
+    SkAutoTDelete<VisualBenchmarkStream> fBenchmarkStream;
+    SkAutoTUnref<Benchmark> fBenchmark;
+
+    // support framework
+    SkAutoTUnref<VisualBench> fOwner;
+
+    typedef VisualModule INHERITED;
+};
+
+#endif
index 8f9e488..bc592a3 100644 (file)
@@ -127,7 +127,9 @@ bool VisualLightweightBenchModule::advanceRecordIfNecessary(SkCanvas* canvas) {
         return false;
     }
 
-    canvas->clear(0xffffffff);
+    fOwner->clear(canvas, SK_ColorWHITE, 2);
+
+
     fBenchmark->preDraw();
     fRecords.push_back();
 
@@ -254,3 +256,7 @@ inline void VisualLightweightBenchModule::timing(SkCanvas* canvas) {
         fCurrentFrame++;
     }
 }
+
+bool VisualLightweightBenchModule::onHandleChar(SkUnichar c) {
+    return true;
+}
index 64b4a11..5d48692 100644 (file)
@@ -29,6 +29,8 @@ public:
 
     void draw(SkCanvas* canvas) override;
 
+    bool onHandleChar(SkUnichar c) override;
+
 private:
     /*
      * The heart of visual bench is an event driven timing loop.
index ec4463d..2f83793 100644 (file)
@@ -25,6 +25,8 @@ public:
 
     virtual void draw(SkCanvas* canvas)=0;
 
+    virtual bool onHandleChar(SkUnichar unichar) = 0;
+
 private:
     typedef SkRefCnt INHERITED;
 };