From f5d1b2de031b4ae2130280dd09eadcccb001ba11 Mon Sep 17 00:00:00 2001 From: jvanverth Date: Tue, 15 Sep 2015 07:40:56 -0700 Subject: [PATCH] Add viewer mode to VisualBench. 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 | 23 ++- tools/VisualBench/VisualBench.h | 2 + tools/VisualBench/VisualInteractiveModule.cpp | 230 +++++++++++++++++++++ tools/VisualBench/VisualInteractiveModule.h | 92 +++++++++ tools/VisualBench/VisualLightweightBenchModule.cpp | 8 +- tools/VisualBench/VisualLightweightBenchModule.h | 2 + tools/VisualBench/VisualModule.h | 2 + 7 files changed, 357 insertions(+), 2 deletions(-) create mode 100755 tools/VisualBench/VisualInteractiveModule.cpp create mode 100755 tools/VisualBench/VisualInteractiveModule.h diff --git a/tools/VisualBench/VisualBench.cpp b/tools/VisualBench/VisualBench.cpp index 3c41204..c4b7c24 100644 --- a/tools/VisualBench/VisualBench.cpp +++ b/tools/VisualBench/VisualBench.cpp @@ -18,15 +18,21 @@ #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 diff --git a/tools/VisualBench/VisualBench.h b/tools/VisualBench/VisualBench.h index 75615f0..c1f61d9 100644 --- a/tools/VisualBench/VisualBench.h +++ b/tools/VisualBench/VisualBench.h @@ -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 index 0000000..538dda7 --- /dev/null +++ b/tools/VisualBench/VisualInteractiveModule.cpp @@ -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 index 0000000..4ff6a36 --- /dev/null +++ b/tools/VisualBench/VisualInteractiveModule.h @@ -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 fBenchmarkStream; + SkAutoTUnref fBenchmark; + + // support framework + SkAutoTUnref fOwner; + + typedef VisualModule INHERITED; +}; + +#endif diff --git a/tools/VisualBench/VisualLightweightBenchModule.cpp b/tools/VisualBench/VisualLightweightBenchModule.cpp index 8f9e488..bc592a3 100644 --- a/tools/VisualBench/VisualLightweightBenchModule.cpp +++ b/tools/VisualBench/VisualLightweightBenchModule.cpp @@ -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; +} diff --git a/tools/VisualBench/VisualLightweightBenchModule.h b/tools/VisualBench/VisualLightweightBenchModule.h index 64b4a11..5d48692 100644 --- a/tools/VisualBench/VisualLightweightBenchModule.h +++ b/tools/VisualBench/VisualLightweightBenchModule.h @@ -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. diff --git a/tools/VisualBench/VisualModule.h b/tools/VisualBench/VisualModule.h index ec4463d..2f83793 100644 --- a/tools/VisualBench/VisualModule.h +++ b/tools/VisualBench/VisualModule.h @@ -25,6 +25,8 @@ public: virtual void draw(SkCanvas* canvas)=0; + virtual bool onHandleChar(SkUnichar unichar) = 0; + private: typedef SkRefCnt INHERITED; }; -- 2.7.4