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
This commit is contained in:
Jamie Gennis 2012-03-28 19:05:54 -07:00
parent 1bb69f015c
commit 74bed55fff
3 changed files with 861 additions and 224 deletions

View File

@ -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.

View File

@ -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;
}
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 @@ 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) {

View File

@ -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,51 +360,107 @@ 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());
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());
// 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());
mTextureRenderer->drawTexture();
}
class TextureRenderer: public RefBase {
public:
TextureRenderer(GLuint texName, const sp<SurfaceTexture>& st):
mTexName(texName),
mST(st) {
}
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));
}
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, 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:
FrameWaiter():
@ -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(-EINVAL, mST->updateTexImage());
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());
// 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