Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 74bed55f authored by Jamie Gennis's avatar Jamie Gennis
Browse files

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
parent 1bb69f01
Loading
Loading
Loading
Loading
+64 −6
Original line number Diff line number Diff line
@@ -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.
+198 −36
Original line number Diff line number Diff line
@@ -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
        // 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) {
+595 −178

File changed.

Preview size limit exceeded, changes collapsed.