Android HWUI backend Nanobench

Uses filtering canvas from utils/android, shared with DM.
Follow-up plans in https://skbug.com/3589, https://skbug.com/3595

R=djsollen@google.com

Review URL: https://codereview.chromium.org/1029423010
This commit is contained in:
tomhudson 2015-03-26 11:28:06 -07:00 committed by Commit bot
parent e0b19d4985
commit d968a6f29e
6 changed files with 438 additions and 97 deletions

View File

@ -57,6 +57,7 @@ public:
kRaster_Backend,
kGPU_Backend,
kPDF_Backend,
kHWUI_Backend,
};
// Call to determine whether the benchmark is intended for

View File

@ -7,6 +7,8 @@
#include <ctype.h>
#include "nanobench.h"
#include "Benchmark.h"
#include "CrashHandler.h"
#include "DecodingBench.h"
@ -32,6 +34,10 @@
#include "SkSurface.h"
#include "SkTaskGroup.h"
#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
#include "nanobenchAndroid.h"
#endif
#if SK_SUPPORT_GPU
#include "gl/GrGLDefines.h"
#include "GrContextFactory.h"
@ -86,24 +92,100 @@ static SkString humanize(double ms) {
}
#define HUMANIZE(ms) humanize(ms).c_str()
static double time(int loops, Benchmark* bench, SkCanvas* canvas, SkGLContext* gl) {
bool Target::init(SkImageInfo info, Benchmark* bench) {
if (Benchmark::kRaster_Backend == config.backend) {
this->surface.reset(SkSurface::NewRaster(info));
if (!this->surface.get()) {
return false;
}
}
return true;
}
bool Target::capturePixels(SkBitmap* bmp) {
if (!this->surface.get()) {
return false;
}
SkCanvas* canvas = this->surface->getCanvas();
if (!canvas) {
return false;
}
bmp->setInfo(canvas->imageInfo());
if (!canvas->readPixels(bmp, 0, 0)) {
SkDebugf("Can't read canvas pixels.\n");
return false;
}
return true;
}
#if SK_SUPPORT_GPU
struct GPUTarget : public Target {
explicit GPUTarget(const Config& c) : Target(c), gl(NULL) { }
SkGLContext* gl;
void setup() override {
this->gl->makeCurrent();
// Make sure we're done with whatever came before.
SK_GL(*this->gl, Finish());
}
void endTiming() override {
if (this->gl) {
SK_GL(*this->gl, Flush());
this->gl->swapBuffers();
}
}
void fence() override {
SK_GL(*this->gl, Finish());
}
bool needsFrameTiming() const override { return true; }
bool init(SkImageInfo info, Benchmark* bench) override {
uint32_t flags = this->config.useDFText ? SkSurfaceProps::kUseDistanceFieldFonts_Flag : 0;
SkSurfaceProps props(flags, SkSurfaceProps::kLegacyFontHost_InitType);
this->surface.reset(SkSurface::NewRenderTarget(gGrFactory->get(this->config.ctxType),
SkSurface::kNo_Budgeted, info,
this->config.samples, &props));
this->gl = gGrFactory->getGLContext(this->config.ctxType);
if (!this->surface.get()) {
return false;
}
return true;
}
void fillOptions(ResultsWriter* log) override {
const GrGLubyte* version;
SK_GL_RET(*this->gl, version, GetString(GR_GL_VERSION));
log->configOption("GL_VERSION", (const char*)(version));
SK_GL_RET(*this->gl, version, GetString(GR_GL_RENDERER));
log->configOption("GL_RENDERER", (const char*) version);
SK_GL_RET(*this->gl, version, GetString(GR_GL_VENDOR));
log->configOption("GL_VENDOR", (const char*) version);
SK_GL_RET(*this->gl, version, GetString(GR_GL_SHADING_LANGUAGE_VERSION));
log->configOption("GL_SHADING_LANGUAGE_VERSION", (const char*) version);
}
};
#endif
static double time(int loops, Benchmark* bench, SkCanvas* canvas, Target* target) {
if (canvas) {
canvas->clear(SK_ColorWHITE);
}
WallTimer timer;
timer.start();
if (target) {
canvas = target->beginTiming(canvas);
}
if (bench) {
bench->draw(loops, canvas);
}
if (canvas) {
canvas->flush();
}
#if SK_SUPPORT_GPU
if (gl) {
SK_GL(*gl, Flush());
gl->swapBuffers();
if (target) {
target->endTiming();
}
#endif
timer.end();
return timer.fWall;
}
@ -137,19 +219,22 @@ static int clamp_loops(int loops) {
return loops;
}
static bool write_canvas_png(SkCanvas* canvas, const SkString& filename) {
static bool write_canvas_png(Target* target, const SkString& filename) {
if (filename.isEmpty()) {
return false;
}
if (kUnknown_SkColorType == canvas->imageInfo().colorType()) {
if (target->surface.get() && target->surface->getCanvas() &&
kUnknown_SkColorType == target->surface->getCanvas()->imageInfo().colorType()) {
return false;
}
SkBitmap bmp;
bmp.setInfo(canvas->imageInfo());
if (!canvas->readPixels(&bmp, 0, 0)) {
SkDebugf("Can't read canvas pixels.\n");
if (!target->capturePixels(&bmp)) {
return false;
}
SkString dir = SkOSPath::Dirname(filename.c_str());
if (!sk_mkdir(dir.c_str())) {
SkDebugf("Can't make dir %s.\n", dir.c_str());
@ -215,14 +300,7 @@ static int cpu_bench(const double overhead, Benchmark* bench, SkCanvas* canvas,
return loops;
}
#if SK_SUPPORT_GPU
static void setup_gl(SkGLContext* gl) {
gl->makeCurrent();
// Make sure we're done with whatever came before.
SK_GL(*gl, Finish());
}
static int gpu_bench(SkGLContext* gl,
static int gpu_bench(Target* target,
Benchmark* bench,
SkCanvas* canvas,
double* samples) {
@ -242,7 +320,7 @@ static int gpu_bench(SkGLContext* gl,
// _this_ round, not still timing last round. We force this by looping
// more times than any reasonable GPU will allow frames to lag.
for (int i = 0; i < FLAGS_gpuFrameLag; i++) {
elapsed = time(loops, bench, canvas, gl);
elapsed = time(loops, bench, canvas, target);
}
} while (elapsed < FLAGS_gpuMs);
@ -250,8 +328,8 @@ static int gpu_bench(SkGLContext* gl,
loops = (int)ceil(loops * FLAGS_gpuMs / elapsed);
loops = clamp_loops(loops);
// Might as well make sure we're not still timing our calibration.
SK_GL(*gl, Finish());
// Make sure we're not still timing our calibration.
target->fence();
} else {
loops = detect_forever_loops(loops);
}
@ -259,16 +337,16 @@ static int gpu_bench(SkGLContext* gl,
// Pretty much the same deal as the calibration: do some warmup to make
// sure we're timing steady-state pipelined frames.
for (int i = 0; i < FLAGS_gpuFrameLag; i++) {
time(loops, bench, canvas, gl);
time(loops, bench, canvas, target);
}
// Now, actually do the timing!
for (int i = 0; i < FLAGS_samples; i++) {
samples[i] = time(loops, bench, canvas, gl) / loops;
samples[i] = time(loops, bench, canvas, target) / loops;
}
return loops;
}
#endif
static SkString to_lower(const char* str) {
SkString lower(str);
@ -278,30 +356,6 @@ static SkString to_lower(const char* str) {
return lower;
}
struct Config {
const char* name;
Benchmark::Backend backend;
SkColorType color;
SkAlphaType alpha;
int samples;
#if SK_SUPPORT_GPU
GrContextFactory::GLContextType ctxType;
bool useDFText;
#else
int bogusInt;
bool bogusBool;
#endif
};
struct Target {
explicit Target(const Config& c) : config(c) {}
const Config config;
SkAutoTDelete<SkSurface> surface;
#if SK_SUPPORT_GPU
SkGLContext* gl;
#endif
};
static bool is_cpu_config_allowed(const char* name) {
for (int i = 0; i < FLAGS_config.count(); i++) {
if (to_lower(FLAGS_config[i]).equals(name)) {
@ -373,6 +427,14 @@ static void create_configs(SkTDArray<Config>* configs) {
#endif
}
#endif
#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
if (is_cpu_config_allowed("hwui")) {
Config config = { "hwui", Benchmark::kHWUI_Backend, kRGBA_8888_SkColorType,
kPremul_SkAlphaType, 0, kBogusGLContextType, false };
configs->push(config);
}
#endif
}
// If bench is enabled for config, returns a Target* for it, otherwise NULL.
@ -384,23 +446,25 @@ static Target* is_enabled(Benchmark* bench, const Config& config) {
SkImageInfo info = SkImageInfo::Make(bench->getSize().fX, bench->getSize().fY,
config.color, config.alpha);
Target* target = new Target(config);
Target* target = NULL;
if (Benchmark::kRaster_Backend == config.backend) {
target->surface.reset(SkSurface::NewRaster(info));
}
switch (config.backend) {
#if SK_SUPPORT_GPU
else if (Benchmark::kGPU_Backend == config.backend) {
uint32_t flags = config.useDFText ? SkSurfaceProps::kUseDistanceFieldFonts_Flag : 0;
SkSurfaceProps props(flags, SkSurfaceProps::kLegacyFontHost_InitType);
target->surface.reset(SkSurface::NewRenderTarget(gGrFactory->get(config.ctxType),
SkSurface::kNo_Budgeted, info,
config.samples, &props));
target->gl = gGrFactory->getGLContext(config.ctxType);
}
case Benchmark::kGPU_Backend:
target = new GPUTarget(config);
break;
#endif
#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
case Benchmark::kHWUI_Backend:
target = new HWUITarget(config, bench);
break;
#endif
default:
target = new Target(config);
break;
}
if (Benchmark::kNonRendering_Backend != config.backend && !target->surface.get()) {
if (!target->init(info, bench)) {
delete target;
return NULL;
}
@ -418,22 +482,6 @@ static void create_targets(SkTDArray<Target*>* targets, Benchmark* b,
}
}
#if SK_SUPPORT_GPU
static void fill_gpu_options(ResultsWriter* log, SkGLContext* ctx) {
const GrGLubyte* version;
SK_GL_RET(*ctx, version, GetString(GR_GL_VERSION));
log->configOption("GL_VERSION", (const char*)(version));
SK_GL_RET(*ctx, version, GetString(GR_GL_RENDERER));
log->configOption("GL_RENDERER", (const char*) version);
SK_GL_RET(*ctx, version, GetString(GR_GL_VENDOR));
log->configOption("GL_VENDOR", (const char*) version);
SK_GL_RET(*ctx, version, GetString(GR_GL_SHADING_LANGUAGE_VERSION));
log->configOption("GL_SHADING_LANGUAGE_VERSION", (const char*) version);
}
#endif
class BenchmarkStream {
public:
@ -790,32 +838,26 @@ int nanobench_main() {
bench->preDraw();
}
for (int j = 0; j < targets.count(); j++) {
// During HWUI output this canvas may be NULL.
SkCanvas* canvas = targets[j]->surface.get() ? targets[j]->surface->getCanvas() : NULL;
const char* config = targets[j]->config.name;
#if SK_SUPPORT_GPU
if (Benchmark::kGPU_Backend == targets[j]->config.backend) {
setup_gl(targets[j]->gl);
}
#endif
targets[j]->setup();
bench->perCanvasPreDraw(canvas);
const int loops =
#if SK_SUPPORT_GPU
Benchmark::kGPU_Backend == targets[j]->config.backend
? gpu_bench(targets[j]->gl, bench.get(), canvas, samples.get())
:
#endif
cpu_bench( overhead, bench.get(), canvas, samples.get());
targets[j]->needsFrameTiming()
? gpu_bench(targets[j], bench.get(), canvas, samples.get())
: cpu_bench(overhead, bench.get(), canvas, samples.get());
bench->perCanvasPostDraw(canvas);
if (canvas && !FLAGS_writePath.isEmpty() && FLAGS_writePath[0]) {
if (Benchmark::kNonRendering_Backend != targets[j]->config.backend &&
!FLAGS_writePath.isEmpty() && FLAGS_writePath[0]) {
SkString pngFilename = SkOSPath::Join(FLAGS_writePath[0], config);
pngFilename = SkOSPath::Join(pngFilename.c_str(), bench->getUniqueName());
pngFilename.append(".png");
write_canvas_png(canvas, pngFilename);
write_canvas_png(targets[j], pngFilename);
}
if (kFailedLoops == loops) {
@ -827,11 +869,7 @@ int nanobench_main() {
log->config(config);
log->configOption("name", bench->getName());
benchStream.fillCurrentOptions(log.get());
#if SK_SUPPORT_GPU
if (Benchmark::kGPU_Backend == targets[j]->config.backend) {
fill_gpu_options(log.get(), targets[j]->gl);
}
#endif
targets[j]->fillOptions(log.get());
log->metric("min_ms", stats.min);
if (runs++ % FLAGS_flushEvery == 0) {
log->flush();

79
bench/nanobench.h Normal file
View File

@ -0,0 +1,79 @@
/*
* 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 nanobench_DEFINED
#define nanobench_DEFINED
#include "Benchmark.h"
#include "SkImageInfo.h"
#include "SkSurface.h"
#include "SkTypes.h"
#if SK_SUPPORT_GPU
#include "GrContextFactory.h"
#endif
class ResultsWriter;
class SkBitmap;
class SkCanvas;
struct Config {
const char* name;
Benchmark::Backend backend;
SkColorType color;
SkAlphaType alpha;
int samples;
#if SK_SUPPORT_GPU
GrContextFactory::GLContextType ctxType;
bool useDFText;
#else
int bogusInt;
bool bogusBool;
#endif
};
struct Target {
explicit Target(const Config& c) : config(c) { }
virtual ~Target() { }
const Config config;
SkAutoTDelete<SkSurface> surface;
/** Called once per target, immediately before any timing or drawing. */
virtual void setup() { }
/** Called *after* the clock timer is started, before the benchmark
is drawn. */
virtual SkCanvas* beginTiming(SkCanvas* canvas) { return canvas; }
/** Called *after* a benchmark is drawn, but before the clock timer
is stopped. */
virtual void endTiming() { }
/** Called between benchmarks (or between calibration and measured
runs) to make sure all pending work in drivers / threads is
complete. */
virtual void fence() { }
/** CPU-like targets can just be timed, but GPU-like
targets need to pay attention to frame boundaries
or other similar details. */
virtual bool needsFrameTiming() const { return false; }
/** Called once per target, during program initialization.
Returns false if initialization fails. */
virtual bool init(SkImageInfo info, Benchmark* bench);
/** Stores any pixels drawn to the screen in the bitmap.
Returns false on error. */
virtual bool capturePixels(SkBitmap* bmp);
/** Writes any config-specific data to the log. */
virtual void fillOptions(ResultsWriter*) { }
};
#endif // nanobench_DEFINED

160
bench/nanobenchAndroid.cpp Normal file
View File

@ -0,0 +1,160 @@
/*
* 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 "nanobenchAndroid.h"
#include "AnimationContext.h"
#include "IContextFactory.h"
#include "SkiaCanvasProxy.h"
#include "android/rect.h"
#include "android/native_window.h"
#include "renderthread/TimeLord.h"
namespace {
/**
* Helper class for setting up android::uirenderer::renderthread::RenderProxy.
*/
class ContextFactory : public android::uirenderer::IContextFactory {
public:
android::uirenderer::AnimationContext* createAnimationContext
(android::uirenderer::renderthread::TimeLord& clock) override {
return new android::uirenderer::AnimationContext(clock);
}
};
}
HWUITarget::HWUITarget(const Config& c, Benchmark* bench) : Target(c) { }
void HWUITarget::setup() {
this->proxy->fence();
}
SkCanvas* HWUITarget::beginTiming(SkCanvas* canvas) {
this->renderer->prepare();
this->renderer->clipRect(0, 0, this->size.width(), this->size.height(),
SkRegion::Op::kReplace_Op);
SkCanvas* targetCanvas = this->renderer->asSkCanvas();
if (targetCanvas) {
this->fc.reset(targetCanvas);
canvas = &this->fc;
// This might minimally distort timing, but canvas isn't valid outside the timer.
canvas->clear(SK_ColorWHITE);
}
return canvas;
}
void HWUITarget::endTiming() {
this->renderer->finish();
this->rootNode->setStagingDisplayList(this->renderer->finishRecording());
this->proxy->syncAndDrawFrame();
// Surprisingly, calling this->proxy->fence() here appears to make no difference to
// the timings we record.
}
void HWUITarget::fence() {
this->proxy->fence();
}
bool HWUITarget::needsFrameTiming() const {
return true;
}
bool HWUITarget::init(SkImageInfo info, Benchmark* bench) {
// extracted from DMSrcSinkAndroid.cpp's HWUISink::draw()
size.set(bench->getSize().x(), bench->getSize().y());
android::BufferQueue::createBufferQueue(&this->producer, &this->consumer);
this->cpuConsumer = new android::CpuConsumer(this->consumer, 1);
this->cpuConsumer->setName(android::String8("SkiaBenchmarkClient"));
this->cpuConsumer->setDefaultBufferSize(size.width(), size.height());
this->androidSurface = new android::Surface(this->producer);
native_window_set_buffers_dimensions(this->androidSurface.get(),
size.width(), size.height());
native_window_set_buffers_format(this->androidSurface.get(),
android::PIXEL_FORMAT_RGBA_8888);
native_window_set_usage(this->androidSurface.get(), GRALLOC_USAGE_SW_READ_OFTEN |
GRALLOC_USAGE_SW_WRITE_NEVER |
GRALLOC_USAGE_HW_RENDER);
this->rootNode.reset(new android::uirenderer::RenderNode());
this->rootNode->incStrong(nullptr);
this->rootNode->mutateStagingProperties().setLeftTopRightBottom
(0, 0, size.width(), size.height());
this->rootNode->mutateStagingProperties().setClipToBounds(false);
this->rootNode->setPropertyFieldsDirty(android::uirenderer::RenderNode::GENERIC);
ContextFactory factory;
this->proxy.reset
(new android::uirenderer::renderthread::RenderProxy(false, this->rootNode, &factory));
this->proxy->loadSystemProperties();
this->proxy->initialize(this->androidSurface.get());
float lightX = size.width() / 2.0f;
android::uirenderer::Vector3 lightVector { lightX, -200.0f, 800.0f };
this->proxy->setup(size.width(), size.height(), lightVector, 800.0f,
255 * 0.075f, 255 * 0.15f);
this->renderer.reset(new android::uirenderer::DisplayListRenderer());
this->renderer->setViewport(size.width(), size.height());
// Since we have no SkSurface for HWUI, other parts of the code base have to
// explicitly work around the fact that it may be invalid / have no SkCanvas.
return true;
}
bool HWUITarget::capturePixels(SkBitmap* bmp) {
SkImageInfo destinationConfig =
SkImageInfo::Make(this->size.width(), this->size.height(),
kRGBA_8888_SkColorType, kPremul_SkAlphaType);
bmp->allocPixels(destinationConfig);
sk_memset32((uint32_t*) bmp->getPixels(), SK_ColorRED,
this->size.width() * this->size.height());
android::CpuConsumer::LockedBuffer nativeBuffer;
android::status_t retval = this->cpuConsumer->lockNextBuffer(&nativeBuffer);
if (retval == android::BAD_VALUE) {
SkDebugf("write_canvas_png() got no buffer; returning transparent");
// No buffer ready to read - commonly triggered by dm sending us
// a no-op source, or calling code that doesn't do anything on this
// backend.
bmp->eraseColor(SK_ColorTRANSPARENT);
return false;
} else if (retval) {
SkDebugf("Failed to lock buffer to read pixels: %d.", retval);
return false;
}
// Move the pixels into the destination SkBitmap
SK_ALWAYSBREAK(nativeBuffer.format == android::PIXEL_FORMAT_RGBA_8888 &&
"Native buffer not RGBA!");
SkImageInfo nativeConfig =
SkImageInfo::Make(nativeBuffer.width, nativeBuffer.height,
kRGBA_8888_SkColorType, kPremul_SkAlphaType);
// Android stride is in pixels, Skia stride is in bytes
SkBitmap nativeWrapper;
bool success =
nativeWrapper.installPixels(nativeConfig, nativeBuffer.data, nativeBuffer.stride * 4);
if (!success) {
SkDebugf("Failed to wrap HWUI buffer in a SkBitmap");
return false;
}
SK_ALWAYSBREAK(bmp->colorType() == kRGBA_8888_SkColorType &&
"Destination buffer not RGBA!");
success =
nativeWrapper.readPixels(destinationConfig, bmp->getPixels(), bmp->rowBytes(), 0, 0);
if (!success) {
SkDebugf("Failed to extract pixels from HWUI buffer");
return false;
}
this->cpuConsumer->unlockBuffer(nativeBuffer);
return true;
}

49
bench/nanobenchAndroid.h Normal file
View File

@ -0,0 +1,49 @@
/*
* 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 nanobenchAndroid_DEFINED
#define nanobenchAndroid_DEFINED
#include "DisplayListRenderer.h"
#include "RenderNode.h"
#include "SkAndroidSDKCanvas.h"
#include "gui/BufferQueue.h"
#include "gui/CpuConsumer.h"
#include "gui/IGraphicBufferConsumer.h"
#include "gui/IGraphicBufferProducer.h"
#include "gui/Surface.h"
#include "renderthread/RenderProxy.h"
#include "nanobench.h"
struct HWUITarget : public Target {
explicit HWUITarget(const Config& c, Benchmark* bench);
SkAutoTDelete<android::uirenderer::RenderNode> rootNode;
SkAutoTDelete<android::uirenderer::renderthread::RenderProxy> proxy;
SkAutoTDelete<android::uirenderer::DisplayListRenderer> renderer;
android::sp<android::IGraphicBufferProducer> producer;
android::sp<android::IGraphicBufferConsumer> consumer;
android::sp<android::CpuConsumer> cpuConsumer;
android::sp<android::Surface> androidSurface;
SkISize size;
SkAndroidSDKCanvas fc;
void setup() override;
SkCanvas* beginTiming(SkCanvas* canvas) override;
void endTiming() override;
void fence() override;
bool needsFrameTiming() const override;
/// Returns false if initialization fails
bool init(SkImageInfo info, Benchmark* bench) override;
bool capturePixels(SkBitmap* bmp) override;
};
#endif // nanobenchAndroid_DEFINED

View File

@ -37,6 +37,20 @@
['skia_android_framework', {
'libraries': [
'-lskia',
'-landroid',
'-lgui',
'-lhwui',
'-lutils',
],
'include_dirs': [
'../../../frameworks/base/libs/hwui/',
'../../../frameworks/native/include/',
],
'sources': [
'../bench/nanobenchAndroid.cpp',
],
'dependencies': [
'utils.gyp:android_utils',
],
}],
],