Merge changes I56779420,I044e44e1,Ic5adfa29,Ied541ab8

* changes:
  SurfaceTexture: add some GL->GL tests.
  SurfaceTexture: fix up a comment.
  SurfaceTexture: add getTransformMatrix tests.
  SurfaceTexture: fix a getTransformMatrix crash.
This commit is contained in:
Jamie Gennis 2011-06-13 11:14:57 -07:00 committed by Android (Google) Code Review
commit 72f6af8630
4 changed files with 398 additions and 5 deletions

View File

@ -188,6 +188,11 @@ private:
status_t setBufferCountServerLocked(int bufferCount);
// computeCurrentTransformMatrix computes the transform matrix for the
// current texture. It uses mCurrentTransform and the current GraphicBuffer
// to compute this matrix and stores it in mCurrentTransformMatrix.
void computeCurrentTransformMatrix();
enum { INVALID_BUFFER_SLOT = -1 };
struct BufferSlot {
@ -288,9 +293,9 @@ private:
// 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.
// mClientBufferCount 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
@ -322,6 +327,11 @@ private:
// gets set to mLastQueuedTransform each time updateTexImage is called.
uint32_t mCurrentTransform;
// mCurrentTransformMatrix is the transform matrix for the current texture.
// It gets computed by computeTransformMatrix each time updateTexImage is
// called.
float mCurrentTransformMatrix[16];
// mCurrentTimestamp is the timestamp for the current texture. It
// gets set to mLastQueuedTimestamp each time updateTexImage is called.
int64_t mCurrentTimestamp;
@ -362,6 +372,7 @@ private:
// variables of SurfaceTexture objects. It must be locked whenever the
// member variables are accessed.
mutable Mutex mMutex;
};
// ----------------------------------------------------------------------------

View File

@ -96,6 +96,7 @@ SurfaceTexture::SurfaceTexture(GLuint tex) :
sp<ISurfaceComposer> composer(ComposerService::getComposerService());
mGraphicBufferAlloc = composer->createGraphicBufferAlloc();
mNextCrop.makeInvalid();
memcpy(mCurrentTransformMatrix, mtxIdentity, sizeof(mCurrentTransformMatrix));
}
SurfaceTexture::~SurfaceTexture() {
@ -547,6 +548,7 @@ status_t SurfaceTexture::updateTexImage() {
mCurrentCrop = mSlots[buf].mCrop;
mCurrentTransform = mSlots[buf].mTransform;
mCurrentTimestamp = mSlots[buf].mTimestamp;
computeCurrentTransformMatrix();
mDequeueCondition.signal();
} else {
// We always bind the texture even if we don't update its contents.
@ -596,8 +598,12 @@ GLenum SurfaceTexture::getCurrentTextureTarget() const {
}
void SurfaceTexture::getTransformMatrix(float mtx[16]) {
LOGV("SurfaceTexture::getTransformMatrix");
Mutex::Autolock lock(mMutex);
memcpy(mtx, mCurrentTransformMatrix, sizeof(mCurrentTransformMatrix));
}
void SurfaceTexture::computeCurrentTransformMatrix() {
LOGV("SurfaceTexture::computeCurrentTransformMatrix");
float xform[16];
for (int i = 0; i < 16; i++) {
@ -684,7 +690,7 @@ void SurfaceTexture::getTransformMatrix(float mtx[16]) {
// coordinate of 0, so SurfaceTexture must behave the same way. We don't
// want to expose this to applications, however, so we must add an
// additional vertical flip to the transform after all the other transforms.
mtxMul(mtx, mtxFlipV, mtxBeforeFlipV);
mtxMul(mCurrentTransformMatrix, mtxFlipV, mtxBeforeFlipV);
}
nsecs_t SurfaceTexture::getTimestamp() {

View File

@ -514,4 +514,112 @@ TEST_F(SurfaceTextureClientTest, DISABLED_SurfaceTextureSyncModeWaitRetire) {
thread->requestExitAndWait();
}
TEST_F(SurfaceTextureClientTest, GetTransformMatrixReturnsVerticalFlip) {
sp<ANativeWindow> anw(mSTC);
sp<SurfaceTexture> st(mST);
android_native_buffer_t* buf[3];
float mtx[16] = {};
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]));
ASSERT_EQ(OK, st->updateTexImage());
st->getTransformMatrix(mtx);
EXPECT_EQ(1.f, mtx[0]);
EXPECT_EQ(0.f, mtx[1]);
EXPECT_EQ(0.f, mtx[2]);
EXPECT_EQ(0.f, mtx[3]);
EXPECT_EQ(0.f, mtx[4]);
EXPECT_EQ(-1.f, mtx[5]);
EXPECT_EQ(0.f, mtx[6]);
EXPECT_EQ(0.f, mtx[7]);
EXPECT_EQ(0.f, mtx[8]);
EXPECT_EQ(0.f, mtx[9]);
EXPECT_EQ(1.f, mtx[10]);
EXPECT_EQ(0.f, mtx[11]);
EXPECT_EQ(0.f, mtx[12]);
EXPECT_EQ(1.f, mtx[13]);
EXPECT_EQ(0.f, mtx[14]);
EXPECT_EQ(1.f, mtx[15]);
}
TEST_F(SurfaceTextureClientTest, GetTransformMatrixSucceedsAfterFreeingBuffers) {
sp<ANativeWindow> anw(mSTC);
sp<SurfaceTexture> st(mST);
android_native_buffer_t* buf[3];
float mtx[16] = {};
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]));
ASSERT_EQ(OK, st->updateTexImage());
ASSERT_EQ(OK, native_window_set_buffer_count(anw.get(), 6)); // frees buffers
st->getTransformMatrix(mtx);
EXPECT_EQ(1.f, mtx[0]);
EXPECT_EQ(0.f, mtx[1]);
EXPECT_EQ(0.f, mtx[2]);
EXPECT_EQ(0.f, mtx[3]);
EXPECT_EQ(0.f, mtx[4]);
EXPECT_EQ(-1.f, mtx[5]);
EXPECT_EQ(0.f, mtx[6]);
EXPECT_EQ(0.f, mtx[7]);
EXPECT_EQ(0.f, mtx[8]);
EXPECT_EQ(0.f, mtx[9]);
EXPECT_EQ(1.f, mtx[10]);
EXPECT_EQ(0.f, mtx[11]);
EXPECT_EQ(0.f, mtx[12]);
EXPECT_EQ(1.f, mtx[13]);
EXPECT_EQ(0.f, mtx[14]);
EXPECT_EQ(1.f, mtx[15]);
}
TEST_F(SurfaceTextureClientTest, GetTransformMatrixSucceedsAfterFreeingBuffersWithCrop) {
sp<ANativeWindow> anw(mSTC);
sp<SurfaceTexture> st(mST);
android_native_buffer_t* buf[3];
float mtx[16] = {};
android_native_rect_t crop;
crop.left = 0;
crop.top = 0;
crop.right = 5;
crop.bottom = 5;
ASSERT_EQ(OK, native_window_set_buffer_count(anw.get(), 4));
ASSERT_EQ(OK, native_window_set_buffers_geometry(anw.get(), 8, 8, 0));
ASSERT_EQ(OK, anw->dequeueBuffer(anw.get(), &buf[0]));
ASSERT_EQ(OK, native_window_set_crop(anw.get(), &crop));
ASSERT_EQ(OK, anw->queueBuffer(anw.get(), buf[0]));
ASSERT_EQ(OK, st->updateTexImage());
ASSERT_EQ(OK, native_window_set_buffer_count(anw.get(), 6)); // frees buffers
st->getTransformMatrix(mtx);
// This accounts for the 1 texel shrink for each edge that's included in the
// transform matrix to avoid texturing outside the crop region.
EXPECT_EQ(.5f, mtx[0]);
EXPECT_EQ(0.f, mtx[1]);
EXPECT_EQ(0.f, mtx[2]);
EXPECT_EQ(0.f, mtx[3]);
EXPECT_EQ(0.f, mtx[4]);
EXPECT_EQ(-.5f, mtx[5]);
EXPECT_EQ(0.f, mtx[6]);
EXPECT_EQ(0.f, mtx[7]);
EXPECT_EQ(0.f, mtx[8]);
EXPECT_EQ(0.f, mtx[9]);
EXPECT_EQ(1.f, mtx[10]);
EXPECT_EQ(0.f, mtx[11]);
EXPECT_EQ(0.f, mtx[12]);
EXPECT_EQ(.5f, mtx[13]);
EXPECT_EQ(0.f, mtx[14]);
EXPECT_EQ(1.f, mtx[15]);
}
} // namespace android

View File

@ -14,11 +14,14 @@
* limitations under the License.
*/
//#define LOG_NDEBUG 0
#include <gtest/gtest.h>
#include <gui/SurfaceTexture.h>
#include <gui/SurfaceTextureClient.h>
#include <ui/GraphicBuffer.h>
#include <utils/String8.h>
#include <utils/threads.h>
#include <surfaceflinger/ISurfaceComposer.h>
#include <surfaceflinger/Surface.h>
@ -618,4 +621,269 @@ TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledYV12BufferWithCrop) {
}
}
/*
* This test is for testing GL -> GL texture streaming via SurfaceTexture. It
* contains functionality to create a producer thread that will perform GL
* rendering to an ANativeWindow that feeds frames to a SurfaceTexture.
* Additionally it supports interlocking the producer and consumer threads so
* that a specific sequence of calls can be deterministically created by the
* test.
*
* The intended usage is as follows:
*
* TEST_F(...) {
* class PT : public ProducerThread {
* virtual void render() {
* ...
* swapBuffers();
* }
* };
*
* runProducerThread(new PT());
*
* // The order of these calls will vary from test to test and may include
* // multiple frames and additional operations (e.g. GL rendering from the
* // texture).
* fc->waitForFrame();
* mST->updateTexImage();
* fc->finishFrame();
* }
*
*/
class SurfaceTextureGLToGLTest : public SurfaceTextureGLTest {
protected:
// ProducerThread is an abstract base class to simplify the creation of
// OpenGL ES frame producer threads.
class ProducerThread : public Thread {
public:
virtual ~ProducerThread() {
}
void setEglObjects(EGLDisplay producerEglDisplay,
EGLSurface producerEglSurface,
EGLContext producerEglContext) {
mProducerEglDisplay = producerEglDisplay;
mProducerEglSurface = producerEglSurface;
mProducerEglContext = producerEglContext;
}
virtual bool threadLoop() {
eglMakeCurrent(mProducerEglDisplay, mProducerEglSurface,
mProducerEglSurface, mProducerEglContext);
render();
eglMakeCurrent(mProducerEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
EGL_NO_CONTEXT);
return false;
}
protected:
virtual void render() = 0;
void swapBuffers() {
eglSwapBuffers(mProducerEglDisplay, mProducerEglSurface);
}
EGLDisplay mProducerEglDisplay;
EGLSurface mProducerEglSurface;
EGLContext mProducerEglContext;
};
// FrameCondition is a utility class for interlocking between the producer
// and consumer threads. The FrameCondition object should be created and
// destroyed in the consumer thread only. The consumer thread should set
// the FrameCondition as the FrameAvailableListener of the SurfaceTexture,
// and should call both waitForFrame and finishFrame once for each expected
// frame.
//
// This interlocking relies on the fact that onFrameAvailable gets called
// synchronously from SurfaceTexture::queueBuffer.
class FrameCondition : public SurfaceTexture::FrameAvailableListener {
public:
// waitForFrame waits for the next frame to arrive. This should be
// called from the consumer thread once for every frame expected by the
// test.
void waitForFrame() {
LOGV("+waitForFrame");
Mutex::Autolock lock(mMutex);
status_t result = mFrameAvailableCondition.wait(mMutex);
LOGV("-waitForFrame");
}
// Allow the producer to return from its swapBuffers call and continue
// on to produce the next frame. This should be called by the consumer
// thread once for every frame expected by the test.
void finishFrame() {
LOGV("+finishFrame");
Mutex::Autolock lock(mMutex);
mFrameFinishCondition.signal();
LOGV("-finishFrame");
}
// This should be called by SurfaceTexture on the producer thread.
virtual void onFrameAvailable() {
LOGV("+onFrameAvailable");
Mutex::Autolock lock(mMutex);
mFrameAvailableCondition.signal();
mFrameFinishCondition.wait(mMutex);
LOGV("-onFrameAvailable");
}
protected:
Mutex mMutex;
Condition mFrameAvailableCondition;
Condition mFrameFinishCondition;
};
SurfaceTextureGLToGLTest():
mProducerEglSurface(EGL_NO_SURFACE),
mProducerEglContext(EGL_NO_CONTEXT) {
}
virtual void SetUp() {
SurfaceTextureGLTest::SetUp();
EGLConfig myConfig = {0};
EGLint numConfigs = 0;
EXPECT_TRUE(eglChooseConfig(mEglDisplay, getConfigAttribs(), &myConfig,
1, &numConfigs));
ASSERT_EQ(EGL_SUCCESS, eglGetError());
mProducerEglSurface = eglCreateWindowSurface(mEglDisplay, myConfig,
mANW.get(), NULL);
ASSERT_EQ(EGL_SUCCESS, eglGetError());
ASSERT_NE(EGL_NO_SURFACE, mProducerEglSurface);
mProducerEglContext = eglCreateContext(mEglDisplay, myConfig,
EGL_NO_CONTEXT, getContextAttribs());
ASSERT_EQ(EGL_SUCCESS, eglGetError());
ASSERT_NE(EGL_NO_CONTEXT, mProducerEglContext);
mFC = new FrameCondition();
mST->setFrameAvailableListener(mFC);
}
virtual void TearDown() {
if (mProducerThread != NULL) {
mProducerThread->requestExitAndWait();
}
if (mProducerEglContext != EGL_NO_CONTEXT) {
eglDestroyContext(mEglDisplay, mProducerEglContext);
}
if (mProducerEglSurface != EGL_NO_SURFACE) {
eglDestroySurface(mEglDisplay, mProducerEglSurface);
}
mProducerThread.clear();
mFC.clear();
}
void runProducerThread(const sp<ProducerThread> producerThread) {
ASSERT_TRUE(mProducerThread == NULL);
mProducerThread = producerThread;
producerThread->setEglObjects(mEglDisplay, mProducerEglSurface,
mProducerEglContext);
producerThread->run();
}
EGLSurface mProducerEglSurface;
EGLContext mProducerEglContext;
sp<ProducerThread> mProducerThread;
sp<FrameCondition> mFC;
};
// XXX: This test is disabled because it causes hangs on some devices.
TEST_F(SurfaceTextureGLToGLTest, DISABLED_UpdateTexImageBeforeFrameFinishedWorks) {
class PT : public ProducerThread {
virtual void render() {
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
swapBuffers();
}
};
runProducerThread(new PT());
mFC->waitForFrame();
mST->updateTexImage();
mFC->finishFrame();
// TODO: Add frame verification once RGB TEX_EXTERNAL_OES is supported!
}
TEST_F(SurfaceTextureGLToGLTest, UpdateTexImageAfterFrameFinishedWorks) {
class PT : public ProducerThread {
virtual void render() {
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
swapBuffers();
}
};
runProducerThread(new PT());
mFC->waitForFrame();
mFC->finishFrame();
mST->updateTexImage();
// TODO: Add frame verification once RGB TEX_EXTERNAL_OES is supported!
}
// XXX: This test is disabled because it causes hangs on some devices.
TEST_F(SurfaceTextureGLToGLTest, DISABLED_RepeatedUpdateTexImageBeforeFrameFinishedWorks) {
enum { NUM_ITERATIONS = 1024 };
class PT : public ProducerThread {
virtual void render() {
for (int i = 0; i < NUM_ITERATIONS; i++) {
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
LOGV("+swapBuffers");
swapBuffers();
LOGV("-swapBuffers");
}
}
};
runProducerThread(new PT());
for (int i = 0; i < NUM_ITERATIONS; i++) {
mFC->waitForFrame();
LOGV("+updateTexImage");
mST->updateTexImage();
LOGV("-updateTexImage");
mFC->finishFrame();
// TODO: Add frame verification once RGB TEX_EXTERNAL_OES is supported!
}
}
// XXX: This test is disabled because it causes hangs on some devices.
TEST_F(SurfaceTextureGLToGLTest, DISABLED_RepeatedUpdateTexImageAfterFrameFinishedWorks) {
enum { NUM_ITERATIONS = 1024 };
class PT : public ProducerThread {
virtual void render() {
for (int i = 0; i < NUM_ITERATIONS; i++) {
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
LOGV("+swapBuffers");
swapBuffers();
LOGV("-swapBuffers");
}
}
};
runProducerThread(new PT());
for (int i = 0; i < NUM_ITERATIONS; i++) {
mFC->waitForFrame();
mFC->finishFrame();
LOGV("+updateTexImage");
mST->updateTexImage();
LOGV("-updateTexImage");
// TODO: Add frame verification once RGB TEX_EXTERNAL_OES is supported!
}
}
} // namespace android