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/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