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.
diff --git a/include/gui/SurfaceTexture.h b/include/gui/SurfaceTexture.h
index 2b31462..9294df6 100644
--- a/include/gui/SurfaceTexture.h
+++ b/include/gui/SurfaceTexture.h
@@ -188,6 +188,11 @@
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 @@
// 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 @@
// 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 @@
// variables of SurfaceTexture objects. It must be locked whenever the
// member variables are accessed.
mutable Mutex mMutex;
+
};
// ----------------------------------------------------------------------------
diff --git a/libs/gui/SurfaceTexture.cpp b/libs/gui/SurfaceTexture.cpp
index ee97dcf..2cda4c8 100644
--- a/libs/gui/SurfaceTexture.cpp
+++ b/libs/gui/SurfaceTexture.cpp
@@ -96,6 +96,7 @@
sp<ISurfaceComposer> composer(ComposerService::getComposerService());
mGraphicBufferAlloc = composer->createGraphicBufferAlloc();
mNextCrop.makeInvalid();
+ memcpy(mCurrentTransformMatrix, mtxIdentity, sizeof(mCurrentTransformMatrix));
}
SurfaceTexture::~SurfaceTexture() {
@@ -547,6 +548,7 @@
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 @@
}
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 @@
// 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() {
diff --git a/libs/gui/tests/SurfaceTextureClient_test.cpp b/libs/gui/tests/SurfaceTextureClient_test.cpp
index 2f704c8..da04b4a 100644
--- a/libs/gui/tests/SurfaceTextureClient_test.cpp
+++ b/libs/gui/tests/SurfaceTextureClient_test.cpp
@@ -514,4 +514,112 @@
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
diff --git a/libs/gui/tests/SurfaceTexture_test.cpp b/libs/gui/tests/SurfaceTexture_test.cpp
index 8747ba5..16280d2 100644
--- a/libs/gui/tests/SurfaceTexture_test.cpp
+++ b/libs/gui/tests/SurfaceTexture_test.cpp
@@ -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 @@
}
}
+/*
+ * 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