From 9c183f2493222000fa512d927cfde3f4c748eda0 Mon Sep 17 00:00:00 2001 From: Jamie Gennis Date: Mon, 3 Dec 2012 16:44:16 -0800 Subject: [PATCH] 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 --- cmds/flatland/Android.mk | 22 ++ cmds/flatland/Composers.cpp | 282 ++++++++++++++ cmds/flatland/Flatland.h | 71 ++++ cmds/flatland/GLHelper.cpp | 461 +++++++++++++++++++++++ cmds/flatland/GLHelper.h | 94 +++++ cmds/flatland/Main.cpp | 715 ++++++++++++++++++++++++++++++++++++ cmds/flatland/README.txt | 74 ++++ cmds/flatland/Renderers.cpp | 197 ++++++++++ 8 files changed, 1916 insertions(+) create mode 100644 cmds/flatland/Android.mk create mode 100644 cmds/flatland/Composers.cpp create mode 100644 cmds/flatland/Flatland.h create mode 100644 cmds/flatland/GLHelper.cpp create mode 100644 cmds/flatland/GLHelper.h create mode 100644 cmds/flatland/Main.cpp create mode 100644 cmds/flatland/README.txt create mode 100644 cmds/flatland/Renderers.cpp diff --git a/cmds/flatland/Android.mk b/cmds/flatland/Android.mk new file mode 100644 index 000000000..5e57f0279 --- /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 000000000..8365a3183 --- /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& 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& 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& 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& 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& 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 000000000..fd26ad329 --- /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 + +#include +#include + +#include + +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& 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 000000000..31b1e063f --- /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 + +#include +#include + +#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, 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, EGLSurface* surface) { + sp bq = new BufferQueue(true, mGraphicBufferAlloc); + sp 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 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 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, 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 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 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 000000000..19fdff986 --- /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 +#include +#include + +#include +#include + +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* surfaceTexture, EGLSurface* surface, + GLuint* name); + + bool createWindowSurface(uint32_t w, uint32_t h, + sp* 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* surfaceTexture, EGLSurface* surface); + + bool computeWindowScale(uint32_t w, uint32_t h, float* scale); + + bool setUpShaders(const ShaderDesc* shaderDescs, size_t numShaders); + + sp mGraphicBufferAlloc; + + EGLDisplay mDisplay; + EGLContext mContext; + EGLSurface mDummySurface; + sp mDummyGLConsumer; + EGLConfig mConfig; + + sp 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 000000000..45f414e4f --- /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 +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#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 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 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 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 mGLConsumer; + EGLSurface mSurface; + + // Used for displaying the surface to a window. + EGLSurface mWindowSurface; + sp 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 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 000000000..ed47b3c4c --- /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 000000000..f1e548866 --- /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