Adding ability to read back fbo data to renderscript

Change-Id: Ie684c26cbcde22f2483b43415dfc39a3de22c5a1
diff --git a/libs/rs/Android.mk b/libs/rs/Android.mk
index 9fabf8d..d9cc6b6 100644
--- a/libs/rs/Android.mk
+++ b/libs/rs/Android.mk
@@ -119,6 +119,7 @@
 	driver/rsdBcc.cpp \
 	driver/rsdCore.cpp \
 	driver/rsdFrameBuffer.cpp \
+	driver/rsdFrameBufferObj.cpp \
 	driver/rsdGL.cpp \
 	driver/rsdMesh.cpp \
 	driver/rsdMeshObj.cpp \
diff --git a/libs/rs/driver/rsdAllocation.cpp b/libs/rs/driver/rsdAllocation.cpp
index 8bfc185..01a0cf6 100644
--- a/libs/rs/driver/rsdAllocation.cpp
+++ b/libs/rs/driver/rsdAllocation.cpp
@@ -19,6 +19,7 @@
 #include "rsdBcc.h"
 #include "rsdRuntime.h"
 #include "rsdAllocation.h"
+#include "rsdFrameBufferObj.h"
 
 #include "rsAllocation.h"
 
@@ -244,6 +245,9 @@
     if (alloc->mHal.state.usageFlags & ~RS_ALLOCATION_USAGE_SCRIPT) {
         drv->uploadDeferred = true;
     }
+
+    drv->readBackFBO = NULL;
+
     return true;
 }
 
@@ -269,6 +273,10 @@
         free(drv->mallocPtr);
         drv->mallocPtr = NULL;
     }
+    if (drv->readBackFBO != NULL) {
+        delete drv->readBackFBO;
+        drv->readBackFBO = NULL;
+    }
     free(drv);
     alloc->mHal.drv = NULL;
 }
@@ -292,13 +300,52 @@
     }
 }
 
+static void rsdAllocationSyncFromFBO(const Context *rsc, const Allocation *alloc) {
+    if (!alloc->getIsScript()) {
+        return; // nothing to sync
+    }
+
+    RsdHal *dc = (RsdHal *)rsc->mHal.drv;
+    RsdFrameBufferObj *lastFbo = dc->gl.currentFrameBuffer;
+
+    DrvAllocation *drv = (DrvAllocation *)alloc->mHal.drv;
+    if (!drv->textureID && !drv->renderTargetID) {
+        return; // nothing was rendered here yet, so nothing to sync
+    }
+    if (drv->readBackFBO == NULL) {
+        drv->readBackFBO = new RsdFrameBufferObj();
+        drv->readBackFBO->setColorTarget(drv, 0);
+        drv->readBackFBO->setDimensions(alloc->getType()->getDimX(),
+                                        alloc->getType()->getDimY());
+    }
+
+    // Bind the framebuffer object so we can read back from it
+    drv->readBackFBO->setActive(rsc);
+
+    // Do the readback
+    glReadPixels(0, 0, alloc->getType()->getDimX(), alloc->getType()->getDimY(),
+                 drv->glFormat, drv->glType, alloc->getPtr());
+
+    // Revert framebuffer to its original
+    lastFbo->setActive(rsc);
+}
 
 
 void rsdAllocationSyncAll(const Context *rsc, const Allocation *alloc,
                          RsAllocationUsageType src) {
     DrvAllocation *drv = (DrvAllocation *)alloc->mHal.drv;
 
-    if (!drv->uploadDeferred) {
+    if (src == RS_ALLOCATION_USAGE_GRAPHICS_RENDER_TARGET) {
+        if(!alloc->getIsRenderTarget()) {
+            rsc->setError(RS_ERROR_FATAL_DRIVER,
+                          "Attempting to sync allocation from render target, "
+                          "for non-render target allocation");
+        } else if (alloc->getType()->getElement()->getKind() != RS_KIND_PIXEL_RGBA) {
+            rsc->setError(RS_ERROR_FATAL_DRIVER, "Cannot only sync from RGBA"
+                                                 "render target");
+        } else {
+            rsdAllocationSyncFromFBO(rsc, alloc);
+        }
         return;
     }
 
@@ -382,7 +429,40 @@
                                const android::renderscript::Allocation *dstAlloc,
                                uint32_t dstXoff, uint32_t dstLod, uint32_t count,
                                const android::renderscript::Allocation *srcAlloc,
-                               uint32_t srcXoff, uint32_t srcLod){
+                               uint32_t srcXoff, uint32_t srcLod) {
+}
+
+uint8_t *getOffsetPtr(const android::renderscript::Allocation *alloc,
+                      uint32_t xoff, uint32_t yoff, uint32_t lod,
+                      RsAllocationCubemapFace face) {
+    uint8_t *ptr = static_cast<uint8_t *>(alloc->getPtr());
+    ptr += alloc->getType()->getLODOffset(lod, xoff, yoff);
+
+    if (face != 0) {
+        uint32_t totalSizeBytes = alloc->getType()->getSizeBytes();
+        uint32_t faceOffset = totalSizeBytes / 6;
+        ptr += faceOffset * (uint32_t)face;
+    }
+    return ptr;
+}
+
+
+void rsdAllocationData2D_alloc_script(const android::renderscript::Context *rsc,
+                                      const android::renderscript::Allocation *dstAlloc,
+                                      uint32_t dstXoff, uint32_t dstYoff, uint32_t dstLod,
+                                      RsAllocationCubemapFace dstFace, uint32_t w, uint32_t h,
+                                      const android::renderscript::Allocation *srcAlloc,
+                                      uint32_t srcXoff, uint32_t srcYoff, uint32_t srcLod,
+                                      RsAllocationCubemapFace srcFace) {
+    uint32_t elementSize = dstAlloc->getType()->getElementSizeBytes();
+    for (uint32_t i = 0; i < h; i ++) {
+        uint8_t *dstPtr = getOffsetPtr(dstAlloc, dstXoff, dstYoff + i, dstLod, dstFace);
+        uint8_t *srcPtr = getOffsetPtr(srcAlloc, srcXoff, srcYoff + i, srcLod, srcFace);
+        memcpy(dstPtr, srcPtr, w * elementSize);
+
+        LOGE("COPIED dstXoff(%u), dstYoff(%u), dstLod(%u), dstFace(%u), w(%u), h(%u), srcXoff(%u), srcYoff(%u), srcLod(%u), srcFace(%u)",
+             dstXoff, dstYoff, dstLod, dstFace, w, h, srcXoff, srcYoff, srcLod, srcFace);
+    }
 }
 
 void rsdAllocationData2D_alloc(const android::renderscript::Context *rsc,
@@ -392,6 +472,14 @@
                                const android::renderscript::Allocation *srcAlloc,
                                uint32_t srcXoff, uint32_t srcYoff, uint32_t srcLod,
                                RsAllocationCubemapFace srcFace) {
+    if (!dstAlloc->getIsScript() && !srcAlloc->getIsScript()) {
+        rsc->setError(RS_ERROR_FATAL_DRIVER, "Non-script allocation copies not "
+                                             "yet implemented.");
+        return;
+    }
+    rsdAllocationData2D_alloc_script(rsc, dstAlloc, dstXoff, dstYoff,
+                                     dstLod, dstFace, w, h, srcAlloc,
+                                     srcXoff, srcYoff, srcLod, srcFace);
 }
 
 void rsdAllocationData3D_alloc(const android::renderscript::Context *rsc,
diff --git a/libs/rs/driver/rsdAllocation.h b/libs/rs/driver/rsdAllocation.h
index 7555c4a..4fc4419 100644
--- a/libs/rs/driver/rsdAllocation.h
+++ b/libs/rs/driver/rsdAllocation.h
@@ -23,6 +23,8 @@
 #include <GLES/gl.h>
 #include <GLES2/gl2.h>
 
+class RsdFrameBufferObj;
+
 struct DrvAllocation {
     // Is this a legal structure to be used as a texture source.
     // Initially this will require 1D or 2D and color data
@@ -42,8 +44,9 @@
     GLenum glType;
     GLenum glFormat;
 
-
     bool uploadDeferred;
+
+    RsdFrameBufferObj * readBackFBO;
 };
 
 GLenum rsdTypeToGLType(RsDataType t);
diff --git a/libs/rs/driver/rsdFrameBuffer.cpp b/libs/rs/driver/rsdFrameBuffer.cpp
index ce72b5d..8c1b12d 100644
--- a/libs/rs/driver/rsdFrameBuffer.cpp
+++ b/libs/rs/driver/rsdFrameBuffer.cpp
@@ -17,6 +17,7 @@
 
 #include "rsdCore.h"
 #include "rsdFrameBuffer.h"
+#include "rsdFrameBufferObj.h"
 #include "rsdAllocation.h"
 
 #include "rsContext.h"
@@ -28,133 +29,70 @@
 using namespace android;
 using namespace android::renderscript;
 
-struct DrvFrameBuffer {
-    GLuint mFBOId;
-};
-
-void checkError(const Context *rsc) {
-    GLenum status;
-    status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
-    switch (status) {
-    case GL_FRAMEBUFFER_COMPLETE:
-        break;
-    case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
-        rsc->setError(RS_ERROR_BAD_VALUE,
-                      "Unable to set up render Target: RFRAMEBUFFER_INCOMPLETE_ATTACHMENT");
-        break;
-    case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
-        rsc->setError(RS_ERROR_BAD_VALUE,
-                      "Unable to set up render Target: GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT");
-        break;
-    case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
-        rsc->setError(RS_ERROR_BAD_VALUE,
-                      "Unable to set up render Target: GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS");
-        break;
-    case GL_FRAMEBUFFER_UNSUPPORTED:
-        rsc->setError(RS_ERROR_BAD_VALUE,
-                      "Unable to set up render Target: GL_FRAMEBUFFER_UNSUPPORTED");
-        break;
-    }
-}
-
-
 void setDepthAttachment(const Context *rsc, const FBOCache *fb) {
-    if (fb->mHal.state.depthTarget.get() != NULL) {
-        DrvAllocation *drv = (DrvAllocation *)fb->mHal.state.depthTarget->mHal.drv;
+    RsdFrameBufferObj *fbo = (RsdFrameBufferObj*)fb->mHal.drv;
 
-        if (drv->textureID) {
-            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
-                                   GL_TEXTURE_2D, drv->textureID, 0);
-        } else {
-            glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
-                                      GL_RENDERBUFFER, drv->renderTargetID);
+    DrvAllocation *depth = NULL;
+    if (fb->mHal.state.depthTarget.get() != NULL) {
+        depth = (DrvAllocation *)fb->mHal.state.depthTarget->mHal.drv;
+
+        if (depth->uploadDeferred) {
+            rsdAllocationSyncAll(rsc, fb->mHal.state.depthTarget.get(),
+                                 RS_ALLOCATION_USAGE_SCRIPT);
         }
-    } else {
-        // Reset last attachment
-        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0);
-        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
     }
+    fbo->setDepthTarget(depth);
 }
 
 void setColorAttachment(const Context *rsc, const FBOCache *fb) {
+    RsdFrameBufferObj *fbo = (RsdFrameBufferObj*)fb->mHal.drv;
     // Now attach color targets
     for (uint32_t i = 0; i < fb->mHal.state.colorTargetsCount; i ++) {
-        uint32_t texID = 0;
+        DrvAllocation *color = NULL;
         if (fb->mHal.state.colorTargets[i].get() != NULL) {
-            DrvAllocation *drv = (DrvAllocation *)fb->mHal.state.colorTargets[i]->mHal.drv;
+            color = (DrvAllocation *)fb->mHal.state.colorTargets[i]->mHal.drv;
 
-            if (drv->textureID) {
-                glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i,
-                                       GL_TEXTURE_2D, drv->textureID, 0);
-            } else {
-                glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i,
-                                          GL_RENDERBUFFER, drv->renderTargetID);
+            if (color->uploadDeferred) {
+                rsdAllocationSyncAll(rsc, fb->mHal.state.colorTargets[i].get(),
+                                     RS_ALLOCATION_USAGE_SCRIPT);
             }
-        } else {
-            // Reset last attachment
-            glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i,
-                                      GL_RENDERBUFFER, 0);
-            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i,
-                                   GL_TEXTURE_2D, 0, 0);
         }
+        fbo->setColorTarget(color, i);
     }
 }
 
-bool renderToFramebuffer(const FBOCache *fb) {
-    if (fb->mHal.state.depthTarget.get() != NULL) {
-        return false;
-    }
-
-    for (uint32_t i = 0; i < fb->mHal.state.colorTargetsCount; i ++) {
-        if (fb->mHal.state.colorTargets[i].get() != NULL) {
-            return false;
-        }
-    }
-    return true;
-}
-
-
 bool rsdFrameBufferInit(const Context *rsc, const FBOCache *fb) {
-    DrvFrameBuffer *drv = (DrvFrameBuffer *)calloc(1, sizeof(DrvFrameBuffer));
-    if (drv == NULL) {
+    RsdFrameBufferObj *fbo = new RsdFrameBufferObj();
+    if (fbo == NULL) {
         return false;
     }
-    fb->mHal.drv = drv;
-    drv->mFBOId = 0;
+    fb->mHal.drv = fbo;
+
+    RsdHal *dc = (RsdHal *)rsc->mHal.drv;
+    dc->gl.currentFrameBuffer = fbo;
 
     return true;
 }
 
 void rsdFrameBufferSetActive(const Context *rsc, const FBOCache *fb) {
-    DrvFrameBuffer *drv = (DrvFrameBuffer *)fb->mHal.drv;
+    setDepthAttachment(rsc, fb);
+    setColorAttachment(rsc, fb);
 
-    bool framebuffer = renderToFramebuffer(fb);
-    if (!framebuffer) {
-        if(drv->mFBOId == 0) {
-            glGenFramebuffers(1, &drv->mFBOId);
-        }
-        glBindFramebuffer(GL_FRAMEBUFFER, drv->mFBOId);
-
-        setDepthAttachment(rsc, fb);
-        setColorAttachment(rsc, fb);
-
-        glViewport(0, 0, fb->mHal.state.colorTargets[0]->getType()->getDimX(),
-                         fb->mHal.state.colorTargets[0]->getType()->getDimY());
-
-        checkError(rsc);
-    } else {
-        glBindFramebuffer(GL_FRAMEBUFFER, 0);
-        glViewport(0, 0, rsc->getWidth(), rsc->getHeight());
+    RsdFrameBufferObj *fbo = (RsdFrameBufferObj *)fb->mHal.drv;
+    if (fb->mHal.state.colorTargets[0].get()) {
+        fbo->setDimensions(fb->mHal.state.colorTargets[0]->getType()->getDimX(),
+                           fb->mHal.state.colorTargets[0]->getType()->getDimY());
+    } else if (fb->mHal.state.depthTarget.get()) {
+        fbo->setDimensions(fb->mHal.state.depthTarget->getType()->getDimX(),
+                           fb->mHal.state.depthTarget->getType()->getDimY());
     }
+
+    fbo->setActive(rsc);
 }
 
 void rsdFrameBufferDestroy(const Context *rsc, const FBOCache *fb) {
-    DrvFrameBuffer *drv = (DrvFrameBuffer *)fb->mHal.drv;
-    if(drv->mFBOId != 0) {
-        glDeleteFramebuffers(1, &drv->mFBOId);
-    }
-
-    free(fb->mHal.drv);
+    RsdFrameBufferObj *fbo = (RsdFrameBufferObj *)fb->mHal.drv;
+    delete fbo;
     fb->mHal.drv = NULL;
 }
 
diff --git a/libs/rs/driver/rsdFrameBufferObj.cpp b/libs/rs/driver/rsdFrameBufferObj.cpp
new file mode 100644
index 0000000..145bf34
--- /dev/null
+++ b/libs/rs/driver/rsdFrameBufferObj.cpp
@@ -0,0 +1,143 @@
+/*
+ * 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.
+ */
+
+
+#include "rsdFrameBufferObj.h"
+#include "rsdAllocation.h"
+
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+using namespace android;
+using namespace android::renderscript;
+
+RsdFrameBufferObj::RsdFrameBufferObj() {
+    mFBOId = 0;
+    mWidth = 0;
+    mHeight = 0;
+    mColorTargetsCount = 1;
+    mColorTargets = new DrvAllocation*[mColorTargetsCount];
+    for (uint32_t i = 0; i < mColorTargetsCount; i ++) {
+        mColorTargets[i] = 0;
+    }
+    mDepthTarget = NULL;
+    mDirty = true;
+}
+
+RsdFrameBufferObj::~RsdFrameBufferObj() {
+    if(mFBOId != 0) {
+        glDeleteFramebuffers(1, &mFBOId);
+    }
+    delete [] mColorTargets;
+}
+
+void RsdFrameBufferObj::checkError(const Context *rsc) {
+    GLenum status;
+    status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+    switch (status) {
+    case GL_FRAMEBUFFER_COMPLETE:
+        break;
+    case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
+        rsc->setError(RS_ERROR_BAD_VALUE,
+                      "Unable to set up render Target: RFRAMEBUFFER_INCOMPLETE_ATTACHMENT");
+        break;
+    case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
+        rsc->setError(RS_ERROR_BAD_VALUE,
+                      "Unable to set up render Target: GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT");
+        break;
+    case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
+        rsc->setError(RS_ERROR_BAD_VALUE,
+                      "Unable to set up render Target: GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS");
+        break;
+    case GL_FRAMEBUFFER_UNSUPPORTED:
+        rsc->setError(RS_ERROR_BAD_VALUE,
+                      "Unable to set up render Target: GL_FRAMEBUFFER_UNSUPPORTED");
+        break;
+    }
+}
+
+
+void RsdFrameBufferObj::setDepthAttachment() {
+    if (mDepthTarget != NULL) {
+        if (mDepthTarget->textureID) {
+            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
+                                   GL_TEXTURE_2D, mDepthTarget->textureID, 0);
+        } else {
+            glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
+                                      GL_RENDERBUFFER, mDepthTarget->renderTargetID);
+        }
+    } else {
+        // Reset last attachment
+        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0);
+        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
+    }
+}
+
+void RsdFrameBufferObj::setColorAttachment() {
+    // Now attach color targets
+    for (uint32_t i = 0; i < mColorTargetsCount; i ++) {
+        if (mColorTargets[i] != NULL) {
+            if (mColorTargets[i]->textureID) {
+                glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i,
+                                       GL_TEXTURE_2D, mColorTargets[i]->textureID, 0);
+            } else {
+                glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i,
+                                          GL_RENDERBUFFER, mColorTargets[i]->renderTargetID);
+            }
+        } else {
+            // Reset last attachment
+            glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i,
+                                      GL_RENDERBUFFER, 0);
+            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i,
+                                   GL_TEXTURE_2D, 0, 0);
+        }
+    }
+}
+
+bool RsdFrameBufferObj::renderToFramebuffer() {
+    if (mDepthTarget != NULL) {
+        return false;
+    }
+
+    for (uint32_t i = 0; i < mColorTargetsCount; i ++) {
+        if (mColorTargets[i] != NULL) {
+            return false;
+        }
+    }
+    return true;
+}
+
+void RsdFrameBufferObj::setActive(const Context *rsc) {
+    bool framebuffer = renderToFramebuffer();
+    if (!framebuffer) {
+        if(mFBOId == 0) {
+            glGenFramebuffers(1, &mFBOId);
+        }
+        glBindFramebuffer(GL_FRAMEBUFFER, mFBOId);
+
+        if (mDirty) {
+            setDepthAttachment();
+            setColorAttachment();
+            mDirty = false;
+        }
+
+        glViewport(0, 0, mWidth, mHeight);
+        checkError(rsc);
+    } else {
+        glBindFramebuffer(GL_FRAMEBUFFER, 0);
+        glViewport(0, 0, rsc->getWidth(), rsc->getHeight());
+    }
+}
diff --git a/libs/rs/driver/rsdFrameBufferObj.h b/libs/rs/driver/rsdFrameBufferObj.h
new file mode 100644
index 0000000..c6e7deb
--- /dev/null
+++ b/libs/rs/driver/rsdFrameBufferObj.h
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+#ifndef _RSD_FRAMEBUFFER_OBJ_H_
+#define _RSD_FRAMEBUFFER_OBJ_H_
+
+#include <rsContext.h>
+
+class DrvAllocation;
+
+class RsdFrameBufferObj {
+public:
+    RsdFrameBufferObj();
+    ~RsdFrameBufferObj();
+
+    void setActive(const android::renderscript::Context *rsc);
+    void setColorTarget(DrvAllocation *color, uint32_t index) {
+        mColorTargets[index] = color;
+        mDirty = true;
+    }
+    void setDepthTarget(DrvAllocation *depth) {
+        mDepthTarget = depth;
+        mDirty = true;
+    }
+    void setDimensions(uint32_t width, uint32_t height) {
+        mWidth = width;
+        mHeight = height;
+    }
+protected:
+    uint32_t mFBOId;
+    DrvAllocation **mColorTargets;
+    uint32_t mColorTargetsCount;
+    DrvAllocation *mDepthTarget;
+
+    uint32_t mWidth;
+    uint32_t mHeight;
+
+    bool mDirty;
+
+    bool renderToFramebuffer();
+    void checkError(const android::renderscript::Context *rsc);
+    void setColorAttachment();
+    void setDepthAttachment();
+};
+
+#endif //_RSD_FRAMEBUFFER_STATE_H_
diff --git a/libs/rs/driver/rsdGL.cpp b/libs/rs/driver/rsdGL.cpp
index a70589b..3ff03b4 100644
--- a/libs/rs/driver/rsdGL.cpp
+++ b/libs/rs/driver/rsdGL.cpp
@@ -39,6 +39,7 @@
 #include "rsContext.h"
 #include "rsdShaderCache.h"
 #include "rsdVertexArray.h"
+#include "rsdFrameBufferObj.h"
 
 using namespace android;
 using namespace android::renderscript;
@@ -294,6 +295,7 @@
     dc->gl.shaderCache = new RsdShaderCache();
     dc->gl.vertexArrayState = new RsdVertexArrayState();
     dc->gl.vertexArrayState->init(dc->gl.gl.maxVertexAttribs);
+    dc->gl.currentFrameBuffer = NULL;
 
     LOGV("initGLThread end %p", rsc);
     return true;
diff --git a/libs/rs/driver/rsdGL.h b/libs/rs/driver/rsdGL.h
index 01c8438..0d5b7e7 100644
--- a/libs/rs/driver/rsdGL.h
+++ b/libs/rs/driver/rsdGL.h
@@ -22,6 +22,7 @@
 
 class RsdShaderCache;
 class RsdVertexArrayState;
+class RsdFrameBufferObj;
 
 typedef void (* InvokeFunc_t)(void);
 typedef void (*WorkerCallback_t)(void *usr, uint32_t idx);
@@ -68,6 +69,7 @@
     uint32_t height;
     RsdShaderCache *shaderCache;
     RsdVertexArrayState *vertexArrayState;
+    RsdFrameBufferObj *currentFrameBuffer;
 } RsdGL;
 
 
diff --git a/libs/rs/driver/rsdMeshObj.cpp b/libs/rs/driver/rsdMeshObj.cpp
index c220ac1..4315c0d 100644
--- a/libs/rs/driver/rsdMeshObj.cpp
+++ b/libs/rs/driver/rsdMeshObj.cpp
@@ -138,7 +138,10 @@
 
     for (uint32_t ct=0; ct < mRSMesh->mHal.state.vertexBuffersCount; ct++) {
         const Allocation *alloc = mRSMesh->mHal.state.vertexBuffers[ct].get();
-        rsdAllocationSyncAll(rsc, alloc, RS_ALLOCATION_USAGE_SCRIPT);
+        DrvAllocation *drv = (DrvAllocation *)alloc->mHal.drv;
+        if (drv->uploadDeferred) {
+            rsdAllocationSyncAll(rsc, alloc, RS_ALLOCATION_USAGE_SCRIPT);
+        }
     }
 
     // update attributes with either buffer information or data ptr based on their current state
@@ -163,7 +166,9 @@
     const Allocation *idxAlloc = prim->mIndexBuffer.get();
     if (idxAlloc) {
         DrvAllocation *drvAlloc = (DrvAllocation *)idxAlloc->mHal.drv;
-        rsdAllocationSyncAll(rsc, idxAlloc, RS_ALLOCATION_USAGE_SCRIPT);
+        if (drvAlloc->uploadDeferred) {
+            rsdAllocationSyncAll(rsc, idxAlloc, RS_ALLOCATION_USAGE_SCRIPT);
+        }
 
         if (drvAlloc->bufferID) {
             glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, drvAlloc->bufferID);
diff --git a/libs/rs/driver/rsdRuntimeStubs.cpp b/libs/rs/driver/rsdRuntimeStubs.cpp
index bd8b3c3..25302aa 100644
--- a/libs/rs/driver/rsdRuntimeStubs.cpp
+++ b/libs/rs/driver/rsdRuntimeStubs.cpp
@@ -95,6 +95,8 @@
                                      Allocation *srcAlloc,
                                      uint32_t srcOff, uint32_t srcMip) {
     GET_TLS();
+    rsrAllocationCopy1DRange(rsc, dstAlloc, dstOff, dstMip, count,
+                             srcAlloc, srcOff, srcMip);
 }
 
 static void SC_AllocationCopy2DRange(Allocation *dstAlloc,
@@ -105,6 +107,11 @@
                                      uint32_t srcXoff, uint32_t srcYoff,
                                      uint32_t srcMip, uint32_t srcFace) {
     GET_TLS();
+    rsrAllocationCopy2DRange(rsc, dstAlloc,
+                             dstXoff, dstYoff, dstMip, dstFace,
+                             width, height,
+                             srcAlloc,
+                             srcXoff, srcYoff, srcMip, srcFace);
 }
 
 
diff --git a/libs/rs/rsFBOCache.cpp b/libs/rs/rsFBOCache.cpp
index 6960ef2..c5c64c2 100644
--- a/libs/rs/rsFBOCache.cpp
+++ b/libs/rs/rsFBOCache.cpp
@@ -80,16 +80,6 @@
         return;
     }
 
-    if (mHal.state.depthTarget.get() != NULL) {
-        mHal.state.depthTarget->syncAll(rsc, RS_ALLOCATION_USAGE_SCRIPT);
-    }
-
-    for (uint32_t i = 0; i < mHal.state.colorTargetsCount; i ++) {
-        if (mHal.state.colorTargets[i].get() != NULL) {
-            mHal.state.colorTargets[i]->syncAll(rsc, RS_ALLOCATION_USAGE_SCRIPT);
-        }
-    }
-
     rsc->mHal.funcs.framebuffer.setActive(rsc, this);
 
     mDirty = false;
diff --git a/tests/RenderScriptTests/FBOTest/AndroidManifest.xml b/tests/RenderScriptTests/FBOTest/AndroidManifest.xml
index c2e0cc6..788e856 100644
--- a/tests/RenderScriptTests/FBOTest/AndroidManifest.xml
+++ b/tests/RenderScriptTests/FBOTest/AndroidManifest.xml
@@ -3,11 +3,20 @@
     package="com.android.fbotest">
     <application android:label="_FBOTest">
         <activity android:name="FBOTest"
+                  android:label="FBO Base Test"
                   android:theme="@android:style/Theme.Black.NoTitleBar">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
-        </activity>        
+        </activity>
+        <activity android:name="FBOSync"
+                  android:label="FBO Sync Test"
+                  android:theme="@android:style/Theme.Black.NoTitleBar">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
     </application>
 </manifest>
diff --git a/tests/RenderScriptTests/FBOTest/src/com/android/fbotest/FBOSync.java b/tests/RenderScriptTests/FBOTest/src/com/android/fbotest/FBOSync.java
new file mode 100644
index 0000000..d30ad7e
--- /dev/null
+++ b/tests/RenderScriptTests/FBOTest/src/com/android/fbotest/FBOSync.java
@@ -0,0 +1,72 @@
+/*
+ * 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.fbotest;
+
+import android.renderscript.RSSurfaceView;
+import android.renderscript.RenderScript;
+
+import android.app.Activity;
+import android.content.res.Configuration;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.provider.Settings.System;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.MenuInflater;
+import android.view.Window;
+import android.widget.Button;
+import android.widget.ListView;
+import android.net.Uri;
+
+import java.lang.Runtime;
+
+public class FBOSync extends Activity {
+
+    private FBOSyncView mView;
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        // Create our Preview view and set it as the content of our
+        // Activity
+        mView = new FBOSyncView(this);
+        setContentView(mView);
+    }
+
+    @Override
+    protected void onResume() {
+        // Ideally a game should implement onResume() and onPause()
+        // to take appropriate action when the activity looses focus
+        super.onResume();
+        mView.resume();
+    }
+
+    @Override
+    protected void onPause() {
+        // Ideally a game should implement onResume() and onPause()
+        // to take appropriate action when the activity looses focus
+        super.onPause();
+        mView.pause();
+    }
+}
+
diff --git a/tests/RenderScriptTests/FBOTest/src/com/android/fbotest/FBOSyncRS.java b/tests/RenderScriptTests/FBOTest/src/com/android/fbotest/FBOSyncRS.java
new file mode 100644
index 0000000..57a117c
--- /dev/null
+++ b/tests/RenderScriptTests/FBOTest/src/com/android/fbotest/FBOSyncRS.java
@@ -0,0 +1,213 @@
+/*
+ * 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.fbotest;
+
+import java.io.Writer;
+
+import android.content.res.Resources;
+import android.renderscript.*;
+import android.renderscript.Element.DataType;
+import android.renderscript.Element.DataKind;
+import android.renderscript.ProgramStore.DepthFunc;
+import android.renderscript.Type.Builder;
+import android.util.Log;
+
+
+public class FBOSyncRS {
+
+    public FBOSyncRS() {
+    }
+
+    public void init(RenderScriptGL rs, Resources res) {
+        mRS = rs;
+        mRes = res;
+        initRS();
+    }
+
+    public void surfaceChanged() {
+        mRS.getWidth();
+        mRS.getHeight();
+    }
+
+    private Resources mRes;
+    private RenderScriptGL mRS;
+    private Sampler mSampler;
+    private ProgramStore mPSBackground;
+    private ProgramFragment mPFBackground;
+    private ProgramVertex mPVBackground;
+    private ProgramVertexFixedFunction.Constants mPVA;
+
+    private Allocation mGridImage;
+    private Allocation mOffscreen;
+    private Allocation mOffscreenDepth;
+    private Allocation mAllocPV;
+    private Allocation mReadBackTest;
+
+    private Font mItalic;
+    private Allocation mTextAlloc;
+
+    private ScriptField_MeshInfo mMeshes;
+    private ScriptC_fbosync mScript;
+
+
+    public void onActionDown(float x, float y) {
+        mScript.invoke_onActionDown(x, y);
+    }
+
+    public void onActionScale(float scale) {
+        mScript.invoke_onActionScale(scale);
+    }
+
+    public void onActionMove(float x, float y) {
+        mScript.invoke_onActionMove(x, y);
+    }
+
+    private void initPFS() {
+        ProgramStore.Builder b = new ProgramStore.Builder(mRS);
+
+        b.setDepthFunc(ProgramStore.DepthFunc.LESS);
+        b.setDitherEnabled(false);
+        b.setDepthMaskEnabled(true);
+        mPSBackground = b.create();
+
+        mScript.set_gPFSBackground(mPSBackground);
+    }
+
+    private void initPF() {
+        Sampler.Builder bs = new Sampler.Builder(mRS);
+        bs.setMinification(Sampler.Value.LINEAR);
+        bs.setMagnification(Sampler.Value.LINEAR);
+        bs.setWrapS(Sampler.Value.CLAMP);
+        bs.setWrapT(Sampler.Value.CLAMP);
+        mSampler = bs.create();
+
+        ProgramFragmentFixedFunction.Builder b = new ProgramFragmentFixedFunction.Builder(mRS);
+        b.setTexture(ProgramFragmentFixedFunction.Builder.EnvMode.REPLACE,
+                     ProgramFragmentFixedFunction.Builder.Format.RGBA, 0);
+        mPFBackground = b.create();
+        mPFBackground.bindSampler(mSampler, 0);
+
+        mScript.set_gPFBackground(mPFBackground);
+    }
+
+    private void initPV() {
+        ProgramVertexFixedFunction.Builder pvb = new ProgramVertexFixedFunction.Builder(mRS);
+        mPVBackground = pvb.create();
+
+        mPVA = new ProgramVertexFixedFunction.Constants(mRS);
+        ((ProgramVertexFixedFunction)mPVBackground).bindConstants(mPVA);
+
+        mScript.set_gPVBackground(mPVBackground);
+    }
+
+    private void loadImage() {
+        mGridImage = Allocation.createFromBitmapResource(mRS, mRes, R.drawable.robot,
+                                                         Allocation.MipmapControl.MIPMAP_ON_SYNC_TO_TEXTURE,
+                                                         Allocation.USAGE_GRAPHICS_TEXTURE);
+        mScript.set_gTGrid(mGridImage);
+    }
+
+    private void initTextAllocation(String fileName) {
+        String allocString = "Displaying file: " + fileName;
+        mTextAlloc = Allocation.createFromString(mRS, allocString, Allocation.USAGE_SCRIPT);
+        mScript.set_gTextAlloc(mTextAlloc);
+    }
+
+    private void initMeshes(FileA3D model) {
+        int numEntries = model.getIndexEntryCount();
+        int numMeshes = 0;
+        for (int i = 0; i < numEntries; i ++) {
+            FileA3D.IndexEntry entry = model.getIndexEntry(i);
+            if (entry != null && entry.getEntryType() == FileA3D.EntryType.MESH) {
+                numMeshes ++;
+            }
+        }
+
+        if (numMeshes > 0) {
+            mMeshes = new ScriptField_MeshInfo(mRS, numMeshes);
+
+            for (int i = 0; i < numEntries; i ++) {
+                FileA3D.IndexEntry entry = model.getIndexEntry(i);
+                if (entry != null && entry.getEntryType() == FileA3D.EntryType.MESH) {
+                    Mesh mesh = entry.getMesh();
+                    mMeshes.set_mMesh(i, mesh, false);
+                    mMeshes.set_mNumIndexSets(i, mesh.getPrimitiveCount(), false);
+                }
+            }
+            mMeshes.copyAll();
+        } else {
+            throw new RSRuntimeException("No valid meshes in file");
+        }
+
+        mScript.bind_gMeshes(mMeshes);
+        mScript.invoke_updateMeshInfo();
+    }
+
+    public void loadA3DFile(String path) {
+        FileA3D model = FileA3D.createFromFile(mRS, path);
+        initMeshes(model);
+        initTextAllocation(path);
+    }
+
+    private void initRS() {
+
+        mScript = new ScriptC_fbosync(mRS, mRes, R.raw.fbosync);
+
+        initPFS();
+        initPF();
+        initPV();
+
+        loadImage();
+
+        Type.Builder b = new Type.Builder(mRS, Element.RGBA_8888(mRS));
+        b.setX(512).setY(512);
+        mOffscreen = Allocation.createTyped(mRS,
+                                            b.create(),
+                                            Allocation.USAGE_SCRIPT |
+                                            Allocation.USAGE_GRAPHICS_TEXTURE |
+                                            Allocation.USAGE_GRAPHICS_RENDER_TARGET);
+        mScript.set_gOffscreen(mOffscreen);
+
+        mReadBackTest = Allocation.createTyped(mRS,
+                                               b.create(),
+                                               Allocation.USAGE_SCRIPT |
+                                               Allocation.USAGE_GRAPHICS_TEXTURE);
+        mScript.set_gReadBackTest(mReadBackTest);
+
+        b = new Type.Builder(mRS,
+                             Element.createPixel(mRS, DataType.UNSIGNED_16,
+                             DataKind.PIXEL_DEPTH));
+        b.setX(512).setY(512);
+        mOffscreenDepth = Allocation.createTyped(mRS,
+                                                 b.create(),
+                                                 Allocation.USAGE_GRAPHICS_RENDER_TARGET);
+        mScript.set_gOffscreenDepth(mOffscreenDepth);
+
+        FileA3D model = FileA3D.createFromResource(mRS, mRes, R.raw.robot);
+        initMeshes(model);
+
+        mItalic = Font.create(mRS, mRes, "serif", Font.Style.ITALIC, 8);
+        mScript.set_gItalic(mItalic);
+
+        initTextAllocation("R.raw.robot");
+
+        mRS.bindRootScript(mScript);
+    }
+}
+
+
+
diff --git a/tests/RenderScriptTests/FBOTest/src/com/android/fbotest/FBOSyncView.java b/tests/RenderScriptTests/FBOTest/src/com/android/fbotest/FBOSyncView.java
new file mode 100644
index 0000000..6a85628
--- /dev/null
+++ b/tests/RenderScriptTests/FBOTest/src/com/android/fbotest/FBOSyncView.java
@@ -0,0 +1,144 @@
+/*
+ * 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.fbotest;
+
+import android.renderscript.RSSurfaceView;
+import android.renderscript.RenderScriptGL;
+
+import android.content.Context;
+import android.view.MotionEvent;
+import android.view.SurfaceHolder;
+import android.view.ScaleGestureDetector;
+import android.util.Log;
+
+public class FBOSyncView extends RSSurfaceView {
+
+    private RenderScriptGL mRS;
+    private FBOSyncRS mRender;
+
+    private ScaleGestureDetector mScaleDetector;
+
+    private static final int INVALID_POINTER_ID = -1;
+    private int mActivePointerId = INVALID_POINTER_ID;
+
+    public FBOSyncView(Context context) {
+        super(context);
+        ensureRenderScript();
+        mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
+    }
+
+    private void ensureRenderScript() {
+        if (mRS == null) {
+            RenderScriptGL.SurfaceConfig sc = new RenderScriptGL.SurfaceConfig();
+            sc.setDepth(16, 24);
+            mRS = createRenderScriptGL(sc);
+            mRender = new FBOSyncRS();
+            mRender.init(mRS, getResources());
+        }
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        ensureRenderScript();
+    }
+
+    @Override
+    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
+        super.surfaceChanged(holder, format, w, h);
+        mRender.surfaceChanged();
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        mRender = null;
+        if (mRS != null) {
+            mRS = null;
+            destroyRenderScriptGL();
+        }
+    }
+
+    public void loadA3DFile(String path) {
+        mRender.loadA3DFile(path);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        mScaleDetector.onTouchEvent(ev);
+
+        boolean ret = false;
+        float x = ev.getX();
+        float y = ev.getY();
+
+        final int action = ev.getAction();
+
+        switch (action & MotionEvent.ACTION_MASK) {
+        case MotionEvent.ACTION_DOWN: {
+            mRender.onActionDown(x, y);
+            mActivePointerId = ev.getPointerId(0);
+            ret = true;
+            break;
+        }
+        case MotionEvent.ACTION_MOVE: {
+            if (!mScaleDetector.isInProgress()) {
+                mRender.onActionMove(x, y);
+            }
+            mRender.onActionDown(x, y);
+            ret = true;
+            break;
+        }
+
+        case MotionEvent.ACTION_UP: {
+            mActivePointerId = INVALID_POINTER_ID;
+            break;
+        }
+
+        case MotionEvent.ACTION_CANCEL: {
+            mActivePointerId = INVALID_POINTER_ID;
+            break;
+        }
+
+        case MotionEvent.ACTION_POINTER_UP: {
+            final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
+                    >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+            final int pointerId = ev.getPointerId(pointerIndex);
+            if (pointerId == mActivePointerId) {
+                // This was our active pointer going up. Choose a new
+                // active pointer and adjust accordingly.
+                final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
+                x = ev.getX(newPointerIndex);
+                y = ev.getY(newPointerIndex);
+                mRender.onActionDown(x, y);
+                mActivePointerId = ev.getPointerId(newPointerIndex);
+            }
+            break;
+        }
+        }
+
+        return ret;
+    }
+
+    private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
+        @Override
+        public boolean onScale(ScaleGestureDetector detector) {
+            mRender.onActionScale(detector.getScaleFactor());
+            return true;
+        }
+    }
+}
+
+
diff --git a/tests/RenderScriptTests/FBOTest/src/com/android/fbotest/fbosync.rs b/tests/RenderScriptTests/FBOTest/src/com/android/fbotest/fbosync.rs
new file mode 100644
index 0000000..b77ccb4
--- /dev/null
+++ b/tests/RenderScriptTests/FBOTest/src/com/android/fbotest/fbosync.rs
@@ -0,0 +1,233 @@
+// 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.
+
+#pragma version(1)
+
+#pragma rs java_package_name(com.android.fbotest)
+
+#include "rs_graphics.rsh"
+
+rs_program_vertex gPVBackground;
+rs_program_fragment gPFBackground;
+
+rs_allocation gTGrid;
+
+rs_program_store gPFSBackground;
+
+rs_font gItalic;
+rs_allocation gTextAlloc;
+
+rs_allocation gOffscreen;
+rs_allocation gOffscreenDepth;
+rs_allocation gReadBackTest;
+
+typedef struct MeshInfo {
+    rs_mesh mMesh;
+    int mNumIndexSets;
+    float3 bBoxMin;
+    float3 bBoxMax;
+} MeshInfo_t;
+
+MeshInfo_t *gMeshes;
+
+static float3 gLookAt;
+
+static float gRotateX;
+static float gRotateY;
+static float gZoom;
+
+static float gLastX;
+static float gLastY;
+
+void onActionDown(float x, float y) {
+    gLastX = x;
+    gLastY = y;
+}
+
+void onActionScale(float scale) {
+
+    gZoom *= 1.0f / scale;
+    gZoom = max(0.1f, min(gZoom, 500.0f));
+}
+
+void onActionMove(float x, float y) {
+    float dx = gLastX - x;
+    float dy = gLastY - y;
+
+    if (fabs(dy) <= 2.0f) {
+        dy = 0.0f;
+    }
+    if (fabs(dx) <= 2.0f) {
+        dx = 0.0f;
+    }
+
+    gRotateY -= dx;
+    if (gRotateY > 360) {
+        gRotateY -= 360;
+    }
+    if (gRotateY < 0) {
+        gRotateY += 360;
+    }
+
+    gRotateX -= dy;
+    gRotateX = min(gRotateX, 80.0f);
+    gRotateX = max(gRotateX, -80.0f);
+
+    gLastX = x;
+    gLastY = y;
+}
+
+void init() {
+    gRotateX = 0.0f;
+    gRotateY = 0.0f;
+    gZoom = 50.0f;
+    gLookAt = 0.0f;
+}
+
+void updateMeshInfo() {
+    rs_allocation allMeshes = rsGetAllocation(gMeshes);
+    int size = rsAllocationGetDimX(allMeshes);
+    gLookAt = 0.0f;
+    float minX, minY, minZ, maxX, maxY, maxZ;
+    for (int i = 0; i < size; i++) {
+        MeshInfo_t *info = (MeshInfo_t*)rsGetElementAt(allMeshes, i);
+        rsgMeshComputeBoundingBox(info->mMesh,
+                                  &minX, &minY, &minZ,
+                                  &maxX, &maxY, &maxZ);
+        info->bBoxMin = (minX, minY, minZ);
+        info->bBoxMax = (maxX, maxY, maxZ);
+        gLookAt += (info->bBoxMin + info->bBoxMax)*0.5f;
+    }
+    gLookAt = gLookAt / (float)size;
+}
+
+static void renderAllMeshes() {
+    rs_allocation allMeshes = rsGetAllocation(gMeshes);
+    int size = rsAllocationGetDimX(allMeshes);
+    gLookAt = 0.0f;
+    float minX, minY, minZ, maxX, maxY, maxZ;
+    for (int i = 0; i < size; i++) {
+        MeshInfo_t *info = (MeshInfo_t*)rsGetElementAt(allMeshes, i);
+        rsgDrawMesh(info->mMesh);
+    }
+}
+
+static void drawDescription() {
+    uint width = rsgGetWidth();
+    uint height = rsgGetHeight();
+    int left = 0, right = 0, top = 0, bottom = 0;
+
+    rsgBindFont(gItalic);
+
+    rsgMeasureText(gTextAlloc, &left, &right, &top, &bottom);
+    rsgDrawText(gTextAlloc, 2 -left, height - 2 + bottom);
+}
+
+static void renderOffscreen(bool useDepth) {
+
+    rsgBindColorTarget(gOffscreen, 0);
+    if (useDepth) {
+        rsgBindDepthTarget(gOffscreenDepth);
+        rsgClearDepth(1.0f);
+    } else {
+        rsgClearDepthTarget();
+    }
+    rsgClearColor(0.8f, 0.0f, 0.0f, 1.0f);
+
+    rsgBindProgramVertex(gPVBackground);
+    rs_matrix4x4 proj;
+    float aspect = (float)rsAllocationGetDimX(gOffscreen) / (float)rsAllocationGetDimY(gOffscreen);
+    rsMatrixLoadPerspective(&proj, 30.0f, aspect, 1.0f, 100.0f);
+    rsgProgramVertexLoadProjectionMatrix(&proj);
+
+    rsgBindProgramFragment(gPFBackground);
+    rsgBindProgramStore(gPFSBackground);
+    rsgBindTexture(gPFBackground, 0, gTGrid);
+
+    rs_matrix4x4 matrix;
+    rsMatrixLoadIdentity(&matrix);
+    // Position our models on the screen
+    rsMatrixTranslate(&matrix, gLookAt.x, gLookAt.y, gLookAt.z - gZoom);
+    rsMatrixRotate(&matrix, gRotateX, 1.0f, 0.0f, 0.0f);
+    rsMatrixRotate(&matrix, gRotateY, 0.0f, 1.0f, 0.0f);
+    rsgProgramVertexLoadModelMatrix(&matrix);
+
+    renderAllMeshes();
+
+    // Render into the frambuffer
+    rsgClearAllRenderTargets();
+}
+
+static void drawOffscreenResult(int posX, int posY, rs_allocation texture) {
+    // display the result
+    rs_matrix4x4 proj, matrix;
+    rsMatrixLoadOrtho(&proj, 0, rsgGetWidth(), rsgGetHeight(), 0, -500, 500);
+    rsgProgramVertexLoadProjectionMatrix(&proj);
+    rsMatrixLoadIdentity(&matrix);
+    rsgProgramVertexLoadModelMatrix(&matrix);
+    rsgBindTexture(gPFBackground, 0, texture);
+    float startX = posX, startY = posY;
+    float width = 256, height = 256;
+    rsgDrawQuadTexCoords(startX, startY, 0, 0, 1,
+                         startX, startY + height, 0, 0, 0,
+                         startX + width, startY + height, 0, 1, 0,
+                         startX + width, startY, 0, 1, 1);
+}
+
+int root(void) {
+
+    rsgClearColor(1.0f, 1.0f, 1.0f, 1.0f);
+    rsgClearDepth(1.0f);
+
+    renderOffscreen(true);
+    drawOffscreenResult(0, 0, gOffscreen);
+
+
+    uint32_t w = rsAllocationGetDimX(gOffscreen);
+    uint32_t h = rsAllocationGetDimY(gOffscreen);
+    uint32_t numElements = w*h;
+
+    rsgAllocationSyncAll(gOffscreen, RS_ALLOCATION_USAGE_GRAPHICS_RENDER_TARGET);
+
+    rsAllocationCopy2DRange(gReadBackTest, 0, 0, 0,
+                            RS_ALLOCATION_CUBEMAP_FACE_POSITIVE_X, w, h,
+                            gOffscreen, 0, 0, 0, RS_ALLOCATION_CUBEMAP_FACE_POSITIVE_X);
+
+    rsgAllocationSyncAll(gReadBackTest);
+    drawOffscreenResult(0, 300, gReadBackTest);
+
+    rsgBindProgramVertex(gPVBackground);
+    rs_matrix4x4 proj;
+    float aspect = (float)rsgGetWidth() / (float)rsgGetHeight();
+    rsMatrixLoadPerspective(&proj, 30.0f, aspect, 1.0f, 100.0f);
+    rsgProgramVertexLoadProjectionMatrix(&proj);
+
+    rsgBindProgramFragment(gPFBackground);
+    rsgBindProgramStore(gPFSBackground);
+    rsgBindTexture(gPFBackground, 0, gTGrid);
+
+    rs_matrix4x4 matrix;
+    rsMatrixLoadIdentity(&matrix);
+    // Position our models on the screen
+    rsMatrixTranslate(&matrix, gLookAt.x, gLookAt.y, gLookAt.z - gZoom);
+    rsMatrixRotate(&matrix, gRotateX, 1.0f, 0.0f, 0.0f);
+    rsMatrixRotate(&matrix, gRotateY, 0.0f, 1.0f, 0.0f);
+    rsgProgramVertexLoadModelMatrix(&matrix);
+
+    renderAllMeshes();
+
+    drawDescription();
+
+    return 0;
+}