flatland: add a GPU hardware benchmark
This change adds a GPU benchmark named 'flatland' that is intended to measure
GPU performance of UI rendering and compositing scenarios at a fixed a clock
frequency. This initial version includes only window compositing scenarios.
Change-Id: I5577863aa3be5c6da8b49cb5d53cc49dec2f7081
diff --git a/cmds/flatland/Main.cpp b/cmds/flatland/Main.cpp
new file mode 100644
index 0000000..45f414e
--- /dev/null
+++ b/cmds/flatland/Main.cpp
@@ -0,0 +1,715 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define ATRACE_TAG ATRACE_TAG_ALWAYS
+
+#include <gui/GraphicBufferAlloc.h>
+#include <gui/Surface.h>
+#include <gui/GLConsumer.h>
+#include <gui/SurfaceTextureClient.h>
+#include <ui/Fence.h>
+#include <utils/Trace.h>
+
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+
+#include <math.h>
+#include <getopt.h>
+
+#include "Flatland.h"
+#include "GLHelper.h"
+
+using namespace ::android;
+
+static uint32_t g_SleepBetweenSamplesMs = 0;
+static bool g_PresentToWindow = false;
+static size_t g_BenchmarkNameLen = 0;
+
+struct BenchmarkDesc {
+ // The name of the test.
+ const char* name;
+
+ // The dimensions of the space in which window layers are specified.
+ uint32_t width;
+ uint32_t height;
+
+ // The screen heights at which to run the test.
+ uint32_t runHeights[MAX_TEST_RUNS];
+
+ // The list of window layers.
+ LayerDesc layers[MAX_NUM_LAYERS];
+};
+
+static const BenchmarkDesc benchmarks[] = {
+ { "16:10 Single Static Window",
+ 2560, 1600, { 800, 1600, 2400 },
+ {
+ { // Window
+ 0, staticGradient, opaque,
+ 0, 50, 2560, 1454,
+ },
+ { // Status bar
+ 0, staticGradient, opaque,
+ 0, 0, 2560, 50,
+ },
+ { // Navigation bar
+ 0, staticGradient, opaque,
+ 0, 1504, 2560, 96,
+ },
+ },
+ },
+
+ { "16:10 App -> Home Transition",
+ 2560, 1600, { 800, 1600, 2400 },
+ {
+ { // Wallpaper
+ 0, staticGradient, opaque,
+ 0, 50, 2560, 1454,
+ },
+ { // Launcher
+ 0, staticGradient, blend,
+ 0, 50, 2560, 1454,
+ },
+ { // Outgoing activity
+ 0, staticGradient, blendShrink,
+ 20, 70, 2520, 1414,
+ },
+ { // Status bar
+ 0, staticGradient, opaque,
+ 0, 0, 2560, 50,
+ },
+ { // Navigation bar
+ 0, staticGradient, opaque,
+ 0, 1504, 2560, 96,
+ },
+ },
+ },
+
+ { "16:10 SurfaceView -> Home Transition",
+ 2560, 1600, { 800, 1600, 2400 },
+ {
+ { // Wallpaper
+ 0, staticGradient, opaque,
+ 0, 50, 2560, 1454,
+ },
+ { // Launcher
+ 0, staticGradient, blend,
+ 0, 50, 2560, 1454,
+ },
+ { // Outgoing SurfaceView
+ 0, staticGradient, blendShrink,
+ 20, 70, 2520, 1414,
+ },
+ { // Outgoing activity
+ 0, staticGradient, blendShrink,
+ 20, 70, 2520, 1414,
+ },
+ { // Status bar
+ 0, staticGradient, opaque,
+ 0, 0, 2560, 50,
+ },
+ { // Navigation bar
+ 0, staticGradient, opaque,
+ 0, 1504, 2560, 96,
+ },
+ },
+ },
+};
+
+static const ShaderDesc shaders[] = {
+ {
+ name: "Blit",
+ vertexShader: {
+ "precision mediump float;",
+ "",
+ "attribute vec4 position;",
+ "attribute vec4 uv;",
+ "",
+ "varying vec4 texCoords;",
+ "",
+ "uniform mat4 objToNdc;",
+ "uniform mat4 uvToTex;",
+ "",
+ "void main() {",
+ " gl_Position = objToNdc * position;",
+ " texCoords = uvToTex * uv;",
+ "}",
+ },
+ fragmentShader: {
+ "#extension GL_OES_EGL_image_external : require",
+ "precision mediump float;",
+ "",
+ "varying vec4 texCoords;",
+ "",
+ "uniform samplerExternalOES blitSrc;",
+ "uniform vec4 modColor;",
+ "",
+ "void main() {",
+ " gl_FragColor = texture2D(blitSrc, texCoords.xy);",
+ " gl_FragColor *= modColor;",
+ "}",
+ },
+ },
+
+ {
+ name: "Gradient",
+ vertexShader: {
+ "precision mediump float;",
+ "",
+ "attribute vec4 position;",
+ "attribute vec4 uv;",
+ "",
+ "varying float interp;",
+ "",
+ "uniform mat4 objToNdc;",
+ "uniform mat4 uvToInterp;",
+ "",
+ "void main() {",
+ " gl_Position = objToNdc * position;",
+ " interp = (uvToInterp * uv).x;",
+ "}",
+ },
+ fragmentShader: {
+ "precision mediump float;",
+ "",
+ "varying float interp;",
+ "",
+ "uniform vec4 color0;",
+ "uniform vec4 color1;",
+ "",
+ "uniform sampler2D ditherKernel;",
+ "uniform float invDitherKernelSize;",
+ "uniform float invDitherKernelSizeSq;",
+ "",
+ "void main() {",
+ " float dither = texture2D(ditherKernel,",
+ " gl_FragCoord.xy * invDitherKernelSize).a;",
+ " dither *= invDitherKernelSizeSq;",
+ " vec4 color = mix(color0, color1, clamp(interp, 0.0, 1.0));",
+ " gl_FragColor = color + vec4(dither, dither, dither, 0.0);",
+ "}",
+ },
+ },
+};
+
+class Layer {
+
+public:
+
+ Layer() :
+ mFirstFrame(true),
+ mGLHelper(NULL),
+ mSurface(EGL_NO_SURFACE) {
+ }
+
+ bool setUp(const LayerDesc& desc, GLHelper* helper) {
+ bool result;
+
+ mDesc = desc;
+ mGLHelper = helper;
+
+ result = mGLHelper->createSurfaceTexture(mDesc.width, mDesc.height,
+ &mGLConsumer, &mSurface, &mTexName);
+ if (!result) {
+ return false;
+ }
+
+ mRenderer = desc.rendererFactory();
+ result = mRenderer->setUp(helper);
+ if (!result) {
+ return false;
+ }
+
+ mComposer = desc.composerFactory();
+ result = mComposer->setUp(desc, helper);
+ if (!result) {
+ return false;
+ }
+
+ return true;
+ }
+
+ void tearDown() {
+ if (mComposer != NULL) {
+ mComposer->tearDown();
+ delete mComposer;
+ mComposer = NULL;
+ }
+
+ if (mRenderer != NULL) {
+ mRenderer->tearDown();
+ delete mRenderer;
+ mRenderer = NULL;
+ }
+
+ if (mSurface != EGL_NO_SURFACE) {
+ mGLHelper->destroySurface(&mSurface);
+ mGLConsumer->abandon();
+ }
+ mGLHelper = NULL;
+ mGLConsumer.clear();
+ }
+
+ bool render() {
+ return mRenderer->render(mSurface);
+ }
+
+ bool prepareComposition() {
+ status_t err;
+
+ err = mGLConsumer->updateTexImage();
+ if (err < 0) {
+ fprintf(stderr, "GLConsumer::updateTexImage error: %d\n", err);
+ return false;
+ }
+
+ return true;
+ }
+
+ bool compose() {
+ return mComposer->compose(mTexName, mGLConsumer);
+ }
+
+private:
+ bool mFirstFrame;
+
+ LayerDesc mDesc;
+
+ GLHelper* mGLHelper;
+
+ GLuint mTexName;
+ sp<GLConsumer> mGLConsumer;
+ EGLSurface mSurface;
+
+ Renderer* mRenderer;
+ Composer* mComposer;
+};
+
+class BenchmarkRunner {
+
+public:
+
+ BenchmarkRunner(const BenchmarkDesc& desc, size_t instance) :
+ mDesc(desc),
+ mInstance(instance),
+ mNumLayers(countLayers(desc)),
+ mGLHelper(NULL),
+ mSurface(EGL_NO_SURFACE),
+ mWindowSurface(EGL_NO_SURFACE) {
+ }
+
+ bool setUp() {
+ ATRACE_CALL();
+
+ bool result;
+ EGLint resulte;
+
+ float scaleFactor = float(mDesc.runHeights[mInstance]) /
+ float(mDesc.height);
+ uint32_t w = uint32_t(scaleFactor * float(mDesc.width));
+ uint32_t h = mDesc.runHeights[mInstance];
+
+ mGLHelper = new GLHelper();
+ result = mGLHelper->setUp(shaders, NELEMS(shaders));
+ if (!result) {
+ return false;
+ }
+
+ GLuint texName;
+ result = mGLHelper->createSurfaceTexture(w, h, &mGLConsumer, &mSurface,
+ &texName);
+ if (!result) {
+ return false;
+ }
+
+ for (size_t i = 0; i < mNumLayers; i++) {
+ // Scale the layer to match the current screen size.
+ LayerDesc ld = mDesc.layers[i];
+ ld.x = int32_t(scaleFactor * float(ld.x));
+ ld.y = int32_t(scaleFactor * float(ld.y));
+ ld.width = uint32_t(scaleFactor * float(ld.width));
+ ld.height = uint32_t(scaleFactor * float(ld.height));
+
+ // Set up the layer.
+ result = mLayers[i].setUp(ld, mGLHelper);
+ if (!result) {
+ return false;
+ }
+ }
+
+ if (g_PresentToWindow) {
+ result = mGLHelper->createWindowSurface(w, h, &mSurfaceControl,
+ &mWindowSurface);
+ if (!result) {
+ return false;
+ }
+
+ result = doFrame(mWindowSurface);
+ if (!result) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ void tearDown() {
+ ATRACE_CALL();
+
+ for (size_t i = 0; i < mNumLayers; i++) {
+ mLayers[i].tearDown();
+ }
+
+ if (mGLHelper != NULL) {
+ if (mWindowSurface != EGL_NO_SURFACE) {
+ mGLHelper->destroySurface(&mWindowSurface);
+ }
+ mGLHelper->destroySurface(&mSurface);
+ mGLConsumer->abandon();
+ mGLConsumer.clear();
+ mSurfaceControl.clear();
+ mGLHelper->tearDown();
+ delete mGLHelper;
+ mGLHelper = NULL;
+ }
+ }
+
+ nsecs_t run(uint32_t warmUpFrames, uint32_t totalFrames) {
+ ATRACE_CALL();
+
+ bool result;
+ status_t err;
+
+ resetColorGenerator();
+
+ // Do the warm-up frames.
+ for (uint32_t i = 0; i < warmUpFrames; i++) {
+ result = doFrame(mSurface);
+ if (!result) {
+ return -1;
+ }
+ }
+
+ // Grab the fence for the start timestamp.
+ sp<Fence> startFence = mGLConsumer->getCurrentFence();
+
+ // the timed frames.
+ for (uint32_t i = warmUpFrames; i < totalFrames; i++) {
+ result = doFrame(mSurface);
+ if (!result) {
+ return -1;
+ }
+ }
+
+ // Grab the fence for the end timestamp.
+ sp<Fence> endFence = mGLConsumer->getCurrentFence();
+
+ // Keep doing frames until the end fence has signaled.
+ while (endFence->wait(0) == -ETIME) {
+ result = doFrame(mSurface);
+ if (!result) {
+ return -1;
+ }
+ }
+
+ // Compute the time delta.
+ nsecs_t startTime = startFence->getSignalTime();
+ nsecs_t endTime = endFence->getSignalTime();
+
+ return endTime - startTime;
+ }
+
+private:
+
+ bool doFrame(EGLSurface surface) {
+ bool result;
+ status_t err;
+
+ for (size_t i = 0; i < mNumLayers; i++) {
+ result = mLayers[i].render();
+ if (!result) {
+ return false;
+ }
+ }
+
+ for (size_t i = 0; i < mNumLayers; i++) {
+ result = mLayers[i].prepareComposition();
+ if (!result) {
+ return false;
+ }
+ }
+
+ result = mGLHelper->makeCurrent(surface);
+ if (!result) {
+ return false;
+ }
+
+ glClearColor(1.0f, 0.0f, 0.0f, 0.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ for (size_t i = 0; i < mNumLayers; i++) {
+ result = mLayers[i].compose();
+ if (!result) {
+ return false;
+ }
+ }
+
+ result = mGLHelper->swapBuffers(surface);
+ if (!result) {
+ return false;
+ }
+
+ err = mGLConsumer->updateTexImage();
+ if (err < 0) {
+ fprintf(stderr, "GLConsumer::updateTexImage error: %d\n", err);
+ return false;
+ }
+
+ return true;
+ }
+
+ static size_t countLayers(const BenchmarkDesc& desc) {
+ size_t i;
+ for (i = 0; i < MAX_NUM_LAYERS; i++) {
+ if (desc.layers[i].rendererFactory == NULL) {
+ break;
+ }
+ }
+ return i;
+ }
+
+ const BenchmarkDesc& mDesc;
+ const size_t mInstance;
+ const size_t mNumLayers;
+
+ GLHelper* mGLHelper;
+
+ // The surface into which layers are composited
+ sp<GLConsumer> mGLConsumer;
+ EGLSurface mSurface;
+
+ // Used for displaying the surface to a window.
+ EGLSurface mWindowSurface;
+ sp<SurfaceControl> mSurfaceControl;
+
+ Layer mLayers[MAX_NUM_LAYERS];
+};
+
+static int cmpDouble(const double* lhs, const double* rhs) {
+ if (*lhs < *rhs) {
+ return -1;
+ } else if (*rhs < *lhs) {
+ return 1;
+ }
+ return 0;
+}
+
+// Run a single benchmark and print the result.
+static bool runTest(const BenchmarkDesc b, size_t run) {
+ bool success = true;
+ double prevResult = 0.0, result = 0.0;
+ Vector<double> samples;
+
+ uint32_t runHeight = b.runHeights[run];
+ uint32_t runWidth = b.width * runHeight / b.height;
+ printf(" %-*s | %4d x %4d | ", g_BenchmarkNameLen, b.name,
+ runWidth, runHeight);
+ fflush(stdout);
+
+ BenchmarkRunner r(b, run);
+ if (!r.setUp()) {
+ fprintf(stderr, "error initializing runner.\n");
+ return false;
+ }
+
+ // The slowest 1/outlierFraction sample results are ignored as potential
+ // outliers.
+ const uint32_t outlierFraction = 16;
+ const double threshold = .0025;
+
+ uint32_t warmUpFrames = 1;
+ uint32_t totalFrames = 5;
+
+ // Find the number of frames needed to run for over 100ms.
+ double runTime = 0.0;
+ while (true) {
+ runTime = double(r.run(warmUpFrames, totalFrames));
+ if (runTime < 50e6) {
+ warmUpFrames *= 2;
+ totalFrames *= 2;
+ } else {
+ break;
+ }
+ }
+
+
+ if (totalFrames - warmUpFrames > 16) {
+ // The test runs too fast to get a stable result. Skip it.
+ printf(" fast");
+ goto done;
+ } else if (totalFrames == 5 && runTime > 200e6) {
+ // The test runs too slow to be very useful. Skip it.
+ printf(" slow");
+ goto done;
+ }
+
+ do {
+ size_t newSamples = samples.size();
+ if (newSamples == 0) {
+ newSamples = 4*outlierFraction;
+ }
+
+ if (newSamples > 512) {
+ printf("varies");
+ goto done;
+ }
+
+ for (size_t i = 0; i < newSamples; i++) {
+ double sample = double(r.run(warmUpFrames, totalFrames));
+
+ if (g_SleepBetweenSamplesMs > 0) {
+ usleep(g_SleepBetweenSamplesMs * 1000);
+ }
+
+ if (sample < 0.0) {
+ success = false;
+ goto done;
+ }
+
+ samples.add(sample);
+ }
+
+ samples.sort(cmpDouble);
+
+ prevResult = result;
+ size_t elem = (samples.size() * (outlierFraction-1) / outlierFraction);
+ result = (samples[elem-1] + samples[elem]) * 0.5;
+ } while (fabs(result - prevResult) > threshold * result);
+
+ printf("%6.3f", result / double(totalFrames - warmUpFrames) / 1e6);
+
+done:
+
+ printf("\n");
+ fflush(stdout);
+ r.tearDown();
+
+ return success;
+}
+
+static void printResultsTableHeader() {
+ const char* scenario = "Scenario";
+ size_t len = strlen(scenario);
+ size_t leftPad = (g_BenchmarkNameLen - len) / 2;
+ size_t rightPad = g_BenchmarkNameLen - len - leftPad;
+ printf(" %*s%s%*s | Resolution | Time (ms)\n", leftPad, "",
+ "Scenario", rightPad, "");
+}
+
+// Run ALL the benchmarks!
+static bool runTests() {
+ printResultsTableHeader();
+
+ for (size_t i = 0; i < NELEMS(benchmarks); i++) {
+ const BenchmarkDesc& b = benchmarks[i];
+ for (size_t j = 0; j < MAX_TEST_RUNS && b.runHeights[j]; j++) {
+ if (!runTest(b, j)) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+// Return the length longest benchmark name.
+static size_t maxBenchmarkNameLen() {
+ size_t maxLen = 0;
+ for (size_t i = 0; i < NELEMS(benchmarks); i++) {
+ const BenchmarkDesc& b = benchmarks[i];
+ size_t len = strlen(b.name);
+ if (len > maxLen) {
+ maxLen = len;
+ }
+ }
+ return maxLen;
+}
+
+// Print the command usage help to stderr.
+static void showHelp(const char *cmd) {
+ fprintf(stderr, "usage: %s [options]\n", cmd);
+ fprintf(stderr, "options include:\n"
+ " -s N sleep for N ms between samples\n"
+ " -d display the test frame to a window\n"
+ " --help print this helpful message and exit\n"
+ );
+}
+
+int main(int argc, char** argv) {
+ if (argc == 2 && 0 == strcmp(argv[1], "--help")) {
+ showHelp(argv[0]);
+ exit(0);
+ }
+
+ for (;;) {
+ int ret;
+ int option_index = 0;
+ static struct option long_options[] = {
+ {"help", no_argument, 0, 0 },
+ { 0, 0, 0, 0 }
+ };
+
+ ret = getopt_long(argc, argv, "ds:",
+ long_options, &option_index);
+
+ if (ret < 0) {
+ break;
+ }
+
+ switch(ret) {
+ case 'd':
+ g_PresentToWindow = true;
+ break;
+
+ case 's':
+ g_SleepBetweenSamplesMs = atoi(optarg);
+ break;
+
+ case 0:
+ if (strcmp(long_options[option_index].name, "help")) {
+ showHelp(argv[0]);
+ exit(0);
+ }
+ break;
+
+ default:
+ showHelp(argv[0]);
+ exit(2);
+ }
+ }
+
+ g_BenchmarkNameLen = maxBenchmarkNameLen();
+
+ printf(" cmdline:");
+ for (int i = 0; i < argc; i++) {
+ printf(" %s", argv[i]);
+ }
+ printf("\n");
+
+ if (!runTests()) {
+ fprintf(stderr, "exiting due to error.\n");
+ return 1;
+ }
+}