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
This commit is contained in:
Jamie Gennis 2012-12-03 16:44:16 -08:00
parent 2adaf04fab
commit 9c183f2493
8 changed files with 1916 additions and 0 deletions

22
cmds/flatland/Android.mk Normal file
View File

@ -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)

282
cmds/flatland/Composers.cpp Normal file
View File

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

71
cmds/flatland/Flatland.h Normal file
View File

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

461
cmds/flatland/GLHelper.cpp Normal file
View File

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

94
cmds/flatland/GLHelper.h Normal file
View File

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

715
cmds/flatland/Main.cpp Normal file
View File

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

74
cmds/flatland/README.txt Normal file
View File

@ -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.

197
cmds/flatland/Renderers.cpp Normal file
View File

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