am 55d3880e: am bb1e7d43: Merge changes I7e973a35,Ib3386fcc into ics-mr1

* commit '55d3880eed3450748eb7b97281e030902ee29c2a':
  SurfaceTexture: add EGL_KHR_fence_sync option
  SurfaceTexture: add a blit-to-FBO test
This commit is contained in:
Mathias Agopian 2011-11-30 10:50:52 -08:00 committed by Android Git Automerger
commit a4fbecd119
4 changed files with 371 additions and 175 deletions

View File

@ -60,10 +60,16 @@ public:
virtual void onFrameAvailable() = 0;
};
// tex indicates the name OpenGL texture to which images are to be streamed.
// This texture name cannot be changed once the SurfaceTexture is created.
// 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.
// 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
// fences should be used to synchronize access to buffers if that behavior
// is enabled at compile-time.
SurfaceTexture(GLuint tex, bool allowSynchronousMode = true,
GLenum texTarget = GL_TEXTURE_EXTERNAL_OES);
GLenum texTarget = GL_TEXTURE_EXTERNAL_OES, bool useFenceSync = true);
virtual ~SurfaceTexture();
@ -276,7 +282,8 @@ private:
mTransform(0),
mScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE),
mTimestamp(0),
mFrameNumber(0) {
mFrameNumber(0),
mFence(EGL_NO_SYNC_KHR) {
mCrop.makeInvalid();
}
@ -349,6 +356,11 @@ private:
// mFrameNumber is the number of the queued frame for this slot.
uint64_t mFrameNumber;
// mFence is the EGL sync object that must signal before the buffer
// associated with this buffer slot may be dequeued. It is initialized
// to EGL_NO_SYNC_KHR when the buffer is created and (optionally, based
// on a compile-time option) set to a new sync object in updateTexImage.
EGLSyncKHR mFence;
};
// mSlots is the array of buffer slots that must be mirrored on the client
@ -472,6 +484,12 @@ private:
// It is set by the setName method.
String8 mName;
// mUseFenceSync indicates whether creation of the EGL_KHR_fence_sync
// extension should be used to prevent buffers from being dequeued before
// it's safe for them to be written. It gets set at construction time and
// never changes.
const bool mUseFenceSync;
// 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.

View File

@ -38,6 +38,12 @@
#include <utils/Log.h>
#include <utils/String8.h>
// This compile option causes SurfaceTexture to return the buffer that is currently
// attached to the GL texture from dequeueBuffer when no other buffers are
// available. It requires the drivers (Gralloc, GL, OMX IL, and Camera) to do
// implicit cross-process synchronization to prevent the buffer from being
// written to before the buffer has (a) been detached from the GL texture and
// (b) all GL reads from the buffer have completed.
#ifdef ALLOW_DEQUEUE_CURRENT_BUFFER
#define FLAG_ALLOW_DEQUEUE_CURRENT_BUFFER true
#warning "ALLOW_DEQUEUE_CURRENT_BUFFER enabled"
@ -45,6 +51,16 @@
#define FLAG_ALLOW_DEQUEUE_CURRENT_BUFFER false
#endif
// This compile option makes SurfaceTexture use the EGL_KHR_fence_sync extension
// to synchronize access to the buffers. It will cause dequeueBuffer to stall,
// waiting for the GL reads for the buffer being dequeued to complete before
// allowing the buffer to be dequeued.
#ifdef USE_FENCE_SYNC
#ifdef ALLOW_DEQUEUE_CURRENT_BUFFER
#error "USE_FENCE_SYNC and ALLOW_DEQUEUE_CURRENT_BUFFER are incompatible"
#endif
#endif
// Macros for including the SurfaceTexture name in log messages
#define ST_LOGV(x, ...) ALOGV("[%s] "x, mName.string(), ##__VA_ARGS__)
#define ST_LOGD(x, ...) LOGD("[%s] "x, mName.string(), ##__VA_ARGS__)
@ -101,7 +117,7 @@ static int32_t createProcessUniqueId() {
}
SurfaceTexture::SurfaceTexture(GLuint tex, bool allowSynchronousMode,
GLenum texTarget) :
GLenum texTarget, bool useFenceSync) :
mDefaultWidth(1),
mDefaultHeight(1),
mPixelFormat(PIXEL_FORMAT_RGBA_8888),
@ -118,6 +134,11 @@ SurfaceTexture::SurfaceTexture(GLuint tex, bool allowSynchronousMode,
mAllowSynchronousMode(allowSynchronousMode),
mConnectedApi(NO_CONNECTED_API),
mAbandoned(false),
#ifdef USE_FENCE_SYNC
mUseFenceSync(useFenceSync),
#else
mUseFenceSync(false),
#endif
mTexTarget(texTarget),
mFrameCounter(0) {
// Choose a name using the PID and a process-unique ID.
@ -263,9 +284,12 @@ status_t SurfaceTexture::dequeueBuffer(int *outBuf, uint32_t w, uint32_t h,
return BAD_VALUE;
}
Mutex::Autolock lock(mMutex);
status_t returnFlags(OK);
EGLDisplay dpy = EGL_NO_DISPLAY;
EGLSyncKHR fence = EGL_NO_SYNC_KHR;
{ // Scope for the lock
Mutex::Autolock lock(mMutex);
int found = -1;
int foundSync = -1;
@ -329,7 +353,8 @@ status_t SurfaceTexture::dequeueBuffer(int *outBuf, uint32_t w, uint32_t h,
// if buffer is FREE it CANNOT be current
LOGW_IF((state == BufferSlot::FREE) && (mCurrentTexture==i),
"dequeueBuffer: buffer %d is both FREE and current!", i);
"dequeueBuffer: buffer %d is both FREE and current!",
i);
if (FLAG_ALLOW_DEQUEUE_CURRENT_BUFFER) {
if (state == BufferSlot::FREE || i == mCurrentTexture) {
@ -341,11 +366,14 @@ status_t SurfaceTexture::dequeueBuffer(int *outBuf, uint32_t w, uint32_t h,
}
} else {
if (state == BufferSlot::FREE) {
/** For Asynchronous mode, we need to return the oldest of free buffers
* There is only one instance when the Framecounter overflows, this logic
* might return the earlier buffer to client. Which is a negligible impact
**/
if (found < 0 || mSlots[i].mFrameNumber < mSlots[found].mFrameNumber) {
/* We return the oldest of the free buffers to avoid
* stalling the producer if possible. This is because
* the consumer may still have pending reads of the
* buffers in flight.
*/
bool isOlder = mSlots[i].mFrameNumber <
mSlots[found].mFrameNumber;
if (found < 0 || isOlder) {
foundSync = i;
found = i;
}
@ -361,8 +389,9 @@ status_t SurfaceTexture::dequeueBuffer(int *outBuf, uint32_t w, uint32_t h,
return -EINVAL;
}
// See whether a buffer has been queued since the last setBufferCount so
// we know whether to perform the MIN_UNDEQUEUED_BUFFERS check below.
// See whether a buffer has been queued since the last
// setBufferCount so we know whether to perform the
// MIN_UNDEQUEUED_BUFFERS check below.
bool bufferHasBeenQueued = mCurrentTexture != INVALID_BUFFER_SLOT;
if (bufferHasBeenQueued) {
// make sure the client is not trying to dequeue more buffers
@ -377,8 +406,8 @@ status_t SurfaceTexture::dequeueBuffer(int *outBuf, uint32_t w, uint32_t h,
}
}
// we're in synchronous mode and didn't find a buffer, we need to wait
// for some buffers to be consumed
// we're in synchronous mode and didn't find a buffer, we need to
// wait for some buffers to be consumed
tryAgain = mSynchronousMode && (foundSync == INVALID_BUFFER_SLOT);
if (tryAgain) {
mDequeueCondition.wait(mMutex);
@ -438,8 +467,10 @@ status_t SurfaceTexture::dequeueBuffer(int *outBuf, uint32_t w, uint32_t h,
}
mSlots[buf].mGraphicBuffer = graphicBuffer;
mSlots[buf].mRequestBufferCalled = false;
mSlots[buf].mFence = EGL_NO_SYNC_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].mEglDisplay = EGL_NO_DISPLAY;
}
@ -450,8 +481,28 @@ status_t SurfaceTexture::dequeueBuffer(int *outBuf, uint32_t w, uint32_t h,
}
returnFlags |= ISurfaceTexture::BUFFER_NEEDS_REALLOCATION;
}
dpy = mSlots[buf].mEglDisplay;
fence = mSlots[buf].mFence;
mSlots[buf].mFence = EGL_NO_SYNC_KHR;
}
if (fence != EGL_NO_SYNC_KHR) {
EGLint result = eglClientWaitSyncKHR(dpy, fence, 0, 1000000000);
// If something goes wrong, log the error, but return the buffer without
// synchronizing access to it. It's too late at this point to abort the
// dequeue operation.
if (result == EGL_FALSE) {
LOGE("dequeueBuffer: error waiting for fence: %#x", eglGetError());
} else if (result == EGL_TIMEOUT_EXPIRED_KHR) {
LOGE("dequeueBuffer: timeout waiting for fence");
}
eglDestroySyncKHR(dpy, fence);
}
ST_LOGV("dequeueBuffer: returning slot=%d buf=%p flags=%#x", buf,
mSlots[buf].mGraphicBuffer->handle, returnFlags);
return returnFlags;
}
@ -714,8 +765,8 @@ status_t SurfaceTexture::updateTexImage() {
// Update the GL texture object.
EGLImageKHR image = mSlots[buf].mEglImage;
if (image == EGL_NO_IMAGE_KHR) {
EGLDisplay dpy = eglGetCurrentDisplay();
if (image == EGL_NO_IMAGE_KHR) {
if (mSlots[buf].mGraphicBuffer == 0) {
ST_LOGE("buffer at slot %d is null", buf);
return BAD_VALUE;
@ -748,17 +799,33 @@ status_t SurfaceTexture::updateTexImage() {
return -EINVAL;
}
ST_LOGV("updateTexImage: (slot=%d buf=%p) -> (slot=%d buf=%p)", mCurrentTexture,
mCurrentTextureBuf != NULL ? mCurrentTextureBuf->handle : 0, buf,
mSlots[buf].mGraphicBuffer->handle);
if (mCurrentTexture != INVALID_BUFFER_SLOT) {
if (mUseFenceSync) {
EGLSyncKHR fence = eglCreateSyncKHR(dpy, EGL_SYNC_FENCE_KHR,
NULL);
if (fence == EGL_NO_SYNC_KHR) {
LOGE("updateTexImage: error creating fence: %#x",
eglGetError());
return -EINVAL;
}
glFlush();
mSlots[mCurrentTexture].mFence = fence;
}
}
ST_LOGV("updateTexImage: (slot=%d buf=%p) -> (slot=%d buf=%p)",
mCurrentTexture,
mCurrentTextureBuf != NULL ? mCurrentTextureBuf->handle : 0,
buf, mSlots[buf].mGraphicBuffer->handle);
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)
if (mSlots[mCurrentTexture].mBufferState == BufferSlot::QUEUED) {
mSlots[mCurrentTexture].mBufferState = BufferSlot::FREE;
}
}
// Update the SurfaceTexture state.
mCurrentTexture = buf;

View File

@ -536,6 +536,20 @@ void fillRGBA8Buffer(uint8_t* buf, int w, int h, int stride) {
}
}
void fillRGBA8BufferSolid(uint8_t* buf, int w, int h, int stride, uint8_t r,
uint8_t g, uint8_t b, uint8_t a) {
const size_t PIXEL_SIZE = 4;
for (int y = 0; y < h; y++) {
for (int x = 0; x < h; x++) {
off_t offset = (y * stride + x) * PIXEL_SIZE;
buf[offset + 0] = r;
buf[offset + 1] = g;
buf[offset + 2] = b;
buf[offset + 3] = a;
}
}
}
TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledYV12BufferNpot) {
const int texWidth = 64;
const int texHeight = 66;
@ -1616,4 +1630,101 @@ TEST_F(SurfaceTextureGLThreadToGLTest,
}
}
class SurfaceTextureFBOTest : public SurfaceTextureGLTest {
protected:
virtual void SetUp() {
SurfaceTextureGLTest::SetUp();
glGenFramebuffers(1, &mFbo);
ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
glGenTextures(1, &mFboTex);
glBindTexture(GL_TEXTURE_2D, mFboTex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, getSurfaceWidth(),
getSurfaceHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
glBindFramebuffer(GL_FRAMEBUFFER, mFbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, mFboTex, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
}
virtual void TearDown() {
SurfaceTextureGLTest::TearDown();
glDeleteTextures(1, &mFboTex);
glDeleteFramebuffers(1, &mFbo);
}
GLuint mFbo;
GLuint mFboTex;
};
// This test is intended to verify that proper synchronization is done when
// rendering into an FBO.
TEST_F(SurfaceTextureFBOTest, BlitFromCpuFilledBufferToFbo) {
const int texWidth = 64;
const int texHeight = 64;
ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(),
texWidth, texHeight, HAL_PIXEL_FORMAT_RGBA_8888));
ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(),
GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN));
android_native_buffer_t* anb;
ASSERT_EQ(NO_ERROR, mANW->dequeueBuffer(mANW.get(), &anb));
ASSERT_TRUE(anb != NULL);
sp<GraphicBuffer> buf(new GraphicBuffer(anb, false));
ASSERT_EQ(NO_ERROR, mANW->lockBuffer(mANW.get(), buf->getNativeBuffer()));
// Fill the buffer with green
uint8_t* img = NULL;
buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img));
fillRGBA8BufferSolid(img, texWidth, texHeight, buf->getStride(), 0, 255,
0, 255);
buf->unlock();
ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), buf->getNativeBuffer()));
ASSERT_EQ(NO_ERROR, mST->updateTexImage());
glBindFramebuffer(GL_FRAMEBUFFER, mFbo);
drawTexture();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
for (int i = 0; i < 4; i++) {
SCOPED_TRACE(String8::format("frame %d", i).string());
ASSERT_EQ(NO_ERROR, mANW->dequeueBuffer(mANW.get(), &anb));
ASSERT_TRUE(anb != NULL);
buf = new GraphicBuffer(anb, false);
ASSERT_EQ(NO_ERROR, mANW->lockBuffer(mANW.get(),
buf->getNativeBuffer()));
// Fill the buffer with red
ASSERT_EQ(NO_ERROR, buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN,
(void**)(&img)));
fillRGBA8BufferSolid(img, texWidth, texHeight, buf->getStride(), 255, 0,
0, 255);
ASSERT_EQ(NO_ERROR, buf->unlock());
ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(),
buf->getNativeBuffer()));
ASSERT_EQ(NO_ERROR, mST->updateTexImage());
drawTexture();
EXPECT_TRUE(checkPixel( 24, 39, 255, 0, 0, 255));
}
glBindFramebuffer(GL_FRAMEBUFFER, mFbo);
EXPECT_TRUE(checkPixel( 24, 39, 0, 255, 0, 255));
}
} // namespace android

View File

@ -28,7 +28,7 @@ namespace android {
SurfaceTextureLayer::SurfaceTextureLayer(GLuint tex, const sp<Layer>& layer)
: SurfaceTexture(tex), mLayer(layer) {
: SurfaceTexture(tex, true, GL_TEXTURE_EXTERNAL_OES, false), mLayer(layer) {
}
SurfaceTextureLayer::~SurfaceTextureLayer() {