SRGB_test.cpp revision 5603a2fbbd1aae74c4635e2f600819fb05d112e0
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
34namespace android {
35
36class SRGBTest : public ::testing::Test {
37protected:
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
241private:
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
363const char SRGBTest::SHOW_DEBUG_STRING[] = "DEBUG_OUTPUT_SECONDS";
364
365TEST_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
399TEST_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