diff --git a/include/gui/ISurfaceTexture.h b/include/gui/ISurfaceTexture.h index 99aa1ad7d..e705c6f58 100644 --- a/include/gui/ISurfaceTexture.h +++ b/include/gui/ISurfaceTexture.h @@ -41,7 +41,10 @@ public: protected: friend class SurfaceTextureClient; - enum { BUFFER_NEEDS_REALLOCATION = 1 }; + enum { + BUFFER_NEEDS_REALLOCATION = 0x1, + RELEASE_ALL_BUFFERS = 0x2, + }; // requestBuffer requests a new buffer for the given index. The server (i.e. // the ISurfaceTexture implementation) assigns the newly created buffer to @@ -94,6 +97,13 @@ protected: // query retrieves some information for this surface // 'what' tokens allowed are that of android_natives.h virtual int query(int what, int* value) = 0; + + // 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. + virtual status_t setSynchronousMode(bool enabled) = 0; }; // ---------------------------------------------------------------------------- diff --git a/include/gui/SurfaceTexture.h b/include/gui/SurfaceTexture.h index 5a371de15..271a35f69 100644 --- a/include/gui/SurfaceTexture.h +++ b/include/gui/SurfaceTexture.h @@ -38,7 +38,10 @@ class IGraphicBufferAlloc; class SurfaceTexture : public BnSurfaceTexture { public: enum { MIN_UNDEQUEUED_BUFFERS = 2 }; - enum { MIN_BUFFER_SLOTS = MIN_UNDEQUEUED_BUFFERS + 1 }; + enum { + MIN_ASYNC_BUFFER_SLOTS = MIN_UNDEQUEUED_BUFFERS + 1, + MIN_SYNC_BUFFER_SLOTS = MIN_UNDEQUEUED_BUFFERS + }; enum { NUM_BUFFER_SLOTS = 32 }; struct FrameAvailableListener : public virtual RefBase { @@ -78,6 +81,13 @@ public: virtual int query(int what, int* value); + // 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. + virtual status_t setSynchronousMode(bool enabled); + // updateTexImage sets the image contents of the target texture to that of // the most recently queued buffer. // @@ -85,6 +95,11 @@ public: // target texture belongs is bound to the calling thread. status_t updateTexImage(); + // setBufferCountServer set the buffer count. If the client has requested + // a buffer count using setBufferCount, the server-buffer count will + // take effect once the client sets the count back to zero. + status_t setBufferCountServer(int bufferCount); + // getTransformMatrix retrieves the 4x4 texture coordinate transform matrix // associated with the texture image set by the most recent call to // updateTexImage. @@ -142,13 +157,6 @@ 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 @@ -163,6 +171,8 @@ private: EGLImageKHR createImage(EGLDisplay dpy, const sp& graphicBuffer); + status_t setBufferCountServerLocked(int bufferCount); + enum { INVALID_BUFFER_SLOT = -1 }; struct BufferSlot { @@ -234,10 +244,18 @@ private: uint32_t mPixelFormat; // mBufferCount is the number of buffer slots that the client and server - // must maintain. It defaults to MIN_BUFFER_SLOTS and can be changed by - // calling setBufferCount. + // must maintain. It defaults to MIN_ASYNC_BUFFER_SLOTS and can be changed + // by calling setBufferCount or setBufferCountServer int mBufferCount; + // mRequestedBufferCount is the number of buffer slots requested by the + // client. The default is zero, which means the client doesn't care how + // many buffers there is. + int mClientBufferCount; + + // mServerBufferCount buffer count requested by the server-side + int mServerBufferCount; + // mCurrentTexture is the buffer slot index of the buffer that is currently // bound to the OpenGL texture. It is initialized to INVALID_BUFFER_SLOT, // indicating that no buffer slot is currently bound to the texture. Note, diff --git a/include/gui/SurfaceTextureClient.h b/include/gui/SurfaceTextureClient.h index c77bc4cd2..e7c6e247b 100644 --- a/include/gui/SurfaceTextureClient.h +++ b/include/gui/SurfaceTextureClient.h @@ -84,7 +84,6 @@ private: int getConnectedApi() const; enum { MIN_UNDEQUEUED_BUFFERS = SurfaceTexture::MIN_UNDEQUEUED_BUFFERS }; - enum { MIN_BUFFER_SLOTS = SurfaceTexture::MIN_BUFFER_SLOTS }; enum { NUM_BUFFER_SLOTS = SurfaceTexture::NUM_BUFFER_SLOTS }; enum { DEFAULT_FORMAT = PIXEL_FORMAT_RGBA_8888 }; diff --git a/libs/gui/ISurfaceTexture.cpp b/libs/gui/ISurfaceTexture.cpp index 0bd0f9721..16e37802b 100644 --- a/libs/gui/ISurfaceTexture.cpp +++ b/libs/gui/ISurfaceTexture.cpp @@ -40,6 +40,7 @@ enum { SET_TRANSFORM, GET_ALLOCATOR, QUERY, + SET_SYNCHRONOUS_MODE, }; @@ -144,6 +145,16 @@ public: return result; } + virtual status_t setSynchronousMode(bool enabled) { + Parcel data, reply; + data.writeInterfaceToken(ISurfaceTexture::getInterfaceDescriptor()); + data.writeInt32(enabled); + remote()->transact(SET_SYNCHRONOUS_MODE, data, &reply); + status_t result = reply.readInt32(); + return result; + } + + }; IMPLEMENT_META_INTERFACE(SurfaceTexture, "android.gui.SurfaceTexture"); @@ -230,6 +241,13 @@ status_t BnSurfaceTexture::onTransact( reply->writeInt32(res); return NO_ERROR; } break; + case SET_SYNCHRONOUS_MODE: { + CHECK_INTERFACE(ISurfaceTexture, data, reply); + bool enabled = data.readInt32(); + status_t res = setSynchronousMode(enabled); + reply->writeInt32(res); + return NO_ERROR; + } break; } return BBinder::onTransact(code, data, reply, flags); } diff --git a/libs/gui/SurfaceTexture.cpp b/libs/gui/SurfaceTexture.cpp index adb468cf9..d8821e276 100644 --- a/libs/gui/SurfaceTexture.cpp +++ b/libs/gui/SurfaceTexture.cpp @@ -81,7 +81,9 @@ SurfaceTexture::SurfaceTexture(GLuint tex) : mDefaultWidth(1), mDefaultHeight(1), mPixelFormat(PIXEL_FORMAT_RGBA_8888), - mBufferCount(MIN_BUFFER_SLOTS), + mBufferCount(MIN_ASYNC_BUFFER_SLOTS), + mClientBufferCount(0), + mServerBufferCount(MIN_ASYNC_BUFFER_SLOTS), mCurrentTexture(INVALID_BUFFER_SLOT), mCurrentTextureTarget(GL_TEXTURE_EXTERNAL_OES), mCurrentTransform(0), @@ -100,22 +102,79 @@ SurfaceTexture::~SurfaceTexture() { freeAllBuffers(); } +status_t SurfaceTexture::setBufferCountServerLocked(int bufferCount) { + if (bufferCount > NUM_BUFFER_SLOTS) + return BAD_VALUE; + + // special-case, nothing to do + if (bufferCount == mBufferCount) + return OK; + + if (!mClientBufferCount && + bufferCount >= mBufferCount) { + // easy, we just have more buffers + mBufferCount = bufferCount; + mServerBufferCount = bufferCount; + mDequeueCondition.signal(); + } else { + // we're here because we're either + // - reducing the number of available buffers + // - or there is a client-buffer-count in effect + + // less than 2 buffers is never allowed + if (bufferCount < 2) + return BAD_VALUE; + + // when there is non client-buffer-count in effect, the client is not + // allowed to dequeue more than one buffer at a time, + // so the next time they dequeue a buffer, we know that they don't + // own one. the actual resizing will happen during the next + // dequeueBuffer. + + mServerBufferCount = bufferCount; + } + return OK; +} + +status_t SurfaceTexture::setBufferCountServer(int bufferCount) { + Mutex::Autolock lock(mMutex); + return setBufferCountServerLocked(bufferCount); +} + status_t SurfaceTexture::setBufferCount(int bufferCount) { LOGV("SurfaceTexture::setBufferCount"); Mutex::Autolock lock(mMutex); - const int minBufferSlots = mSynchronousMode ? - MIN_BUFFER_SLOTS-1 : MIN_BUFFER_SLOTS; + // Error out if the user has dequeued buffers + for (int i=0 ; i= minBufferSlots) ? + mServerBufferCount : minBufferSlots; + return setBufferCountServerLocked(bufferCount); + } + + // We don't allow the client to set a buffer-count less than + // MIN_ASYNC_BUFFER_SLOTS (3), there is no reason for it. + if (bufferCount < MIN_ASYNC_BUFFER_SLOTS) { return BAD_VALUE; } + // here we're guaranteed that the client doesn't have dequeued buffers + // and will release all of its buffer references. freeAllBuffers(); mBufferCount = bufferCount; + mClientBufferCount = bufferCount; mCurrentTexture = INVALID_BUFFER_SLOT; mQueue.clear(); - mQueue.reserve(mSynchronousMode ? mBufferCount : 1); mDequeueCondition.signal(); return OK; } @@ -152,10 +211,56 @@ status_t SurfaceTexture::dequeueBuffer(int *outBuf, uint32_t w, uint32_t h, } Mutex::Autolock lock(mMutex); + + status_t returnFlags(OK); + int found, foundSync; int dequeuedCount = 0; bool tryAgain = true; while (tryAgain) { + // We need to wait for the FIFO to drain if the number of buffer + // needs to change. + // + // The condition "number of buffer needs to change" is true if + // - the client doesn't care about how many buffers there are + // - AND the actual number of buffer is different from what was + // set in the last setBufferCountServer() + // - OR - + // setBufferCountServer() was set to a value incompatible with + // the synchronization mode (for instance because the sync mode + // changed since) + // + // As long as this condition is true AND the FIFO is not empty, we + // wait on mDequeueCondition. + + int minBufferCountNeeded = mSynchronousMode ? + MIN_SYNC_BUFFER_SLOTS : MIN_ASYNC_BUFFER_SLOTS; + + if (!mClientBufferCount && + ((mServerBufferCount != mBufferCount) || + (mServerBufferCount < minBufferCountNeeded))) { + // wait for the FIFO to drain + while (!mQueue.isEmpty()) { + mDequeueCondition.wait(mMutex); + } + minBufferCountNeeded = mSynchronousMode ? + MIN_SYNC_BUFFER_SLOTS : MIN_ASYNC_BUFFER_SLOTS; + } + + + if (!mClientBufferCount && + ((mServerBufferCount != mBufferCount) || + (mServerBufferCount < minBufferCountNeeded))) { + // here we're guaranteed that mQueue is empty + freeAllBuffers(); + mBufferCount = mServerBufferCount; + if (mBufferCount < minBufferCountNeeded) + mBufferCount = minBufferCountNeeded; + mCurrentTexture = INVALID_BUFFER_SLOT; + returnFlags |= ISurfaceTexture::RELEASE_ALL_BUFFERS; + } + + // look for a free buffer to give to the client found = INVALID_BUFFER_SLOT; foundSync = INVALID_BUFFER_SLOT; dequeuedCount = 0; @@ -172,22 +277,34 @@ status_t SurfaceTexture::dequeueBuffer(int *outBuf, uint32_t w, uint32_t h, } } } + + // clients are not allowed to dequeue more than one buffer + // if they didn't set a buffer count. + if (!mClientBufferCount && dequeuedCount) { + return -EINVAL; + } + + // make sure the client is not trying to dequeue more buffers + // than allowed. + const int avail = mBufferCount - (dequeuedCount+1); + if (avail < (MIN_UNDEQUEUED_BUFFERS-int(mSynchronousMode))) { + LOGE("dequeueBuffer: MIN_UNDEQUEUED_BUFFERS=%d exceeded (dequeued=%d)", + MIN_UNDEQUEUED_BUFFERS-int(mSynchronousMode), + dequeuedCount); + return -EBUSY; + } + // we're in synchronous mode and didn't find a buffer, we need to wait + // for for some buffers to be consumed 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 (mSynchronousMode && found == INVALID_BUFFER_SLOT) { + // foundSync guaranteed to be != INVALID_BUFFER_SLOT + found = foundSync; } if (found == INVALID_BUFFER_SLOT) { @@ -238,22 +355,31 @@ status_t SurfaceTexture::dequeueBuffer(int *outBuf, uint32_t w, uint32_t h, mSlots[buf].mEglImage = EGL_NO_IMAGE_KHR; mSlots[buf].mEglDisplay = EGL_NO_DISPLAY; } - return ISurfaceTexture::BUFFER_NEEDS_REALLOCATION; + returnFlags |= ISurfaceTexture::BUFFER_NEEDS_REALLOCATION; } - return OK; + return returnFlags; } status_t SurfaceTexture::setSynchronousMode(bool enabled) { Mutex::Autolock lock(mMutex); + + status_t err = OK; + if (!enabled) { + // going to asynchronous mode, drain the queue + while (mSynchronousMode != enabled && !mQueue.isEmpty()) { + mDequeueCondition.wait(mMutex); + } + } + if (mSynchronousMode != enabled) { + // - if we're going to asynchronous mode, the queue is guaranteed to be + // empty here + // - if the client set the number of buffers, we're guaranteed that + // we have at least 3 (because we don't allow less) mSynchronousMode = enabled; - freeAllBuffers(); - mCurrentTexture = INVALID_BUFFER_SLOT; - mQueue.clear(); - mQueue.reserve(mSynchronousMode ? mBufferCount : 1); mDequeueCondition.signal(); } - return NO_ERROR; + return err; } status_t SurfaceTexture::queueBuffer(int buf, int64_t timestamp) { @@ -267,6 +393,9 @@ status_t SurfaceTexture::queueBuffer(int buf, int64_t timestamp) { LOGE("queueBuffer: slot %d is not owned by the client (state=%d)", buf, mSlots[buf].mBufferState); return -EINVAL; + } else if (buf == mCurrentTexture) { + LOGE("queueBuffer: slot %d is current!", buf); + return -EINVAL; } else if (!mSlots[buf].mRequestBufferCalled) { LOGE("queueBuffer: slot %d was enqueued without requesting a buffer", buf); @@ -342,6 +471,9 @@ status_t SurfaceTexture::updateTexImage() { Fifo::iterator front(mQueue.begin()); buf = *front; mQueue.erase(front); + if (mQueue.isEmpty()) { + mDequeueCondition.signal(); + } } // Initially both mCurrentTexture and buf are INVALID_BUFFER_SLOT, diff --git a/libs/gui/SurfaceTextureClient.cpp b/libs/gui/SurfaceTextureClient.cpp index f3ce44b53..6f103208f 100644 --- a/libs/gui/SurfaceTextureClient.cpp +++ b/libs/gui/SurfaceTextureClient.cpp @@ -39,6 +39,9 @@ SurfaceTextureClient::SurfaceTextureClient( ANativeWindow::query = query; ANativeWindow::perform = perform; + const_cast(ANativeWindow::minSwapInterval) = 0; + const_cast(ANativeWindow::maxSwapInterval) = 1; + // Get a reference to the allocator. mAllocator = mSurfaceTexture->getAllocator(); } @@ -90,22 +93,39 @@ int SurfaceTextureClient::perform(ANativeWindow* window, int operation, ...) { } int SurfaceTextureClient::setSwapInterval(int interval) { - return INVALID_OPERATION; + // EGL specification states: + // interval is silently clamped to minimum and maximum implementation + // dependent values before being stored. + // Although we don't have to, we apply the same logic here. + + if (interval < minSwapInterval) + interval = minSwapInterval; + + if (interval > maxSwapInterval) + interval = maxSwapInterval; + + status_t res = mSurfaceTexture->setSynchronousMode(interval ? true : false); + + return res; } int SurfaceTextureClient::dequeueBuffer(android_native_buffer_t** buffer) { LOGV("SurfaceTextureClient::dequeueBuffer"); Mutex::Autolock lock(mMutex); int buf = -1; - status_t err = mSurfaceTexture->dequeueBuffer(&buf, mReqWidth, mReqHeight, + status_t result = mSurfaceTexture->dequeueBuffer(&buf, mReqWidth, mReqHeight, mReqFormat, mReqUsage); - if (err < 0) { + if (result < 0) { LOGV("dequeueBuffer: ISurfaceTexture::dequeueBuffer(%d, %d, %d, %d)" - "failed: %d", err, mReqWidth, mReqHeight, mReqFormat, mReqUsage); - return err; + "failed: %d", result, mReqWidth, mReqHeight, mReqFormat, mReqUsage); + return result; } sp& gbuf(mSlots[buf]); - if (err == ISurfaceTexture::BUFFER_NEEDS_REALLOCATION || gbuf == 0) { + if (result & ISurfaceTexture::RELEASE_ALL_BUFFERS) { + freeAllBuffers(); + } + + if ((result & ISurfaceTexture::BUFFER_NEEDS_REALLOCATION) || gbuf == 0) { gbuf = mSurfaceTexture->requestBuffer(buf); if (gbuf == 0) { LOGE("dequeueBuffer: ISurfaceTexture::requestBuffer failed"); diff --git a/libs/gui/tests/SurfaceTextureClient_test.cpp b/libs/gui/tests/SurfaceTextureClient_test.cpp index e9c341193..59a4cc5b2 100644 --- a/libs/gui/tests/SurfaceTextureClient_test.cpp +++ b/libs/gui/tests/SurfaceTextureClient_test.cpp @@ -259,6 +259,7 @@ TEST_F(SurfaceTextureClientTest, SurfaceTextureSetDefaultSizeAfterDequeue) { sp anw(mSTC); sp st(mST); ANativeWindowBuffer* buf[2]; + ASSERT_EQ(OK, native_window_set_buffer_count(anw.get(), 4)); ASSERT_EQ(OK, anw->dequeueBuffer(anw.get(), &buf[0])); ASSERT_EQ(OK, anw->dequeueBuffer(anw.get(), &buf[1])); EXPECT_NE(buf[0], buf[1]); @@ -280,6 +281,7 @@ TEST_F(SurfaceTextureClientTest, SurfaceTextureSetDefaultSizeVsGeometry) { sp anw(mSTC); sp st(mST); ANativeWindowBuffer* buf[2]; + ASSERT_EQ(OK, native_window_set_buffer_count(anw.get(), 4)); EXPECT_EQ(OK, st->setDefaultBufferSize(16, 8)); ASSERT_EQ(OK, anw->dequeueBuffer(anw.get(), &buf[0])); ASSERT_EQ(OK, anw->dequeueBuffer(anw.get(), &buf[1])); @@ -307,7 +309,7 @@ TEST_F(SurfaceTextureClientTest, SurfaceTextureTooManyUpdateTexImage) { sp st(mST); android_native_buffer_t* buf[3]; ASSERT_EQ(OK, st->setSynchronousMode(false)); - ASSERT_EQ(OK, native_window_set_buffer_count(anw.get(), 3)); + ASSERT_EQ(OK, native_window_set_buffer_count(anw.get(), 4)); ASSERT_EQ(OK, anw->dequeueBuffer(anw.get(), &buf[0])); ASSERT_EQ(OK, anw->queueBuffer(anw.get(), buf[0])); @@ -315,7 +317,7 @@ TEST_F(SurfaceTextureClientTest, SurfaceTextureTooManyUpdateTexImage) { EXPECT_EQ(OK, st->updateTexImage()); ASSERT_EQ(OK, st->setSynchronousMode(true)); - ASSERT_EQ(OK, native_window_set_buffer_count(anw.get(), 2)); + ASSERT_EQ(OK, native_window_set_buffer_count(anw.get(), 3)); ASSERT_EQ(OK, anw->dequeueBuffer(anw.get(), &buf[0])); ASSERT_EQ(OK, anw->queueBuffer(anw.get(), buf[0])); @@ -421,15 +423,6 @@ TEST_F(SurfaceTextureClientTest, SurfaceTextureSyncModeDequeueCurrent) { EXPECT_EQ(firstBuf, buf[2]); } -TEST_F(SurfaceTextureClientTest, SurfaceTextureSyncModeTwoBuffers) { - sp anw(mSTC); - sp st(mST); - ASSERT_EQ(OK, st->setSynchronousMode(true)); - EXPECT_EQ(OK, native_window_set_buffer_count(anw.get(), 3)); - EXPECT_EQ(OK, native_window_set_buffer_count(anw.get(), 2)); - EXPECT_NE(OK, native_window_set_buffer_count(anw.get(), 1)); -} - TEST_F(SurfaceTextureClientTest, SurfaceTextureSyncModeMinUndequeued) { sp anw(mSTC); sp st(mST); @@ -490,7 +483,7 @@ TEST_F(SurfaceTextureClientTest, SurfaceTextureSyncModeWaitRetire) { android_native_buffer_t* buf[3]; ASSERT_EQ(OK, st->setSynchronousMode(true)); - ASSERT_EQ(OK, native_window_set_buffer_count(anw.get(), 2)); + ASSERT_EQ(OK, native_window_set_buffer_count(anw.get(), 3)); // dequeue/queue/update so we have a current buffer ASSERT_EQ(OK, anw->dequeueBuffer(anw.get(), &buf[0])); ASSERT_EQ(OK, anw->queueBuffer(anw.get(), buf[0]));