SurfaceTexture: add context attach & detach
This change adds the detachFromContext and attachToContext methods to
SurfaceTexture. These methods allow the SurfaceTexture to switch from
one consumer GLES context to another. This change also includes a few
cleanups to the error return codes in updateTexImage.
Change-Id: I0df1eb599aa7b6f58f07431f242f8f09269559ed
diff --git a/libs/gui/SurfaceTexture.cpp b/libs/gui/SurfaceTexture.cpp
index 07248f6..18c86fa 100644
--- a/libs/gui/SurfaceTexture.cpp
+++ b/libs/gui/SurfaceTexture.cpp
@@ -118,7 +118,8 @@
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 @@
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 @@
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 @@
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 @@
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;
- }
- if (failed) {
- mBufferQueue->releaseBuffer(buf, dpy, mEGLSlots[buf].mFence);
- return -EINVAL;
+ ST_LOGE("updateTexImage: error binding external texture image %p "
+ "(slot %d): %#04x", image, buf, error);
+ err = UNKNOWN_ERROR;
}
- 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) {
+ err = syncForReleaseLocked(dpy);
+ }
+
+ 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 @@
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 @@
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 @@
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) {