Merge "First draft of fbo in renderscript. Updating samples and benchmark"
diff --git a/include/gui/ISurfaceTexture.h b/include/gui/ISurfaceTexture.h
index 6ed3c6f..d2d3bb8 100644
--- a/include/gui/ISurfaceTexture.h
+++ b/include/gui/ISurfaceTexture.h
@@ -36,6 +36,8 @@
 public:
     DECLARE_META_INTERFACE(SurfaceTexture);
 
+    enum { BUFFER_NEEDS_REALLOCATION = 1 };
+
     // requestBuffer requests a new buffer for the given index. The server (i.e.
     // the ISurfaceTexture implementation) assigns the newly created buffer to
     // the given slot index, and the client is expected to mirror the
@@ -56,6 +58,8 @@
     // should call requestBuffer to assign a new buffer to that slot. The client
     // is expected to either call cancelBuffer on the dequeued slot or to fill
     // in the contents of its associated buffer contents and call queueBuffer.
+    // If dequeueBuffer return BUFFER_NEEDS_REALLOCATION, the client is
+    // expected to call requestBuffer immediately.
     virtual status_t dequeueBuffer(int *slot) = 0;
 
     // queueBuffer indicates that the client has finished filling in the
diff --git a/include/gui/SurfaceTexture.h b/include/gui/SurfaceTexture.h
index afa64d3..585d288 100644
--- a/include/gui/SurfaceTexture.h
+++ b/include/gui/SurfaceTexture.h
@@ -121,6 +121,12 @@
     // buffers before the client is done with them.
     sp<IBinder> getAllocator();
 
+    // setDefaultBufferSize is used to set the size of buffers returned by
+    // requestBuffers when a with and height of zero is requested.
+    // A call to setDefaultBufferSize() may trigger requestBuffers() to
+    // be called from the client.
+    status_t setDefaultBufferSize(uint32_t w, uint32_t h);
+
 private:
 
     // freeAllBuffers frees the resources (both GraphicBuffer and EGLImage) for
@@ -158,6 +164,23 @@
     // for a slot when requestBuffer is called with that slot's index.
     BufferSlot mSlots[NUM_BUFFER_SLOTS];
 
+    // mDefaultWidth holds the default width of allocated buffers. It is used
+    // in requestBuffers() if a width and height of zero is specified.
+    uint32_t mDefaultWidth;
+
+    // mDefaultHeight holds the default height of allocated buffers. It is used
+    // in requestBuffers() if a width and height of zero is specified.
+    uint32_t mDefaultHeight;
+
+    // mPixelFormat holds the pixel format of allocated buffers. It is used
+    // in requestBuffers() if a format of zero is specified.
+    uint32_t mPixelFormat;
+
+    // mUseDefaultSize indicates whether or not the default size should be used
+    // that is, if the last requestBuffer has been called with both width
+    // and height null.
+    bool mUseDefaultSize;
+
     // 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.
diff --git a/include/ui/Input.h b/include/ui/Input.h
index 8e8b61b..0dc29c8 100644
--- a/include/ui/Input.h
+++ b/include/ui/Input.h
@@ -37,10 +37,16 @@
  * Additional private constants not defined in ndk/ui/input.h.
  */
 enum {
-    /*
-     * Private control to determine when an app is tracking a key sequence.
-     */
-    AKEY_EVENT_FLAG_START_TRACKING = 0x40000000
+    /* Private control to determine when an app is tracking a key sequence. */
+    AKEY_EVENT_FLAG_START_TRACKING = 0x40000000,
+
+    /* Key event is inconsistent with previously sent key events. */
+    AKEY_EVENT_FLAG_TAINTED = 0x80000000,
+};
+
+enum {
+    /* Motion event is inconsistent with previously sent motion events. */
+    AMOTION_EVENT_FLAG_TAINTED = 0x80000000,
 };
 
 enum {
@@ -128,6 +134,12 @@
     // input device or an application with system-wide event injection permission.
     POLICY_FLAG_TRUSTED = 0x02000000,
 
+    // Indicates that the input event has passed through an input filter.
+    POLICY_FLAG_FILTERED = 0x04000000,
+
+    // Disables automatic key repeating behavior.
+    POLICY_FLAG_DISABLE_KEY_REPEAT = 0x08000000,
+
     /* These flags are set by the input reader policy as it intercepts each event. */
 
     // Indicates that the screen was off when the event was received and the event
@@ -322,6 +334,8 @@
 
     inline int32_t getFlags() const { return mFlags; }
 
+    inline void setFlags(int32_t flags) { mFlags = flags; }
+
     inline int32_t getEdgeFlags() const { return mEdgeFlags; }
 
     inline void setEdgeFlags(int32_t edgeFlags) { mEdgeFlags = edgeFlags; }
diff --git a/include/ui/egl/android_natives.h b/include/ui/egl/android_natives.h
index 972e799..0a6e4fb 100644
--- a/include/ui/egl/android_natives.h
+++ b/include/ui/egl/android_natives.h
@@ -291,6 +291,9 @@
     void* reserved_proc[2];
 };
 
+// Backwards compatibility...  please switch to ANativeWindow.
+typedef struct ANativeWindow android_native_window_t;
+
 /*
  *  native_window_set_usage(..., usage)
  *  Sets the intended usage flags for the next buffers
diff --git a/libs/gui/SurfaceTexture.cpp b/libs/gui/SurfaceTexture.cpp
index cdaca47..f4e2a67 100644
--- a/libs/gui/SurfaceTexture.cpp
+++ b/libs/gui/SurfaceTexture.cpp
@@ -76,6 +76,10 @@
 static void mtxMul(float out[16], const float a[16], const float b[16]);
 
 SurfaceTexture::SurfaceTexture(GLuint tex) :
+    mDefaultWidth(1),
+    mDefaultHeight(1),
+    mPixelFormat(PIXEL_FORMAT_RGBA_8888),
+    mUseDefaultSize(true),
     mBufferCount(MIN_BUFFER_SLOTS),
     mCurrentTexture(INVALID_BUFFER_SLOT),
     mCurrentTransform(0),
@@ -115,6 +119,16 @@
     return OK;
 }
 
+status_t SurfaceTexture::setDefaultBufferSize(uint32_t w, uint32_t h)
+{
+    Mutex::Autolock lock(mMutex);
+    if ((w != mDefaultWidth) || (h != mDefaultHeight)) {
+        mDefaultWidth = w;
+        mDefaultHeight = h;
+    }
+    return OK;
+}
+
 sp<GraphicBuffer> SurfaceTexture::requestBuffer(int buf,
         uint32_t w, uint32_t h, uint32_t format, uint32_t usage) {
     LOGV("SurfaceTexture::requestBuffer");
@@ -124,12 +138,34 @@
                 mBufferCount, buf);
         return 0;
     }
+    if ((w && !h) || (!w & h)) {
+        LOGE("requestBuffer: invalid size: w=%u, h=%u: %d", w, h, buf);
+        return 0;
+    }
+
+    const bool useDefaultSize = !w && !h;
+    if (useDefaultSize) {
+        // use the default size
+        w = mDefaultWidth;
+        h = mDefaultHeight;
+    }
+
+    const bool updateFormat = (format != 0);
+    if (!updateFormat) {
+        // keep the current (or default) format
+        format = mPixelFormat;
+    }
+
     usage |= GraphicBuffer::USAGE_HW_TEXTURE;
     sp<GraphicBuffer> graphicBuffer(
             mGraphicBufferAlloc->createGraphicBuffer(w, h, format, usage));
     if (graphicBuffer == 0) {
         LOGE("requestBuffer: SurfaceComposer::createGraphicBuffer failed");
     } else {
+        mUseDefaultSize = useDefaultSize;
+        if (updateFormat) {
+            mPixelFormat = format;
+        }
         mSlots[buf].mGraphicBuffer = graphicBuffer;
         if (mSlots[buf].mEglImage != EGL_NO_IMAGE_KHR) {
             eglDestroyImageKHR(mSlots[buf].mEglDisplay, mSlots[buf].mEglImage);
@@ -155,7 +191,18 @@
     if (found == INVALID_BUFFER_SLOT) {
         return -EBUSY;
     }
+
     *buf = found;
+
+    const sp<GraphicBuffer>& buffer(mSlots[found].mGraphicBuffer);
+    if (buffer == NULL) {
+        return ISurfaceTexture::BUFFER_NEEDS_REALLOCATION;
+    }
+    if ((mUseDefaultSize) &&
+        ((uint32_t(buffer->width) != mDefaultWidth) ||
+         (uint32_t(buffer->height) != mDefaultHeight))) {
+        return ISurfaceTexture::BUFFER_NEEDS_REALLOCATION;
+    }
     return OK;
 }
 
@@ -312,10 +359,10 @@
         } else {
             tx = 0.0f;
         }
-        if (mCurrentCrop.right < buf->getWidth()) {
+        if (mCurrentCrop.right < int32_t(buf->getWidth())) {
             xshrink++;
         }
-        if (mCurrentCrop.bottom < buf->getHeight()) {
+        if (mCurrentCrop.bottom < int32_t(buf->getHeight())) {
             ty = (float(buf->getHeight() - mCurrentCrop.bottom) + 1.0f) /
                     float(buf->getHeight());
             yshrink++;
diff --git a/libs/gui/SurfaceTextureClient.cpp b/libs/gui/SurfaceTextureClient.cpp
index a4812d0..29fc4d3 100644
--- a/libs/gui/SurfaceTextureClient.cpp
+++ b/libs/gui/SurfaceTextureClient.cpp
@@ -25,8 +25,8 @@
 
 SurfaceTextureClient::SurfaceTextureClient(
         const sp<ISurfaceTexture>& surfaceTexture):
-        mSurfaceTexture(surfaceTexture), mAllocator(0), mReqWidth(1),
-        mReqHeight(1), mReqFormat(DEFAULT_FORMAT), mReqUsage(0),
+        mSurfaceTexture(surfaceTexture), mAllocator(0), mReqWidth(0),
+        mReqHeight(0), mReqFormat(DEFAULT_FORMAT), mReqUsage(0),
         mTimestamp(NATIVE_WINDOW_TIMESTAMP_AUTO), mMutex() {
     // Initialize the ANativeWindow function pointers.
     ANativeWindow::setSwapInterval  = setSwapInterval;
@@ -100,7 +100,8 @@
         return err;
     }
     sp<GraphicBuffer>& gbuf(mSlots[buf]);
-    if (gbuf == 0 || gbuf->getWidth() != mReqWidth ||
+    if (err == ISurfaceTexture::BUFFER_NEEDS_REALLOCATION ||
+        gbuf == 0 || gbuf->getWidth() != mReqWidth ||
         gbuf->getHeight() != mReqHeight ||
         uint32_t(gbuf->getPixelFormat()) != mReqFormat ||
         (gbuf->getUsage() & mReqUsage) != mReqUsage) {
diff --git a/libs/gui/tests/SurfaceTextureClient_test.cpp b/libs/gui/tests/SurfaceTextureClient_test.cpp
index 94b05bc..348171d 100644
--- a/libs/gui/tests/SurfaceTextureClient_test.cpp
+++ b/libs/gui/tests/SurfaceTextureClient_test.cpp
@@ -100,4 +100,151 @@
     eglTerminate(dpy);
 }
 
+TEST_F(SurfaceTextureClientTest, BufferGeometryInvalidSizesFail) {
+    sp<ANativeWindow> anw(mSTC);
+
+    EXPECT_GT(OK, native_window_set_buffers_geometry(anw.get(), -1,  0,  0));
+    EXPECT_GT(OK, native_window_set_buffers_geometry(anw.get(),  0, -1,  0));
+    EXPECT_GT(OK, native_window_set_buffers_geometry(anw.get(),  0,  0, -1));
+    EXPECT_GT(OK, native_window_set_buffers_geometry(anw.get(), -1, -1,  0));
+    EXPECT_GT(OK, native_window_set_buffers_geometry(anw.get(),  0,  8,  0));
+    EXPECT_GT(OK, native_window_set_buffers_geometry(anw.get(),  8,  0,  0));
+}
+
+TEST_F(SurfaceTextureClientTest, DefaultGeometryValues) {
+    sp<ANativeWindow> anw(mSTC);
+    android_native_buffer_t* buf;
+    ASSERT_EQ(OK, anw->dequeueBuffer(anw.get(), &buf));
+    EXPECT_EQ(1, buf->width);
+    EXPECT_EQ(1, buf->height);
+    EXPECT_EQ(PIXEL_FORMAT_RGBA_8888, buf->format);
+    ASSERT_EQ(OK, anw->cancelBuffer(anw.get(), buf));
+}
+
+TEST_F(SurfaceTextureClientTest, BufferGeometryCanBeSet) {
+    sp<ANativeWindow> anw(mSTC);
+    android_native_buffer_t* buf;
+    EXPECT_EQ(OK, native_window_set_buffers_geometry(anw.get(), 16, 8, PIXEL_FORMAT_RGB_565));
+    ASSERT_EQ(OK, anw->dequeueBuffer(anw.get(), &buf));
+    EXPECT_EQ(16, buf->width);
+    EXPECT_EQ(8, buf->height);
+    EXPECT_EQ(PIXEL_FORMAT_RGB_565, buf->format);
+    ASSERT_EQ(OK, anw->cancelBuffer(anw.get(), buf));
+}
+
+TEST_F(SurfaceTextureClientTest, BufferGeometryDefaultSizeSetFormat) {
+    sp<ANativeWindow> anw(mSTC);
+    android_native_buffer_t* buf;
+    EXPECT_EQ(OK, native_window_set_buffers_geometry(anw.get(), 0, 0, PIXEL_FORMAT_RGB_565));
+    ASSERT_EQ(OK, anw->dequeueBuffer(anw.get(), &buf));
+    EXPECT_EQ(1, buf->width);
+    EXPECT_EQ(1, buf->height);
+    EXPECT_EQ(PIXEL_FORMAT_RGB_565, buf->format);
+    ASSERT_EQ(OK, anw->cancelBuffer(anw.get(), buf));
+}
+
+TEST_F(SurfaceTextureClientTest, BufferGeometrySetSizeDefaultFormat) {
+    sp<ANativeWindow> anw(mSTC);
+    android_native_buffer_t* buf;
+    EXPECT_EQ(OK, native_window_set_buffers_geometry(anw.get(), 16, 8, 0));
+    ASSERT_EQ(OK, anw->dequeueBuffer(anw.get(), &buf));
+    EXPECT_EQ(16, buf->width);
+    EXPECT_EQ(8, buf->height);
+    EXPECT_EQ(PIXEL_FORMAT_RGBA_8888, buf->format);
+    ASSERT_EQ(OK, anw->cancelBuffer(anw.get(), buf));
+}
+
+TEST_F(SurfaceTextureClientTest, BufferGeometrySizeCanBeUnset) {
+    sp<ANativeWindow> anw(mSTC);
+    android_native_buffer_t* buf;
+    EXPECT_EQ(OK, native_window_set_buffers_geometry(anw.get(), 16, 8, 0));
+    ASSERT_EQ(OK, anw->dequeueBuffer(anw.get(), &buf));
+    EXPECT_EQ(16, buf->width);
+    EXPECT_EQ(8, buf->height);
+    EXPECT_EQ(PIXEL_FORMAT_RGBA_8888, buf->format);
+    ASSERT_EQ(OK, anw->cancelBuffer(anw.get(), buf));
+    EXPECT_EQ(OK, native_window_set_buffers_geometry(anw.get(), 0, 0, 0));
+    ASSERT_EQ(OK, anw->dequeueBuffer(anw.get(), &buf));
+    EXPECT_EQ(1, buf->width);
+    EXPECT_EQ(1, buf->height);
+    EXPECT_EQ(PIXEL_FORMAT_RGBA_8888, buf->format);
+    ASSERT_EQ(OK, anw->cancelBuffer(anw.get(), buf));
+}
+
+TEST_F(SurfaceTextureClientTest, BufferGeometrySizeCanBeChangedWithoutFormat) {
+    sp<ANativeWindow> anw(mSTC);
+    android_native_buffer_t* buf;
+    EXPECT_EQ(OK, native_window_set_buffers_geometry(anw.get(), 0, 0, PIXEL_FORMAT_RGB_565));
+    ASSERT_EQ(OK, anw->dequeueBuffer(anw.get(), &buf));
+    EXPECT_EQ(1, buf->width);
+    EXPECT_EQ(1, buf->height);
+    EXPECT_EQ(PIXEL_FORMAT_RGB_565, buf->format);
+    ASSERT_EQ(OK, anw->cancelBuffer(anw.get(), buf));
+    EXPECT_EQ(OK, native_window_set_buffers_geometry(anw.get(), 16, 8, 0));
+    ASSERT_EQ(OK, anw->dequeueBuffer(anw.get(), &buf));
+    EXPECT_EQ(16, buf->width);
+    EXPECT_EQ(8, buf->height);
+    EXPECT_EQ(PIXEL_FORMAT_RGB_565, buf->format);
+    ASSERT_EQ(OK, anw->cancelBuffer(anw.get(), buf));
+}
+
+TEST_F(SurfaceTextureClientTest, SurfaceTextureSetDefaultSize) {
+    sp<ANativeWindow> anw(mSTC);
+    sp<SurfaceTexture> st(mST);
+    android_native_buffer_t* buf;
+    EXPECT_EQ(OK, st->setDefaultBufferSize(16, 8));
+    ASSERT_EQ(OK, anw->dequeueBuffer(anw.get(), &buf));
+    EXPECT_EQ(16, buf->width);
+    EXPECT_EQ(8, buf->height);
+    EXPECT_EQ(PIXEL_FORMAT_RGBA_8888, buf->format);
+    ASSERT_EQ(OK, anw->cancelBuffer(anw.get(), buf));
+}
+
+TEST_F(SurfaceTextureClientTest, SurfaceTextureSetDefaultSizeAfterDequeue) {
+    sp<ANativeWindow> anw(mSTC);
+    sp<SurfaceTexture> st(mST);
+    android_native_buffer_t* buf[2];
+    ASSERT_EQ(OK, anw->dequeueBuffer(anw.get(), &buf[0]));
+    ASSERT_EQ(OK, anw->dequeueBuffer(anw.get(), &buf[1]));
+    EXPECT_NE(buf[0], buf[1]);
+    ASSERT_EQ(OK, anw->cancelBuffer(anw.get(), buf[0]));
+    ASSERT_EQ(OK, anw->cancelBuffer(anw.get(), buf[1]));
+    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]));
+    EXPECT_NE(buf[0], buf[1]);
+    EXPECT_EQ(16, buf[0]->width);
+    EXPECT_EQ(16, buf[1]->width);
+    EXPECT_EQ(8, buf[0]->height);
+    EXPECT_EQ(8, buf[1]->height);
+    ASSERT_EQ(OK, anw->cancelBuffer(anw.get(), buf[0]));
+    ASSERT_EQ(OK, anw->cancelBuffer(anw.get(), buf[1]));
+}
+
+TEST_F(SurfaceTextureClientTest, SurfaceTextureSetDefaultSizeVsGeometry) {
+    sp<ANativeWindow> anw(mSTC);
+    sp<SurfaceTexture> st(mST);
+    android_native_buffer_t* buf[2];
+    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]));
+    EXPECT_NE(buf[0], buf[1]);
+    EXPECT_EQ(16, buf[0]->width);
+    EXPECT_EQ(16, buf[1]->width);
+    EXPECT_EQ(8, buf[0]->height);
+    EXPECT_EQ(8, buf[1]->height);
+    ASSERT_EQ(OK, anw->cancelBuffer(anw.get(), buf[0]));
+    ASSERT_EQ(OK, anw->cancelBuffer(anw.get(), buf[1]));
+    EXPECT_EQ(OK, native_window_set_buffers_geometry(anw.get(), 12, 24, 0));
+    ASSERT_EQ(OK, anw->dequeueBuffer(anw.get(), &buf[0]));
+    ASSERT_EQ(OK, anw->dequeueBuffer(anw.get(), &buf[1]));
+    EXPECT_NE(buf[0], buf[1]);
+    EXPECT_EQ(12, buf[0]->width);
+    EXPECT_EQ(12, buf[1]->width);
+    EXPECT_EQ(24, buf[0]->height);
+    EXPECT_EQ(24, buf[1]->height);
+    ASSERT_EQ(OK, anw->cancelBuffer(anw.get(), buf[0]));
+    ASSERT_EQ(OK, anw->cancelBuffer(anw.get(), buf[1]));
+}
+
 }
diff --git a/libs/ui/Input.cpp b/libs/ui/Input.cpp
index 440ec00..bbe579e 100644
--- a/libs/ui/Input.cpp
+++ b/libs/ui/Input.cpp
@@ -724,7 +724,7 @@
             LOGD("  %d: position (%0.3f, %0.3f), vx=%0.3f, vy=%0.3f, speed=%0.3f",
                     id, positions[index].x, positions[index].y, vx, vy, sqrtf(vx * vx + vy * vy));
         } else {
-            assert(vx == 0 && vy == 0);
+            LOG_ASSERT(vx == 0 && vy == 0);
             LOGD("  %d: position (%0.3f, %0.3f), velocity not available",
                     id, positions[index].x, positions[index].y);
         }
diff --git a/opengl/libs/EGL/egl.cpp b/opengl/libs/EGL/egl.cpp
index a53b375..6474c87 100644
--- a/opengl/libs/EGL/egl.cpp
+++ b/opengl/libs/EGL/egl.cpp
@@ -120,7 +120,7 @@
 
     SortedVector<egl_object_t*> objects;
 
-    egl_display_t() : magic('_dpy'), numTotalConfigs(0), configs(0) { }
+    egl_display_t() : magic('_dpy'), numTotalConfigs(0), configs(0), refs(0) { }
     ~egl_display_t() { magic = 0; }
     inline bool isReady() const { return (refs > 0); }
     inline bool isValid() const { return magic == '_dpy'; }
diff --git a/opengl/libs/GLES2_dbg/src/api.h b/opengl/libs/GLES2_dbg/src/api.h
index 93aef62..b9fc341 100644
--- a/opengl/libs/GLES2_dbg/src/api.h
+++ b/opengl/libs/GLES2_dbg/src/api.h
@@ -22,8 +22,7 @@
     unsigned readSize = GetBytesPerPixel(readFormat, readType) * width * height; \
     void * readData = dbg->GetReadPixelsBuffer(readSize); \
     dbg->hooks->gl.glReadPixels(x, y, width, height, readFormat, readType, readData); \
-    const unsigned compressedSize = dbg->CompressReadPixelBuffer(); \
-    msg.set_data(dbg->lzf_buf, compressedSize); \
+    dbg->CompressReadPixelBuffer(msg.mutable_data()); \
     msg.set_data_type(msg.ReferencedImage); \
     msg.set_pixel_format(readFormat); \
     msg.set_pixel_type(readType);
@@ -43,8 +42,7 @@
         DbgContext * const dbg = getDbgContextThreadSpecific(); \
         const unsigned size = GetBytesPerPixel(format, type) * width * height; \
         assert(0 < size); \
-        unsigned compressedSize = dbg->Compress(pixels, size); \
-        msg.set_data(dbg->lzf_buf, compressedSize); \
+        dbg->Compress(pixels, size, msg.mutable_data()); \
     }
 
 #define EXTEND_Debug_glTexSubImage2D EXTEND_Debug_glTexImage2D
diff --git a/opengl/libs/GLES2_dbg/src/dbgcontext.cpp b/opengl/libs/GLES2_dbg/src/dbgcontext.cpp
index 3ef0752..cc7336c 100644
--- a/opengl/libs/GLES2_dbg/src/dbgcontext.cpp
+++ b/opengl/libs/GLES2_dbg/src/dbgcontext.cpp
@@ -26,8 +26,7 @@
 
 DbgContext::DbgContext(const unsigned version, const gl_hooks_t * const hooks,
                        const unsigned MAX_VERTEX_ATTRIBS)
-        : lzf_buf(NULL), lzf_bufSize(0)
-        , lzf_readIndex(0), lzf_refSize(0), lzf_refBufSize(0)
+        : lzf_buf(NULL), lzf_readIndex(0), lzf_refSize(0), lzf_refBufSize(0)
         , version(version), hooks(hooks)
         , MAX_VERTEX_ATTRIBS(MAX_VERTEX_ATTRIBS)
         , vertexAttribs(new VertexAttrib[MAX_VERTEX_ATTRIBS])
@@ -109,16 +108,26 @@
     }
 }
 
-unsigned DbgContext::Compress(const void * in_data, unsigned in_len)
+void DbgContext::Compress(const void * in_data, unsigned int in_len,
+                          std::string * const outStr)
 {
-    if (lzf_bufSize < in_len * 1.05f) {
-        lzf_bufSize = in_len * 1.05f;
-        lzf_buf = (char *)realloc(lzf_buf, lzf_bufSize);
+    if (!lzf_buf)
+        lzf_buf = (char *)malloc(LZF_CHUNK_SIZE);
+    const uint32_t totalDecompSize = in_len;
+    outStr->append((const char *)&totalDecompSize, sizeof(totalDecompSize));
+    for (unsigned int i = 0; i < in_len; i += LZF_CHUNK_SIZE) {
+        uint32_t chunkSize = LZF_CHUNK_SIZE;
+        if (i + LZF_CHUNK_SIZE > in_len)
+            chunkSize = in_len - i;
+        const uint32_t compSize = lzf_compress((const char *)in_data + i, chunkSize,
+                                               lzf_buf, LZF_CHUNK_SIZE);
+        outStr->append((const char *)&chunkSize, sizeof(chunkSize));
+        outStr->append((const char *)&compSize, sizeof(compSize));
+        if (compSize > 0)
+            outStr->append(lzf_buf, compSize);
+        else // compressed chunk bigger than LZF_CHUNK_SIZE (and uncompressed)
+            outStr->append((const char *)in_data + i, chunkSize);
     }
-    unsigned compressedSize = lzf_compress((const char *)in_data,
-                                           in_len, lzf_buf, lzf_bufSize);
-    assert (0 < compressedSize);
-    return compressedSize;
 }
 
 void * DbgContext::GetReadPixelsBuffer(const unsigned size)
@@ -140,13 +149,13 @@
     return lzf_ref[lzf_readIndex];
 }
 
-unsigned DbgContext::CompressReadPixelBuffer()
+void DbgContext::CompressReadPixelBuffer(std::string * const outStr)
 {
     unsigned * const ref = lzf_ref[lzf_readIndex ^ 1];
     unsigned * const src = lzf_ref[lzf_readIndex];
     for (unsigned i = 0; i < lzf_refSize / sizeof(*ref) + 1; i++)
         ref[i] ^= src[i];
-    return Compress(ref, lzf_refSize);
+    Compress(ref, lzf_refSize, outStr);
 }
 
 void DbgContext::glUseProgram(GLuint program)
diff --git a/opengl/libs/GLES2_dbg/src/header.h b/opengl/libs/GLES2_dbg/src/header.h
index 7e9aa4e..9218da5 100644
--- a/opengl/libs/GLES2_dbg/src/header.h
+++ b/opengl/libs/GLES2_dbg/src/header.h
@@ -56,21 +56,18 @@
 namespace android
 {
 
-struct GLFunctionBitfield
-{
+struct GLFunctionBitfield {
     unsigned char field [24]; // 8 * 24 = 192
-    
-    void Bit(const glesv2debugger::Message_Function function, bool bit)
-    {
+
+    void Bit(const glesv2debugger::Message_Function function, bool bit) {
         const unsigned byte = function / 8, mask = 1 << (function % 8);
         if (bit)
             field[byte] |= mask;
         else
             field[byte] &= ~mask;
     }
-    
-    bool Bit(const glesv2debugger::Message_Function function) const
-    {
+
+    bool Bit(const glesv2debugger::Message_Function function) const {
         const unsigned byte = function / 8, mask = 1 << (function % 8);
         return field[byte] & mask;
     }
@@ -78,7 +75,8 @@
 
 struct DbgContext {
 private:
-    unsigned lzf_bufSize;
+    static const unsigned int LZF_CHUNK_SIZE = 256 * 1024;
+    char * lzf_buf; // malloc / free; for lzf chunk compression
 
     // used as buffer and reference frame for ReadPixels; malloc/free
     unsigned * lzf_ref [2];
@@ -86,14 +84,12 @@
     unsigned lzf_refSize, lzf_refBufSize; // bytes
 
 public:
-    char * lzf_buf; // auto malloc/free; output of lzf_compress
-
     const unsigned version; // 0 is GLES1, 1 is GLES2
     const gl_hooks_t * const hooks;
     const unsigned MAX_VERTEX_ATTRIBS;
-    
+
     GLFunctionBitfield expectResponse;
-    
+
     struct VertexAttrib {
         GLenum type; // element data type
         unsigned size; // number of data per element
@@ -122,21 +118,23 @@
     GLuint program;
     unsigned maxAttrib; // number of slots used by program
 
-    DbgContext(const unsigned version, const gl_hooks_t * const hooks, const unsigned MAX_VERTEX_ATTRIBS);
+    DbgContext(const unsigned version, const gl_hooks_t * const hooks,
+               const unsigned MAX_VERTEX_ATTRIBS);
     ~DbgContext();
 
     void Fetch(const unsigned index, std::string * const data) const;
-    unsigned Compress(const void * in_data, unsigned in_len); // compressed to lzf_buf
+    void Compress(const void * in_data, unsigned in_len, std::string * const outStr);
     void * GetReadPixelsBuffer(const unsigned size);
     bool IsReadPixelBuffer(const void * const ptr)  {
         return ptr == lzf_ref[lzf_readIndex];
     }
-    unsigned CompressReadPixelBuffer();
+    void CompressReadPixelBuffer(std::string * const outStr);
 
     void glUseProgram(GLuint program);
     void glEnableVertexAttribArray(GLuint index);
     void glDisableVertexAttribArray(GLuint index);
-    void glVertexAttribPointer(GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr);
+    void glVertexAttribPointer(GLuint indx, GLint size, GLenum type,
+                               GLboolean normalized, GLsizei stride, const GLvoid* ptr);
     void glBindBuffer(GLenum target, GLuint buffer);
     void glBufferData(GLenum target, GLsizeiptr size, const GLvoid* data, GLenum usage);
     void glBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid* data);
@@ -148,7 +146,8 @@
 #define DBGCONTEXT(ctx) DbgContext * const ctx = getDbgContextThreadSpecific();
 
 struct FunctionCall {
-    virtual const int * operator()(gl_hooks_t::gl_t const * const _c, glesv2debugger::Message & msg) = 0;
+    virtual const int * operator()(gl_hooks_t::gl_t const * const _c,
+                                   glesv2debugger::Message & msg) = 0;
     virtual ~FunctionCall() {}
 };
 
@@ -168,5 +167,5 @@
 float Send(const glesv2debugger::Message & msg, glesv2debugger::Message & cmd);
 void SetProp(DbgContext * const dbg, const glesv2debugger::Message & cmd);
 const int * GenerateCall(DbgContext * const dbg, const glesv2debugger::Message & cmd,
-                  glesv2debugger::Message & msg, const int * const prevRet);
+                         glesv2debugger::Message & msg, const int * const prevRet);
 }; // namespace android {
diff --git a/opengl/libs/GLES2_dbg/src/vertex.cpp b/opengl/libs/GLES2_dbg/src/vertex.cpp
index a9cf9e7..471e5ad 100644
--- a/opengl/libs/GLES2_dbg/src/vertex.cpp
+++ b/opengl/libs/GLES2_dbg/src/vertex.cpp
@@ -39,7 +39,6 @@
     msg.set_arg6(reinterpret_cast<int>(pixels));
 
     const unsigned size = width * height * GetBytesPerPixel(format, type);
-    unsigned compressed = 0;
     if (!expectResponse)
         cmd.set_function(glesv2debugger::Message_Function_CONTINUE);
     Send(msg, cmd);
@@ -56,13 +55,12 @@
             msg.set_type(glesv2debugger::Message_Type_AfterCall);
             msg.set_expect_response(expectResponse);
             if (dbg->IsReadPixelBuffer(pixels)) {
-                compressed = dbg->CompressReadPixelBuffer();
+                dbg->CompressReadPixelBuffer(msg.mutable_data());
                 msg.set_data_type(msg.ReferencedImage);
             } else {
-                compressed = dbg->Compress(pixels, size);
+                dbg->Compress(pixels, size, msg.mutable_data());
                 msg.set_data_type(msg.NonreferencedImage);
             }
-            msg.set_data(dbg->lzf_buf, compressed);
             if (!expectResponse)
                 cmd.set_function(glesv2debugger::Message_Function_SKIP);
             Send(msg, cmd);
diff --git a/opengl/tests/gl2_cameraeye/Android.mk b/opengl/tests/gl2_cameraeye/Android.mk
new file mode 100644
index 0000000..4a43a9e
--- /dev/null
+++ b/opengl/tests/gl2_cameraeye/Android.mk
@@ -0,0 +1,16 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+# Only compile source java files in this apk.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := GL2CameraEye
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
+
+# Use the following include to make our test apk.
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/opengl/tests/gl2_cameraeye/AndroidManifest.xml b/opengl/tests/gl2_cameraeye/AndroidManifest.xml
new file mode 100644
index 0000000..c53f7be
--- /dev/null
+++ b/opengl/tests/gl2_cameraeye/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- Declare the contents of this Android application.  The namespace
+     attribute brings in the Android platform namespace, and the package
+     supplies a unique name for the application.  When writing your
+     own application, the package name must be changed from "com.example.*"
+     to come from a domain that you own or have control over. -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.gl2cameraeye">
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-feature android:name="android.hardware.camera" />
+    <uses-feature android:name="android.hardware.camera.autofocus" />
+    <uses-feature android:glEsVersion="0x00020000" />
+    <application android:label="@string/gl2cameraeye_name">
+        <activity android:name="GL2CameraEye">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/opengl/tests/gl2_cameraeye/res/values/strings.xml b/opengl/tests/gl2_cameraeye/res/values/strings.xml
new file mode 100644
index 0000000..386b930
--- /dev/null
+++ b/opengl/tests/gl2_cameraeye/res/values/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+
+    <string name="gl2cameraeye_name">GL2CameraEye</string>
+
+</resources>
diff --git a/opengl/tests/gl2_cameraeye/src/com/android/gl2cameraeye/GL2CameraEye.java b/opengl/tests/gl2_cameraeye/src/com/android/gl2cameraeye/GL2CameraEye.java
new file mode 100644
index 0000000..561e4c5
--- /dev/null
+++ b/opengl/tests/gl2_cameraeye/src/com/android/gl2cameraeye/GL2CameraEye.java
@@ -0,0 +1,508 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gl2cameraeye;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+import android.app.Activity;
+import android.content.pm.ActivityInfo;
+import android.os.Bundle;
+import android.view.MotionEvent;
+import android.content.Context;
+import android.util.Log;
+
+import android.opengl.GLES20;
+import android.opengl.GLSurfaceView;
+import android.opengl.GLUtils;
+import android.opengl.Matrix;
+
+import android.graphics.SurfaceTexture;
+
+import android.hardware.Camera;
+import android.hardware.SensorManager;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.Sensor;
+
+public class GL2CameraEye extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mGLView = new CamGLSurfaceView(this);
+        setContentView(mGLView);
+        setRequestedOrientation (ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        mGLView.onPause();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mGLView.onResume();
+    }
+
+    private GLSurfaceView mGLView;
+}
+
+class CamGLSurfaceView extends GLSurfaceView implements SensorEventListener {
+    public CamGLSurfaceView(Context context) {
+        super(context);
+        setEGLContextClientVersion(2);
+        mRenderer = new CamRenderer(context);
+        setRenderer(mRenderer);
+
+        mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
+        mAcceleration = mSensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION);
+    }
+
+    public boolean onTouchEvent(final MotionEvent event) {
+        queueEvent(new Runnable(){
+                public void run() {
+                mRenderer.setPosition(event.getX() / getWidth(),
+                                      event.getY() / getHeight());
+            }});
+        return true;
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        mCamera.stopPreview();
+        mCamera.release();
+
+        mSensorManager.unregisterListener(this);
+    }
+
+    @Override
+    public void onResume() {
+        mCamera = Camera.open();
+        Camera.Parameters p = mCamera.getParameters();
+        // No changes to default camera parameters
+        mCamera.setParameters(p);
+
+        queueEvent(new Runnable(){
+                public void run() {
+                    mRenderer.setCamera(mCamera);
+                }});
+
+        mSensorManager.registerListener(this, mAcceleration, SensorManager.SENSOR_DELAY_GAME);
+        super.onResume();
+    }
+
+    public void onSensorChanged(SensorEvent event) {
+        if (event.sensor.getType() == Sensor.TYPE_LINEAR_ACCELERATION) {
+            final float[] accelerationVector = event.values;
+            queueEvent(new Runnable(){
+                    public void run() {
+                        mRenderer.setAcceleration(accelerationVector);
+                    }});
+        }
+    }
+
+    public void onAccuracyChanged(Sensor sensor, int accuracy) {
+        // Ignoring sensor accuracy changes.
+    }
+
+    CamRenderer mRenderer;
+    Camera mCamera;
+
+    SensorManager mSensorManager;
+    Sensor mAcceleration;
+}
+
+class CamRenderer implements GLSurfaceView.Renderer, SurfaceTexture.OnFrameAvailableListener {
+
+    public CamRenderer(Context context) {
+        mContext = context;
+
+        mTriangleVertices = ByteBuffer.allocateDirect(mTriangleVerticesData.length
+                * FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();
+        mTriangleVertices.put(mTriangleVerticesData).position(0);
+
+        Matrix.setIdentityM(mSTMatrix, 0);
+        Matrix.setIdentityM(mMMatrix, 0);
+
+        float[] defaultAcceleration = {0.f,0.f,0.f};
+        setAcceleration(defaultAcceleration);
+        mPos[0] = 0.f;
+        mPos[1] = 0.f;
+        mPos[2] = 0.f;
+        mVel[0] = 0.f;
+        mVel[1] = 0.f;
+        mVel[2] = 0.f;
+
+    }
+
+    /* The following set methods are not synchronized, so should only
+     * be called within the rendering thread context. Use GLSurfaceView.queueEvent for safe access.
+     */
+    public void setPosition(float x, float y) {
+        /* Map from screen (0,0)-(1,1) to scene coordinates */
+        mPos[0] = (x*2-1)*mRatio;
+        mPos[1] = (-y)*2+1;
+        mPos[2] = 0.f;
+        mVel[0] = 0;
+        mVel[1] = 0;
+        mVel[2] = 0;
+    }
+
+    public void setCamera(Camera camera) {
+        mCamera = camera;
+        Camera.Size previewSize = camera.getParameters().getPreviewSize();
+        mCameraRatio = (float)previewSize.width/previewSize.height;
+    }
+
+    public void setAcceleration(float[] accelerationVector) {
+        mGForce[0] = accelerationVector[0];
+        mGForce[1] = accelerationVector[1];
+        mGForce[2] = accelerationVector[2];
+    }
+
+    public void onDrawFrame(GL10 glUnused) {
+        synchronized(this) {
+            if (updateSurface) {
+                mSurface.updateTexImage();
+
+                mSurface.getTransformMatrix(mSTMatrix);
+                long timestamp = mSurface.getTimestamp();
+                doPhysics(timestamp);
+
+                updateSurface = false;
+            }
+        }
+
+        // Ignore the passed-in GL10 interface, and use the GLES20
+        // class's static methods instead.
+        GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
+        GLES20.glUseProgram(mProgram);
+        checkGlError("glUseProgram");
+
+        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+        GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureID);
+
+        mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
+        GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false,
+                TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
+        checkGlError("glVertexAttribPointer maPosition");
+        GLES20.glEnableVertexAttribArray(maPositionHandle);
+        checkGlError("glEnableVertexAttribArray maPositionHandle");
+
+        mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
+        GLES20.glVertexAttribPointer(maTextureHandle, 3, GLES20.GL_FLOAT, false,
+                TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
+        checkGlError("glVertexAttribPointer maTextureHandle");
+        GLES20.glEnableVertexAttribArray(maTextureHandle);
+        checkGlError("glEnableVertexAttribArray maTextureHandle");
+
+        Matrix.multiplyMM(mMVPMatrix, 0, mVMatrix, 0, mMMatrix, 0);
+        Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mMVPMatrix, 0);
+
+        GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0);
+        GLES20.glUniformMatrix4fv(muSTMatrixHandle, 1, false, mSTMatrix, 0);
+        GLES20.glUniform1f(muCRatioHandle, mCameraRatio);
+
+        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+        checkGlError("glDrawArrays");
+    }
+
+    public void onSurfaceChanged(GL10 glUnused, int width, int height) {
+        // Ignore the passed-in GL10 interface, and use the GLES20
+        // class's static methods instead.
+        GLES20.glViewport(0, 0, width, height);
+        mRatio = (float) width / height;
+        Matrix.frustumM(mProjMatrix, 0, -mRatio, mRatio, -1, 1, 3, 7);
+    }
+
+    public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
+        // Ignore the passed-in GL10 interface, and use the GLES20
+        // class's static methods instead.
+
+        /* Set up alpha blending and an Android background color */
+        GLES20.glEnable(GLES20.GL_BLEND);
+        GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
+        GLES20.glClearColor(0.643f, 0.776f, 0.223f, 1.0f);
+
+        /* Set up shaders and handles to their variables */
+        mProgram = createProgram(mVertexShader, mFragmentShader);
+        if (mProgram == 0) {
+            return;
+        }
+        maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
+        checkGlError("glGetAttribLocation aPosition");
+        if (maPositionHandle == -1) {
+            throw new RuntimeException("Could not get attrib location for aPosition");
+        }
+        maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord");
+        checkGlError("glGetAttribLocation aTextureCoord");
+        if (maTextureHandle == -1) {
+            throw new RuntimeException("Could not get attrib location for aTextureCoord");
+        }
+
+        muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
+        checkGlError("glGetUniformLocation uMVPMatrix");
+        if (muMVPMatrixHandle == -1) {
+            throw new RuntimeException("Could not get attrib location for uMVPMatrix");
+        }
+
+        muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix");
+        checkGlError("glGetUniformLocation uSTMatrix");
+        if (muMVPMatrixHandle == -1) {
+            throw new RuntimeException("Could not get attrib location for uSTMatrix");
+        }
+
+        muCRatioHandle = GLES20.glGetUniformLocation(mProgram, "uCRatio");
+        checkGlError("glGetUniformLocation uCRatio");
+        if (muMVPMatrixHandle == -1) {
+            throw new RuntimeException("Could not get attrib location for uCRatio");
+        }
+
+        /*
+         * Create our texture. This has to be done each time the
+         * surface is created.
+         */
+
+        int[] textures = new int[1];
+        GLES20.glGenTextures(1, textures, 0);
+
+        mTextureID = textures[0];
+        GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureID);
+        checkGlError("glBindTexture mTextureID");
+
+        // Can't do mipmapping with camera source
+        GLES20.glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER,
+                GLES20.GL_NEAREST);
+        GLES20.glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER,
+                GLES20.GL_LINEAR);
+        // Clamp to edge is the only option
+        GLES20.glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S,
+                GLES20.GL_CLAMP_TO_EDGE);
+        GLES20.glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T,
+                GLES20.GL_CLAMP_TO_EDGE);
+        checkGlError("glTexParameteri mTextureID");
+
+        /*
+         * Create the SurfaceTexture that will feed this textureID, and pass it to the camera
+         */
+
+        mSurface = new SurfaceTexture(mTextureID);
+        mSurface.setOnFrameAvailableListener(this);
+        try {
+            mCamera.setPreviewTexture(mSurface);
+        } catch (IOException t) {
+            Log.e(TAG, "Cannot set preview texture target!");
+        }
+
+        /* Start the camera */
+        mCamera.startPreview();
+
+        Matrix.setLookAtM(mVMatrix, 0, 0, 0, 5f, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
+
+        mLastTime = 0;
+
+        synchronized(this) {
+            updateSurface = false;
+        }
+    }
+
+    synchronized public void onFrameAvailable(SurfaceTexture surface) {
+        /* For simplicity, SurfaceTexture calls here when it has new
+         * data available.  Call may come in from some random thread,
+         * so let's be safe and use synchronize. No OpenGL calls can be done here.
+         */
+        updateSurface = true;
+    }
+
+    private void doPhysics(long timestamp) {
+        /*
+         * Move the camera surface around based on some simple spring physics with drag
+         */
+
+        if (mLastTime == 0)
+            mLastTime = timestamp;
+
+        float deltaT = (timestamp - mLastTime)/1000000000.f; // To seconds
+
+        float springStrength = 20.f;
+        float frictionCoeff = 10.f;
+        float mass = 10.f;
+        float gMultiplier = 4.f;
+        /* Only update physics every 30 ms */
+        if (deltaT > 0.030f) {
+            mLastTime = timestamp;
+
+            float[] totalForce = new float[3];
+            totalForce[0] = -mPos[0] * springStrength - mVel[0]*frictionCoeff + gMultiplier*mGForce[0]*mass;
+            totalForce[1] = -mPos[1] * springStrength - mVel[1]*frictionCoeff + gMultiplier*mGForce[1]*mass;
+            totalForce[2] = -mPos[2] * springStrength - mVel[2]*frictionCoeff + gMultiplier*mGForce[2]*mass;
+
+            float[] accel = new float[3];
+            accel[0] = totalForce[0]/mass;
+            accel[1] = totalForce[1]/mass;
+            accel[2] = totalForce[2]/mass;
+
+            /* Not a very accurate integrator */
+            mVel[0] = mVel[0] + accel[0]*deltaT;
+            mVel[1] = mVel[1] + accel[1]*deltaT;
+            mVel[2] = mVel[2] + accel[2]*deltaT;
+
+            mPos[0] = mPos[0] + mVel[0]*deltaT;
+            mPos[1] = mPos[1] + mVel[1]*deltaT;
+            mPos[2] = mPos[2] + mVel[2]*deltaT;
+
+            Matrix.setIdentityM(mMMatrix, 0);
+            Matrix.translateM(mMMatrix, 0, mPos[0], mPos[1], mPos[2]);
+        }
+
+    }
+
+    private int loadShader(int shaderType, String source) {
+        int shader = GLES20.glCreateShader(shaderType);
+        if (shader != 0) {
+            GLES20.glShaderSource(shader, source);
+            GLES20.glCompileShader(shader);
+            int[] compiled = new int[1];
+            GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
+            if (compiled[0] == 0) {
+                Log.e(TAG, "Could not compile shader " + shaderType + ":");
+                Log.e(TAG, GLES20.glGetShaderInfoLog(shader));
+                GLES20.glDeleteShader(shader);
+                shader = 0;
+            }
+        }
+        return shader;
+    }
+
+    private int createProgram(String vertexSource, String fragmentSource) {
+        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
+        if (vertexShader == 0) {
+            return 0;
+        }
+        int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
+        if (pixelShader == 0) {
+            return 0;
+        }
+
+        int program = GLES20.glCreateProgram();
+        if (program != 0) {
+            GLES20.glAttachShader(program, vertexShader);
+            checkGlError("glAttachShader");
+            GLES20.glAttachShader(program, pixelShader);
+            checkGlError("glAttachShader");
+            GLES20.glLinkProgram(program);
+            int[] linkStatus = new int[1];
+            GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
+            if (linkStatus[0] != GLES20.GL_TRUE) {
+                Log.e(TAG, "Could not link program: ");
+                Log.e(TAG, GLES20.glGetProgramInfoLog(program));
+                GLES20.glDeleteProgram(program);
+                program = 0;
+            }
+        }
+        return program;
+    }
+
+    private void checkGlError(String op) {
+        int error;
+        while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
+            Log.e(TAG, op + ": glError " + error);
+            throw new RuntimeException(op + ": glError " + error);
+        }
+    }
+
+    private static final int FLOAT_SIZE_BYTES = 4;
+    private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
+    private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
+    private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
+    private final float[] mTriangleVerticesData = {
+        // X, Y, Z, U, V
+        -1.0f, -1.0f, 0, 0.f, 0.f,
+        1.0f, -1.0f, 0, 1.f, 0.f,
+        -1.0f,  1.0f, 0, 0.f, 1.f,
+        1.0f,   1.0f, 0, 1.f, 1.f,
+    };
+
+    private FloatBuffer mTriangleVertices;
+
+    private final String mVertexShader =
+        "uniform mat4 uMVPMatrix;\n" +
+        "uniform mat4 uSTMatrix;\n" +
+        "uniform float uCRatio;\n" +
+        "attribute vec4 aPosition;\n" +
+        "attribute vec4 aTextureCoord;\n" +
+        "varying vec2 vTextureCoord;\n" +
+        "varying vec2 vTextureNormCoord;\n" +
+        "void main() {\n" +
+        "  vec4 scaledPos = aPosition;\n" +
+        "  scaledPos.x = scaledPos.x * uCRatio;\n" +
+        "  gl_Position = uMVPMatrix * scaledPos;\n" +
+        "  vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" +
+        "  vTextureNormCoord = aTextureCoord.xy;\n" +
+        "}\n";
+
+    private final String mFragmentShader =
+        "#extension GL_OES_EGL_image_external : require\n" +
+        "precision mediump float;\n" +
+        "varying vec2 vTextureCoord;\n" +
+        "varying vec2 vTextureNormCoord;\n" +
+        "uniform samplerExternalOES sTexture;\n" +
+        "void main() {\n" +
+        "  gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
+        "  gl_FragColor.a = 1.0-min(length(vTextureNormCoord-0.5)*2.0,1.0);\n" +
+        "}\n";
+
+    private float[] mMVPMatrix = new float[16];
+    private float[] mProjMatrix = new float[16];
+    private float[] mMMatrix = new float[16];
+    private float[] mVMatrix = new float[16];
+    private float[] mSTMatrix = new float[16];
+
+    private int mProgram;
+    private int mTextureID;
+    private int muMVPMatrixHandle;
+    private int muSTMatrixHandle;
+    private int muCRatioHandle;
+    private int maPositionHandle;
+    private int maTextureHandle;
+
+    private float mRatio = 1.0f;
+    private float mCameraRatio = 1.0f;
+    private float[] mVel = new float[3];
+    private float[] mPos = new float[3];
+    private float[] mGForce = new float[3];
+
+    private long mLastTime;
+
+    private SurfaceTexture mSurface;
+    private Camera mCamera;
+    private boolean updateSurface = false;
+
+    private Context mContext;
+    private static String TAG = "CamRenderer";
+
+    // Magic key
+    private static int GL_TEXTURE_EXTERNAL_OES = 0x8D65;
+}