Add the concept of synchronous dequeueBuffer in SurfaceTexture

Change-Id: Ic94cbab092953243a0746e04bbe1b2eb0cc930ef
This commit is contained in:
Mathias Agopian 2011-04-21 18:52:51 -07:00
parent eafabcdc16
commit b3e518c820
2 changed files with 184 additions and 70 deletions

View File

@ -142,6 +142,13 @@ public:
// getCurrentTransform returns the transform of the current buffer // getCurrentTransform returns the transform of the current buffer
uint32_t getCurrentTransform() const; 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: protected:
// freeAllBuffers frees the resources (both GraphicBuffer and EGLImage) for // freeAllBuffers frees the resources (both GraphicBuffer and EGLImage) for
@ -159,6 +166,16 @@ private:
enum { INVALID_BUFFER_SLOT = -1 }; enum { INVALID_BUFFER_SLOT = -1 };
struct BufferSlot { 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 // mGraphicBuffer points to the buffer allocated for this slot or is NULL
// if no buffer has been allocated. // if no buffer has been allocated.
sp<GraphicBuffer> mGraphicBuffer; sp<GraphicBuffer> mGraphicBuffer;
@ -169,11 +186,32 @@ private:
// mEglDisplay is the EGLDisplay used to create mEglImage. // mEglDisplay is the EGLDisplay used to create mEglImage.
EGLDisplay mEglDisplay; 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 // 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 // set to true when dequeueBuffer returns the slot and is reset to false
// when the client calls either queueBuffer or cancelBuffer on the slot. // 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 // 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. // gets set to mLastQueuedTimestamp each time updateTexImage is called.
int64_t mCurrentTimestamp; 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 // mNextCrop is the crop rectangle that will be used for the next buffer
// that gets queued. It is set by calling setCrop. // that gets queued. It is set by calling setCrop.
Rect mNextCrop; Rect mNextCrop;
@ -271,6 +290,16 @@ private:
// queueBuffer. // queueBuffer.
sp<FrameAvailableListener> mFrameAvailableListener; 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 // mMutex is the mutex used to prevent concurrent access to the member
// variables of SurfaceTexture objects. It must be locked whenever the // variables of SurfaceTexture objects. It must be locked whenever the
// member variables are accessed. // member variables are accessed.

View File

@ -86,17 +86,10 @@ SurfaceTexture::SurfaceTexture(GLuint tex) :
mCurrentTextureTarget(GL_TEXTURE_EXTERNAL_OES), mCurrentTextureTarget(GL_TEXTURE_EXTERNAL_OES),
mCurrentTransform(0), mCurrentTransform(0),
mCurrentTimestamp(0), mCurrentTimestamp(0),
mLastQueued(INVALID_BUFFER_SLOT),
mLastQueuedTransform(0),
mLastQueuedTimestamp(0),
mNextTransform(0), mNextTransform(0),
mTexName(tex) { mTexName(tex),
mSynchronousMode(false) {
LOGV("SurfaceTexture::SurfaceTexture"); 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()); sp<ISurfaceComposer> composer(ComposerService::getComposerService());
mGraphicBufferAlloc = composer->createGraphicBufferAlloc(); mGraphicBufferAlloc = composer->createGraphicBufferAlloc();
mNextCrop.makeInvalid(); mNextCrop.makeInvalid();
@ -109,16 +102,21 @@ SurfaceTexture::~SurfaceTexture() {
status_t SurfaceTexture::setBufferCount(int bufferCount) { status_t SurfaceTexture::setBufferCount(int bufferCount) {
LOGV("SurfaceTexture::setBufferCount"); 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; return BAD_VALUE;
} }
Mutex::Autolock lock(mMutex);
freeAllBuffers(); freeAllBuffers();
mBufferCount = bufferCount; mBufferCount = bufferCount;
mCurrentTexture = INVALID_BUFFER_SLOT; mCurrentTexture = INVALID_BUFFER_SLOT;
mLastQueued = INVALID_BUFFER_SLOT; mQueue.clear();
mQueue.reserve(mSynchronousMode ? mBufferCount : 1);
mDequeueCondition.signal();
return OK; return OK;
} }
@ -140,6 +138,7 @@ sp<GraphicBuffer> SurfaceTexture::requestBuffer(int buf) {
mBufferCount, buf); mBufferCount, buf);
return 0; return 0;
} }
mSlots[buf].mRequestBufferCalled = true;
return mSlots[buf].mGraphicBuffer; return mSlots[buf].mGraphicBuffer;
} }
@ -153,14 +152,44 @@ status_t SurfaceTexture::dequeueBuffer(int *outBuf, uint32_t w, uint32_t h,
} }
Mutex::Autolock lock(mMutex); Mutex::Autolock lock(mMutex);
int found = INVALID_BUFFER_SLOT; int found, foundSync;
for (int i = 0; i < mBufferCount; i++) { int dequeuedCount = 0;
if (!mSlots[i].mOwnedByClient && i != mCurrentTexture && i != mLastQueued) { bool tryAgain = true;
mSlots[i].mOwnedByClient = true; while (tryAgain) {
found = i; found = INVALID_BUFFER_SLOT;
break; 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) { if (found == INVALID_BUFFER_SLOT) {
return -EBUSY; return -EBUSY;
} }
@ -181,7 +210,11 @@ status_t SurfaceTexture::dequeueBuffer(int *outBuf, uint32_t w, uint32_t h,
format = mPixelFormat; 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) || if ((buffer == NULL) ||
(uint32_t(buffer->width) != w) || (uint32_t(buffer->width) != w) ||
(uint32_t(buffer->height) != h) || (uint32_t(buffer->height) != h) ||
@ -199,6 +232,7 @@ status_t SurfaceTexture::dequeueBuffer(int *outBuf, uint32_t w, uint32_t h,
mPixelFormat = format; mPixelFormat = format;
} }
mSlots[buf].mGraphicBuffer = graphicBuffer; mSlots[buf].mGraphicBuffer = graphicBuffer;
mSlots[buf].mRequestBufferCalled = false;
if (mSlots[buf].mEglImage != EGL_NO_IMAGE_KHR) { if (mSlots[buf].mEglImage != EGL_NO_IMAGE_KHR) {
eglDestroyImageKHR(mSlots[buf].mEglDisplay, mSlots[buf].mEglImage); eglDestroyImageKHR(mSlots[buf].mEglDisplay, mSlots[buf].mEglImage);
mSlots[buf].mEglImage = EGL_NO_IMAGE_KHR; 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; 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) { status_t SurfaceTexture::queueBuffer(int buf, int64_t timestamp) {
LOGV("SurfaceTexture::queueBuffer"); LOGV("SurfaceTexture::queueBuffer");
Mutex::Autolock lock(mMutex); Mutex::Autolock lock(mMutex);
if (buf < 0 || mBufferCount <= buf) { if (buf < 0 || buf >= mBufferCount) {
LOGE("queueBuffer: slot index out of range [0, %d]: %d", LOGE("queueBuffer: slot index out of range [0, %d]: %d",
mBufferCount, buf); mBufferCount, buf);
return -EINVAL; return -EINVAL;
} else if (!mSlots[buf].mOwnedByClient) { } else if (mSlots[buf].mBufferState != BufferSlot::DEQUEUED) {
LOGE("queueBuffer: slot %d is not owned by the client", buf); LOGE("queueBuffer: slot %d is not owned by the client (state=%d)",
buf, mSlots[buf].mBufferState);
return -EINVAL; return -EINVAL;
} else if (mSlots[buf].mGraphicBuffer == 0) { } else if (!mSlots[buf].mRequestBufferCalled) {
LOGE("queueBuffer: slot %d was enqueued without requesting a buffer", LOGE("queueBuffer: slot %d was enqueued without requesting a buffer",
buf); buf);
return -EINVAL; return -EINVAL;
} }
mSlots[buf].mOwnedByClient = false;
mLastQueued = buf; if (mSynchronousMode) {
mLastQueuedCrop = mNextCrop; // in synchronous mode we queue all buffers in a FIFO
mLastQueuedTransform = mNextTransform; mQueue.push_back(buf);
mLastQueuedTimestamp = timestamp; } 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) { if (mFrameAvailableListener != 0) {
mFrameAvailableListener->onFrameAvailable(); mFrameAvailableListener->onFrameAvailable();
} }
mDequeueCondition.signal();
return OK; return OK;
} }
void SurfaceTexture::cancelBuffer(int buf) { void SurfaceTexture::cancelBuffer(int buf) {
LOGV("SurfaceTexture::cancelBuffer"); LOGV("SurfaceTexture::cancelBuffer");
Mutex::Autolock lock(mMutex); Mutex::Autolock lock(mMutex);
if (buf < 0 || mBufferCount <= buf) { if (buf < 0 || buf >= mBufferCount) {
LOGE("cancelBuffer: slot index out of range [0, %d]: %d", mBufferCount, LOGE("cancelBuffer: slot index out of range [0, %d]: %d",
buf); mBufferCount, buf);
return; return;
} else if (!mSlots[buf].mOwnedByClient) { } else if (mSlots[buf].mBufferState != BufferSlot::DEQUEUED) {
LOGE("cancelBuffer: slot %d is not owned by the client", buf); LOGE("cancelBuffer: slot %d is not owned by the client (state=%d)",
buf, mSlots[buf].mBufferState);
return; return;
} }
mSlots[buf].mOwnedByClient = false; mSlots[buf].mBufferState = BufferSlot::FREE;
mDequeueCondition.signal();
} }
status_t SurfaceTexture::setCrop(const Rect& crop) { status_t SurfaceTexture::setCrop(const Rect& crop) {
@ -267,16 +335,25 @@ status_t SurfaceTexture::updateTexImage() {
LOGV("SurfaceTexture::updateTexImage"); LOGV("SurfaceTexture::updateTexImage");
Mutex::Autolock lock(mMutex); 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. // so this check will fail until a buffer gets queued.
if (mCurrentTexture != mLastQueued) { if (mCurrentTexture != buf) {
// Update the GL texture object. // Update the GL texture object.
EGLImageKHR image = mSlots[mLastQueued].mEglImage; EGLImageKHR image = mSlots[buf].mEglImage;
if (image == EGL_NO_IMAGE_KHR) { if (image == EGL_NO_IMAGE_KHR) {
EGLDisplay dpy = eglGetCurrentDisplay(); EGLDisplay dpy = eglGetCurrentDisplay();
image = createImage(dpy, mSlots[mLastQueued].mGraphicBuffer); image = createImage(dpy, mSlots[buf].mGraphicBuffer);
mSlots[mLastQueued].mEglImage = image; mSlots[buf].mEglImage = image;
mSlots[mLastQueued].mEglDisplay = dpy; mSlots[buf].mEglDisplay = dpy;
if (image == EGL_NO_IMAGE_KHR) { if (image == EGL_NO_IMAGE_KHR) {
// NOTE: if dpy was invalid, createImage() is guaranteed to // NOTE: if dpy was invalid, createImage() is guaranteed to
// fail. so we'd end up here. // fail. so we'd end up here.
@ -289,8 +366,7 @@ status_t SurfaceTexture::updateTexImage() {
LOGE("GL error cleared before updating SurfaceTexture: %#04x", error); LOGE("GL error cleared before updating SurfaceTexture: %#04x", error);
} }
GLenum target = getTextureTarget( GLenum target = getTextureTarget(mSlots[buf].mGraphicBuffer->format);
mSlots[mLastQueued].mGraphicBuffer->format);
if (target != mCurrentTextureTarget) { if (target != mCurrentTextureTarget) {
glDeleteTextures(1, &mTexName); glDeleteTextures(1, &mTexName);
} }
@ -300,20 +376,29 @@ status_t SurfaceTexture::updateTexImage() {
bool failed = false; bool failed = false;
while ((error = glGetError()) != GL_NO_ERROR) { while ((error = glGetError()) != GL_NO_ERROR) {
LOGE("error binding external texture image %p (slot %d): %#04x", LOGE("error binding external texture image %p (slot %d): %#04x",
image, mLastQueued, error); image, buf, error);
failed = true; failed = true;
} }
if (failed) { if (failed) {
return -EINVAL; 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. // Update the SurfaceTexture state.
mCurrentTexture = mLastQueued; mCurrentTexture = buf;
mCurrentTextureTarget = target; mCurrentTextureTarget = target;
mCurrentTextureBuf = mSlots[mCurrentTexture].mGraphicBuffer; mCurrentTextureBuf = mSlots[buf].mGraphicBuffer;
mCurrentCrop = mLastQueuedCrop; mCurrentCrop = mSlots[buf].mLastQueuedCrop;
mCurrentTransform = mLastQueuedTransform; mCurrentTransform = mSlots[buf].mLastQueuedTransform;
mCurrentTimestamp = mLastQueuedTimestamp; mCurrentTimestamp = mSlots[buf].mLastQueuedTimestamp;
mDequeueCondition.signal();
} else { } else {
// We always bind the texture even if we don't update its contents. // We always bind the texture even if we don't update its contents.
glBindTexture(mCurrentTextureTarget, mTexName); glBindTexture(mCurrentTextureTarget, mTexName);
@ -469,7 +554,7 @@ sp<IBinder> SurfaceTexture::getAllocator() {
void SurfaceTexture::freeAllBuffers() { void SurfaceTexture::freeAllBuffers() {
for (int i = 0; i < NUM_BUFFER_SLOTS; i++) { for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
mSlots[i].mGraphicBuffer = 0; mSlots[i].mGraphicBuffer = 0;
mSlots[i].mOwnedByClient = false; mSlots[i].mBufferState = BufferSlot::FREE;
if (mSlots[i].mEglImage != EGL_NO_IMAGE_KHR) { if (mSlots[i].mEglImage != EGL_NO_IMAGE_KHR) {
eglDestroyImageKHR(mSlots[i].mEglDisplay, mSlots[i].mEglImage); eglDestroyImageKHR(mSlots[i].mEglDisplay, mSlots[i].mEglImage);
mSlots[i].mEglImage = EGL_NO_IMAGE_KHR; mSlots[i].mEglImage = EGL_NO_IMAGE_KHR;