| /* |
| * 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/SurfaceControl.h> |
| #include <gui/GLConsumer.h> |
| #include <gui/Surface.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; |
| } |
| } |