Home | History | Annotate | Download | only in tests
      1 /*
      2  * Copyright 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #define LOG_TAG "SRGB_test"
     18 //#define LOG_NDEBUG 0
     19 
     20 #include "GLTest.h"
     21 
     22 #include <gui/CpuConsumer.h>
     23 #include <gui/Surface.h>
     24 #include <gui/SurfaceComposerClient.h>
     25 
     26 #include <EGL/egl.h>
     27 #include <EGL/eglext.h>
     28 #include <GLES3/gl3.h>
     29 
     30 #include <android/native_window.h>
     31 
     32 #include <gtest/gtest.h>
     33 
     34 namespace android {
     35 
     36 class SRGBTest : public ::testing::Test {
     37 protected:
     38     // Class constants
     39     enum {
     40         DISPLAY_WIDTH = 512,
     41         DISPLAY_HEIGHT = 512,
     42         PIXEL_SIZE = 4, // bytes or components
     43         DISPLAY_SIZE = DISPLAY_WIDTH * DISPLAY_HEIGHT * PIXEL_SIZE,
     44         ALPHA_VALUE = 223, // should be in [0, 255]
     45         TOLERANCE = 1,
     46     };
     47     static const char SHOW_DEBUG_STRING[];
     48 
     49     SRGBTest() :
     50             mInputSurface(), mCpuConsumer(), mLockedBuffer(),
     51             mEglDisplay(EGL_NO_DISPLAY), mEglConfig(),
     52             mEglContext(EGL_NO_CONTEXT), mEglSurface(EGL_NO_SURFACE),
     53             mComposerClient(), mSurfaceControl(), mOutputSurface() {
     54     }
     55 
     56     virtual ~SRGBTest() {
     57         if (mEglDisplay != EGL_NO_DISPLAY) {
     58             if (mEglSurface != EGL_NO_SURFACE) {
     59                 eglDestroySurface(mEglDisplay, mEglSurface);
     60             }
     61             if (mEglContext != EGL_NO_CONTEXT) {
     62                 eglDestroyContext(mEglDisplay, mEglContext);
     63             }
     64             eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
     65                     EGL_NO_CONTEXT);
     66             eglTerminate(mEglDisplay);
     67         }
     68     }
     69 
     70     virtual void SetUp() {
     71         sp<IGraphicBufferProducer> producer;
     72         sp<IGraphicBufferConsumer> consumer;
     73         BufferQueue::createBufferQueue(&producer, &consumer);
     74         ASSERT_EQ(NO_ERROR, consumer->setDefaultBufferSize(
     75                 DISPLAY_WIDTH, DISPLAY_HEIGHT));
     76         mCpuConsumer = new CpuConsumer(consumer, 1);
     77         String8 name("CpuConsumer_for_SRGBTest");
     78         mCpuConsumer->setName(name);
     79         mInputSurface = new Surface(producer);
     80 
     81         ASSERT_NO_FATAL_FAILURE(createEGLSurface(mInputSurface.get()));
     82         ASSERT_NO_FATAL_FAILURE(createDebugSurface());
     83     }
     84 
     85     virtual void TearDown() {
     86         ASSERT_NO_FATAL_FAILURE(copyToDebugSurface());
     87         ASSERT_TRUE(mLockedBuffer.data != NULL);
     88         ASSERT_EQ(NO_ERROR, mCpuConsumer->unlockBuffer(mLockedBuffer));
     89     }
     90 
     91     static float linearToSRGB(float l) {
     92         if (l <= 0.0031308f) {
     93             return l * 12.92f;
     94         } else {
     95             return 1.055f * pow(l, (1 / 2.4f)) - 0.055f;
     96         }
     97     }
     98 
     99     static float srgbToLinear(float s) {
    100         if (s <= 0.04045) {
    101             return s / 12.92f;
    102         } else {
    103             return pow(((s + 0.055f) / 1.055f), 2.4f);
    104         }
    105     }
    106 
    107     static uint8_t srgbToLinear(uint8_t u) {
    108         float f = u / 255.0f;
    109         return static_cast<uint8_t>(srgbToLinear(f) * 255.0f + 0.5f);
    110     }
    111 
    112     void fillTexture(bool writeAsSRGB) {
    113         uint8_t* textureData = new uint8_t[DISPLAY_SIZE];
    114 
    115         for (int y = 0; y < DISPLAY_HEIGHT; ++y) {
    116             for (int x = 0; x < DISPLAY_WIDTH; ++x) {
    117                 float realValue = static_cast<float>(x) / (DISPLAY_WIDTH - 1);
    118                 realValue *= ALPHA_VALUE / 255.0f; // Premultiply by alpha
    119                 if (writeAsSRGB) {
    120                     realValue = linearToSRGB(realValue);
    121                 }
    122 
    123                 int offset = (y * DISPLAY_WIDTH + x) * PIXEL_SIZE;
    124                 for (int c = 0; c < 3; ++c) {
    125                     uint8_t intValue = static_cast<uint8_t>(
    126                             realValue * 255.0f + 0.5f);
    127                     textureData[offset + c] = intValue;
    128                 }
    129                 textureData[offset + 3] = ALPHA_VALUE;
    130             }
    131         }
    132 
    133         glTexImage2D(GL_TEXTURE_2D, 0, writeAsSRGB ? GL_SRGB8_ALPHA8 : GL_RGBA8,
    134                 DISPLAY_WIDTH, DISPLAY_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE,
    135                 textureData);
    136         ASSERT_EQ(GL_NO_ERROR, glGetError());
    137 
    138         delete[] textureData;
    139     }
    140 
    141     void initShaders() {
    142         static const char vertexSource[] =
    143             "attribute vec4 vPosition;\n"
    144             "varying vec2 texCoords;\n"
    145             "void main() {\n"
    146             "  texCoords = 0.5 * (vPosition.xy + vec2(1.0, 1.0));\n"
    147             "  gl_Position = vPosition;\n"
    148             "}\n";
    149 
    150         static const char fragmentSource[] =
    151             "precision mediump float;\n"
    152             "uniform sampler2D texSampler;\n"
    153             "varying vec2 texCoords;\n"
    154             "void main() {\n"
    155             "  gl_FragColor = texture2D(texSampler, texCoords);\n"
    156             "}\n";
    157 
    158         GLuint program;
    159         {
    160             SCOPED_TRACE("Creating shader program");
    161             ASSERT_NO_FATAL_FAILURE(GLTest::createProgram(
    162                     vertexSource, fragmentSource, &program));
    163         }
    164 
    165         GLint positionHandle = glGetAttribLocation(program, "vPosition");
    166         ASSERT_EQ(GL_NO_ERROR, glGetError());
    167         ASSERT_NE(-1, positionHandle);
    168 
    169         GLint samplerHandle = glGetUniformLocation(program, "texSampler");
    170         ASSERT_EQ(GL_NO_ERROR, glGetError());
    171         ASSERT_NE(-1, samplerHandle);
    172 
    173         static const GLfloat vertices[] = {
    174             -1.0f, 1.0f,
    175             -1.0f, -1.0f,
    176             1.0f, -1.0f,
    177             1.0f, 1.0f,
    178         };
    179 
    180         glVertexAttribPointer(positionHandle, 2, GL_FLOAT, GL_FALSE, 0, vertices);
    181         ASSERT_EQ(GL_NO_ERROR, glGetError());
    182         glEnableVertexAttribArray(positionHandle);
    183         ASSERT_EQ(GL_NO_ERROR, glGetError());
    184 
    185         glUseProgram(program);
    186         ASSERT_EQ(GL_NO_ERROR, glGetError());
    187         glUniform1i(samplerHandle, 0);
    188         ASSERT_EQ(GL_NO_ERROR, glGetError());
    189 
    190         GLuint textureHandle;
    191         glGenTextures(1, &textureHandle);
    192         ASSERT_EQ(GL_NO_ERROR, glGetError());
    193         glBindTexture(GL_TEXTURE_2D, textureHandle);
    194         ASSERT_EQ(GL_NO_ERROR, glGetError());
    195 
    196         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    197         ASSERT_EQ(GL_NO_ERROR, glGetError());
    198         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    199         ASSERT_EQ(GL_NO_ERROR, glGetError());
    200         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    201         ASSERT_EQ(GL_NO_ERROR, glGetError());
    202         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    203         ASSERT_EQ(GL_NO_ERROR, glGetError());
    204     }
    205 
    206     void drawTexture(bool asSRGB, GLint x, GLint y, GLsizei width,
    207             GLsizei height) {
    208         ASSERT_NO_FATAL_FAILURE(fillTexture(asSRGB));
    209         glViewport(x, y, width, height);
    210         ASSERT_EQ(GL_NO_ERROR, glGetError());
    211         glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
    212         ASSERT_EQ(GL_NO_ERROR, glGetError());
    213     }
    214 
    215     void checkLockedBuffer(PixelFormat format) {
    216         ASSERT_EQ(mLockedBuffer.format, format);
    217         ASSERT_EQ(mLockedBuffer.width, DISPLAY_WIDTH);
    218         ASSERT_EQ(mLockedBuffer.height, DISPLAY_HEIGHT);
    219     }
    220 
    221     static bool withinTolerance(int a, int b) {
    222         int diff = a - b;
    223         return diff >= 0 ? diff <= TOLERANCE : -diff <= TOLERANCE;
    224     }
    225 
    226     // Primary producer and consumer
    227     sp<Surface> mInputSurface;
    228     sp<CpuConsumer> mCpuConsumer;
    229     CpuConsumer::LockedBuffer mLockedBuffer;
    230 
    231     EGLDisplay mEglDisplay;
    232     EGLConfig mEglConfig;
    233     EGLContext mEglContext;
    234     EGLSurface mEglSurface;
    235 
    236     // Auxiliary display output
    237     sp<SurfaceComposerClient> mComposerClient;
    238     sp<SurfaceControl> mSurfaceControl;
    239     sp<Surface> mOutputSurface;
    240 
    241 private:
    242     void createEGLSurface(Surface* inputSurface) {
    243         mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    244         ASSERT_EQ(EGL_SUCCESS, eglGetError());
    245         ASSERT_NE(EGL_NO_DISPLAY, mEglDisplay);
    246 
    247         EXPECT_TRUE(eglInitialize(mEglDisplay, NULL, NULL));
    248         ASSERT_EQ(EGL_SUCCESS, eglGetError());
    249 
    250         static const EGLint configAttribs[] = {
    251             EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
    252             EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT_KHR,
    253             EGL_RED_SIZE, 8,
    254             EGL_GREEN_SIZE, 8,
    255             EGL_BLUE_SIZE, 8,
    256             EGL_ALPHA_SIZE, 8,
    257             EGL_NONE };
    258 
    259         EGLint numConfigs = 0;
    260         EXPECT_TRUE(eglChooseConfig(mEglDisplay, configAttribs, &mEglConfig, 1,
    261                 &numConfigs));
    262         ASSERT_EQ(EGL_SUCCESS, eglGetError());
    263         ASSERT_GT(numConfigs, 0);
    264 
    265         static const EGLint contextAttribs[] = {
    266             EGL_CONTEXT_CLIENT_VERSION, 3,
    267             EGL_NONE } ;
    268 
    269         mEglContext = eglCreateContext(mEglDisplay, mEglConfig, EGL_NO_CONTEXT,
    270                 contextAttribs);
    271         ASSERT_EQ(EGL_SUCCESS, eglGetError());
    272         ASSERT_NE(EGL_NO_CONTEXT, mEglContext);
    273 
    274         mEglSurface = eglCreateWindowSurface(mEglDisplay, mEglConfig,
    275                 inputSurface, NULL);
    276         ASSERT_EQ(EGL_SUCCESS, eglGetError());
    277         ASSERT_NE(EGL_NO_SURFACE, mEglSurface);
    278 
    279         EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
    280                 mEglContext));
    281         ASSERT_EQ(EGL_SUCCESS, eglGetError());
    282     }
    283 
    284     void createDebugSurface() {
    285         if (getenv(SHOW_DEBUG_STRING) == NULL) return;
    286 
    287         mComposerClient = new SurfaceComposerClient;
    288         ASSERT_EQ(NO_ERROR, mComposerClient->initCheck());
    289 
    290         mSurfaceControl = mComposerClient->createSurface(
    291                 String8("SRGBTest Surface"), DISPLAY_WIDTH, DISPLAY_HEIGHT,
    292                 PIXEL_FORMAT_RGBA_8888);
    293 
    294         ASSERT_TRUE(mSurfaceControl != NULL);
    295         ASSERT_TRUE(mSurfaceControl->isValid());
    296 
    297         SurfaceComposerClient::openGlobalTransaction();
    298         ASSERT_EQ(NO_ERROR, mSurfaceControl->setLayer(0x7FFFFFFF));
    299         ASSERT_EQ(NO_ERROR, mSurfaceControl->show());
    300         SurfaceComposerClient::closeGlobalTransaction();
    301 
    302         ANativeWindow_Buffer outBuffer;
    303         ARect inOutDirtyBounds;
    304         mOutputSurface = mSurfaceControl->getSurface();
    305         mOutputSurface->lock(&outBuffer, &inOutDirtyBounds);
    306         uint8_t* bytePointer = reinterpret_cast<uint8_t*>(outBuffer.bits);
    307         for (int y = 0; y < outBuffer.height; ++y) {
    308             int rowOffset = y * outBuffer.stride; // pixels
    309             for (int x = 0; x < outBuffer.width; ++x) {
    310                 int colOffset = (rowOffset + x) * PIXEL_SIZE; // bytes
    311                 for (int c = 0; c < PIXEL_SIZE; ++c) {
    312                     int offset = colOffset + c;
    313                     bytePointer[offset] = ((c + 1) * 56) - 1;
    314                 }
    315             }
    316         }
    317         mOutputSurface->unlockAndPost();
    318     }
    319 
    320     void copyToDebugSurface() {
    321         if (!mOutputSurface.get()) return;
    322 
    323         size_t bufferSize = mLockedBuffer.height * mLockedBuffer.stride *
    324                 PIXEL_SIZE;
    325 
    326         ANativeWindow_Buffer outBuffer;
    327         ARect outBufferBounds;
    328         mOutputSurface->lock(&outBuffer, &outBufferBounds);
    329         ASSERT_EQ(mLockedBuffer.width, outBuffer.width);
    330         ASSERT_EQ(mLockedBuffer.height, outBuffer.height);
    331         ASSERT_EQ(mLockedBuffer.stride, outBuffer.stride);
    332 
    333         if (mLockedBuffer.format == outBuffer.format) {
    334             memcpy(outBuffer.bits, mLockedBuffer.data, bufferSize);
    335         } else {
    336             ASSERT_EQ(mLockedBuffer.format, PIXEL_FORMAT_sRGB_A_8888);
    337             ASSERT_EQ(outBuffer.format, PIXEL_FORMAT_RGBA_8888);
    338             uint8_t* outPointer = reinterpret_cast<uint8_t*>(outBuffer.bits);
    339             for (int y = 0; y < outBuffer.height; ++y) {
    340                 int rowOffset = y * outBuffer.stride; // pixels
    341                 for (int x = 0; x < outBuffer.width; ++x) {
    342                     int colOffset = (rowOffset + x) * PIXEL_SIZE; // bytes
    343 
    344                     // RGB are converted
    345                     for (int c = 0; c < (PIXEL_SIZE - 1); ++c) {
    346                         outPointer[colOffset + c] = srgbToLinear(
    347                                 mLockedBuffer.data[colOffset + c]);
    348                     }
    349 
    350                     // Alpha isn't converted
    351                     outPointer[colOffset + 3] =
    352                             mLockedBuffer.data[colOffset + 3];
    353                 }
    354             }
    355         }
    356         mOutputSurface->unlockAndPost();
    357 
    358         int sleepSeconds = atoi(getenv(SHOW_DEBUG_STRING));
    359         sleep(sleepSeconds);
    360     }
    361 };
    362 
    363 const char SRGBTest::SHOW_DEBUG_STRING[] = "DEBUG_OUTPUT_SECONDS";
    364 
    365 TEST_F(SRGBTest, GLRenderFromSRGBTexture) {
    366     ASSERT_NO_FATAL_FAILURE(initShaders());
    367 
    368     // The RGB texture is displayed in the top half
    369     ASSERT_NO_FATAL_FAILURE(drawTexture(false, 0, DISPLAY_HEIGHT / 2,
    370             DISPLAY_WIDTH, DISPLAY_HEIGHT / 2));
    371 
    372     // The SRGB texture is displayed in the bottom half
    373     ASSERT_NO_FATAL_FAILURE(drawTexture(true, 0, 0,
    374             DISPLAY_WIDTH, DISPLAY_HEIGHT / 2));
    375 
    376     eglSwapBuffers(mEglDisplay, mEglSurface);
    377     ASSERT_EQ(EGL_SUCCESS, eglGetError());
    378 
    379     // Lock
    380     ASSERT_EQ(NO_ERROR, mCpuConsumer->lockNextBuffer(&mLockedBuffer));
    381     ASSERT_NO_FATAL_FAILURE(checkLockedBuffer(PIXEL_FORMAT_RGBA_8888));
    382 
    383     // Compare a pixel in the middle of each texture
    384     int midSRGBOffset = (DISPLAY_HEIGHT / 4) * mLockedBuffer.stride *
    385             PIXEL_SIZE;
    386     int midRGBOffset = midSRGBOffset * 3;
    387     midRGBOffset += (DISPLAY_WIDTH / 2) * PIXEL_SIZE;
    388     midSRGBOffset += (DISPLAY_WIDTH / 2) * PIXEL_SIZE;
    389     for (int c = 0; c < PIXEL_SIZE; ++c) {
    390         int expectedValue = mLockedBuffer.data[midRGBOffset + c];
    391         int actualValue = mLockedBuffer.data[midSRGBOffset + c];
    392         ASSERT_PRED2(withinTolerance, expectedValue, actualValue);
    393     }
    394 
    395     // mLockedBuffer is unlocked in TearDown so we can copy data from it to
    396     // the debug surface if necessary
    397 }
    398 
    399 TEST_F(SRGBTest, RenderToSRGBSurface) {
    400     ASSERT_NO_FATAL_FAILURE(initShaders());
    401 
    402     // By default, the first buffer we write into will be RGB
    403 
    404     // Render an RGB texture across the whole surface
    405     ASSERT_NO_FATAL_FAILURE(drawTexture(false, 0, 0,
    406             DISPLAY_WIDTH, DISPLAY_HEIGHT));
    407     eglSwapBuffers(mEglDisplay, mEglSurface);
    408     ASSERT_EQ(EGL_SUCCESS, eglGetError());
    409 
    410     // Lock
    411     ASSERT_EQ(NO_ERROR, mCpuConsumer->lockNextBuffer(&mLockedBuffer));
    412     ASSERT_NO_FATAL_FAILURE(checkLockedBuffer(PIXEL_FORMAT_RGBA_8888));
    413 
    414     // Save the values of the middle pixel for later comparison against SRGB
    415     uint8_t values[PIXEL_SIZE] = {};
    416     int middleOffset = (DISPLAY_HEIGHT / 2) * mLockedBuffer.stride *
    417             PIXEL_SIZE;
    418     middleOffset += (DISPLAY_WIDTH / 2) * PIXEL_SIZE;
    419     for (int c = 0; c < PIXEL_SIZE; ++c) {
    420         values[c] = mLockedBuffer.data[middleOffset + c];
    421     }
    422 
    423     // Unlock
    424     ASSERT_EQ(NO_ERROR, mCpuConsumer->unlockBuffer(mLockedBuffer));
    425 
    426     // Switch to SRGB window surface
    427 #define EGL_GL_COLORSPACE_KHR      EGL_VG_COLORSPACE
    428 #define EGL_GL_COLORSPACE_SRGB_KHR EGL_VG_COLORSPACE_sRGB
    429 
    430     static const int srgbAttribs[] = {
    431         EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_SRGB_KHR,
    432         EGL_NONE,
    433     };
    434 
    435     EXPECT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
    436             mEglContext));
    437     ASSERT_EQ(EGL_SUCCESS, eglGetError());
    438 
    439     EXPECT_TRUE(eglDestroySurface(mEglDisplay, mEglSurface));
    440     ASSERT_EQ(EGL_SUCCESS, eglGetError());
    441 
    442     mEglSurface = eglCreateWindowSurface(mEglDisplay, mEglConfig,
    443             mInputSurface.get(), srgbAttribs);
    444     ASSERT_EQ(EGL_SUCCESS, eglGetError());
    445     ASSERT_NE(EGL_NO_SURFACE, mEglSurface);
    446 
    447     EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
    448             mEglContext));
    449     ASSERT_EQ(EGL_SUCCESS, eglGetError());
    450 
    451     // Render the texture again
    452     ASSERT_NO_FATAL_FAILURE(drawTexture(false, 0, 0,
    453             DISPLAY_WIDTH, DISPLAY_HEIGHT));
    454     eglSwapBuffers(mEglDisplay, mEglSurface);
    455     ASSERT_EQ(EGL_SUCCESS, eglGetError());
    456 
    457     // Lock
    458     ASSERT_EQ(NO_ERROR, mCpuConsumer->lockNextBuffer(&mLockedBuffer));
    459 
    460     // Make sure we actually got the SRGB buffer on the consumer side
    461     ASSERT_NO_FATAL_FAILURE(checkLockedBuffer(PIXEL_FORMAT_sRGB_A_8888));
    462 
    463     // Verify that the stored value is the same, accounting for RGB/SRGB
    464     for (int c = 0; c < PIXEL_SIZE; ++c) {
    465         // The alpha value should be equivalent before linear->SRGB
    466         float rgbAsSRGB = (c == 3) ? values[c] / 255.0f :
    467                 linearToSRGB(values[c] / 255.0f);
    468         int expectedValue = rgbAsSRGB * 255.0f + 0.5f;
    469         int actualValue = mLockedBuffer.data[middleOffset + c];
    470         ASSERT_PRED2(withinTolerance, expectedValue, actualValue);
    471     }
    472 
    473     // mLockedBuffer is unlocked in TearDown so we can copy data from it to
    474     // the debug surface if necessary
    475 }
    476 
    477 } // namespace android
    478