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;
+    }
+}