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

Commit b3e518c8 authored by Mathias Agopian's avatar Mathias Agopian
Browse files

Add the concept of synchronous dequeueBuffer in SurfaceTexture

Change-Id: Ic94cbab092953243a0746e04bbe1b2eb0cc930ef
parent eafabcdc
Loading
Loading
Loading
Loading
+50 −21
Original line number Diff line number Diff line
@@ -142,6 +142,13 @@ public:
    // getCurrentTransform returns the transform of the current buffer
    uint32_t getCurrentTransform() const;

    // setSynchronousMode set whether dequeueBuffer is synchronous or
    // asynchronous. In synchronous mode, dequeueBuffer blocks until
    // a buffer is available, the currently bound buffer can be dequeued and
    // queued buffers will be retired in order.
    // The default mode is asynchronous.
    status_t setSynchronousMode(bool enabled);

protected:

    // freeAllBuffers frees the resources (both GraphicBuffer and EGLImage) for
@@ -159,6 +166,16 @@ private:
    enum { INVALID_BUFFER_SLOT = -1 };

    struct BufferSlot {

        BufferSlot()
            : mEglImage(EGL_NO_IMAGE_KHR),
              mEglDisplay(EGL_NO_DISPLAY),
              mBufferState(BufferSlot::FREE),
              mRequestBufferCalled(false),
              mLastQueuedTransform(0),
              mLastQueuedTimestamp(0) {
        }

        // mGraphicBuffer points to the buffer allocated for this slot or is NULL
        // if no buffer has been allocated.
        sp<GraphicBuffer> mGraphicBuffer;
@@ -169,11 +186,32 @@ private:
        // mEglDisplay is the EGLDisplay used to create mEglImage.
        EGLDisplay mEglDisplay;

        // mOwnedByClient indicates whether the slot is currently accessible to a
        // mBufferState indicates whether the slot is currently accessible to a
        // client and should not be used by the SurfaceTexture object. It gets
        // set to true when dequeueBuffer returns the slot and is reset to false
        // when the client calls either queueBuffer or cancelBuffer on the slot.
        bool mOwnedByClient;
        enum { DEQUEUED=-2, FREE=-1, QUEUED=0 };
        int8_t mBufferState;


        // mRequestBufferCalled is used for validating that the client did
        // call requestBuffer() when told to do so. Technically this is not
        // needed but useful for debugging and catching client bugs.
        bool mRequestBufferCalled;

        // mLastQueuedCrop is the crop rectangle for the buffer that was most
        // recently queued. This gets set to mNextCrop each time queueBuffer gets
        // called.
        Rect mLastQueuedCrop;

        // mLastQueuedTransform is the transform identifier for the buffer that was
        // most recently queued. This gets set to mNextTransform each time
        // queueBuffer gets called.
        uint32_t mLastQueuedTransform;

        // mLastQueuedTimestamp is the timestamp for the buffer that was most
        // recently queued. This gets set by queueBuffer.
        int64_t mLastQueuedTimestamp;
    };

    // mSlots is the array of buffer slots that must be mirrored on the client
@@ -230,25 +268,6 @@ private:
    // gets set to mLastQueuedTimestamp each time updateTexImage is called.
    int64_t mCurrentTimestamp;

    // mLastQueued is the buffer slot index of the most recently enqueued buffer.
    // At construction time it is initialized to INVALID_BUFFER_SLOT, and is
    // updated each time queueBuffer is called.
    int mLastQueued;

    // mLastQueuedCrop is the crop rectangle for the buffer that was most
    // recently queued. This gets set to mNextCrop each time queueBuffer gets
    // called.
    Rect mLastQueuedCrop;

    // mLastQueuedTransform is the transform identifier for the buffer that was
    // most recently queued. This gets set to mNextTransform each time
    // queueBuffer gets called.
    uint32_t mLastQueuedTransform;

    // mLastQueuedTimestamp is the timestamp for the buffer that was most
    // recently queued. This gets set by queueBuffer.
    int64_t mLastQueuedTimestamp;

    // mNextCrop is the crop rectangle that will be used for the next buffer
    // that gets queued. It is set by calling setCrop.
    Rect mNextCrop;
@@ -271,6 +290,16 @@ private:
    // queueBuffer.
    sp<FrameAvailableListener> mFrameAvailableListener;

    // mSynchronousMode whether we're in synchronous mode or not
    bool mSynchronousMode;

    // mDequeueCondition condition used for dequeueBuffer in synchronous mode
    mutable Condition mDequeueCondition;

    // mQueue is a FIFO of queued buffers used in synchronous mode
    typedef Vector<int> Fifo;
    Fifo mQueue;

    // 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.
+134 −49
Original line number Diff line number Diff line
@@ -86,17 +86,10 @@ SurfaceTexture::SurfaceTexture(GLuint tex) :
    mCurrentTextureTarget(GL_TEXTURE_EXTERNAL_OES),
    mCurrentTransform(0),
    mCurrentTimestamp(0),
    mLastQueued(INVALID_BUFFER_SLOT),
    mLastQueuedTransform(0),
    mLastQueuedTimestamp(0),
    mNextTransform(0),
    mTexName(tex) {
    mTexName(tex),
    mSynchronousMode(false) {
    LOGV("SurfaceTexture::SurfaceTexture");
    for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
        mSlots[i].mEglImage = EGL_NO_IMAGE_KHR;
        mSlots[i].mEglDisplay = EGL_NO_DISPLAY;
        mSlots[i].mOwnedByClient = false;
    }
    sp<ISurfaceComposer> composer(ComposerService::getComposerService());
    mGraphicBufferAlloc = composer->createGraphicBufferAlloc();
    mNextCrop.makeInvalid();
@@ -109,16 +102,21 @@ SurfaceTexture::~SurfaceTexture() {

status_t SurfaceTexture::setBufferCount(int bufferCount) {
    LOGV("SurfaceTexture::setBufferCount");
    Mutex::Autolock lock(mMutex);

    const int minBufferSlots = mSynchronousMode ?
            MIN_BUFFER_SLOTS-1 : MIN_BUFFER_SLOTS;

    if (bufferCount < MIN_BUFFER_SLOTS) {
    if (bufferCount < minBufferSlots) {
        return BAD_VALUE;
    }

    Mutex::Autolock lock(mMutex);
    freeAllBuffers();
    mBufferCount = bufferCount;
    mCurrentTexture = INVALID_BUFFER_SLOT;
    mLastQueued = INVALID_BUFFER_SLOT;
    mQueue.clear();
    mQueue.reserve(mSynchronousMode ? mBufferCount : 1);
    mDequeueCondition.signal();
    return OK;
}

@@ -140,6 +138,7 @@ sp<GraphicBuffer> SurfaceTexture::requestBuffer(int buf) {
                mBufferCount, buf);
        return 0;
    }
    mSlots[buf].mRequestBufferCalled = true;
    return mSlots[buf].mGraphicBuffer;
}

@@ -153,14 +152,44 @@ status_t SurfaceTexture::dequeueBuffer(int *outBuf, uint32_t w, uint32_t h,
    }

    Mutex::Autolock lock(mMutex);
    int found = INVALID_BUFFER_SLOT;
    int found, foundSync;
    int dequeuedCount = 0;
    bool tryAgain = true;
    while (tryAgain) {
        found = INVALID_BUFFER_SLOT;
        foundSync = INVALID_BUFFER_SLOT;
        dequeuedCount = 0;
        for (int i = 0; i < mBufferCount; i++) {
        if (!mSlots[i].mOwnedByClient && i != mCurrentTexture && i != mLastQueued) {
            mSlots[i].mOwnedByClient = true;
            const int state = mSlots[i].mBufferState;
            if (state == BufferSlot::DEQUEUED) {
                dequeuedCount++;
            }
            if (state == BufferSlot::FREE || i == mCurrentTexture) {
                foundSync = i;
                if (i != mCurrentTexture) {
                    found = i;
                    break;
                }
            }
        }
        // we're in synchronous mode and didn't find a buffer, we need to wait
        tryAgain = mSynchronousMode && (foundSync == INVALID_BUFFER_SLOT);
        if (tryAgain) {
            mDequeueCondition.wait(mMutex);
        }
    }

    if (mSynchronousMode) {
        // we're dequeuing more buffers than allowed in synchronous mode
        if ((mBufferCount - (dequeuedCount+1)) < MIN_UNDEQUEUED_BUFFERS-1)
            return -EBUSY;

        if (found == INVALID_BUFFER_SLOT) {
            // foundSync guaranteed to be != INVALID_BUFFER_SLOT
            found = foundSync;
        }
    }

    if (found == INVALID_BUFFER_SLOT) {
        return -EBUSY;
    }
@@ -181,7 +210,11 @@ status_t SurfaceTexture::dequeueBuffer(int *outBuf, uint32_t w, uint32_t h,
        format = mPixelFormat;
    }

    const sp<GraphicBuffer>& buffer(mSlots[found].mGraphicBuffer);
    // buffer is now in DEQUEUED (but can also be current at the same time,
    // if we're in synchronous mode)
    mSlots[buf].mBufferState = BufferSlot::DEQUEUED;

    const sp<GraphicBuffer>& buffer(mSlots[buf].mGraphicBuffer);
    if ((buffer == NULL) ||
        (uint32_t(buffer->width)  != w) ||
        (uint32_t(buffer->height) != h) ||
@@ -199,6 +232,7 @@ status_t SurfaceTexture::dequeueBuffer(int *outBuf, uint32_t w, uint32_t h,
            mPixelFormat = format;
        }
        mSlots[buf].mGraphicBuffer = graphicBuffer;
        mSlots[buf].mRequestBufferCalled = false;
        if (mSlots[buf].mEglImage != EGL_NO_IMAGE_KHR) {
            eglDestroyImageKHR(mSlots[buf].mEglDisplay, mSlots[buf].mEglImage);
            mSlots[buf].mEglImage = EGL_NO_IMAGE_KHR;
@@ -209,44 +243,78 @@ status_t SurfaceTexture::dequeueBuffer(int *outBuf, uint32_t w, uint32_t h,
    return OK;
}

status_t SurfaceTexture::setSynchronousMode(bool enabled) {
    Mutex::Autolock lock(mMutex);
    if (mSynchronousMode != enabled) {
        mSynchronousMode = enabled;
        freeAllBuffers();
        mCurrentTexture = INVALID_BUFFER_SLOT;
        mQueue.clear();
        mQueue.reserve(mSynchronousMode ? mBufferCount : 1);
        mDequeueCondition.signal();
    }
    return NO_ERROR;
}

status_t SurfaceTexture::queueBuffer(int buf, int64_t timestamp) {
    LOGV("SurfaceTexture::queueBuffer");
    Mutex::Autolock lock(mMutex);
    if (buf < 0 || mBufferCount <= buf) {
    if (buf < 0 || buf >= mBufferCount) {
        LOGE("queueBuffer: slot index out of range [0, %d]: %d",
                mBufferCount, buf);
        return -EINVAL;
    } else if (!mSlots[buf].mOwnedByClient) {
        LOGE("queueBuffer: slot %d is not owned by the client", buf);
    } else if (mSlots[buf].mBufferState != BufferSlot::DEQUEUED) {
        LOGE("queueBuffer: slot %d is not owned by the client (state=%d)",
                buf, mSlots[buf].mBufferState);
        return -EINVAL;
    } else if (mSlots[buf].mGraphicBuffer == 0) {
    } else if (!mSlots[buf].mRequestBufferCalled) {
        LOGE("queueBuffer: slot %d was enqueued without requesting a buffer",
                buf);
        return -EINVAL;
    }
    mSlots[buf].mOwnedByClient = false;
    mLastQueued = buf;
    mLastQueuedCrop = mNextCrop;
    mLastQueuedTransform = mNextTransform;
    mLastQueuedTimestamp = timestamp;

    if (mSynchronousMode) {
        // in synchronous mode we queue all buffers in a FIFO
        mQueue.push_back(buf);
    } else {
        // in asynchronous mode we only keep the most recent buffer
        if (mQueue.empty()) {
            mQueue.push_back(buf);
        } else {
            Fifo::iterator front(mQueue.begin());
            // buffer currently queued is freed
            mSlots[*front].mBufferState = BufferSlot::FREE;
            // and we record the new buffer index in the queued list
            *front = buf;
        }
    }

    mSlots[buf].mBufferState = BufferSlot::QUEUED;
    mSlots[buf].mLastQueuedCrop = mNextCrop;
    mSlots[buf].mLastQueuedTransform = mNextTransform;
    mSlots[buf].mLastQueuedTimestamp = timestamp;

    if (mFrameAvailableListener != 0) {
        mFrameAvailableListener->onFrameAvailable();
    }
    mDequeueCondition.signal();
    return OK;
}

void SurfaceTexture::cancelBuffer(int buf) {
    LOGV("SurfaceTexture::cancelBuffer");
    Mutex::Autolock lock(mMutex);
    if (buf < 0 || mBufferCount <= buf) {
        LOGE("cancelBuffer: slot index out of range [0, %d]: %d", mBufferCount,
                buf);
    if (buf < 0 || buf >= mBufferCount) {
        LOGE("cancelBuffer: slot index out of range [0, %d]: %d",
                mBufferCount, buf);
        return;
    } else if (!mSlots[buf].mOwnedByClient) {
        LOGE("cancelBuffer: slot %d is not owned by the client", buf);
    } else if (mSlots[buf].mBufferState != BufferSlot::DEQUEUED) {
        LOGE("cancelBuffer: slot %d is not owned by the client (state=%d)",
                buf, mSlots[buf].mBufferState);
        return;
    }
    mSlots[buf].mOwnedByClient = false;
    mSlots[buf].mBufferState = BufferSlot::FREE;
    mDequeueCondition.signal();
}

status_t SurfaceTexture::setCrop(const Rect& crop) {
@@ -267,16 +335,25 @@ status_t SurfaceTexture::updateTexImage() {
    LOGV("SurfaceTexture::updateTexImage");
    Mutex::Autolock lock(mMutex);

    // Initially both mCurrentTexture and mLastQueued are INVALID_BUFFER_SLOT,
    int buf = mCurrentTexture;
    if (!mQueue.empty()) {
        // in asynchronous mode the list is guaranteed to be one buffer deep,
        // while in synchronous mode we use the oldest buffer
        Fifo::iterator front(mQueue.begin());
        buf = *front;
        mQueue.erase(front);
    }

    // Initially both mCurrentTexture and buf are INVALID_BUFFER_SLOT,
    // so this check will fail until a buffer gets queued.
    if (mCurrentTexture != mLastQueued) {
    if (mCurrentTexture != buf) {
        // Update the GL texture object.
        EGLImageKHR image = mSlots[mLastQueued].mEglImage;
        EGLImageKHR image = mSlots[buf].mEglImage;
        if (image == EGL_NO_IMAGE_KHR) {
            EGLDisplay dpy = eglGetCurrentDisplay();
            image = createImage(dpy, mSlots[mLastQueued].mGraphicBuffer);
            mSlots[mLastQueued].mEglImage = image;
            mSlots[mLastQueued].mEglDisplay = dpy;
            image = createImage(dpy, mSlots[buf].mGraphicBuffer);
            mSlots[buf].mEglImage = image;
            mSlots[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.
@@ -289,8 +366,7 @@ status_t SurfaceTexture::updateTexImage() {
            LOGE("GL error cleared before updating SurfaceTexture: %#04x", error);
        }

        GLenum target = getTextureTarget(
                mSlots[mLastQueued].mGraphicBuffer->format);
        GLenum target = getTextureTarget(mSlots[buf].mGraphicBuffer->format);
        if (target != mCurrentTextureTarget) {
            glDeleteTextures(1, &mTexName);
        }
@@ -300,20 +376,29 @@ status_t SurfaceTexture::updateTexImage() {
        bool failed = false;
        while ((error = glGetError()) != GL_NO_ERROR) {
            LOGE("error binding external texture image %p (slot %d): %#04x",
                    image, mLastQueued, error);
                    image, buf, error);
            failed = true;
        }
        if (failed) {
            return -EINVAL;
        }

        if (mCurrentTexture != INVALID_BUFFER_SLOT) {
            // the current buffer becomes FREE if it was still in the queued
            // state. If it has already been given to the client
            // (synchronous mode), then it stays in DEQUEUED state.
            if (mSlots[mCurrentTexture].mBufferState == BufferSlot::QUEUED)
                mSlots[mCurrentTexture].mBufferState = BufferSlot::FREE;
        }

        // Update the SurfaceTexture state.
        mCurrentTexture = mLastQueued;
        mCurrentTexture = buf;
        mCurrentTextureTarget = target;
        mCurrentTextureBuf = mSlots[mCurrentTexture].mGraphicBuffer;
        mCurrentCrop = mLastQueuedCrop;
        mCurrentTransform = mLastQueuedTransform;
        mCurrentTimestamp = mLastQueuedTimestamp;
        mCurrentTextureBuf = mSlots[buf].mGraphicBuffer;
        mCurrentCrop = mSlots[buf].mLastQueuedCrop;
        mCurrentTransform = mSlots[buf].mLastQueuedTransform;
        mCurrentTimestamp = mSlots[buf].mLastQueuedTimestamp;
        mDequeueCondition.signal();
    } else {
        // We always bind the texture even if we don't update its contents.
        glBindTexture(mCurrentTextureTarget, mTexName);
@@ -469,7 +554,7 @@ sp<IBinder> SurfaceTexture::getAllocator() {
void SurfaceTexture::freeAllBuffers() {
    for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
        mSlots[i].mGraphicBuffer = 0;
        mSlots[i].mOwnedByClient = false;
        mSlots[i].mBufferState = BufferSlot::FREE;
        if (mSlots[i].mEglImage != EGL_NO_IMAGE_KHR) {
            eglDestroyImageKHR(mSlots[i].mEglDisplay, mSlots[i].mEglImage);
            mSlots[i].mEglImage = EGL_NO_IMAGE_KHR;