/* * Copyright (C) 2011 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 LOG_NDEBUG 0 #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace android { class GLTest : public ::testing::Test { protected: GLTest(): mEglDisplay(EGL_NO_DISPLAY), mEglSurface(EGL_NO_SURFACE), mEglContext(EGL_NO_CONTEXT) { } virtual void SetUp() { EGLBoolean returnValue; mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); ASSERT_EQ(EGL_SUCCESS, eglGetError()); ASSERT_NE(EGL_NO_DISPLAY, mEglDisplay); EGLint majorVersion; EGLint minorVersion; EXPECT_TRUE(eglInitialize(mEglDisplay, &majorVersion, &minorVersion)); ASSERT_EQ(EGL_SUCCESS, eglGetError()); RecordProperty("EglVersionMajor", majorVersion); RecordProperty("EglVersionMajor", minorVersion); EGLConfig myConfig = {0}; EGLint numConfigs = 0; EXPECT_TRUE(eglChooseConfig(mEglDisplay, getConfigAttribs(), &myConfig, 1, &numConfigs)); ASSERT_EQ(EGL_SUCCESS, eglGetError()); char* displaySecsEnv = getenv("GLTEST_DISPLAY_SECS"); if (displaySecsEnv != NULL) { mDisplaySecs = atoi(displaySecsEnv); if (mDisplaySecs < 0) { mDisplaySecs = 0; } } else { mDisplaySecs = 0; } if (mDisplaySecs > 0) { mComposerClient = new SurfaceComposerClient; ASSERT_EQ(NO_ERROR, mComposerClient->initCheck()); mSurfaceControl = mComposerClient->createSurface( String8("Test Surface"), 0, getSurfaceWidth(), getSurfaceHeight(), PIXEL_FORMAT_RGB_888, 0); ASSERT_TRUE(mSurfaceControl != NULL); ASSERT_TRUE(mSurfaceControl->isValid()); ASSERT_EQ(NO_ERROR, mComposerClient->openTransaction()); ASSERT_EQ(NO_ERROR, mSurfaceControl->setLayer(30000)); ASSERT_EQ(NO_ERROR, mSurfaceControl->show()); ASSERT_EQ(NO_ERROR, mComposerClient->closeTransaction()); sp window = mSurfaceControl->getSurface(); mEglSurface = eglCreateWindowSurface(mEglDisplay, myConfig, window.get(), NULL); } else { EGLint pbufferAttribs[] = { EGL_WIDTH, getSurfaceWidth(), EGL_HEIGHT, getSurfaceHeight(), EGL_NONE }; mEglSurface = eglCreatePbufferSurface(mEglDisplay, myConfig, pbufferAttribs); } ASSERT_EQ(EGL_SUCCESS, eglGetError()); ASSERT_NE(EGL_NO_SURFACE, mEglSurface); mEglContext = eglCreateContext(mEglDisplay, myConfig, EGL_NO_CONTEXT, getContextAttribs()); ASSERT_EQ(EGL_SUCCESS, eglGetError()); ASSERT_NE(EGL_NO_CONTEXT, mEglContext); EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)); ASSERT_EQ(EGL_SUCCESS, eglGetError()); EGLint w, h; EXPECT_TRUE(eglQuerySurface(mEglDisplay, mEglSurface, EGL_WIDTH, &w)); ASSERT_EQ(EGL_SUCCESS, eglGetError()); EXPECT_TRUE(eglQuerySurface(mEglDisplay, mEglSurface, EGL_HEIGHT, &h)); ASSERT_EQ(EGL_SUCCESS, eglGetError()); RecordProperty("EglSurfaceWidth", w); RecordProperty("EglSurfaceHeight", h); glViewport(0, 0, w, h); ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); } virtual void TearDown() { // Display the result if (mDisplaySecs > 0 && mEglSurface != EGL_NO_SURFACE) { eglSwapBuffers(mEglDisplay, mEglSurface); sleep(mDisplaySecs); } if (mComposerClient != NULL) { mComposerClient->dispose(); } if (mEglContext != EGL_NO_CONTEXT) { eglDestroyContext(mEglDisplay, mEglContext); } if (mEglSurface != EGL_NO_SURFACE) { eglDestroySurface(mEglDisplay, mEglSurface); } if (mEglDisplay != EGL_NO_DISPLAY) { eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglTerminate(mEglDisplay); } ASSERT_EQ(EGL_SUCCESS, eglGetError()); } virtual EGLint const* getConfigAttribs() { static EGLint sDefaultConfigAttribs[] = { EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_DEPTH_SIZE, 16, EGL_STENCIL_SIZE, 8, EGL_NONE }; return sDefaultConfigAttribs; } virtual EGLint const* getContextAttribs() { static EGLint sDefaultContextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; return sDefaultContextAttribs; } virtual EGLint getSurfaceWidth() { return 64; } virtual EGLint getSurfaceHeight() { return 64; } void loadShader(GLenum shaderType, const char* pSource, GLuint* outShader) { GLuint shader = glCreateShader(shaderType); ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); if (shader) { glShaderSource(shader, 1, &pSource, NULL); ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); glCompileShader(shader); ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); GLint compiled = 0; glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); if (!compiled) { GLint infoLen = 0; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen); ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); if (infoLen) { char* buf = (char*) malloc(infoLen); if (buf) { glGetShaderInfoLog(shader, infoLen, NULL, buf); printf("Shader compile log:\n%s\n", buf); free(buf); FAIL(); } } else { char* buf = (char*) malloc(0x1000); if (buf) { glGetShaderInfoLog(shader, 0x1000, NULL, buf); printf("Shader compile log:\n%s\n", buf); free(buf); FAIL(); } } glDeleteShader(shader); shader = 0; } } ASSERT_TRUE(shader != 0); *outShader = shader; } void createProgram(const char* pVertexSource, const char* pFragmentSource, GLuint* outPgm) { GLuint vertexShader, fragmentShader; { SCOPED_TRACE("compiling vertex shader"); loadShader(GL_VERTEX_SHADER, pVertexSource, &vertexShader); if (HasFatalFailure()) { return; } } { SCOPED_TRACE("compiling fragment shader"); loadShader(GL_FRAGMENT_SHADER, pFragmentSource, &fragmentShader); if (HasFatalFailure()) { return; } } GLuint program = glCreateProgram(); ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); if (program) { glAttachShader(program, vertexShader); ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); glAttachShader(program, fragmentShader); ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); glLinkProgram(program); GLint linkStatus = GL_FALSE; glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); if (linkStatus != GL_TRUE) { GLint bufLength = 0; glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength); if (bufLength) { char* buf = (char*) malloc(bufLength); if (buf) { glGetProgramInfoLog(program, bufLength, NULL, buf); printf("Program link log:\n%s\n", buf); free(buf); FAIL(); } } glDeleteProgram(program); program = 0; } } glDeleteShader(vertexShader); glDeleteShader(fragmentShader); ASSERT_TRUE(program != 0); *outPgm = program; } ::testing::AssertionResult checkPixel(int x, int y, int r, int g, int b, int a) { GLubyte pixel[4]; String8 msg; glReadPixels(x, y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); GLenum err = glGetError(); if (err != GL_NO_ERROR) { msg += String8::format("error reading pixel: %#x", err); while ((err = glGetError()) != GL_NO_ERROR) { msg += String8::format(", %#x", err); } fprintf(stderr, "pixel check failure: %s\n", msg.string()); return ::testing::AssertionFailure( ::testing::Message(msg.string())); } if (r >= 0 && GLubyte(r) != pixel[0]) { msg += String8::format("r(%d isn't %d)", pixel[0], r); } if (g >= 0 && GLubyte(g) != pixel[1]) { if (!msg.isEmpty()) { msg += " "; } msg += String8::format("g(%d isn't %d)", pixel[1], g); } if (b >= 0 && GLubyte(b) != pixel[2]) { if (!msg.isEmpty()) { msg += " "; } msg += String8::format("b(%d isn't %d)", pixel[2], b); } if (a >= 0 && GLubyte(a) != pixel[3]) { if (!msg.isEmpty()) { msg += " "; } msg += String8::format("a(%d isn't %d)", pixel[3], a); } if (!msg.isEmpty()) { fprintf(stderr, "pixel check failure: %s\n", msg.string()); return ::testing::AssertionFailure( ::testing::Message(msg.string())); } else { return ::testing::AssertionSuccess(); } } int mDisplaySecs; sp mComposerClient; sp mSurfaceControl; EGLDisplay mEglDisplay; EGLSurface mEglSurface; EGLContext mEglContext; }; // XXX: Code above this point should live elsewhere class SurfaceTextureGLTest : public GLTest { protected: static const GLint TEX_ID = 123; virtual void SetUp() { GLTest::SetUp(); mST = new SurfaceTexture(TEX_ID); mSTC = new SurfaceTextureClient(mST); mANW = mSTC; const char vsrc[] = "attribute vec4 vPosition;\n" "varying vec2 texCoords;\n" "uniform mat4 texMatrix;\n" "void main() {\n" " vec2 vTexCoords = 0.5 * (vPosition.xy + vec2(1.0, 1.0));\n" " texCoords = (texMatrix * vec4(vTexCoords, 0.0, 1.0)).xy;\n" " gl_Position = vPosition;\n" "}\n"; const char fsrc[] = "#extension GL_OES_EGL_image_external : require\n" "precision mediump float;\n" "uniform samplerExternalOES texSampler;\n" "varying vec2 texCoords;\n" "void main() {\n" " gl_FragColor = texture2D(texSampler, texCoords);\n" "}\n"; { SCOPED_TRACE("creating shader program"); createProgram(vsrc, fsrc, &mPgm); if (HasFatalFailure()) { return; } } mPositionHandle = glGetAttribLocation(mPgm, "vPosition"); ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); ASSERT_NE(-1, mPositionHandle); mTexSamplerHandle = glGetUniformLocation(mPgm, "texSampler"); ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); ASSERT_NE(-1, mTexSamplerHandle); mTexMatrixHandle = glGetUniformLocation(mPgm, "texMatrix"); ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); ASSERT_NE(-1, mTexMatrixHandle); } // drawTexture draws the SurfaceTexture over the entire GL viewport. void drawTexture() { const GLfloat triangleVertices[] = { -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, }; glVertexAttribPointer(mPositionHandle, 2, GL_FLOAT, GL_FALSE, 0, triangleVertices); ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); glEnableVertexAttribArray(mPositionHandle); ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); glUseProgram(mPgm); glUniform1i(mTexSamplerHandle, 0); ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); glBindTexture(GL_TEXTURE_EXTERNAL_OES, TEX_ID); ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); GLfloat texMatrix[16]; mST->getTransformMatrix(texMatrix); glUniformMatrix4fv(mTexMatrixHandle, 1, GL_FALSE, texMatrix); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); } sp mST; sp mSTC; sp mANW; GLuint mPgm; GLint mPositionHandle; GLint mTexSamplerHandle; GLint mTexMatrixHandle; }; // Fill a YV12 buffer with a multi-colored checkerboard pattern void fillYV12Buffer(uint8_t* buf, int w, int h, int stride) { const int blockWidth = w > 16 ? w / 16 : 1; const int blockHeight = h > 16 ? h / 16 : 1; const int yuvTexOffsetY = 0; int yuvTexStrideY = stride; int yuvTexOffsetV = yuvTexStrideY * h; int yuvTexStrideV = (yuvTexStrideY/2 + 0xf) & ~0xf; int yuvTexOffsetU = yuvTexOffsetV + yuvTexStrideV * h/2; int yuvTexStrideU = yuvTexStrideV; for (int x = 0; x < w; x++) { for (int y = 0; y < h; y++) { int parityX = (x / blockWidth) & 1; int parityY = (y / blockHeight) & 1; unsigned char intensity = (parityX ^ parityY) ? 63 : 191; buf[yuvTexOffsetY + (y * yuvTexStrideY) + x] = intensity; if (x < w / 2 && y < h / 2) { buf[yuvTexOffsetU + (y * yuvTexStrideU) + x] = intensity; if (x * 2 < w / 2 && y * 2 < h / 2) { buf[yuvTexOffsetV + (y*2 * yuvTexStrideV) + x*2 + 0] = buf[yuvTexOffsetV + (y*2 * yuvTexStrideV) + x*2 + 1] = buf[yuvTexOffsetV + ((y*2+1) * yuvTexStrideV) + x*2 + 0] = buf[yuvTexOffsetV + ((y*2+1) * yuvTexStrideV) + x*2 + 1] = intensity; } } } } } // Fill a YV12 buffer with red outside a given rectangle and green inside it. void fillYV12BufferRect(uint8_t* buf, int w, int h, int stride, const android_native_rect_t& rect) { const int yuvTexOffsetY = 0; int yuvTexStrideY = stride; int yuvTexOffsetV = yuvTexStrideY * h; int yuvTexStrideV = (yuvTexStrideY/2 + 0xf) & ~0xf; int yuvTexOffsetU = yuvTexOffsetV + yuvTexStrideV * h/2; int yuvTexStrideU = yuvTexStrideV; for (int x = 0; x < w; x++) { for (int y = 0; y < h; y++) { bool inside = rect.left <= x && x < rect.right && rect.top <= y && y < rect.bottom; buf[yuvTexOffsetY + (y * yuvTexStrideY) + x] = inside ? 240 : 64; if (x < w / 2 && y < h / 2) { bool inside = rect.left <= 2*x && 2*x < rect.right && rect.top <= 2*y && 2*y < rect.bottom; buf[yuvTexOffsetU + (y * yuvTexStrideU) + x] = 16; buf[yuvTexOffsetV + (y * yuvTexStrideV) + x] = inside ? 16 : 255; } } } } TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledYV12BufferNpot) { const int yuvTexWidth = 64; const int yuvTexHeight = 66; ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(), yuvTexWidth, yuvTexHeight, HAL_PIXEL_FORMAT_YV12)); ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(), GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN)); ANativeWindowBuffer* anb; ASSERT_EQ(NO_ERROR, mANW->dequeueBuffer(mANW.get(), &anb)); ASSERT_TRUE(anb != NULL); sp buf(new GraphicBuffer(anb, false)); ASSERT_EQ(NO_ERROR, mANW->lockBuffer(mANW.get(), buf->getNativeBuffer())); // Fill the buffer with the a checkerboard pattern uint8_t* img = NULL; buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img)); fillYV12Buffer(img, yuvTexWidth, yuvTexHeight, buf->getStride()); buf->unlock(); ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), buf->getNativeBuffer())); mST->updateTexImage(); glClearColor(0.2, 0.2, 0.2, 0.2); glClear(GL_COLOR_BUFFER_BIT); drawTexture(); EXPECT_TRUE(checkPixel( 0, 0, 255, 127, 255, 255)); EXPECT_TRUE(checkPixel(63, 0, 0, 133, 0, 255)); EXPECT_TRUE(checkPixel(63, 63, 0, 133, 0, 255)); EXPECT_TRUE(checkPixel( 0, 63, 255, 127, 255, 255)); EXPECT_TRUE(checkPixel(22, 44, 247, 70, 255, 255)); EXPECT_TRUE(checkPixel(45, 52, 209, 32, 235, 255)); EXPECT_TRUE(checkPixel(52, 51, 100, 255, 73, 255)); EXPECT_TRUE(checkPixel( 7, 31, 155, 0, 118, 255)); EXPECT_TRUE(checkPixel(31, 9, 148, 71, 110, 255)); EXPECT_TRUE(checkPixel(29, 35, 255, 127, 255, 255)); EXPECT_TRUE(checkPixel(36, 22, 155, 29, 0, 255)); } // XXX: This test is disabled because it it currently broken on all devices to // which I have access. Some of the checkPixel calls are not correct because // I just copied them from the npot test above and haven't bothered to figure // out the correct values. TEST_F(SurfaceTextureGLTest, DISABLED_TexturingFromCpuFilledYV12BufferPow2) { const int yuvTexWidth = 64; const int yuvTexHeight = 64; ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(), yuvTexWidth, yuvTexHeight, HAL_PIXEL_FORMAT_YV12)); ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(), GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN)); ANativeWindowBuffer* anb; ASSERT_EQ(NO_ERROR, mANW->dequeueBuffer(mANW.get(), &anb)); ASSERT_TRUE(anb != NULL); sp buf(new GraphicBuffer(anb, false)); ASSERT_EQ(NO_ERROR, mANW->lockBuffer(mANW.get(), buf->getNativeBuffer())); // Fill the buffer with the a checkerboard pattern uint8_t* img = NULL; buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img)); fillYV12Buffer(img, yuvTexWidth, yuvTexHeight, buf->getStride()); buf->unlock(); ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), buf->getNativeBuffer())); mST->updateTexImage(); glClearColor(0.2, 0.2, 0.2, 0.2); glClear(GL_COLOR_BUFFER_BIT); drawTexture(); EXPECT_TRUE(checkPixel( 0, 0, 255, 127, 255, 255)); EXPECT_TRUE(checkPixel(63, 0, 0, 133, 0, 255)); EXPECT_TRUE(checkPixel(63, 63, 0, 133, 0, 255)); EXPECT_TRUE(checkPixel( 0, 63, 255, 127, 255, 255)); EXPECT_TRUE(checkPixel(22, 19, 247, 70, 255, 255)); EXPECT_TRUE(checkPixel(45, 11, 209, 32, 235, 255)); EXPECT_TRUE(checkPixel(52, 12, 100, 255, 73, 255)); EXPECT_TRUE(checkPixel( 7, 32, 155, 0, 118, 255)); EXPECT_TRUE(checkPixel(31, 54, 148, 71, 110, 255)); EXPECT_TRUE(checkPixel(29, 28, 255, 127, 255, 255)); EXPECT_TRUE(checkPixel(36, 41, 155, 29, 0, 255)); } TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledYV12BufferWithCrop) { const int yuvTexWidth = 64; const int yuvTexHeight = 66; ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(), yuvTexWidth, yuvTexHeight, HAL_PIXEL_FORMAT_YV12)); ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(), GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN)); android_native_rect_t crops[] = { {4, 6, 22, 36}, {0, 6, 22, 36}, {4, 0, 22, 36}, {4, 6, yuvTexWidth, 36}, {4, 6, 22, yuvTexHeight}, }; for (int i = 0; i < 5; i++) { const android_native_rect_t& crop(crops[i]); SCOPED_TRACE(String8::format("rect{ l: %d t: %d r: %d b: %d }", crop.left, crop.top, crop.right, crop.bottom).string()); ASSERT_EQ(NO_ERROR, native_window_set_crop(mANW.get(), &crop)); ANativeWindowBuffer* anb; ASSERT_EQ(NO_ERROR, mANW->dequeueBuffer(mANW.get(), &anb)); ASSERT_TRUE(anb != NULL); sp buf(new GraphicBuffer(anb, false)); ASSERT_EQ(NO_ERROR, mANW->lockBuffer(mANW.get(), buf->getNativeBuffer())); uint8_t* img = NULL; buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img)); fillYV12BufferRect(img, yuvTexWidth, yuvTexHeight, buf->getStride(), crop); buf->unlock(); ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), buf->getNativeBuffer())); mST->updateTexImage(); glClearColor(0.2, 0.2, 0.2, 0.2); glClear(GL_COLOR_BUFFER_BIT); drawTexture(); EXPECT_TRUE(checkPixel( 0, 0, 82, 255, 35, 255)); EXPECT_TRUE(checkPixel(63, 0, 82, 255, 35, 255)); EXPECT_TRUE(checkPixel(63, 63, 82, 255, 35, 255)); EXPECT_TRUE(checkPixel( 0, 63, 82, 255, 35, 255)); EXPECT_TRUE(checkPixel(25, 14, 82, 255, 35, 255)); EXPECT_TRUE(checkPixel(35, 31, 82, 255, 35, 255)); EXPECT_TRUE(checkPixel(57, 6, 82, 255, 35, 255)); EXPECT_TRUE(checkPixel( 5, 42, 82, 255, 35, 255)); EXPECT_TRUE(checkPixel(32, 33, 82, 255, 35, 255)); EXPECT_TRUE(checkPixel(16, 26, 82, 255, 35, 255)); EXPECT_TRUE(checkPixel(46, 51, 82, 255, 35, 255)); } } /* * This test is for testing GL -> GL texture streaming via SurfaceTexture. It * contains functionality to create a producer thread that will perform GL * rendering to an ANativeWindow that feeds frames to a SurfaceTexture. * Additionally it supports interlocking the producer and consumer threads so * that a specific sequence of calls can be deterministically created by the * test. * * The intended usage is as follows: * * TEST_F(...) { * class PT : public ProducerThread { * virtual void render() { * ... * swapBuffers(); * } * }; * * runProducerThread(new PT()); * * // The order of these calls will vary from test to test and may include * // multiple frames and additional operations (e.g. GL rendering from the * // texture). * fc->waitForFrame(); * mST->updateTexImage(); * fc->finishFrame(); * } * */ class SurfaceTextureGLToGLTest : public SurfaceTextureGLTest { protected: // ProducerThread is an abstract base class to simplify the creation of // OpenGL ES frame producer threads. class ProducerThread : public Thread { public: virtual ~ProducerThread() { } void setEglObjects(EGLDisplay producerEglDisplay, EGLSurface producerEglSurface, EGLContext producerEglContext) { mProducerEglDisplay = producerEglDisplay; mProducerEglSurface = producerEglSurface; mProducerEglContext = producerEglContext; } virtual bool threadLoop() { eglMakeCurrent(mProducerEglDisplay, mProducerEglSurface, mProducerEglSurface, mProducerEglContext); render(); eglMakeCurrent(mProducerEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); return false; } protected: virtual void render() = 0; void swapBuffers() { eglSwapBuffers(mProducerEglDisplay, mProducerEglSurface); } EGLDisplay mProducerEglDisplay; EGLSurface mProducerEglSurface; EGLContext mProducerEglContext; }; // FrameCondition is a utility class for interlocking between the producer // and consumer threads. The FrameCondition object should be created and // destroyed in the consumer thread only. The consumer thread should set // the FrameCondition as the FrameAvailableListener of the SurfaceTexture, // and should call both waitForFrame and finishFrame once for each expected // frame. // // This interlocking relies on the fact that onFrameAvailable gets called // synchronously from SurfaceTexture::queueBuffer. class FrameCondition : public SurfaceTexture::FrameAvailableListener { public: // waitForFrame waits for the next frame to arrive. This should be // called from the consumer thread once for every frame expected by the // test. void waitForFrame() { LOGV("+waitForFrame"); Mutex::Autolock lock(mMutex); status_t result = mFrameAvailableCondition.wait(mMutex); LOGV("-waitForFrame"); } // Allow the producer to return from its swapBuffers call and continue // on to produce the next frame. This should be called by the consumer // thread once for every frame expected by the test. void finishFrame() { LOGV("+finishFrame"); Mutex::Autolock lock(mMutex); mFrameFinishCondition.signal(); LOGV("-finishFrame"); } // This should be called by SurfaceTexture on the producer thread. virtual void onFrameAvailable() { LOGV("+onFrameAvailable"); Mutex::Autolock lock(mMutex); mFrameAvailableCondition.signal(); mFrameFinishCondition.wait(mMutex); LOGV("-onFrameAvailable"); } protected: Mutex mMutex; Condition mFrameAvailableCondition; Condition mFrameFinishCondition; }; SurfaceTextureGLToGLTest(): mProducerEglSurface(EGL_NO_SURFACE), mProducerEglContext(EGL_NO_CONTEXT) { } virtual void SetUp() { SurfaceTextureGLTest::SetUp(); EGLConfig myConfig = {0}; EGLint numConfigs = 0; EXPECT_TRUE(eglChooseConfig(mEglDisplay, getConfigAttribs(), &myConfig, 1, &numConfigs)); ASSERT_EQ(EGL_SUCCESS, eglGetError()); mProducerEglSurface = eglCreateWindowSurface(mEglDisplay, myConfig, mANW.get(), NULL); ASSERT_EQ(EGL_SUCCESS, eglGetError()); ASSERT_NE(EGL_NO_SURFACE, mProducerEglSurface); mProducerEglContext = eglCreateContext(mEglDisplay, myConfig, EGL_NO_CONTEXT, getContextAttribs()); ASSERT_EQ(EGL_SUCCESS, eglGetError()); ASSERT_NE(EGL_NO_CONTEXT, mProducerEglContext); mFC = new FrameCondition(); mST->setFrameAvailableListener(mFC); } virtual void TearDown() { if (mProducerThread != NULL) { mProducerThread->requestExitAndWait(); } if (mProducerEglContext != EGL_NO_CONTEXT) { eglDestroyContext(mEglDisplay, mProducerEglContext); } if (mProducerEglSurface != EGL_NO_SURFACE) { eglDestroySurface(mEglDisplay, mProducerEglSurface); } mProducerThread.clear(); mFC.clear(); } void runProducerThread(const sp producerThread) { ASSERT_TRUE(mProducerThread == NULL); mProducerThread = producerThread; producerThread->setEglObjects(mEglDisplay, mProducerEglSurface, mProducerEglContext); producerThread->run(); } EGLSurface mProducerEglSurface; EGLContext mProducerEglContext; sp mProducerThread; sp mFC; }; // XXX: This test is disabled because it causes hangs on some devices. TEST_F(SurfaceTextureGLToGLTest, DISABLED_UpdateTexImageBeforeFrameFinishedWorks) { class PT : public ProducerThread { virtual void render() { glClearColor(0.0f, 1.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); swapBuffers(); } }; runProducerThread(new PT()); mFC->waitForFrame(); mST->updateTexImage(); mFC->finishFrame(); // TODO: Add frame verification once RGB TEX_EXTERNAL_OES is supported! } TEST_F(SurfaceTextureGLToGLTest, UpdateTexImageAfterFrameFinishedWorks) { class PT : public ProducerThread { virtual void render() { glClearColor(0.0f, 1.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); swapBuffers(); } }; runProducerThread(new PT()); mFC->waitForFrame(); mFC->finishFrame(); mST->updateTexImage(); // TODO: Add frame verification once RGB TEX_EXTERNAL_OES is supported! } // XXX: This test is disabled because it causes hangs on some devices. TEST_F(SurfaceTextureGLToGLTest, DISABLED_RepeatedUpdateTexImageBeforeFrameFinishedWorks) { enum { NUM_ITERATIONS = 1024 }; class PT : public ProducerThread { virtual void render() { for (int i = 0; i < NUM_ITERATIONS; i++) { glClearColor(0.0f, 1.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); LOGV("+swapBuffers"); swapBuffers(); LOGV("-swapBuffers"); } } }; runProducerThread(new PT()); for (int i = 0; i < NUM_ITERATIONS; i++) { mFC->waitForFrame(); LOGV("+updateTexImage"); mST->updateTexImage(); LOGV("-updateTexImage"); mFC->finishFrame(); // TODO: Add frame verification once RGB TEX_EXTERNAL_OES is supported! } } // XXX: This test is disabled because it causes hangs on some devices. TEST_F(SurfaceTextureGLToGLTest, DISABLED_RepeatedUpdateTexImageAfterFrameFinishedWorks) { enum { NUM_ITERATIONS = 1024 }; class PT : public ProducerThread { virtual void render() { for (int i = 0; i < NUM_ITERATIONS; i++) { glClearColor(0.0f, 1.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); LOGV("+swapBuffers"); swapBuffers(); LOGV("-swapBuffers"); } } }; runProducerThread(new PT()); for (int i = 0; i < NUM_ITERATIONS; i++) { mFC->waitForFrame(); mFC->finishFrame(); LOGV("+updateTexImage"); mST->updateTexImage(); LOGV("-updateTexImage"); // TODO: Add frame verification once RGB TEX_EXTERNAL_OES is supported! } } } // namespace android