Merge "flatland: add a GPU hardware benchmark"
diff --git a/cmds/flatland/Android.mk b/cmds/flatland/Android.mk
new file mode 100644
index 0000000..5e57f02
--- /dev/null
+++ b/cmds/flatland/Android.mk
@@ -0,0 +1,22 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:=   \
+    Composers.cpp   \
+    GLHelper.cpp    \
+    Renderers.cpp   \
+    Main.cpp        \
+
+LOCAL_MODULE:= flatland
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SHARED_LIBRARIES := \
+    libEGL      \
+    libGLESv2   \
+    libcutils   \
+    libgui      \
+    libui       \
+    libutils    \
+
+include $(BUILD_EXECUTABLE)
diff --git a/cmds/flatland/Composers.cpp b/cmds/flatland/Composers.cpp
new file mode 100644
index 0000000..8365a31
--- /dev/null
+++ b/cmds/flatland/Composers.cpp
@@ -0,0 +1,282 @@
+/*
+ * 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.
+ */
+
+#include "Flatland.h"
+#include "GLHelper.h"
+
+namespace android {
+
+class Blitter {
+public:
+
+    bool setUp(GLHelper* helper) {
+        bool result;
+
+        result = helper->getShaderProgram("Blit", &mBlitPgm);
+        if (!result) {
+            return false;
+        }
+
+        mPosAttribLoc = glGetAttribLocation(mBlitPgm, "position");
+        mUVAttribLoc = glGetAttribLocation(mBlitPgm, "uv");
+        mUVToTexUniformLoc = glGetUniformLocation(mBlitPgm, "uvToTex");
+        mObjToNdcUniformLoc = glGetUniformLocation(mBlitPgm, "objToNdc");
+        mBlitSrcSamplerLoc = glGetUniformLocation(mBlitPgm, "blitSrc");
+        mModColorUniformLoc = glGetUniformLocation(mBlitPgm, "modColor");
+
+        return true;
+    }
+
+    bool blit(GLuint texName, const float* texMatrix,
+            int32_t x, int32_t y, uint32_t w, uint32_t h) {
+        float modColor[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
+        return modBlit(texName, texMatrix, modColor, x, y, w, h);
+    }
+
+    bool modBlit(GLuint texName, const float* texMatrix, float* modColor,
+            int32_t x, int32_t y, uint32_t w, uint32_t h) {
+        glUseProgram(mBlitPgm);
+
+        GLint vp[4];
+        glGetIntegerv(GL_VIEWPORT, vp);
+        float screenToNdc[16] = {
+            2.0f/float(vp[2]),  0.0f,               0.0f,   0.0f,
+            0.0f,               -2.0f/float(vp[3]), 0.0f,   0.0f,
+            0.0f,               0.0f,               1.0f,   0.0f,
+            -1.0f,              1.0f,               0.0f,   1.0f,
+        };
+        const float pos[] = {
+            float(x),   float(y),
+            float(x+w), float(y),
+            float(x),   float(y+h),
+            float(x+w), float(y+h),
+        };
+        const float uv[] = {
+            0.0f, 0.0f,
+            1.0f, 0.0f,
+            0.0f, 1.0f,
+            1.0f, 1.0f,
+        };
+
+        glVertexAttribPointer(mPosAttribLoc, 2, GL_FLOAT, GL_FALSE, 0, pos);
+        glVertexAttribPointer(mUVAttribLoc, 2, GL_FLOAT, GL_FALSE, 0, uv);
+        glEnableVertexAttribArray(mPosAttribLoc);
+        glEnableVertexAttribArray(mUVAttribLoc);
+
+        glUniformMatrix4fv(mObjToNdcUniformLoc, 1, GL_FALSE, screenToNdc);
+        glUniformMatrix4fv(mUVToTexUniformLoc, 1, GL_FALSE, texMatrix);
+        glUniform4fv(mModColorUniformLoc, 1, modColor);
+
+        glActiveTexture(GL_TEXTURE0);
+        glBindTexture(GL_TEXTURE_EXTERNAL_OES, texName);
+        glUniform1i(mBlitSrcSamplerLoc, 0);
+
+        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+
+        glDisableVertexAttribArray(mPosAttribLoc);
+        glDisableVertexAttribArray(mUVAttribLoc);
+
+        if (glGetError() != GL_NO_ERROR) {
+            fprintf(stderr, "GL error!\n");
+        }
+
+        return true;
+    }
+
+private:
+    GLuint mBlitPgm;
+    GLint mPosAttribLoc;
+    GLint mUVAttribLoc;
+    GLint mUVToTexUniformLoc;
+    GLint mObjToNdcUniformLoc;
+    GLint mBlitSrcSamplerLoc;
+    GLint mModColorUniformLoc;
+};
+
+class ComposerBase : public Composer {
+public:
+    virtual ~ComposerBase() {}
+
+    virtual bool setUp(const LayerDesc& desc,
+            GLHelper* helper) {
+        mLayerDesc = desc;
+        return setUp(helper);
+    }
+
+    virtual void tearDown() {
+    }
+
+    virtual bool compose(GLuint texName, const sp<GLConsumer>& glc) {
+        return true;
+    }
+
+protected:
+    virtual bool setUp(GLHelper* helper) {
+        return true;
+    }
+
+    LayerDesc mLayerDesc;
+};
+
+Composer* nocomp() {
+    class NoComp : public ComposerBase {
+    };
+    return new NoComp();
+}
+
+Composer* opaque() {
+    class OpaqueComp : public ComposerBase {
+        virtual bool setUp(GLHelper* helper) {
+            return mBlitter.setUp(helper);
+        }
+
+        virtual bool compose(GLuint texName, const sp<GLConsumer>& glc) {
+            float texMatrix[16];
+            glc->getTransformMatrix(texMatrix);
+
+            int32_t x = mLayerDesc.x;
+            int32_t y = mLayerDesc.y;
+            int32_t w = mLayerDesc.width;
+            int32_t h = mLayerDesc.height;
+
+            return mBlitter.blit(texName, texMatrix, x, y, w, h);
+        }
+
+        Blitter mBlitter;
+    };
+    return new OpaqueComp();
+}
+
+Composer* opaqueShrink() {
+    class OpaqueComp : public ComposerBase {
+        virtual bool setUp(GLHelper* helper) {
+            mParity = false;
+            return mBlitter.setUp(helper);
+        }
+
+        virtual bool compose(GLuint texName, const sp<GLConsumer>& glc) {
+            float texMatrix[16];
+            glc->getTransformMatrix(texMatrix);
+
+            int32_t x = mLayerDesc.x;
+            int32_t y = mLayerDesc.y;
+            int32_t w = mLayerDesc.width;
+            int32_t h = mLayerDesc.height;
+
+            mParity = !mParity;
+            if (mParity) {
+                x += w / 128;
+                y += h / 128;
+                w -= w / 64;
+                h -= h / 64;
+            }
+
+            return mBlitter.blit(texName, texMatrix, x, y, w, h);
+        }
+
+        Blitter mBlitter;
+        bool mParity;
+    };
+    return new OpaqueComp();
+}
+
+Composer* blend() {
+    class BlendComp : public ComposerBase {
+        virtual bool setUp(GLHelper* helper) {
+            return mBlitter.setUp(helper);
+        }
+
+        virtual bool compose(GLuint texName, const sp<GLConsumer>& glc) {
+            bool result;
+
+            float texMatrix[16];
+            glc->getTransformMatrix(texMatrix);
+
+            float modColor[4] = { .75f, .75f, .75f, .75f };
+
+            int32_t x = mLayerDesc.x;
+            int32_t y = mLayerDesc.y;
+            int32_t w = mLayerDesc.width;
+            int32_t h = mLayerDesc.height;
+
+            glEnable(GL_BLEND);
+            glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+
+            result = mBlitter.modBlit(texName, texMatrix, modColor,
+                    x, y, w, h);
+            if (!result) {
+                return false;
+            }
+
+            glDisable(GL_BLEND);
+
+            return true;
+        }
+
+        Blitter mBlitter;
+    };
+    return new BlendComp();
+}
+
+Composer* blendShrink() {
+    class BlendShrinkComp : public ComposerBase {
+        virtual bool setUp(GLHelper* helper) {
+            mParity = false;
+            return mBlitter.setUp(helper);
+        }
+
+        virtual bool compose(GLuint texName, const sp<GLConsumer>& glc) {
+            bool result;
+
+            float texMatrix[16];
+            glc->getTransformMatrix(texMatrix);
+
+            float modColor[4] = { .75f, .75f, .75f, .75f };
+
+            int32_t x = mLayerDesc.x;
+            int32_t y = mLayerDesc.y;
+            int32_t w = mLayerDesc.width;
+            int32_t h = mLayerDesc.height;
+
+            mParity = !mParity;
+            if (mParity) {
+                x += w / 128;
+                y += h / 128;
+                w -= w / 64;
+                h -= h / 64;
+            }
+
+            glEnable(GL_BLEND);
+            glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+
+            result = mBlitter.modBlit(texName, texMatrix, modColor,
+                    x, y, w, h);
+            if (!result) {
+                return false;
+            }
+
+            glDisable(GL_BLEND);
+
+            return true;
+        }
+
+        Blitter mBlitter;
+        bool mParity;
+    };
+    return new BlendShrinkComp();
+}
+
+} // namespace android
diff --git a/cmds/flatland/Flatland.h b/cmds/flatland/Flatland.h
new file mode 100644
index 0000000..fd26ad3
--- /dev/null
+++ b/cmds/flatland/Flatland.h
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+#include <stdint.h>
+
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+
+#include <gui/GLConsumer.h>
+
+namespace android {
+
+#define NELEMS(x) ((int) (sizeof(x) / sizeof((x)[0])))
+
+enum { MAX_NUM_LAYERS = 16 };
+enum { MAX_TEST_RUNS = 16 };
+
+class Composer;
+class Renderer;
+class GLHelper;
+
+struct LayerDesc {
+    uint32_t flags;
+    Renderer* (*rendererFactory)();
+    Composer* (*composerFactory)();
+    int32_t x;
+    int32_t y;
+    uint32_t width;
+    uint32_t height;
+};
+
+void resetColorGenerator();
+
+class Composer {
+public:
+    virtual ~Composer() {}
+    virtual bool setUp(const LayerDesc& desc, GLHelper* helper) = 0;
+    virtual void tearDown() = 0;
+    virtual bool compose(GLuint texName, const sp<GLConsumer>& glc) = 0;
+};
+
+Composer* nocomp();
+Composer* opaque();
+Composer* opaqueShrink();
+Composer* blend();
+Composer* blendShrink();
+
+class Renderer {
+public:
+    virtual ~Renderer() {}
+    virtual bool setUp(GLHelper* helper) = 0;
+    virtual void tearDown() = 0;
+    virtual bool render(EGLSurface surface) = 0;
+};
+
+Renderer* staticGradient();
+
+} // namespace android
diff --git a/cmds/flatland/GLHelper.cpp b/cmds/flatland/GLHelper.cpp
new file mode 100644
index 0000000..31b1e06
--- /dev/null
+++ b/cmds/flatland/GLHelper.cpp
@@ -0,0 +1,461 @@
+/*
+ * 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.
+ */
+
+#include <ui/Fence.h>
+
+#include <ui/DisplayInfo.h>
+#include <gui/SurfaceComposerClient.h>
+
+#include "GLHelper.h"
+
+ namespace android {
+
+GLHelper::GLHelper() :
+    mGraphicBufferAlloc(new GraphicBufferAlloc()),
+    mDisplay(EGL_NO_DISPLAY),
+    mContext(EGL_NO_CONTEXT),
+    mDummySurface(EGL_NO_SURFACE),
+    mConfig(0),
+    mShaderPrograms(NULL),
+    mDitherTexture(0) {
+}
+
+GLHelper::~GLHelper() {
+}
+
+bool GLHelper::setUp(const ShaderDesc* shaderDescs, size_t numShaders) {
+    bool result;
+
+    mDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+    if (mDisplay == EGL_NO_DISPLAY) {
+        fprintf(stderr, "eglGetDisplay error: %#x\n", eglGetError());
+        return false;
+    }
+
+    EGLint majorVersion;
+    EGLint minorVersion;
+    result = eglInitialize(mDisplay, &majorVersion, &minorVersion);
+    if (result != EGL_TRUE) {
+        fprintf(stderr, "eglInitialize error: %#x\n", eglGetError());
+        return false;
+    }
+
+    EGLint numConfigs = 0;
+    EGLint configAttribs[] = {
+        EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+        EGL_RED_SIZE, 8,
+        EGL_GREEN_SIZE, 8,
+        EGL_BLUE_SIZE, 8,
+        EGL_ALPHA_SIZE, 8,
+        EGL_NONE
+    };
+    result = eglChooseConfig(mDisplay, configAttribs, &mConfig, 1,
+            &numConfigs);
+    if (result != EGL_TRUE) {
+        fprintf(stderr, "eglChooseConfig error: %#x\n", eglGetError());
+        return false;
+    }
+
+    EGLint contextAttribs[] = {
+        EGL_CONTEXT_CLIENT_VERSION, 2,
+        EGL_NONE
+    };
+    mContext = eglCreateContext(mDisplay, mConfig, EGL_NO_CONTEXT,
+            contextAttribs);
+    if (mContext == EGL_NO_CONTEXT) {
+        fprintf(stderr, "eglCreateContext error: %#x\n", eglGetError());
+        return false;
+    }
+
+    bool resultb = createNamedSurfaceTexture(0, 1, 1, &mDummyGLConsumer,
+            &mDummySurface);
+    if (!resultb) {
+        return false;
+    }
+
+    resultb = makeCurrent(mDummySurface);
+    if (!resultb) {
+        return false;
+    }
+
+    resultb = setUpShaders(shaderDescs, numShaders);
+    if (!resultb) {
+        return false;
+    }
+
+    return true;
+}
+
+void GLHelper::tearDown() {
+    if (mShaderPrograms != NULL) {
+        delete[] mShaderPrograms;
+        mShaderPrograms = NULL;
+    }
+
+    if (mSurfaceComposerClient != NULL) {
+        mSurfaceComposerClient->dispose();
+        mSurfaceComposerClient.clear();
+    }
+
+    if (mDisplay != EGL_NO_DISPLAY) {
+        eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
+                EGL_NO_CONTEXT);
+    }
+
+    if (mContext != EGL_NO_CONTEXT) {
+        eglDestroyContext(mDisplay, mContext);
+    }
+
+    if (mDummySurface != EGL_NO_SURFACE) {
+        eglDestroySurface(mDisplay, mDummySurface);
+    }
+
+    mDisplay = EGL_NO_DISPLAY;
+    mContext = EGL_NO_CONTEXT;
+    mDummySurface = EGL_NO_SURFACE;
+    mDummyGLConsumer.clear();
+    mConfig = 0;
+}
+
+bool GLHelper::makeCurrent(EGLSurface surface) {
+    EGLint result;
+
+    result = eglMakeCurrent(mDisplay, surface, surface, mContext);
+    if (result != EGL_TRUE) {
+        fprintf(stderr, "eglMakeCurrent error: %#x\n", eglGetError());
+        return false;
+    }
+
+    EGLint w, h;
+    eglQuerySurface(mDisplay, surface, EGL_WIDTH, &w);
+    eglQuerySurface(mDisplay, surface, EGL_HEIGHT, &h);
+    glViewport(0, 0, w, h);
+
+    return true;
+}
+
+bool GLHelper::createSurfaceTexture(uint32_t w, uint32_t h,
+        sp<GLConsumer>* glConsumer, EGLSurface* surface,
+        GLuint* name) {
+    if (!makeCurrent(mDummySurface)) {
+        return false;
+    }
+
+    *name = 0;
+    glGenTextures(1, name);
+    if (*name == 0) {
+        fprintf(stderr, "glGenTextures error: %#x\n", glGetError());
+        return false;
+    }
+
+    return createNamedSurfaceTexture(*name, w, h, glConsumer, surface);
+}
+
+void GLHelper::destroySurface(EGLSurface* surface) {
+    if (eglGetCurrentSurface(EGL_READ) == *surface ||
+            eglGetCurrentSurface(EGL_DRAW) == *surface) {
+        eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
+                EGL_NO_CONTEXT);
+    }
+    eglDestroySurface(mDisplay, *surface);
+    *surface = EGL_NO_SURFACE;
+}
+
+bool GLHelper::swapBuffers(EGLSurface surface) {
+    EGLint result;
+    result = eglSwapBuffers(mDisplay, surface);
+    if (result != EGL_TRUE) {
+        fprintf(stderr, "eglSwapBuffers error: %#x\n", eglGetError());
+        return false;
+    }
+    return true;
+}
+
+bool GLHelper::getShaderProgram(const char* name, GLuint* outPgm) {
+    for (size_t i = 0; i < mNumShaders; i++) {
+        if (strcmp(mShaderDescs[i].name, name) == 0) {
+            *outPgm = mShaderPrograms[i];
+            return true;
+        }
+    }
+
+    fprintf(stderr, "unknown shader name: \"%s\"\n", name);
+
+    return false;
+}
+
+bool GLHelper::createNamedSurfaceTexture(GLuint name, uint32_t w, uint32_t h,
+        sp<GLConsumer>* glConsumer, EGLSurface* surface) {
+    sp<BufferQueue> bq = new BufferQueue(true, mGraphicBufferAlloc);
+    sp<GLConsumer> glc = new GLConsumer(name, true,
+            GL_TEXTURE_EXTERNAL_OES, false, bq);
+    glc->setDefaultBufferSize(w, h);
+    glc->setDefaultMaxBufferCount(3);
+    glc->setConsumerUsageBits(GRALLOC_USAGE_HW_COMPOSER);
+
+    sp<ANativeWindow> anw = new SurfaceTextureClient(bq);
+    EGLSurface s = eglCreateWindowSurface(mDisplay, mConfig, anw.get(), NULL);
+    if (s == EGL_NO_SURFACE) {
+        fprintf(stderr, "eglCreateWindowSurface error: %#x\n", eglGetError());
+        return false;
+    }
+
+    *glConsumer = glc;
+    *surface = s;
+    return true;
+}
+
+bool GLHelper::computeWindowScale(uint32_t w, uint32_t h, float* scale) {
+    sp<IBinder> dpy = mSurfaceComposerClient->getBuiltInDisplay(0);
+    if (dpy == NULL) {
+        fprintf(stderr, "SurfaceComposer::getBuiltInDisplay failed.\n");
+        return false;
+    }
+
+    DisplayInfo info;
+    status_t err = mSurfaceComposerClient->getDisplayInfo(dpy, &info);
+    if (err != NO_ERROR) {
+        fprintf(stderr, "SurfaceComposer::getDisplayInfo failed: %#x\n", err);
+        return false;
+    }
+
+    float scaleX = float(info.w) / float(w);
+    float scaleY = float(info.h) / float(h);
+    *scale = scaleX < scaleY ? scaleX : scaleY;
+
+    return true;
+}
+
+bool GLHelper::createWindowSurface(uint32_t w, uint32_t h,
+        sp<SurfaceControl>* surfaceControl, EGLSurface* surface) {
+    bool result;
+    status_t err;
+
+    if (mSurfaceComposerClient == NULL) {
+        mSurfaceComposerClient = new SurfaceComposerClient;
+    }
+    err = mSurfaceComposerClient->initCheck();
+    if (err != NO_ERROR) {
+        fprintf(stderr, "SurfaceComposerClient::initCheck error: %#x\n", err);
+        return false;
+    }
+
+    sp<SurfaceControl> sc = mSurfaceComposerClient->createSurface(
+            String8("Benchmark"), w, h, PIXEL_FORMAT_RGBA_8888, 0);
+    if (sc == NULL || !sc->isValid()) {
+        fprintf(stderr, "Failed to create SurfaceControl.\n");
+        return false;
+    }
+
+    float scale;
+    result = computeWindowScale(w, h, &scale);
+    if (!result) {
+        return false;
+    }
+
+    SurfaceComposerClient::openGlobalTransaction();
+    err = sc->setLayer(0x7FFFFFFF);
+    if (err != NO_ERROR) {
+        fprintf(stderr, "SurfaceComposer::setLayer error: %#x\n", err);
+        return false;
+    }
+    err = sc->setMatrix(scale, 0.0f, 0.0f, scale);
+    if (err != NO_ERROR) {
+        fprintf(stderr, "SurfaceComposer::setMatrix error: %#x\n", err);
+        return false;
+    }
+
+    err = sc->show();
+    if (err != NO_ERROR) {
+        fprintf(stderr, "SurfaceComposer::show error: %#x\n", err);
+        return false;
+    }
+    SurfaceComposerClient::closeGlobalTransaction();
+
+    sp<ANativeWindow> anw = sc->getSurface();
+    EGLSurface s = eglCreateWindowSurface(mDisplay, mConfig, anw.get(), NULL);
+    if (s == EGL_NO_SURFACE) {
+        fprintf(stderr, "eglCreateWindowSurface error: %#x\n", eglGetError());
+        return false;
+    }
+
+    *surfaceControl = sc;
+    *surface = s;
+    return true;
+}
+
+static bool compileShader(GLenum shaderType, const char* src,
+        GLuint* outShader) {
+    GLuint shader = glCreateShader(shaderType);
+    if (shader == 0) {
+        fprintf(stderr, "glCreateShader error: %#x\n", glGetError());
+        return false;
+    }
+
+    glShaderSource(shader, 1, &src, NULL);
+    glCompileShader(shader);
+
+    GLint compiled = 0;
+    glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
+    if (!compiled) {
+        GLint infoLen = 0;
+        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
+        if (infoLen) {
+            char* buf = new char[infoLen];
+            if (buf) {
+                glGetShaderInfoLog(shader, infoLen, NULL, buf);
+                fprintf(stderr, "Shader compile log:\n%s\n", buf);
+                delete[] buf;
+            }
+        }
+        glDeleteShader(shader);
+        return false;
+    }
+    *outShader = shader;
+    return true;
+}
+
+static void printShaderSource(const char* const* src) {
+    for (size_t i = 0; i < MAX_SHADER_LINES && src[i] != NULL; i++) {
+        fprintf(stderr, "%3d: %s\n", i+1, src[i]);
+    }
+}
+
+static const char* makeShaderString(const char* const* src) {
+    size_t len = 0;
+    for (size_t i = 0; i < MAX_SHADER_LINES && src[i] != NULL; i++) {
+        // The +1 is for the '\n' that will be added.
+        len += strlen(src[i]) + 1;
+    }
+
+    char* result = new char[len+1];
+    char* end = result;
+    for (size_t i = 0; i < MAX_SHADER_LINES && src[i] != NULL; i++) {
+        strcpy(end, src[i]);
+        end += strlen(src[i]);
+        *end = '\n';
+        end++;
+    }
+    *end = '\0';
+
+    return result;
+}
+
+static bool compileShaderLines(GLenum shaderType, const char* const* lines,
+        GLuint* outShader) {
+    const char* src = makeShaderString(lines);
+    bool result = compileShader(shaderType, src, outShader);
+    if (!result) {
+        fprintf(stderr, "Shader source:\n");
+        printShaderSource(lines);
+        return false;
+    }
+    delete[] src;
+
+    return true;
+}
+
+static bool linkShaderProgram(GLuint vs, GLuint fs, GLuint* outPgm) {
+    GLuint program = glCreateProgram();
+    if (program == 0) {
+        fprintf(stderr, "glCreateProgram error: %#x\n", glGetError());
+        return false;
+    }
+
+    glAttachShader(program, vs);
+    glAttachShader(program, fs);
+    glLinkProgram(program);
+    GLint linkStatus = GL_FALSE;
+    glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
+    if (linkStatus != GL_TRUE) {
+        GLint bufLength = 0;
+        glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength);
+        if (bufLength) {
+            char* buf = new char[bufLength];
+            if (buf) {
+                glGetProgramInfoLog(program, bufLength, NULL, buf);
+                fprintf(stderr, "Program link log:\n%s\n", buf);
+                delete[] buf;
+            }
+        }
+        glDeleteProgram(program);
+        program = 0;
+    }
+
+    *outPgm = program;
+    return program != 0;
+}
+
+bool GLHelper::setUpShaders(const ShaderDesc* shaderDescs, size_t numShaders) {
+    mShaderPrograms = new GLuint[numShaders];
+    bool result = true;
+
+    for (size_t i = 0; i < numShaders && result; i++) {
+        GLuint vs, fs;
+
+        result = compileShaderLines(GL_VERTEX_SHADER,
+                shaderDescs[i].vertexShader, &vs);
+        if (!result) {
+            return false;
+        }
+
+        result = compileShaderLines(GL_FRAGMENT_SHADER,
+                shaderDescs[i].fragmentShader, &fs);
+        if (!result) {
+            glDeleteShader(vs);
+            return false;
+        }
+
+        result = linkShaderProgram(vs, fs, &mShaderPrograms[i]);
+        glDeleteShader(vs);
+        glDeleteShader(fs);
+    }
+
+    mNumShaders = numShaders;
+    mShaderDescs = shaderDescs;
+
+    return result;
+}
+
+bool GLHelper::getDitherTexture(GLuint* outTexName) {
+    if (mDitherTexture == 0) {
+        const uint8_t pattern[] = {
+             0,  8,  2, 10,
+            12,  4, 14,  6,
+             3, 11,  1,  9,
+            15,  7, 13,  5
+        };
+
+        glGenTextures(1, &mDitherTexture);
+        glBindTexture(GL_TEXTURE_2D, mDitherTexture);
+
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, DITHER_KERNEL_SIZE,
+                DITHER_KERNEL_SIZE, 0, GL_ALPHA, GL_UNSIGNED_BYTE, &pattern);
+    }
+
+    *outTexName = mDitherTexture;
+
+    return true;
+}
+
+}
diff --git a/cmds/flatland/GLHelper.h b/cmds/flatland/GLHelper.h
new file mode 100644
index 0000000..19fdff9
--- /dev/null
+++ b/cmds/flatland/GLHelper.h
@@ -0,0 +1,94 @@
+/*
+ * 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.
+ */
+
+#include <gui/GraphicBufferAlloc.h>
+#include <gui/GLConsumer.h>
+#include <gui/SurfaceTextureClient.h>
+
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+
+namespace android {
+
+class SurfaceComposerClient;
+class SurfaceControl;
+
+enum { MAX_SHADER_LINES = 128 };
+
+struct ShaderDesc {
+    const char* name;
+    const char* vertexShader[MAX_SHADER_LINES];
+    const char* fragmentShader[MAX_SHADER_LINES];
+};
+
+class GLHelper {
+
+public:
+
+    enum { DITHER_KERNEL_SIZE = 4 };
+
+    GLHelper();
+
+    ~GLHelper();
+
+    bool setUp(const ShaderDesc* shaderDescs, size_t numShaders);
+
+    void tearDown();
+
+    bool makeCurrent(EGLSurface surface);
+
+    bool createSurfaceTexture(uint32_t w, uint32_t h,
+            sp<GLConsumer>* surfaceTexture, EGLSurface* surface,
+            GLuint* name);
+
+    bool createWindowSurface(uint32_t w, uint32_t h,
+            sp<SurfaceControl>* surfaceControl, EGLSurface* surface);
+
+    void destroySurface(EGLSurface* surface);
+
+    bool swapBuffers(EGLSurface surface);
+
+    bool getShaderProgram(const char* name, GLuint* outPgm);
+
+    bool getDitherTexture(GLuint* outTexName);
+
+private:
+
+    bool createNamedSurfaceTexture(GLuint name, uint32_t w, uint32_t h,
+            sp<GLConsumer>* surfaceTexture, EGLSurface* surface);
+
+    bool computeWindowScale(uint32_t w, uint32_t h, float* scale);
+
+    bool setUpShaders(const ShaderDesc* shaderDescs, size_t numShaders);
+
+    sp<GraphicBufferAlloc> mGraphicBufferAlloc;
+
+    EGLDisplay mDisplay;
+    EGLContext mContext;
+    EGLSurface mDummySurface;
+    sp<GLConsumer> mDummyGLConsumer;
+    EGLConfig mConfig;
+
+    sp<SurfaceComposerClient> mSurfaceComposerClient;
+
+    GLuint* mShaderPrograms;
+    const ShaderDesc* mShaderDescs;
+    size_t mNumShaders;
+
+    GLuint mDitherTexture;
+};
+
+} // namespace android
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;
+    }
+}
diff --git a/cmds/flatland/README.txt b/cmds/flatland/README.txt
new file mode 100644
index 0000000..ed47b3c
--- /dev/null
+++ b/cmds/flatland/README.txt
@@ -0,0 +1,74 @@
+Flatland is a benchmark for measuring GPU performance in various 2D UI
+rendering and window compositing scenarios.  It is designed to be used early
+in the device development process to evaluate GPU hardware (e.g. for SoC
+selection).  It uses OpenGL ES 2.0, gralloc, and the Android explicit
+synchronization framework, so it can only be run on devices with drivers
+supporting those HALs.
+
+
+Preparing a Device
+
+Because it's measuring hardware performance, flatland should be run in as
+consistent and static an environment as possible.  The display should be
+turned off and background services should be stopped before running the
+benchmark.  Running 'adb shell stop' after turning off the display is probably
+sufficient for this, but if there are device- specific background services
+that consume much CPU cycles, memory bandwidth, or might otherwise interfere
+with GPU rendering, those should be stopped as well (and ideally they'd be
+fixed or eliminated for production devices).
+
+Additionally, all relevant hardware clocks should be locked at a particular
+frequency when running flatland.  At a minimum this includes the CPU, GPU, and
+memory bus clocks.  Running flatland with dynamic clocking essentially
+measures the behavior of the dynamic clocking algorithm under a fairly
+unrealistic workload, and will likely result in unstable and useless results.
+
+If running the benchmark with the clocks locked causes thermal issues, the -s
+command line option can be used to insert a sleep (specified in milliseconds)
+in between each benchmark sample run.  Regardless of the scenario being
+measured, each sample measurement runs for between 50 and 200 ms, so a sleep
+time between 10 and 50 ms should address most thermal problems.
+
+
+Interpreting the Output
+
+The output of flatland should look something like this:
+
+ cmdline: flatland
+               Scenario               | Resolution  | Time (ms)
+ 16:10 Single Static Window           | 1280 x  800 |   fast
+ 16:10 Single Static Window           | 2560 x 1600 |  5.368
+ 16:10 Single Static Window           | 3840 x 2400 | 11.979
+ 16:10 App -> Home Transition         | 1280 x  800 |  4.069
+ 16:10 App -> Home Transition         | 2560 x 1600 | 15.911
+ 16:10 App -> Home Transition         | 3840 x 2400 | 38.795
+ 16:10 SurfaceView -> Home Transition | 1280 x  800 |  5.387
+ 16:10 SurfaceView -> Home Transition | 2560 x 1600 | 21.147
+ 16:10 SurfaceView -> Home Transition | 3840 x 2400 |   slow
+
+The first column is simply a description of the scenario that's being
+simulated.  The second column indicates the resolution at which the scenario
+was measured.  The third column is the measured benchmark result.  It
+indicates the expected time in milliseconds that a single frame of the
+scenario takes to complete.
+
+The third column may also contain one of three other values:
+
+    fast - This indicates that frames of the scenario completed too fast to be
+    reliably benchmarked.  This corresponds to a frame time less than 3 ms.
+    Rather than spending time trying (and likely failing) to get a stable
+    result, the scenario was skipped.
+
+    slow - This indicates that frames of the scenario took too long to
+    complete.  This corresponds to a frame time over 50 ms.  Rather than
+    simulating a scenario that is obviously impractical on this device, the
+    scenario was skipped.
+
+    varies - This indicates that the scenario was measured, but it did not
+    yield a stable result.  Occasionally this happens with an otherwise stable
+    scenario.  In this case, simply rerunning flatland should yield a valid
+    result.  If a scenario repeatedly results in a 'varies' output, that
+    probably indicates that something is wrong with the environment in which
+    flatland is being run.  Check that the hardware clock frequencies are
+    locked and that no heavy-weight services / daemons are running in the
+    background.
diff --git a/cmds/flatland/Renderers.cpp b/cmds/flatland/Renderers.cpp
new file mode 100644
index 0000000..f1e5488
--- /dev/null
+++ b/cmds/flatland/Renderers.cpp
@@ -0,0 +1,197 @@
+/*
+ * 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.
+ */
+
+#include "Flatland.h"
+#include "GLHelper.h"
+
+namespace android {
+
+static float colors[][4] = {
+    { .85f, .14f, .44f, 1.0f },
+    { .91f, .72f, .10f, 1.0f },
+    { .04f, .66f, .42f, 1.0f },
+    { .84f, .39f, .68f, 1.0f },
+    { .38f, .53f, .78f, 1.0f },
+};
+
+static size_t g_colorIndex;
+
+const float* genColor() {
+    float* color = colors[g_colorIndex];
+    g_colorIndex = (g_colorIndex + 1) % NELEMS(colors);
+    return color;
+}
+
+void resetColorGenerator() {
+    g_colorIndex = 0;
+}
+
+class GradientRenderer {
+
+public:
+
+    bool setUp(GLHelper* helper) {
+        bool result;
+
+        result = helper->getShaderProgram("Gradient", &mGradPgm);
+        if (!result) {
+            return false;
+        }
+
+        result = helper->getDitherTexture(&mDitherTexName);
+        if (!result) {
+            return false;
+        }
+
+        mPosAttribLoc = glGetAttribLocation(mGradPgm, "position");
+        mUVAttribLoc = glGetAttribLocation(mGradPgm, "uv");
+        mUVToInterpUniformLoc = glGetUniformLocation(mGradPgm, "uvToInterp");
+        mObjToNdcUniformLoc = glGetUniformLocation(mGradPgm, "objToNdc");
+        mDitherKernelSamplerLoc = glGetUniformLocation(mGradPgm, "ditherKernel");
+        mInvDitherKernelSizeUniformLoc = glGetUniformLocation(mGradPgm,
+                "invDitherKernelSize");
+        mInvDitherKernelSizeSqUniformLoc = glGetUniformLocation(mGradPgm,
+                "invDitherKernelSizeSq");
+        mColor0UniformLoc = glGetUniformLocation(mGradPgm, "color0");
+        mColor1UniformLoc = glGetUniformLocation(mGradPgm, "color1");
+
+        return true;
+    }
+
+    void tearDown() {
+    }
+
+    bool drawGradient() {
+        float identity[16] = {
+            1.0f,   0.0f,   0.0f,   0.0f,
+            0.0f,   1.0f,   0.0f,   0.0f,
+            0.0f,   0.0f,   1.0f,   0.0f,
+            0.0f,   0.0f,   0.0f,   1.0f,
+        };
+        const float pos[] = {
+            -1.0f,  -1.0f,
+            1.0f,   -1.0f,
+            -1.0f,  1.0f,
+            1.0f,   1.0f,
+        };
+        const float uv[] = {
+            0.0f, 0.0f,
+            1.0f, 0.0f,
+            0.0f, 1.0f,
+            1.0f, 1.0f,
+        };
+        const float* color0 = genColor();
+        const float* color1 = genColor();
+
+        glUseProgram(mGradPgm);
+
+        glVertexAttribPointer(mPosAttribLoc, 2, GL_FLOAT, GL_FALSE, 0, pos);
+        glVertexAttribPointer(mUVAttribLoc, 2, GL_FLOAT, GL_FALSE, 0, uv);
+        glEnableVertexAttribArray(mPosAttribLoc);
+        glEnableVertexAttribArray(mUVAttribLoc);
+
+        float invDitherKernelSize = 1.0f / float(GLHelper::DITHER_KERNEL_SIZE);
+        float invDitherKernelSizeSq = invDitherKernelSize * invDitherKernelSize;
+
+        glUniformMatrix4fv(mObjToNdcUniformLoc, 1, GL_FALSE, identity);
+        glUniformMatrix4fv(mUVToInterpUniformLoc, 1, GL_FALSE, identity);
+        glUniform1f(mInvDitherKernelSizeUniformLoc, invDitherKernelSize);
+        glUniform1f(mInvDitherKernelSizeSqUniformLoc, invDitherKernelSizeSq);
+        glUniform4fv(mColor0UniformLoc, 1, color0);
+        glUniform4fv(mColor1UniformLoc, 1, color1);
+
+        if (glGetError() != GL_NO_ERROR) {
+            fprintf(stderr, "GL error! 0\n");
+        }
+
+        glActiveTexture(GL_TEXTURE0);
+        glBindTexture(GL_TEXTURE_2D, mDitherTexName);
+
+        if (glGetError() != GL_NO_ERROR) {
+            fprintf(stderr, "GL error! 1\n");
+        }
+
+        glUniform1i(mDitherKernelSamplerLoc, 0);
+
+        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+
+        glDisableVertexAttribArray(mPosAttribLoc);
+        glDisableVertexAttribArray(mUVAttribLoc);
+
+        if (glGetError() != GL_NO_ERROR) {
+            fprintf(stderr, "GL error! 2\n");
+        }
+
+        return true;
+    }
+
+    GLuint mGradPgm;
+    GLuint mDitherTexName;
+    GLuint mPosAttribLoc;
+    GLuint mUVAttribLoc;
+    GLuint mObjToNdcUniformLoc;
+    GLuint mUVToInterpUniformLoc;
+    GLuint mDitherKernelSamplerLoc;
+    GLuint mInvDitherKernelSizeUniformLoc;
+    GLuint mInvDitherKernelSizeSqUniformLoc;
+    GLuint mColor0UniformLoc;
+    GLuint mColor1UniformLoc;
+};
+
+Renderer* staticGradient() {
+    class NoRenderer : public Renderer {
+        virtual bool setUp(GLHelper* helper) {
+            mIsFirstFrame = true;
+            mGLHelper = helper;
+            return mGradientRenderer.setUp(helper);
+        }
+
+        virtual void tearDown() {
+            mGradientRenderer.tearDown();
+        }
+
+        virtual bool render(EGLSurface surface) {
+            if (mIsFirstFrame) {
+                bool result;
+                mIsFirstFrame = false;
+
+                result = mGLHelper->makeCurrent(surface);
+                if (!result) {
+                    return false;
+                }
+
+                result = mGradientRenderer.drawGradient();
+                if (!result) {
+                    return false;
+                }
+
+                result = mGLHelper->swapBuffers(surface);
+                if (!result) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        bool mIsFirstFrame;
+        GLHelper* mGLHelper;
+        GradientRenderer mGradientRenderer;
+    };
+    return new NoRenderer;
+}
+
+
+} // namespace android