libgui: have ST::updateTexImage check the GL ctx

This change adds a check to SurfaceTexture::updateTexImage to verify
that the current GL context is the same as the one that was used for
previous updateTexImage calls.

Change-Id: If02d2f787bcfdb528046dc9ddf6665f8a90e1bf4
diff --git a/include/gui/SurfaceTexture.h b/include/gui/SurfaceTexture.h
index f8dc3ac..cd490f2 100644
--- a/include/gui/SurfaceTexture.h
+++ b/include/gui/SurfaceTexture.h
@@ -260,7 +260,6 @@
     struct EGLSlot {
         EGLSlot()
         : mEglImage(EGL_NO_IMAGE_KHR),
-          mEglDisplay(EGL_NO_DISPLAY),
           mFence(EGL_NO_SYNC_KHR) {
         }
 
@@ -269,9 +268,6 @@
         // 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
@@ -279,6 +275,17 @@
         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
diff --git a/libs/gui/SurfaceTexture.cpp b/libs/gui/SurfaceTexture.cpp
index d8dd5bd..0f21255 100644
--- a/libs/gui/SurfaceTexture.cpp
+++ b/libs/gui/SurfaceTexture.cpp
@@ -115,6 +115,8 @@
     mUseFenceSync(false),
 #endif
     mTexTarget(texTarget),
+    mEglDisplay(EGL_NO_DISPLAY),
+    mEglContext(EGL_NO_CONTEXT),
     mAbandoned(false),
     mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT)
 {
@@ -178,6 +180,22 @@
         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
@@ -188,17 +206,14 @@
         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);
@@ -206,7 +221,6 @@
             }
             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.
@@ -229,8 +243,7 @@
             failed = true;
         }
         if (failed) {
-            mBufferQueue->releaseBuffer(buf, mEGLSlots[buf].mEglDisplay,
-                    mEGLSlots[buf].mFence);
+            mBufferQueue->releaseBuffer(buf, dpy, mEGLSlots[buf].mFence);
             return -EINVAL;
         }
 
@@ -241,7 +254,7 @@
                 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;
                 }
@@ -256,8 +269,7 @@
                 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.
@@ -459,10 +471,9 @@
     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;
     }
 }
 
diff --git a/libs/gui/tests/SurfaceTexture_test.cpp b/libs/gui/tests/SurfaceTexture_test.cpp
index 71cf577..d6357ca 100644
--- a/libs/gui/tests/SurfaceTexture_test.cpp
+++ b/libs/gui/tests/SurfaceTexture_test.cpp
@@ -560,6 +560,27 @@
     }
 }
 
+// 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;
@@ -873,19 +894,7 @@
     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();
 
@@ -927,19 +936,7 @@
     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();
 
@@ -1095,18 +1092,12 @@
     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);
@@ -1742,4 +1733,45 @@
     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