blob: fe626370f54bc461be6c3bc0dd64303fc9b37d0a [file] [log] [blame]
Dan Stoza3ed4e0b2013-12-11 15:21:11 -08001/*
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
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<BufferQueue> bufferQueue = new BufferQueue();
72 ASSERT_EQ(NO_ERROR, bufferQueue->setDefaultBufferSize(
73 DISPLAY_WIDTH, DISPLAY_HEIGHT));
74 mCpuConsumer = new CpuConsumer(bufferQueue, 1);
75 String8 name("CpuConsumer_for_SRGBTest");
76 mCpuConsumer->setName(name);
77 mInputSurface = new Surface(bufferQueue);
78
79 ASSERT_NO_FATAL_FAILURE(createEGLSurface(mInputSurface.get()));
80 ASSERT_NO_FATAL_FAILURE(createDebugSurface());
81 }
82
83 virtual void TearDown() {
84 ASSERT_NO_FATAL_FAILURE(copyToDebugSurface());
85 mCpuConsumer->unlockBuffer(mLockedBuffer);
86 }
87
88 static float linearToSRGB(float l) {
89 if (l <= 0.0031308f) {
90 return l * 12.92f;
91 } else {
92 return 1.055f * pow(l, (1 / 2.4f)) - 0.055f;
93 }
94 }
95
96 void fillTexture(bool writeAsSRGB) {
97 uint8_t* textureData = new uint8_t[DISPLAY_SIZE];
98
99 for (int y = 0; y < DISPLAY_HEIGHT; ++y) {
100 for (int x = 0; x < DISPLAY_WIDTH; ++x) {
101 float realValue = static_cast<float>(x) / (DISPLAY_WIDTH - 1);
102 realValue *= ALPHA_VALUE / 255.0f; // Premultiply by alpha
103 if (writeAsSRGB) {
104 realValue = linearToSRGB(realValue);
105 }
106
107 int offset = (y * DISPLAY_WIDTH + x) * PIXEL_SIZE;
108 for (int c = 0; c < 3; ++c) {
109 uint8_t intValue = static_cast<uint8_t>(
110 realValue * 255.0f + 0.5f);
111 textureData[offset + c] = intValue;
112 }
113 textureData[offset + 3] = ALPHA_VALUE;
114 }
115 }
116
117 glTexImage2D(GL_TEXTURE_2D, 0, writeAsSRGB ? GL_SRGB8_ALPHA8 : GL_RGBA8,
118 DISPLAY_WIDTH, DISPLAY_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE,
119 textureData);
120 ASSERT_EQ(GL_NO_ERROR, glGetError());
121
122 delete[] textureData;
123 }
124
125 static bool withinTolerance(int a, int b) {
126 int diff = a - b;
127 return diff >= 0 ? diff <= TOLERANCE : -diff <= TOLERANCE;
128 }
129
130 // Primary producer and consumer
131 sp<Surface> mInputSurface;
132 sp<CpuConsumer> mCpuConsumer;
133 CpuConsumer::LockedBuffer mLockedBuffer;
134
135 EGLDisplay mEglDisplay;
136 EGLConfig mEglConfig;
137 EGLContext mEglContext;
138 EGLSurface mEglSurface;
139
140 // Auxiliary display output
141 sp<SurfaceComposerClient> mComposerClient;
142 sp<SurfaceControl> mSurfaceControl;
143 sp<Surface> mOutputSurface;
144
145private:
146 void createEGLSurface(Surface* inputSurface) {
147 mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
148 ASSERT_EQ(EGL_SUCCESS, eglGetError());
149 ASSERT_NE(EGL_NO_DISPLAY, mEglDisplay);
150
151 EXPECT_TRUE(eglInitialize(mEglDisplay, NULL, NULL));
152 ASSERT_EQ(EGL_SUCCESS, eglGetError());
153
154 static const EGLint configAttribs[] = {
155 EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
156 EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT_KHR,
157 EGL_RED_SIZE, 8,
158 EGL_GREEN_SIZE, 8,
159 EGL_BLUE_SIZE, 8,
160 EGL_ALPHA_SIZE, 8,
161 EGL_NONE };
162
163 EGLint numConfigs = 0;
164 EXPECT_TRUE(eglChooseConfig(mEglDisplay, configAttribs, &mEglConfig, 1,
165 &numConfigs));
166 ASSERT_EQ(EGL_SUCCESS, eglGetError());
167
168 static const EGLint contextAttribs[] = {
169 EGL_CONTEXT_CLIENT_VERSION, 3,
170 EGL_NONE } ;
171
172 mEglContext = eglCreateContext(mEglDisplay, mEglConfig, EGL_NO_CONTEXT,
173 contextAttribs);
174 ASSERT_EQ(EGL_SUCCESS, eglGetError());
175 ASSERT_NE(EGL_NO_CONTEXT, mEglContext);
176
177 mEglSurface = eglCreateWindowSurface(mEglDisplay, mEglConfig,
178 inputSurface, NULL);
179 ASSERT_EQ(EGL_SUCCESS, eglGetError());
180 ASSERT_NE(EGL_NO_SURFACE, mEglSurface);
181
182 EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
183 mEglContext));
184 ASSERT_EQ(EGL_SUCCESS, eglGetError());
185 }
186
187 void createDebugSurface() {
188 if (getenv(SHOW_DEBUG_STRING) == NULL) return;
189
190 mComposerClient = new SurfaceComposerClient;
191 ASSERT_EQ(NO_ERROR, mComposerClient->initCheck());
192
193 mSurfaceControl = mComposerClient->createSurface(
194 String8("SRGBTest Surface"), DISPLAY_WIDTH, DISPLAY_HEIGHT,
195 PIXEL_FORMAT_RGBA_8888);
196
197 ASSERT_TRUE(mSurfaceControl != NULL);
198 ASSERT_TRUE(mSurfaceControl->isValid());
199
200 SurfaceComposerClient::openGlobalTransaction();
201 ASSERT_EQ(NO_ERROR, mSurfaceControl->setLayer(0x7FFFFFFF));
202 ASSERT_EQ(NO_ERROR, mSurfaceControl->show());
203 SurfaceComposerClient::closeGlobalTransaction();
204
205 ANativeWindow_Buffer outBuffer;
206 ARect inOutDirtyBounds;
207 mOutputSurface = mSurfaceControl->getSurface();
208 mOutputSurface->lock(&outBuffer, &inOutDirtyBounds);
209 for (int y = 0; y < outBuffer.height; ++y) {
210 int rowOffset = y * outBuffer.stride;
211 for (int x = 0; x < outBuffer.width; ++x) {
212 int colOffset = rowOffset + x;
213 for (int c = 0; c < 4; ++c) {
214 int offset = colOffset * PIXEL_SIZE + c;
215 uint8_t* bytePointer =
216 reinterpret_cast<uint8_t*>(outBuffer.bits);
217 bytePointer[offset] = ((c + 1) * 56) - 1;
218 }
219 }
220 }
221 mOutputSurface->unlockAndPost();
222 }
223
224 void copyToDebugSurface() {
225 if (!mOutputSurface.get()) return;
226
227 size_t bufferSize = mLockedBuffer.height * mLockedBuffer.stride *
228 PIXEL_SIZE;
229
230 ANativeWindow_Buffer outBuffer;
231 ARect outBufferBounds;
232 mOutputSurface->lock(&outBuffer, &outBufferBounds);
233 ASSERT_EQ(mLockedBuffer.height, outBuffer.height);
234 ASSERT_EQ(mLockedBuffer.stride, outBuffer.stride);
235 ASSERT_EQ(mLockedBuffer.format, outBuffer.format);
236 memcpy(outBuffer.bits, mLockedBuffer.data, bufferSize);
237 mOutputSurface->unlockAndPost();
238
239 int sleepSeconds = atoi(getenv(SHOW_DEBUG_STRING));
240 sleep(sleepSeconds);
241 }
242};
243
244const char SRGBTest::SHOW_DEBUG_STRING[] = "DEBUG_OUTPUT_SECONDS";
245
246TEST_F(SRGBTest, GLRenderFromSRGBTexture) {
247 static const char vertexSource[] =
248 "attribute vec4 vPosition;\n"
249 "varying vec2 texCoords;\n"
250 "void main() {\n"
251 " texCoords = 0.5 * (vPosition.xy + vec2(1.0, 1.0));\n"
252 " gl_Position = vPosition;\n"
253 "}\n";
254
255 static const char fragmentSource[] =
256 "precision mediump float;\n"
257 "uniform sampler2D texSampler;\n"
258 "varying vec2 texCoords;\n"
259 "void main() {\n"
260 " gl_FragColor = texture2D(texSampler, texCoords);\n"
261 "}\n";
262
263 GLuint program;
264 {
265 SCOPED_TRACE("Creating shader program");
266 ASSERT_NO_FATAL_FAILURE(GLTest::createProgram(
267 vertexSource, fragmentSource, &program));
268 }
269
270 GLint positionHandle = glGetAttribLocation(program, "vPosition");
271 ASSERT_EQ(GL_NO_ERROR, glGetError());
272 ASSERT_NE(-1, positionHandle);
273
274 GLint samplerHandle = glGetUniformLocation(program, "texSampler");
275 ASSERT_EQ(GL_NO_ERROR, glGetError());
276 ASSERT_NE(-1, samplerHandle);
277
278 static const GLfloat vertices[] = {
279 -1.0f, 1.0f,
280 -1.0f, -1.0f,
281 1.0f, -1.0f,
282 1.0f, 1.0f,
283 };
284
285 glVertexAttribPointer(positionHandle, 2, GL_FLOAT, GL_FALSE, 0, vertices);
286 ASSERT_EQ(GL_NO_ERROR, glGetError());
287 glEnableVertexAttribArray(positionHandle);
288 ASSERT_EQ(GL_NO_ERROR, glGetError());
289
290 glUseProgram(program);
291 ASSERT_EQ(GL_NO_ERROR, glGetError());
292 glUniform1i(samplerHandle, 0);
293 ASSERT_EQ(GL_NO_ERROR, glGetError());
294
295 GLuint textureHandle;
296 glGenTextures(1, &textureHandle);
297 ASSERT_EQ(GL_NO_ERROR, glGetError());
298 glBindTexture(GL_TEXTURE_2D, textureHandle);
299 ASSERT_EQ(GL_NO_ERROR, glGetError());
300
301 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
302 ASSERT_EQ(GL_NO_ERROR, glGetError());
303 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
304 ASSERT_EQ(GL_NO_ERROR, glGetError());
305 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
306 ASSERT_EQ(GL_NO_ERROR, glGetError());
307 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
308 ASSERT_EQ(GL_NO_ERROR, glGetError());
309
310 // The RGB texture is displayed in the top half
311 ASSERT_NO_FATAL_FAILURE(fillTexture(false));
312 glViewport(0, DISPLAY_HEIGHT / 2, DISPLAY_WIDTH, DISPLAY_HEIGHT / 2);
313 ASSERT_EQ(GL_NO_ERROR, glGetError());
314 glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
315 ASSERT_EQ(GL_NO_ERROR, glGetError());
316
317 // The SRGB texture is displayed in the bottom half
318 ASSERT_NO_FATAL_FAILURE(fillTexture(true));
319 glViewport(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT / 2);
320 ASSERT_EQ(GL_NO_ERROR, glGetError());
321 glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
322 ASSERT_EQ(GL_NO_ERROR, glGetError());
323
324 eglSwapBuffers(mEglDisplay, mEglSurface);
325 ASSERT_EQ(EGL_SUCCESS, eglGetError());
326
327 ASSERT_EQ(NO_ERROR, mCpuConsumer->lockNextBuffer(&mLockedBuffer));
328 ASSERT_EQ(mLockedBuffer.format, PIXEL_FORMAT_RGBA_8888);
329 ASSERT_EQ(mLockedBuffer.width, DISPLAY_WIDTH);
330 ASSERT_EQ(mLockedBuffer.height, DISPLAY_HEIGHT);
331 int midSRGBOffset = (DISPLAY_HEIGHT / 4) * mLockedBuffer.stride *
332 PIXEL_SIZE;
333 int midRGBOffset = midSRGBOffset * 3;
334 midRGBOffset += (DISPLAY_WIDTH / 2) * PIXEL_SIZE;
335 midSRGBOffset += (DISPLAY_WIDTH / 2) * PIXEL_SIZE;
336 for (int c = 0; c < 4; ++c) {
337 ASSERT_PRED2(withinTolerance,
338 static_cast<int>(mLockedBuffer.data[midRGBOffset]),
339 static_cast<int>(mLockedBuffer.data[midSRGBOffset]));
340 }
341 // mLockedBuffer is unlocked in TearDown so we can copy data from it to
342 // the debug surface if necessary
343}
344
345} // namespace android