diff options
author | 2012-04-01 15:41:24 -0700 | |
---|---|---|
committer | 2012-04-01 15:41:24 -0700 | |
commit | 0e1080f887bcd80c75910cabe7b2eca224bf739c (patch) | |
tree | 4bd4911cdeba79ed4d1d092ee63bbafdfffe492c | |
parent | 851ef8f1bfbb164d61b1528a529a464f0a60dbaf (diff) | |
parent | 74bed55fff0132be319bcd1703970516ae28b3a9 (diff) |
Merge "SurfaceTexture: add context attach & detach"
-rw-r--r-- | include/gui/SurfaceTexture.h | 70 | ||||
-rw-r--r-- | libs/gui/SurfaceTexture.cpp | 234 | ||||
-rw-r--r-- | libs/gui/tests/SurfaceTexture_test.cpp | 773 |
3 files changed, 857 insertions, 220 deletions
diff --git a/include/gui/SurfaceTexture.h b/include/gui/SurfaceTexture.h index cd490f2a7a..496cfba035 100644 --- a/include/gui/SurfaceTexture.h +++ b/include/gui/SurfaceTexture.h @@ -55,8 +55,7 @@ public: }; // SurfaceTexture constructs a new SurfaceTexture object. tex indicates the - // name of the OpenGL ES texture to which images are to be streamed. This - // texture name cannot be changed once the SurfaceTexture is created. + // name of the OpenGL ES texture to which images are to be streamed. // allowSynchronousMode specifies whether or not synchronous mode can be // enabled. texTarget specifies the OpenGL ES texture target to which the // texture will be bound in updateTexImage. useFenceSync specifies whether @@ -64,6 +63,21 @@ public: // is enabled at compile-time. A custom bufferQueue can be specified // if behavior for queue/dequeue/connect etc needs to be customized. // Otherwise a default BufferQueue will be created and used. + // + // For legacy reasons, the SurfaceTexture is created in a state where it is + // considered attached to an OpenGL ES context for the purposes of the + // attachToContext and detachFromContext methods. However, despite being + // considered "attached" to a context, the specific OpenGL ES context + // doesn't get latched until the first call to updateTexImage. After that + // point, all calls to updateTexImage must be made with the same OpenGL ES + // context current. + // + // A SurfaceTexture may be detached from one OpenGL ES context and then + // attached to a different context using the detachFromContext and + // attachToContext methods, respectively. The intention of these methods is + // purely to allow a SurfaceTexture to be transferred from one consumer + // context to another. If such a transfer is not needed there is no + // requirement that either of these methods be called. SurfaceTexture(GLuint tex, bool allowSynchronousMode = true, GLenum texTarget = GL_TEXTURE_EXTERNAL_OES, bool useFenceSync = true, const sp<BufferQueue> &bufferQueue = 0); @@ -175,8 +189,37 @@ public: virtual status_t connect(int api, uint32_t* outWidth, uint32_t* outHeight, uint32_t* outTransform); + // getBufferQueue returns the BufferQueue object to which this + // SurfaceTexture is connected. sp<BufferQueue> getBufferQueue() const; + // detachFromContext detaches the SurfaceTexture from the calling thread's + // current OpenGL ES context. This context must be the same as the context + // that was current for previous calls to updateTexImage. + // + // Detaching a SurfaceTexture from an OpenGL ES context will result in the + // deletion of the OpenGL ES texture object into which the images were being + // streamed. After a SurfaceTexture has been detached from the OpenGL ES + // context calls to updateTexImage will fail returning INVALID_OPERATION + // until the SurfaceTexture is attached to a new OpenGL ES context using the + // attachToContext method. + status_t detachFromContext(); + + // attachToContext attaches a SurfaceTexture that is currently in the + // 'detached' state to the current OpenGL ES context. A SurfaceTexture is + // in the 'detached' state iff detachFromContext has successfully been + // called and no calls to attachToContext have succeeded since the last + // detachFromContext call. Calls to attachToContext made on a + // SurfaceTexture that is not in the 'detached' state will result in an + // INVALID_OPERATION error. + // + // The tex argument specifies the OpenGL ES texture object name in the + // new context into which the image contents will be streamed. A successful + // call to attachToContext will result in this texture object being bound to + // the texture target and populated with the image contents that were + // current at the time of the last call to detachFromContext. + status_t attachToContext(GLuint tex); + // dump our state in a String virtual void dump(String8& result) const; virtual void dump(String8& result, const char* prefix, char* buffer, size_t SIZE) const; @@ -209,6 +252,12 @@ private: // to compute this matrix and stores it in mCurrentTransformMatrix. void computeCurrentTransformMatrix(); + // syncForReleaseLocked performs the synchronization needed to release the + // current slot from an OpenGL ES context. If needed it will set the + // current slot's fence to guard against a producer accessing the buffer + // before the outstanding accesses have completed. + status_t syncForReleaseLocked(EGLDisplay dpy); + // mCurrentTextureBuf is the graphic buffer of the current texture. It's // possible that this buffer is not associated with any buffer slot, so we // must track it separately in order to support the getCurrentBuffer method. @@ -237,8 +286,8 @@ private: // mTexName is the name of the OpenGL texture to which streamed images will // be bound when updateTexImage is called. It is set at construction time - // changed with a call to setTexName. - const GLuint mTexName; + // and can be changed with a call to attachToContext. + GLuint mTexName; // mUseFenceSync indicates whether creation of the EGL_KHR_fence_sync // extension should be used to prevent buffers from being dequeued before @@ -277,13 +326,14 @@ private: // mEglDisplay is the EGLDisplay with which this SurfaceTexture is currently // associated. It is intialized to EGL_NO_DISPLAY and gets set to the - // current display when updateTexImage is called for the first time. + // current display when updateTexImage is called for the first time and when + // attachToContext is called. EGLDisplay mEglDisplay; // mEglContext is the OpenGL ES context with which this SurfaceTexture is // currently associated. It is initialized to EGL_NO_CONTEXT and gets set // to the current GL context when updateTexImage is called for the first - // time. + // time and when attachToContext is called. EGLContext mEglContext; // mEGLSlots stores the buffers that have been allocated by the BufferQueue @@ -323,6 +373,14 @@ private: // if none is supplied sp<BufferQueue> mBufferQueue; + // mAttached indicates whether the SurfaceTexture is currently attached to + // an OpenGL ES context. For legacy reasons, this is initialized to true, + // indicating that the SurfaceTexture is considered to be attached to + // whatever context is current at the time of the first updateTexImage call. + // It is set to false by detachFromContext, and then set to true again by + // attachToContext. + bool mAttached; + // mMutex is the mutex used to prevent concurrent access to the member // variables of SurfaceTexture objects. It must be locked whenever the // member variables are accessed. diff --git a/libs/gui/SurfaceTexture.cpp b/libs/gui/SurfaceTexture.cpp index 07248f63df..18c86fab5e 100644 --- a/libs/gui/SurfaceTexture.cpp +++ b/libs/gui/SurfaceTexture.cpp @@ -118,7 +118,8 @@ SurfaceTexture::SurfaceTexture(GLuint tex, bool allowSynchronousMode, mEglDisplay(EGL_NO_DISPLAY), mEglContext(EGL_NO_CONTEXT), mAbandoned(false), - mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT) + mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT), + mAttached(true) { // Choose a name using the PID and a process-unique ID. mName = String8::format("unnamed-%d-%d", getpid(), createProcessUniqueId()); @@ -176,21 +177,29 @@ status_t SurfaceTexture::updateTexImage() { Mutex::Autolock lock(mMutex); if (mAbandoned) { - ST_LOGE("calling updateTexImage() on an abandoned SurfaceTexture"); + ST_LOGE("updateTexImage: SurfaceTexture is abandoned!"); return NO_INIT; } + if (!mAttached) { + ST_LOGE("updateTexImage: SurfaceTexture is not attached to an OpenGL " + "ES context"); + return INVALID_OPERATION; + } + EGLDisplay dpy = eglGetCurrentDisplay(); EGLContext ctx = eglGetCurrentContext(); - if (mEglDisplay != dpy && mEglDisplay != EGL_NO_DISPLAY) { + if ((mEglDisplay != dpy && mEglDisplay != EGL_NO_DISPLAY) || + dpy == EGL_NO_DISPLAY) { ST_LOGE("updateTexImage: invalid current EGLDisplay"); - return -EINVAL; + return INVALID_OPERATION; } - if (mEglContext != ctx && mEglContext != EGL_NO_CONTEXT) { + if ((mEglContext != ctx && mEglContext != EGL_NO_CONTEXT) || + ctx == EGL_NO_CONTEXT) { ST_LOGE("updateTexImage: invalid current EGLContext"); - return -EINVAL; + return INVALID_OPERATION; } mEglDisplay = dpy; @@ -216,7 +225,7 @@ status_t SurfaceTexture::updateTexImage() { EGLImageKHR image = mEGLSlots[buf].mEglImage; if (image == EGL_NO_IMAGE_KHR) { if (item.mGraphicBuffer == 0) { - ST_LOGE("buffer at slot %d is null", buf); + ST_LOGE("updateTexImage: buffer at slot %d is null", buf); return BAD_VALUE; } image = createImage(dpy, item.mGraphicBuffer); @@ -224,7 +233,7 @@ status_t SurfaceTexture::updateTexImage() { if (image == EGL_NO_IMAGE_KHR) { // NOTE: if dpy was invalid, createImage() is guaranteed to // fail. so we'd end up here. - return -EINVAL; + return UNKNOWN_ERROR; } } @@ -236,31 +245,23 @@ status_t SurfaceTexture::updateTexImage() { glBindTexture(mTexTarget, mTexName); glEGLImageTargetTexture2DOES(mTexTarget, (GLeglImageOES)image); - bool failed = false; + status_t err = OK; while ((error = glGetError()) != GL_NO_ERROR) { - ST_LOGE("error binding external texture image %p (slot %d): %#04x", - image, buf, error); - failed = true; + ST_LOGE("updateTexImage: error binding external texture image %p " + "(slot %d): %#04x", image, buf, error); + err = UNKNOWN_ERROR; } - if (failed) { - mBufferQueue->releaseBuffer(buf, dpy, mEGLSlots[buf].mFence); - return -EINVAL; + + if (err == OK) { + err = syncForReleaseLocked(dpy); } - if (mCurrentTexture != BufferQueue::INVALID_BUFFER_SLOT) { - if (mUseFenceSync) { - EGLSyncKHR fence = eglCreateSyncKHR(dpy, EGL_SYNC_FENCE_KHR, - NULL); - if (fence == EGL_NO_SYNC_KHR) { - ALOGE("updateTexImage: error creating fence: %#x", - eglGetError()); - mBufferQueue->releaseBuffer(buf, dpy, - mEGLSlots[buf].mFence); - return -EINVAL; - } - glFlush(); - mEGLSlots[mCurrentTexture].mFence = fence; - } + if (err != OK) { + // Release the buffer we just acquired. It's not safe to + // release the old buffer, so instead we just drop the new frame. + mBufferQueue->releaseBuffer(buf, dpy, mEGLSlots[buf].mFence); + mEGLSlots[buf].mFence = EGL_NO_SYNC_KHR; + return err; } ST_LOGV("updateTexImage: (slot=%d buf=%p) -> (slot=%d buf=%p)", @@ -268,9 +269,12 @@ status_t SurfaceTexture::updateTexImage() { mCurrentTextureBuf != NULL ? mCurrentTextureBuf->handle : 0, buf, item.mGraphicBuffer != NULL ? item.mGraphicBuffer->handle : 0); - // release old buffer - mBufferQueue->releaseBuffer(mCurrentTexture, dpy, - mEGLSlots[mCurrentTexture].mFence); + // Release the old buffer + if (mCurrentTexture != BufferQueue::INVALID_BUFFER_SLOT) { + mBufferQueue->releaseBuffer(mCurrentTexture, dpy, + mEGLSlots[mCurrentTexture].mFence); + mEGLSlots[mCurrentTexture].mFence = EGL_NO_SYNC_KHR; + } // Update the SurfaceTexture state. mCurrentTexture = buf; @@ -280,10 +284,6 @@ status_t SurfaceTexture::updateTexImage() { mCurrentScalingMode = item.mScalingMode; mCurrentTimestamp = item.mTimestamp; computeCurrentTransformMatrix(); - - // Now that we've passed the point at which failures can happen, - // it's safe to remove the buffer from the front of the queue. - } else { // We always bind the texture even if we don't update its contents. glBindTexture(mTexTarget, mTexName); @@ -292,6 +292,168 @@ status_t SurfaceTexture::updateTexImage() { return OK; } +status_t SurfaceTexture::detachFromContext() { + ATRACE_CALL(); + ST_LOGV("detachFromContext"); + Mutex::Autolock lock(mMutex); + + if (mAbandoned) { + ST_LOGE("detachFromContext: abandoned SurfaceTexture"); + return NO_INIT; + } + + if (!mAttached) { + ST_LOGE("detachFromContext: SurfaceTexture is not attached to a " + "context"); + return INVALID_OPERATION; + } + + EGLDisplay dpy = eglGetCurrentDisplay(); + EGLContext ctx = eglGetCurrentContext(); + + if (mEglDisplay != dpy && mEglDisplay != EGL_NO_DISPLAY) { + ST_LOGE("detachFromContext: invalid current EGLDisplay"); + return INVALID_OPERATION; + } + + if (mEglContext != ctx && mEglContext != EGL_NO_CONTEXT) { + ST_LOGE("detachFromContext: invalid current EGLContext"); + return INVALID_OPERATION; + } + + if (dpy != EGL_NO_DISPLAY && ctx != EGL_NO_CONTEXT) { + status_t err = syncForReleaseLocked(dpy); + if (err != OK) { + return err; + } + + glDeleteTextures(1, &mTexName); + } + + mEglDisplay = EGL_NO_DISPLAY; + mEglContext = EGL_NO_CONTEXT; + mAttached = false; + + return OK; +} + +status_t SurfaceTexture::attachToContext(GLuint tex) { + ATRACE_CALL(); + ST_LOGV("attachToContext"); + Mutex::Autolock lock(mMutex); + + if (mAbandoned) { + ST_LOGE("attachToContext: abandoned SurfaceTexture"); + return NO_INIT; + } + + if (mAttached) { + ST_LOGE("attachToContext: SurfaceTexture is already attached to a " + "context"); + return INVALID_OPERATION; + } + + EGLDisplay dpy = eglGetCurrentDisplay(); + EGLContext ctx = eglGetCurrentContext(); + + if (dpy == EGL_NO_DISPLAY) { + ST_LOGE("attachToContext: invalid current EGLDisplay"); + return INVALID_OPERATION; + } + + if (ctx == EGL_NO_CONTEXT) { + ST_LOGE("attachToContext: invalid current EGLContext"); + return INVALID_OPERATION; + } + + // We need to bind the texture regardless of whether there's a current + // buffer. + glBindTexture(mTexTarget, tex); + + if (mCurrentTextureBuf != NULL) { + // If the current buffer is no longer associated with a slot, then it + // doesn't have an EGLImage. In that case we create one now, but we also + // destroy it once we've used it to attach the buffer to the OpenGL ES + // texture. + bool imageNeedsDestroy = false; + EGLImageKHR image = EGL_NO_IMAGE_KHR; + if (mCurrentTexture != BufferQueue::INVALID_BUFFER_SLOT) { + image = mEGLSlots[mCurrentTexture].mEglImage; + imageNeedsDestroy = false; + } else { + image = createImage(dpy, mCurrentTextureBuf); + if (image == EGL_NO_IMAGE_KHR) { + return UNKNOWN_ERROR; + } + imageNeedsDestroy = true; + } + + // Attach the current buffer to the GL texture. + glEGLImageTargetTexture2DOES(mTexTarget, (GLeglImageOES)image); + + GLint error; + status_t err = OK; + while ((error = glGetError()) != GL_NO_ERROR) { + ST_LOGE("attachToContext: error binding external texture image %p " + "(slot %d): %#04x", image, mCurrentTexture, error); + err = UNKNOWN_ERROR; + } + + if (imageNeedsDestroy) { + eglDestroyImageKHR(dpy, image); + } + + if (err != OK) { + return err; + } + } + + mEglDisplay = dpy; + mEglContext = ctx; + mTexName = tex; + mAttached = true; + + return OK; +} + +status_t SurfaceTexture::syncForReleaseLocked(EGLDisplay dpy) { + ST_LOGV("syncForReleaseLocked"); + + if (mUseFenceSync && mCurrentTexture != BufferQueue::INVALID_BUFFER_SLOT) { + EGLSyncKHR fence = mEGLSlots[mCurrentTexture].mFence; + if (fence != EGL_NO_SYNC_KHR) { + // There is already a fence for the current slot. We need to wait + // on that before replacing it with another fence to ensure that all + // outstanding buffer accesses have completed before the producer + // accesses it. + EGLint result = eglClientWaitSyncKHR(dpy, fence, 0, 1000000000); + if (result == EGL_FALSE) { + ST_LOGE("syncForReleaseLocked: error waiting for previous " + "fence: %#x", eglGetError()); + return UNKNOWN_ERROR; + } else if (result == EGL_TIMEOUT_EXPIRED_KHR) { + ST_LOGE("syncForReleaseLocked: timeout waiting for previous " + "fence"); + return TIMED_OUT; + } + eglDestroySyncKHR(dpy, fence); + } + + // Create a fence for the outstanding accesses in the current OpenGL ES + // context. + fence = eglCreateSyncKHR(dpy, EGL_SYNC_FENCE_KHR, NULL); + if (fence == EGL_NO_SYNC_KHR) { + ST_LOGE("syncForReleaseLocked: error creating fence: %#x", + eglGetError()); + return UNKNOWN_ERROR; + } + glFlush(); + mEGLSlots[mCurrentTexture].mFence = fence; + } + + return OK; +} + bool SurfaceTexture::isExternalFormat(uint32_t format) { switch (format) { diff --git a/libs/gui/tests/SurfaceTexture_test.cpp b/libs/gui/tests/SurfaceTexture_test.cpp index d6357ca9f2..bf347c6081 100644 --- a/libs/gui/tests/SurfaceTexture_test.cpp +++ b/libs/gui/tests/SurfaceTexture_test.cpp @@ -191,100 +191,6 @@ protected: return 512; } - 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; - } - - static int abs(int value) { - return value > 0 ? value : -value; - } - ::testing::AssertionResult checkPixel(int x, int y, int r, int g, int b, int a, int tolerance=2) { GLubyte pixel[4]; @@ -340,6 +246,98 @@ protected: EGLConfig mGlConfig; }; +static 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; +} + +static void createProgram(const char* pVertexSource, + const char* pFragmentSource, GLuint* outPgm) { + GLuint vertexShader, fragmentShader; + { + SCOPED_TRACE("compiling vertex shader"); + ASSERT_NO_FATAL_FAILURE(loadShader(GL_VERTEX_SHADER, pVertexSource, + &vertexShader)); + } + { + SCOPED_TRACE("compiling fragment shader"); + ASSERT_NO_FATAL_FAILURE(loadShader(GL_FRAGMENT_SHADER, pFragmentSource, + &fragmentShader)); + } + + 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; +} + +static int abs(int value) { + return value > 0 ? value : -value; +} + + // XXX: Code above this point should live elsewhere class SurfaceTextureGLTest : public GLTest { @@ -351,43 +349,8 @@ protected: 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); + mTextureRenderer = new TextureRenderer(TEX_ID, mST); + ASSERT_NO_FATAL_FAILURE(mTextureRenderer->SetUp()); } virtual void TearDown() { @@ -397,50 +360,106 @@ protected: GLTest::TearDown(); } - // 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()); + mTextureRenderer->drawTexture(); + } - 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()); + class TextureRenderer: public RefBase { + public: + TextureRenderer(GLuint texName, const sp<SurfaceTexture>& st): + mTexName(texName), + mST(st) { + } - // XXX: These calls are not needed for GL_TEXTURE_EXTERNAL_OES as - // they're setting the defautls for that target, but when hacking things - // to use GL_TEXTURE_2D they are needed to achieve the same behavior. - glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, - GL_LINEAR); - ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); - glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, - GL_LINEAR); - ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); - glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, - GL_CLAMP_TO_EDGE); - ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); - glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, - GL_CLAMP_TO_EDGE); - ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + void SetUp() { + 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"); + ASSERT_NO_FATAL_FAILURE(createProgram(vsrc, fsrc, &mPgm)); + } - GLfloat texMatrix[16]; - mST->getTransformMatrix(texMatrix); - glUniformMatrix4fv(mTexMatrixHandle, 1, GL_FALSE, texMatrix); + 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); + } - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); - } + // 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, mTexName); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + + // XXX: These calls are not needed for GL_TEXTURE_EXTERNAL_OES as + // they're setting the defautls for that target, but when hacking + // things to use GL_TEXTURE_2D they are needed to achieve the same + // behavior. + glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, + GL_LINEAR); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, + GL_LINEAR); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, + GL_CLAMP_TO_EDGE); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, + GL_CLAMP_TO_EDGE); + 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()); + } + + GLuint mTexName; + sp<SurfaceTexture> mST; + GLuint mPgm; + GLint mPositionHandle; + GLint mTexSamplerHandle; + GLint mTexMatrixHandle; + }; class FrameWaiter : public SurfaceTexture::FrameAvailableListener { public: @@ -470,11 +489,7 @@ protected: sp<SurfaceTexture> mST; sp<SurfaceTextureClient> mSTC; sp<ANativeWindow> mANW; - - GLuint mPgm; - GLint mPositionHandle; - GLint mTexSamplerHandle; - GLint mTexMatrixHandle; + sp<TextureRenderer> mTextureRenderer; }; // Fill a YV12 buffer with a multi-colored checkerboard pattern @@ -1735,6 +1750,9 @@ TEST_F(SurfaceTextureFBOTest, BlitFromCpuFilledBufferToFbo) { class SurfaceTextureMultiContextGLTest : public SurfaceTextureGLTest { protected: + enum { SECOND_TEX_ID = 123 }; + enum { THIRD_TEX_ID = 456 }; + SurfaceTextureMultiContextGLTest(): mSecondEglContext(EGL_NO_CONTEXT) { } @@ -1742,13 +1760,39 @@ protected: virtual void SetUp() { SurfaceTextureGLTest::SetUp(); + // Set up the secondary context and texture renderer. mSecondEglContext = eglCreateContext(mEglDisplay, mGlConfig, EGL_NO_CONTEXT, getContextAttribs()); ASSERT_EQ(EGL_SUCCESS, eglGetError()); ASSERT_NE(EGL_NO_CONTEXT, mSecondEglContext); + + ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mSecondEglContext)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + mSecondTextureRenderer = new TextureRenderer(SECOND_TEX_ID, mST); + ASSERT_NO_FATAL_FAILURE(mSecondTextureRenderer->SetUp()); + + // Set up the tertiary context and texture renderer. + mThirdEglContext = eglCreateContext(mEglDisplay, mGlConfig, + EGL_NO_CONTEXT, getContextAttribs()); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + ASSERT_NE(EGL_NO_CONTEXT, mThirdEglContext); + + ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mThirdEglContext)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + mThirdTextureRenderer = new TextureRenderer(THIRD_TEX_ID, mST); + ASSERT_NO_FATAL_FAILURE(mThirdTextureRenderer->SetUp()); + + // Switch back to the primary context to start the tests. + ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mEglContext)); } virtual void TearDown() { + if (mThirdEglContext != EGL_NO_CONTEXT) { + eglDestroyContext(mEglDisplay, mThirdEglContext); + } if (mSecondEglContext != EGL_NO_CONTEXT) { eglDestroyContext(mEglDisplay, mSecondEglContext); } @@ -1756,6 +1800,10 @@ protected: } EGLContext mSecondEglContext; + sp<TextureRenderer> mSecondTextureRenderer; + + EGLContext mThirdEglContext; + sp<TextureRenderer> mThirdTextureRenderer; }; TEST_F(SurfaceTextureMultiContextGLTest, UpdateFromMultipleContextsFails) { @@ -1765,13 +1813,382 @@ TEST_F(SurfaceTextureMultiContextGLTest, UpdateFromMultipleContextsFails) { ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); // Latch the texture contents on the primary context. - mST->updateTexImage(); + fw->waitForFrame(); + ASSERT_EQ(OK, mST->updateTexImage()); // Attempt to latch the texture on the secondary context. - EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mSecondEglContext)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + ASSERT_EQ(INVALID_OPERATION, mST->updateTexImage()); +} + +TEST_F(SurfaceTextureMultiContextGLTest, DetachFromContextSucceeds) { + sp<FrameWaiter> fw(new FrameWaiter); + mST->setFrameAvailableListener(fw); + + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + + // Latch the texture contents on the primary context. + fw->waitForFrame(); + ASSERT_EQ(OK, mST->updateTexImage()); + + // Detach from the primary context. + ASSERT_EQ(OK, mST->detachFromContext()); + + // Check that the GL texture was deleted. + EXPECT_EQ(GL_FALSE, glIsTexture(TEX_ID)); +} + +TEST_F(SurfaceTextureMultiContextGLTest, + DetachFromContextSucceedsAfterProducerDisconnect) { + sp<FrameWaiter> fw(new FrameWaiter); + mST->setFrameAvailableListener(fw); + + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + + // Latch the texture contents on the primary context. + fw->waitForFrame(); + ASSERT_EQ(OK, mST->updateTexImage()); + + // Detach from the primary context. + native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_CPU); + ASSERT_EQ(OK, mST->detachFromContext()); + + // Check that the GL texture was deleted. + EXPECT_EQ(GL_FALSE, glIsTexture(TEX_ID)); +} + +TEST_F(SurfaceTextureMultiContextGLTest, DetachFromContextFailsWhenAbandoned) { + sp<FrameWaiter> fw(new FrameWaiter); + mST->setFrameAvailableListener(fw); + + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + + // Latch the texture contents on the primary context. + fw->waitForFrame(); + ASSERT_EQ(OK, mST->updateTexImage()); + + // Attempt to detach from the primary context. + mST->abandon(); + ASSERT_EQ(NO_INIT, mST->detachFromContext()); +} + +TEST_F(SurfaceTextureMultiContextGLTest, DetachFromContextFailsWhenDetached) { + sp<FrameWaiter> fw(new FrameWaiter); + mST->setFrameAvailableListener(fw); + + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + + // Latch the texture contents on the primary context. + fw->waitForFrame(); + ASSERT_EQ(OK, mST->updateTexImage()); + + // Detach from the primary context. + ASSERT_EQ(OK, mST->detachFromContext()); + + // Attempt to detach from the primary context again. + ASSERT_EQ(INVALID_OPERATION, mST->detachFromContext()); +} + +TEST_F(SurfaceTextureMultiContextGLTest, DetachFromContextFailsWithNoDisplay) { + sp<FrameWaiter> fw(new FrameWaiter); + mST->setFrameAvailableListener(fw); + + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + + // Latch the texture contents on the primary context. + fw->waitForFrame(); + ASSERT_EQ(OK, mST->updateTexImage()); + + // Make there be no current display. + ASSERT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + // Attempt to detach from the primary context. + ASSERT_EQ(INVALID_OPERATION, mST->detachFromContext()); +} + +TEST_F(SurfaceTextureMultiContextGLTest, DetachFromContextFailsWithNoContext) { + sp<FrameWaiter> fw(new FrameWaiter); + mST->setFrameAvailableListener(fw); + + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + + // Latch the texture contents on the primary context. + fw->waitForFrame(); + ASSERT_EQ(OK, mST->updateTexImage()); + + // Make current context be incorrect. + ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mSecondEglContext)); ASSERT_EQ(EGL_SUCCESS, eglGetError()); - ASSERT_EQ(-EINVAL, mST->updateTexImage()); + + // Attempt to detach from the primary context. + ASSERT_EQ(INVALID_OPERATION, mST->detachFromContext()); +} + +TEST_F(SurfaceTextureMultiContextGLTest, UpdateTexImageFailsWhenDetached) { + sp<FrameWaiter> fw(new FrameWaiter); + mST->setFrameAvailableListener(fw); + + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + + // Detach from the primary context. + ASSERT_EQ(OK, mST->detachFromContext()); + + // Attempt to latch the texture contents on the primary context. + fw->waitForFrame(); + ASSERT_EQ(INVALID_OPERATION, mST->updateTexImage()); +} + +TEST_F(SurfaceTextureMultiContextGLTest, AttachToContextSucceeds) { + sp<FrameWaiter> fw(new FrameWaiter); + mST->setFrameAvailableListener(fw); + + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + + // Latch the texture contents on the primary context. + fw->waitForFrame(); + ASSERT_EQ(OK, mST->updateTexImage()); + + // Detach from the primary context. + ASSERT_EQ(OK, mST->detachFromContext()); + + // Attach to the secondary context. + ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mSecondEglContext)); + ASSERT_EQ(OK, mST->attachToContext(SECOND_TEX_ID)); + + // Verify that the texture object was created and bound. + GLint texBinding = -1; + glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &texBinding); + EXPECT_EQ(SECOND_TEX_ID, texBinding); + + // Try to use the texture from the secondary context. + glClearColor(0.2, 0.2, 0.2, 0.2); + glClear(GL_COLOR_BUFFER_BIT); + glViewport(0, 0, 1, 1); + mSecondTextureRenderer->drawTexture(); + ASSERT_TRUE(checkPixel( 0, 0, 35, 35, 35, 35)); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); +} + +TEST_F(SurfaceTextureMultiContextGLTest, + AttachToContextSucceedsAfterProducerDisconnect) { + sp<FrameWaiter> fw(new FrameWaiter); + mST->setFrameAvailableListener(fw); + + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + + // Latch the texture contents on the primary context. + fw->waitForFrame(); + ASSERT_EQ(OK, mST->updateTexImage()); + + // Detach from the primary context. + native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_CPU); + ASSERT_EQ(OK, mST->detachFromContext()); + + // Attach to the secondary context. + ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mSecondEglContext)); + ASSERT_EQ(OK, mST->attachToContext(SECOND_TEX_ID)); + + // Verify that the texture object was created and bound. + GLint texBinding = -1; + glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &texBinding); + EXPECT_EQ(SECOND_TEX_ID, texBinding); + + // Try to use the texture from the secondary context. + glClearColor(0.2, 0.2, 0.2, 0.2); + glClear(GL_COLOR_BUFFER_BIT); + glViewport(0, 0, 1, 1); + mSecondTextureRenderer->drawTexture(); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + ASSERT_TRUE(checkPixel( 0, 0, 35, 35, 35, 35)); +} + +TEST_F(SurfaceTextureMultiContextGLTest, + AttachToContextSucceedsBeforeUpdateTexImage) { + sp<FrameWaiter> fw(new FrameWaiter); + mST->setFrameAvailableListener(fw); + + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + + // Detach from the primary context. + native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_CPU); + ASSERT_EQ(OK, mST->detachFromContext()); + + // Attach to the secondary context. + ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mSecondEglContext)); + ASSERT_EQ(OK, mST->attachToContext(SECOND_TEX_ID)); + + // Verify that the texture object was created and bound. + GLint texBinding = -1; + glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &texBinding); + EXPECT_EQ(SECOND_TEX_ID, texBinding); + + // Latch the texture contents on the primary context. + fw->waitForFrame(); + ASSERT_EQ(OK, mST->updateTexImage()); + + // Try to use the texture from the secondary context. + glClearColor(0.2, 0.2, 0.2, 0.2); + glClear(GL_COLOR_BUFFER_BIT); + glViewport(0, 0, 1, 1); + mSecondTextureRenderer->drawTexture(); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + ASSERT_TRUE(checkPixel( 0, 0, 35, 35, 35, 35)); +} + +TEST_F(SurfaceTextureMultiContextGLTest, AttachToContextFailsWhenAbandoned) { + sp<FrameWaiter> fw(new FrameWaiter); + mST->setFrameAvailableListener(fw); + + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + + // Latch the texture contents on the primary context. + fw->waitForFrame(); + ASSERT_EQ(OK, mST->updateTexImage()); + + // Detach from the primary context. + ASSERT_EQ(OK, mST->detachFromContext()); + + // Attempt to attach to the secondary context. + mST->abandon(); + + // Attempt to attach to the primary context. + ASSERT_EQ(NO_INIT, mST->attachToContext(SECOND_TEX_ID)); +} + +TEST_F(SurfaceTextureMultiContextGLTest, AttachToContextFailsWhenAttached) { + sp<FrameWaiter> fw(new FrameWaiter); + mST->setFrameAvailableListener(fw); + + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + + // Latch the texture contents on the primary context. + fw->waitForFrame(); + ASSERT_EQ(OK, mST->updateTexImage()); + + // Attempt to attach to the primary context. + ASSERT_EQ(INVALID_OPERATION, mST->attachToContext(SECOND_TEX_ID)); +} + +TEST_F(SurfaceTextureMultiContextGLTest, + AttachToContextFailsWhenAttachedBeforeUpdateTexImage) { + sp<FrameWaiter> fw(new FrameWaiter); + mST->setFrameAvailableListener(fw); + + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + + // Attempt to attach to the primary context. + ASSERT_EQ(INVALID_OPERATION, mST->attachToContext(SECOND_TEX_ID)); +} + +TEST_F(SurfaceTextureMultiContextGLTest, AttachToContextFailsWithNoDisplay) { + sp<FrameWaiter> fw(new FrameWaiter); + mST->setFrameAvailableListener(fw); + + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + + // Latch the texture contents on the primary context. + fw->waitForFrame(); + ASSERT_EQ(OK, mST->updateTexImage()); + + // Detach from the primary context. + ASSERT_EQ(OK, mST->detachFromContext()); + + // Make there be no current display. + ASSERT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + // Attempt to attach with no context current. + ASSERT_EQ(INVALID_OPERATION, mST->attachToContext(SECOND_TEX_ID)); +} + +TEST_F(SurfaceTextureMultiContextGLTest, AttachToContextSucceedsTwice) { + sp<FrameWaiter> fw(new FrameWaiter); + mST->setFrameAvailableListener(fw); + + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + + // Latch the texture contents on the primary context. + fw->waitForFrame(); + ASSERT_EQ(OK, mST->updateTexImage()); + + // Detach from the primary context. + ASSERT_EQ(OK, mST->detachFromContext()); + + // Attach to the secondary context. + ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mSecondEglContext)); + ASSERT_EQ(OK, mST->attachToContext(SECOND_TEX_ID)); + + // Detach from the secondary context. + ASSERT_EQ(OK, mST->detachFromContext()); + + // Attach to the tertiary context. + ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mThirdEglContext)); + ASSERT_EQ(OK, mST->attachToContext(THIRD_TEX_ID)); + + // Verify that the texture object was created and bound. + GLint texBinding = -1; + glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &texBinding); + EXPECT_EQ(THIRD_TEX_ID, texBinding); + + // Try to use the texture from the tertiary context. + glClearColor(0.2, 0.2, 0.2, 0.2); + glClear(GL_COLOR_BUFFER_BIT); + glViewport(0, 0, 1, 1); + mThirdTextureRenderer->drawTexture(); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + ASSERT_TRUE(checkPixel( 0, 0, 35, 35, 35, 35)); +} + +TEST_F(SurfaceTextureMultiContextGLTest, + AttachToContextSucceedsTwiceBeforeUpdateTexImage) { + sp<FrameWaiter> fw(new FrameWaiter); + mST->setFrameAvailableListener(fw); + + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + + // Detach from the primary context. + ASSERT_EQ(OK, mST->detachFromContext()); + + // Attach to the secondary context. + ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mSecondEglContext)); + ASSERT_EQ(OK, mST->attachToContext(SECOND_TEX_ID)); + + // Detach from the secondary context. + ASSERT_EQ(OK, mST->detachFromContext()); + + // Attach to the tertiary context. + ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mThirdEglContext)); + ASSERT_EQ(OK, mST->attachToContext(THIRD_TEX_ID)); + + // Verify that the texture object was created and bound. + GLint texBinding = -1; + glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &texBinding); + EXPECT_EQ(THIRD_TEX_ID, texBinding); + + // Latch the texture contents on the tertiary context. + fw->waitForFrame(); + ASSERT_EQ(OK, mST->updateTexImage()); + + // Try to use the texture from the tertiary context. + glClearColor(0.2, 0.2, 0.2, 0.2); + glClear(GL_COLOR_BUFFER_BIT); + glViewport(0, 0, 1, 1); + mThirdTextureRenderer->drawTexture(); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + ASSERT_TRUE(checkPixel( 0, 0, 35, 35, 35, 35)); } } // namespace android |