• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
SRGBTest()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 
~SRGBTest()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 
SetUp()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 
TearDown()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 
linearToSRGB(float l)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 
srgbToLinear(float s)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 
srgbToLinear(uint8_t u)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 
fillTexture(bool writeAsSRGB)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 
initShaders()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 
drawTexture(bool asSRGB,GLint x,GLint y,GLsizei width,GLsizei height)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 
checkLockedBuffer(PixelFormat format)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 
withinTolerance(int a,int b)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:
createEGLSurface(Surface * inputSurface)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 
createDebugSurface()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 
copyToDebugSurface()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 
TEST_F(SRGBTest,GLRenderFromSRGBTexture)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 
TEST_F(SRGBTest,RenderToSRGBSurface)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