Add the concept of synchronous dequeueBuffer in SurfaceTexture
Change-Id: Ic94cbab092953243a0746e04bbe1b2eb0cc930ef
diff --git a/include/gui/SurfaceTexture.h b/include/gui/SurfaceTexture.h
index 152e9bf..5a371de 100644
--- a/include/gui/SurfaceTexture.h
+++ b/include/gui/SurfaceTexture.h
@@ -142,6 +142,13 @@
// 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 @@
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 @@
// 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 @@
// 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 @@
// 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.
diff --git a/libs/gui/SurfaceTexture.cpp b/libs/gui/SurfaceTexture.cpp
index 0db30a0..adb468c 100644
--- a/libs/gui/SurfaceTexture.cpp
+++ b/libs/gui/SurfaceTexture.cpp
@@ -86,17 +86,10 @@
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 @@
status_t SurfaceTexture::setBufferCount(int bufferCount) {
LOGV("SurfaceTexture::setBufferCount");
+ Mutex::Autolock lock(mMutex);
- if (bufferCount < MIN_BUFFER_SLOTS) {
+ const int minBufferSlots = mSynchronousMode ?
+ MIN_BUFFER_SLOTS-1 : 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 @@
mBufferCount, buf);
return 0;
}
+ mSlots[buf].mRequestBufferCalled = true;
return mSlots[buf].mGraphicBuffer;
}
@@ -153,14 +152,44 @@
}
Mutex::Autolock lock(mMutex);
- int found = INVALID_BUFFER_SLOT;
- for (int i = 0; i < mBufferCount; i++) {
- if (!mSlots[i].mOwnedByClient && i != mCurrentTexture && i != mLastQueued) {
- mSlots[i].mOwnedByClient = true;
- found = i;
- break;
+ 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++) {
+ 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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;