Loading include/gui/SurfaceTexture.h +11 −4 Original line number Diff line number Diff line Loading @@ -260,7 +260,6 @@ private: struct EGLSlot { EGLSlot() : mEglImage(EGL_NO_IMAGE_KHR), mEglDisplay(EGL_NO_DISPLAY), mFence(EGL_NO_SYNC_KHR) { } Loading @@ -269,9 +268,6 @@ private: // mEglImage is the EGLImage created from mGraphicBuffer. EGLImageKHR mEglImage; // mEglDisplay is the EGLDisplay used to create mEglImage. EGLDisplay mEglDisplay; // mFence is the EGL sync object that must signal before the buffer // associated with this buffer slot may be dequeued. It is initialized // to EGL_NO_SYNC_KHR when the buffer is created and (optionally, based Loading @@ -279,6 +275,17 @@ private: EGLSyncKHR mFence; }; // 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. 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. EGLContext mEglContext; // mEGLSlots stores the buffers that have been allocated by the BufferQueue // for each buffer slot. It is initialized to null pointers, and gets // filled in with the result of BufferQueue::acquire when the Loading libs/gui/SurfaceTexture.cpp +23 −12 Original line number Diff line number Diff line Loading @@ -115,6 +115,8 @@ SurfaceTexture::SurfaceTexture(GLuint tex, bool allowSynchronousMode, mUseFenceSync(false), #endif mTexTarget(texTarget), mEglDisplay(EGL_NO_DISPLAY), mEglContext(EGL_NO_CONTEXT), mAbandoned(false), mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT) { Loading Loading @@ -178,6 +180,22 @@ status_t SurfaceTexture::updateTexImage() { return NO_INIT; } EGLDisplay dpy = eglGetCurrentDisplay(); EGLContext ctx = eglGetCurrentContext(); if (mEglDisplay != dpy && mEglDisplay != EGL_NO_DISPLAY) { ST_LOGE("updateTexImage: invalid current EGLDisplay"); return -EINVAL; } if (mEglContext != ctx && mEglContext != EGL_NO_CONTEXT) { ST_LOGE("updateTexImage: invalid current EGLContext"); return -EINVAL; } mEglDisplay = dpy; mEglContext = ctx; BufferQueue::BufferItem item; // In asynchronous mode the list is guaranteed to be one buffer Loading @@ -188,17 +206,14 @@ status_t SurfaceTexture::updateTexImage() { if (item.mGraphicBuffer != NULL) { mEGLSlots[buf].mGraphicBuffer = 0; if (mEGLSlots[buf].mEglImage != EGL_NO_IMAGE_KHR) { eglDestroyImageKHR(mEGLSlots[buf].mEglDisplay, mEGLSlots[buf].mEglImage); eglDestroyImageKHR(dpy, mEGLSlots[buf].mEglImage); mEGLSlots[buf].mEglImage = EGL_NO_IMAGE_KHR; mEGLSlots[buf].mEglDisplay = EGL_NO_DISPLAY; } mEGLSlots[buf].mGraphicBuffer = item.mGraphicBuffer; } // Update the GL texture object. EGLImageKHR image = mEGLSlots[buf].mEglImage; EGLDisplay dpy = eglGetCurrentDisplay(); if (image == EGL_NO_IMAGE_KHR) { if (item.mGraphicBuffer == 0) { ST_LOGE("buffer at slot %d is null", buf); Loading @@ -206,7 +221,6 @@ status_t SurfaceTexture::updateTexImage() { } image = createImage(dpy, item.mGraphicBuffer); mEGLSlots[buf].mEglImage = image; mEGLSlots[buf].mEglDisplay = dpy; if (image == EGL_NO_IMAGE_KHR) { // NOTE: if dpy was invalid, createImage() is guaranteed to // fail. so we'd end up here. Loading @@ -229,8 +243,7 @@ status_t SurfaceTexture::updateTexImage() { failed = true; } if (failed) { mBufferQueue->releaseBuffer(buf, mEGLSlots[buf].mEglDisplay, mEGLSlots[buf].mFence); mBufferQueue->releaseBuffer(buf, dpy, mEGLSlots[buf].mFence); return -EINVAL; } Loading @@ -241,7 +254,7 @@ status_t SurfaceTexture::updateTexImage() { if (fence == EGL_NO_SYNC_KHR) { ALOGE("updateTexImage: error creating fence: %#x", eglGetError()); mBufferQueue->releaseBuffer(buf, mEGLSlots[buf].mEglDisplay, mBufferQueue->releaseBuffer(buf, dpy, mEGLSlots[buf].mFence); return -EINVAL; } Loading @@ -256,8 +269,7 @@ status_t SurfaceTexture::updateTexImage() { buf, item.mGraphicBuffer != NULL ? item.mGraphicBuffer->handle : 0); // release old buffer mBufferQueue->releaseBuffer(mCurrentTexture, mEGLSlots[mCurrentTexture].mEglDisplay, mBufferQueue->releaseBuffer(mCurrentTexture, dpy, mEGLSlots[mCurrentTexture].mFence); // Update the SurfaceTexture state. Loading Loading @@ -459,10 +471,9 @@ void SurfaceTexture::freeBufferLocked(int slotIndex) { if (mEGLSlots[slotIndex].mEglImage != EGL_NO_IMAGE_KHR) { EGLImageKHR img = mEGLSlots[slotIndex].mEglImage; if (img != EGL_NO_IMAGE_KHR) { eglDestroyImageKHR(mEGLSlots[slotIndex].mEglDisplay, img); eglDestroyImageKHR(mEglDisplay, img); } mEGLSlots[slotIndex].mEglImage = EGL_NO_IMAGE_KHR; mEGLSlots[slotIndex].mEglDisplay = EGL_NO_DISPLAY; } } Loading libs/gui/tests/SurfaceTexture_test.cpp +66 −34 Original line number Diff line number Diff line Loading @@ -560,6 +560,27 @@ void fillRGBA8BufferSolid(uint8_t* buf, int w, int h, int stride, uint8_t r, } } // Produce a single RGBA8 frame by filling a buffer with a checkerboard pattern // using the CPU. This assumes that the ANativeWindow is already configured to // allow this to be done (e.g. the format is set to RGBA8). // // Calls to this function should be wrapped in an ASSERT_NO_FATAL_FAILURE(). void produceOneRGBA8Frame(const sp<ANativeWindow>& anw) { android_native_buffer_t* anb; ASSERT_EQ(NO_ERROR, anw->dequeueBuffer(anw.get(), &anb)); ASSERT_TRUE(anb != NULL); sp<GraphicBuffer> buf(new GraphicBuffer(anb, false)); ASSERT_EQ(NO_ERROR, anw->lockBuffer(anw.get(), buf->getNativeBuffer())); uint8_t* img = NULL; ASSERT_EQ(NO_ERROR, buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img))); fillRGBA8Buffer(img, buf->getWidth(), buf->getHeight(), buf->getStride()); ASSERT_EQ(NO_ERROR, buf->unlock()); ASSERT_EQ(NO_ERROR, anw->queueBuffer(anw.get(), buf->getNativeBuffer())); } TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledYV12BufferNpot) { const int texWidth = 64; const int texHeight = 66; Loading Loading @@ -873,19 +894,7 @@ TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledRGBABufferNpot) { ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(), GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN)); android_native_buffer_t* anb; ASSERT_EQ(NO_ERROR, mANW->dequeueBuffer(mANW.get(), &anb)); ASSERT_TRUE(anb != NULL); sp<GraphicBuffer> 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)); fillRGBA8Buffer(img, texWidth, texHeight, buf->getStride()); buf->unlock(); ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), buf->getNativeBuffer())); ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); mST->updateTexImage(); Loading Loading @@ -927,19 +936,7 @@ TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledRGBABufferPow2) { ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(), GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN)); android_native_buffer_t* anb; ASSERT_EQ(NO_ERROR, mANW->dequeueBuffer(mANW.get(), &anb)); ASSERT_TRUE(anb != NULL); sp<GraphicBuffer> 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)); fillRGBA8Buffer(img, texWidth, texHeight, buf->getStride()); buf->unlock(); ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), buf->getNativeBuffer())); ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); mST->updateTexImage(); Loading Loading @@ -1095,18 +1092,12 @@ protected: 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, mProducerEglSurface = eglCreateWindowSurface(mEglDisplay, mGlConfig, mANW.get(), NULL); ASSERT_EQ(EGL_SUCCESS, eglGetError()); ASSERT_NE(EGL_NO_SURFACE, mProducerEglSurface); mProducerEglContext = eglCreateContext(mEglDisplay, myConfig, mProducerEglContext = eglCreateContext(mEglDisplay, mGlConfig, EGL_NO_CONTEXT, getContextAttribs()); ASSERT_EQ(EGL_SUCCESS, eglGetError()); ASSERT_NE(EGL_NO_CONTEXT, mProducerEglContext); Loading Loading @@ -1742,4 +1733,45 @@ TEST_F(SurfaceTextureFBOTest, BlitFromCpuFilledBufferToFbo) { EXPECT_TRUE(checkPixel( 24, 39, 0, 255, 0, 255)); } class SurfaceTextureMultiContextGLTest : public SurfaceTextureGLTest { protected: SurfaceTextureMultiContextGLTest(): mSecondEglContext(EGL_NO_CONTEXT) { } virtual void SetUp() { SurfaceTextureGLTest::SetUp(); mSecondEglContext = eglCreateContext(mEglDisplay, mGlConfig, EGL_NO_CONTEXT, getContextAttribs()); ASSERT_EQ(EGL_SUCCESS, eglGetError()); ASSERT_NE(EGL_NO_CONTEXT, mSecondEglContext); } virtual void TearDown() { if (mSecondEglContext != EGL_NO_CONTEXT) { eglDestroyContext(mEglDisplay, mSecondEglContext); } SurfaceTextureGLTest::TearDown(); } EGLContext mSecondEglContext; }; TEST_F(SurfaceTextureMultiContextGLTest, UpdateFromMultipleContextsFails) { sp<FrameWaiter> fw(new FrameWaiter); mST->setFrameAvailableListener(fw); ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); // Latch the texture contents on the primary context. mST->updateTexImage(); // Attempt to latch the texture on the secondary context. EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mSecondEglContext)); ASSERT_EQ(EGL_SUCCESS, eglGetError()); ASSERT_EQ(-EINVAL, mST->updateTexImage()); } } // namespace android Loading
include/gui/SurfaceTexture.h +11 −4 Original line number Diff line number Diff line Loading @@ -260,7 +260,6 @@ private: struct EGLSlot { EGLSlot() : mEglImage(EGL_NO_IMAGE_KHR), mEglDisplay(EGL_NO_DISPLAY), mFence(EGL_NO_SYNC_KHR) { } Loading @@ -269,9 +268,6 @@ private: // mEglImage is the EGLImage created from mGraphicBuffer. EGLImageKHR mEglImage; // mEglDisplay is the EGLDisplay used to create mEglImage. EGLDisplay mEglDisplay; // mFence is the EGL sync object that must signal before the buffer // associated with this buffer slot may be dequeued. It is initialized // to EGL_NO_SYNC_KHR when the buffer is created and (optionally, based Loading @@ -279,6 +275,17 @@ private: EGLSyncKHR mFence; }; // 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. 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. EGLContext mEglContext; // mEGLSlots stores the buffers that have been allocated by the BufferQueue // for each buffer slot. It is initialized to null pointers, and gets // filled in with the result of BufferQueue::acquire when the Loading
libs/gui/SurfaceTexture.cpp +23 −12 Original line number Diff line number Diff line Loading @@ -115,6 +115,8 @@ SurfaceTexture::SurfaceTexture(GLuint tex, bool allowSynchronousMode, mUseFenceSync(false), #endif mTexTarget(texTarget), mEglDisplay(EGL_NO_DISPLAY), mEglContext(EGL_NO_CONTEXT), mAbandoned(false), mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT) { Loading Loading @@ -178,6 +180,22 @@ status_t SurfaceTexture::updateTexImage() { return NO_INIT; } EGLDisplay dpy = eglGetCurrentDisplay(); EGLContext ctx = eglGetCurrentContext(); if (mEglDisplay != dpy && mEglDisplay != EGL_NO_DISPLAY) { ST_LOGE("updateTexImage: invalid current EGLDisplay"); return -EINVAL; } if (mEglContext != ctx && mEglContext != EGL_NO_CONTEXT) { ST_LOGE("updateTexImage: invalid current EGLContext"); return -EINVAL; } mEglDisplay = dpy; mEglContext = ctx; BufferQueue::BufferItem item; // In asynchronous mode the list is guaranteed to be one buffer Loading @@ -188,17 +206,14 @@ status_t SurfaceTexture::updateTexImage() { if (item.mGraphicBuffer != NULL) { mEGLSlots[buf].mGraphicBuffer = 0; if (mEGLSlots[buf].mEglImage != EGL_NO_IMAGE_KHR) { eglDestroyImageKHR(mEGLSlots[buf].mEglDisplay, mEGLSlots[buf].mEglImage); eglDestroyImageKHR(dpy, mEGLSlots[buf].mEglImage); mEGLSlots[buf].mEglImage = EGL_NO_IMAGE_KHR; mEGLSlots[buf].mEglDisplay = EGL_NO_DISPLAY; } mEGLSlots[buf].mGraphicBuffer = item.mGraphicBuffer; } // Update the GL texture object. EGLImageKHR image = mEGLSlots[buf].mEglImage; EGLDisplay dpy = eglGetCurrentDisplay(); if (image == EGL_NO_IMAGE_KHR) { if (item.mGraphicBuffer == 0) { ST_LOGE("buffer at slot %d is null", buf); Loading @@ -206,7 +221,6 @@ status_t SurfaceTexture::updateTexImage() { } image = createImage(dpy, item.mGraphicBuffer); mEGLSlots[buf].mEglImage = image; mEGLSlots[buf].mEglDisplay = dpy; if (image == EGL_NO_IMAGE_KHR) { // NOTE: if dpy was invalid, createImage() is guaranteed to // fail. so we'd end up here. Loading @@ -229,8 +243,7 @@ status_t SurfaceTexture::updateTexImage() { failed = true; } if (failed) { mBufferQueue->releaseBuffer(buf, mEGLSlots[buf].mEglDisplay, mEGLSlots[buf].mFence); mBufferQueue->releaseBuffer(buf, dpy, mEGLSlots[buf].mFence); return -EINVAL; } Loading @@ -241,7 +254,7 @@ status_t SurfaceTexture::updateTexImage() { if (fence == EGL_NO_SYNC_KHR) { ALOGE("updateTexImage: error creating fence: %#x", eglGetError()); mBufferQueue->releaseBuffer(buf, mEGLSlots[buf].mEglDisplay, mBufferQueue->releaseBuffer(buf, dpy, mEGLSlots[buf].mFence); return -EINVAL; } Loading @@ -256,8 +269,7 @@ status_t SurfaceTexture::updateTexImage() { buf, item.mGraphicBuffer != NULL ? item.mGraphicBuffer->handle : 0); // release old buffer mBufferQueue->releaseBuffer(mCurrentTexture, mEGLSlots[mCurrentTexture].mEglDisplay, mBufferQueue->releaseBuffer(mCurrentTexture, dpy, mEGLSlots[mCurrentTexture].mFence); // Update the SurfaceTexture state. Loading Loading @@ -459,10 +471,9 @@ void SurfaceTexture::freeBufferLocked(int slotIndex) { if (mEGLSlots[slotIndex].mEglImage != EGL_NO_IMAGE_KHR) { EGLImageKHR img = mEGLSlots[slotIndex].mEglImage; if (img != EGL_NO_IMAGE_KHR) { eglDestroyImageKHR(mEGLSlots[slotIndex].mEglDisplay, img); eglDestroyImageKHR(mEglDisplay, img); } mEGLSlots[slotIndex].mEglImage = EGL_NO_IMAGE_KHR; mEGLSlots[slotIndex].mEglDisplay = EGL_NO_DISPLAY; } } Loading
libs/gui/tests/SurfaceTexture_test.cpp +66 −34 Original line number Diff line number Diff line Loading @@ -560,6 +560,27 @@ void fillRGBA8BufferSolid(uint8_t* buf, int w, int h, int stride, uint8_t r, } } // Produce a single RGBA8 frame by filling a buffer with a checkerboard pattern // using the CPU. This assumes that the ANativeWindow is already configured to // allow this to be done (e.g. the format is set to RGBA8). // // Calls to this function should be wrapped in an ASSERT_NO_FATAL_FAILURE(). void produceOneRGBA8Frame(const sp<ANativeWindow>& anw) { android_native_buffer_t* anb; ASSERT_EQ(NO_ERROR, anw->dequeueBuffer(anw.get(), &anb)); ASSERT_TRUE(anb != NULL); sp<GraphicBuffer> buf(new GraphicBuffer(anb, false)); ASSERT_EQ(NO_ERROR, anw->lockBuffer(anw.get(), buf->getNativeBuffer())); uint8_t* img = NULL; ASSERT_EQ(NO_ERROR, buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img))); fillRGBA8Buffer(img, buf->getWidth(), buf->getHeight(), buf->getStride()); ASSERT_EQ(NO_ERROR, buf->unlock()); ASSERT_EQ(NO_ERROR, anw->queueBuffer(anw.get(), buf->getNativeBuffer())); } TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledYV12BufferNpot) { const int texWidth = 64; const int texHeight = 66; Loading Loading @@ -873,19 +894,7 @@ TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledRGBABufferNpot) { ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(), GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN)); android_native_buffer_t* anb; ASSERT_EQ(NO_ERROR, mANW->dequeueBuffer(mANW.get(), &anb)); ASSERT_TRUE(anb != NULL); sp<GraphicBuffer> 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)); fillRGBA8Buffer(img, texWidth, texHeight, buf->getStride()); buf->unlock(); ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), buf->getNativeBuffer())); ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); mST->updateTexImage(); Loading Loading @@ -927,19 +936,7 @@ TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledRGBABufferPow2) { ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(), GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN)); android_native_buffer_t* anb; ASSERT_EQ(NO_ERROR, mANW->dequeueBuffer(mANW.get(), &anb)); ASSERT_TRUE(anb != NULL); sp<GraphicBuffer> 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)); fillRGBA8Buffer(img, texWidth, texHeight, buf->getStride()); buf->unlock(); ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), buf->getNativeBuffer())); ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); mST->updateTexImage(); Loading Loading @@ -1095,18 +1092,12 @@ protected: 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, mProducerEglSurface = eglCreateWindowSurface(mEglDisplay, mGlConfig, mANW.get(), NULL); ASSERT_EQ(EGL_SUCCESS, eglGetError()); ASSERT_NE(EGL_NO_SURFACE, mProducerEglSurface); mProducerEglContext = eglCreateContext(mEglDisplay, myConfig, mProducerEglContext = eglCreateContext(mEglDisplay, mGlConfig, EGL_NO_CONTEXT, getContextAttribs()); ASSERT_EQ(EGL_SUCCESS, eglGetError()); ASSERT_NE(EGL_NO_CONTEXT, mProducerEglContext); Loading Loading @@ -1742,4 +1733,45 @@ TEST_F(SurfaceTextureFBOTest, BlitFromCpuFilledBufferToFbo) { EXPECT_TRUE(checkPixel( 24, 39, 0, 255, 0, 255)); } class SurfaceTextureMultiContextGLTest : public SurfaceTextureGLTest { protected: SurfaceTextureMultiContextGLTest(): mSecondEglContext(EGL_NO_CONTEXT) { } virtual void SetUp() { SurfaceTextureGLTest::SetUp(); mSecondEglContext = eglCreateContext(mEglDisplay, mGlConfig, EGL_NO_CONTEXT, getContextAttribs()); ASSERT_EQ(EGL_SUCCESS, eglGetError()); ASSERT_NE(EGL_NO_CONTEXT, mSecondEglContext); } virtual void TearDown() { if (mSecondEglContext != EGL_NO_CONTEXT) { eglDestroyContext(mEglDisplay, mSecondEglContext); } SurfaceTextureGLTest::TearDown(); } EGLContext mSecondEglContext; }; TEST_F(SurfaceTextureMultiContextGLTest, UpdateFromMultipleContextsFails) { sp<FrameWaiter> fw(new FrameWaiter); mST->setFrameAvailableListener(fw); ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); // Latch the texture contents on the primary context. mST->updateTexImage(); // Attempt to latch the texture on the secondary context. EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mSecondEglContext)); ASSERT_EQ(EGL_SUCCESS, eglGetError()); ASSERT_EQ(-EINVAL, mST->updateTexImage()); } } // namespace android