First draft of fbo in renderscript.
Updating samples and benchmark

Change-Id: I469bf8b842fca72b59475c8fa024c12cf0e14954
diff --git a/tests/RenderScriptTests/FBOTest/Android.mk b/tests/RenderScriptTests/FBOTest/Android.mk
new file mode 100644
index 0000000..55525c4
--- /dev/null
+++ b/tests/RenderScriptTests/FBOTest/Android.mk
@@ -0,0 +1,30 @@
+#
+# Copyright (C) 2008 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.
+#
+
+ifneq ($(TARGET_SIMULATOR),true)
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src)
+
+LOCAL_PACKAGE_NAME := FBOTest
+
+include $(BUILD_PACKAGE)
+
+endif
diff --git a/tests/RenderScriptTests/FBOTest/AndroidManifest.xml b/tests/RenderScriptTests/FBOTest/AndroidManifest.xml
new file mode 100644
index 0000000..c2e0cc6
--- /dev/null
+++ b/tests/RenderScriptTests/FBOTest/AndroidManifest.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.fbotest">
+    <application android:label="_FBOTest">
+        <activity android:name="FBOTest"
+                  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/res/drawable/robot.png b/tests/RenderScriptTests/FBOTest/res/drawable/robot.png
new file mode 100644
index 0000000..f7353fd
--- /dev/null
+++ b/tests/RenderScriptTests/FBOTest/res/drawable/robot.png
Binary files differ
diff --git a/tests/RenderScriptTests/FBOTest/res/raw/robot.a3d b/tests/RenderScriptTests/FBOTest/res/raw/robot.a3d
new file mode 100644
index 0000000..f48895c
--- /dev/null
+++ b/tests/RenderScriptTests/FBOTest/res/raw/robot.a3d
Binary files differ
diff --git a/tests/RenderScriptTests/FBOTest/src/com/android/fbotest/FBOTest.java b/tests/RenderScriptTests/FBOTest/src/com/android/fbotest/FBOTest.java
new file mode 100644
index 0000000..79d60124
--- /dev/null
+++ b/tests/RenderScriptTests/FBOTest/src/com/android/fbotest/FBOTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2008 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.Config;
+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 FBOTest extends Activity {
+
+    private FBOTestView 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 FBOTestView(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/FBOTestRS.java b/tests/RenderScriptTests/FBOTest/src/com/android/fbotest/FBOTestRS.java
new file mode 100644
index 0000000..9e30c4b5
--- /dev/null
+++ b/tests/RenderScriptTests/FBOTest/src/com/android/fbotest/FBOTestRS.java
@@ -0,0 +1,205 @@
+/*
+ * 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 FBOTestRS {
+
+    public FBOTestRS() {
+    }
+
+    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 Font mItalic;
+    private Allocation mTextAlloc;
+
+    private ScriptField_MeshInfo mMeshes;
+    private ScriptC_fbotest 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_fbotest(mRS, mRes, R.raw.fbotest);
+
+        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_GRAPHICS_TEXTURE |
+                                            Allocation.USAGE_GRAPHICS_RENDER_TARGET);
+        mScript.set_gOffscreen(mOffscreen);
+
+        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/FBOTestView.java b/tests/RenderScriptTests/FBOTest/src/com/android/fbotest/FBOTestView.java
new file mode 100644
index 0000000..c9598ee
--- /dev/null
+++ b/tests/RenderScriptTests/FBOTest/src/com/android/fbotest/FBOTestView.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 FBOTestView extends RSSurfaceView {
+
+    private RenderScriptGL mRS;
+    private FBOTestRS mRender;
+
+    private ScaleGestureDetector mScaleDetector;
+
+    private static final int INVALID_POINTER_ID = -1;
+    private int mActivePointerId = INVALID_POINTER_ID;
+
+    public FBOTestView(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 FBOTestRS();
+            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/fbotest.rs b/tests/RenderScriptTests/FBOTest/src/com/android/fbotest/fbotest.rs
new file mode 100644
index 0000000..31dd3e9
--- /dev/null
+++ b/tests/RenderScriptTests/FBOTest/src/com/android/fbotest/fbotest.rs
@@ -0,0 +1,221 @@
+// 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;
+
+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.8f, 0.8f, 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) {
+    // display the result
+    rs_matrix4x4 proj, matrix;
+    rsMatrixLoadOrtho(&proj, 0, rsgGetWidth(), rsgGetHeight(), 0, -500, 500);
+    rsgProgramVertexLoadProjectionMatrix(&proj);
+    rsMatrixLoadIdentity(&matrix);
+    rsgProgramVertexLoadModelMatrix(&matrix);
+    rsgBindTexture(gPFBackground, 0, gOffscreen);
+    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(int launchID) {
+
+    rsgClearColor(1.0f, 1.0f, 1.0f, 1.0f);
+    rsgClearDepth(1.0f);
+
+    renderOffscreen(true);
+    drawOffscreenResult(0, 0);
+
+    renderOffscreen(false);
+    drawOffscreenResult(0, 256);
+
+    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;
+}
diff --git a/tests/RenderScriptTests/PerfTest/src/com/android/perftest/RsBenchRS.java b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/RsBenchRS.java
index 9757ec6..5443ef8 100644
--- a/tests/RenderScriptTests/PerfTest/src/com/android/perftest/RsBenchRS.java
+++ b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/RsBenchRS.java
@@ -22,6 +22,8 @@
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.renderscript.*;
+import android.renderscript.Element.DataKind;
+import android.renderscript.Element.DataType;
 import android.renderscript.Allocation.MipmapControl;
 import android.renderscript.Program.TextureType;
 import android.renderscript.ProgramStore.DepthFunc;
@@ -399,6 +401,23 @@
         initProgramRaster();
         initCustomShaders();
 
+        Type.Builder b = new Type.Builder(mRS, Element.RGBA_8888(mRS));
+        b.setX(1280).setY(720);
+        Allocation offscreen = Allocation.createTyped(mRS,
+                                                      b.create(),
+                                                      Allocation.USAGE_GRAPHICS_TEXTURE |
+                                                      Allocation.USAGE_GRAPHICS_RENDER_TARGET);
+        mScript.set_gRenderBufferColor(offscreen);
+
+        b = new Type.Builder(mRS,
+                             Element.createPixel(mRS, DataType.UNSIGNED_16,
+                             DataKind.PIXEL_DEPTH));
+        b.setX(1280).setY(720);
+        offscreen = Allocation.createTyped(mRS,
+                                           b.create(),
+                                           Allocation.USAGE_GRAPHICS_RENDER_TARGET);
+        mScript.set_gRenderBufferDepth(offscreen);
+
         mRS.bindRootScript(mScript);
     }
 }
diff --git a/tests/RenderScriptTests/PerfTest/src/com/android/perftest/rsbench.rs b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/rsbench.rs
index 3c92725..fd0f16f 100644
--- a/tests/RenderScriptTests/PerfTest/src/com/android/perftest/rsbench.rs
+++ b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/rsbench.rs
@@ -76,11 +76,17 @@
 rs_program_fragment gProgFragmentPixelLight;
 rs_program_fragment gProgFragmentMultitex;
 
+rs_allocation gRenderBufferColor;
+rs_allocation gRenderBufferDepth;
+
 float gDt = 0;
 
 void init() {
 }
 
+static int gRenderSurfaceW;
+static int gRenderSurfaceH;
+
 static const char *sampleText = "This is a sample of small text for performace";
 // Offsets for multiple layer of text
 static int textOffsets[] = { 0,  0, -5, -5, 5,  5, -8, -8, 8,  8};
@@ -91,6 +97,11 @@
                              0.5f, 0.6f, 0.7f, 1.0f,
 };
 
+static void setupOffscreenTarget() {
+    rsgBindColorTarget(gRenderBufferColor, 0);
+    rsgBindDepthTarget(gRenderBufferDepth);
+}
+
 static void displayFontSamples(int fillNum) {
 
     rs_font fonts[5];
@@ -100,8 +111,8 @@
     rsSetObject(&fonts[3], gFontSerifBoldItalic);
     rsSetObject(&fonts[4], gFontSans);
 
-    uint width = rsgGetWidth();
-    uint height = rsgGetHeight();
+    uint width = gRenderSurfaceW;
+    uint height = gRenderSurfaceH;
     int left = 0, right = 0, top = 0, bottom = 0;
     rsgMeasureText(sampleText, &left, &right, &top, &bottom);
 
@@ -136,7 +147,7 @@
     rsgBindProgramVertex(gProgVertex);
     // Setup the projection matrix
     rs_matrix4x4 proj;
-    rsMatrixLoadOrtho(&proj, 0, rsgGetWidth(), rsgGetHeight(), 0, -500, 500);
+    rsMatrixLoadOrtho(&proj, 0, gRenderSurfaceW, gRenderSurfaceH, 0, -500, 500);
     rsgProgramVertexLoadProjectionMatrix(&proj);
 }
 
@@ -158,7 +169,7 @@
 
     for (int i = 0; i < quadCount; i ++) {
         float startX = 10 * i, startY = 10 * i;
-        float width = rsgGetWidth() - startX, height = rsgGetHeight() - startY;
+        float width = gRenderSurfaceW - startX, height = gRenderSurfaceH - startY;
         rsgDrawQuadTexCoords(startX, startY, 0, 0, 0,
                              startX, startY + height, 0, 0, 1,
                              startX + width, startY + height, 0, 1, 1,
@@ -216,7 +227,7 @@
 
     bindProgramVertexOrtho();
     rs_matrix4x4 matrix;
-    rsMatrixLoadTranslate(&matrix, rsgGetWidth()/2, rsgGetHeight()/2, 0);
+    rsMatrixLoadTranslate(&matrix, gRenderSurfaceW/2, gRenderSurfaceH/2, 0);
     rsgProgramVertexLoadModelMatrix(&matrix);
 
     // Fragment shader with texture
@@ -344,7 +355,7 @@
     rsgBindProgramRaster(gCullBack);
     // Setup the projection matrix with 30 degree field of view
     rs_matrix4x4 proj;
-    float aspect = (float)rsgGetWidth() / (float)rsgGetHeight();
+    float aspect = (float)gRenderSurfaceW / (float)gRenderSurfaceH;
     rsMatrixLoadPerspective(&proj, 30.0f, aspect, 0.1f, 100.0f);
     rsgProgramVertexLoadProjectionMatrix(&proj);
 
@@ -445,7 +456,7 @@
     }
 
     // Setup the projection matrix
-    float aspect = (float)rsgGetWidth() / (float)rsgGetHeight();
+    float aspect = (float)gRenderSurfaceW / (float)gRenderSurfaceH;
     rsMatrixLoadPerspective(&gVSConstants->proj, 30.0f, aspect, 0.1f, 100.0f);
     setupCustomShaderLights();
 
@@ -476,7 +487,7 @@
     gVSConstPixel->time = rsUptimeMillis()*0.005;
 
     // Setup the projection matrix
-    float aspect = (float)rsgGetWidth() / (float)rsgGetHeight();
+    float aspect = (float)gRenderSurfaceW / (float)gRenderSurfaceH;
     rsMatrixLoadPerspective(&gVSConstPixel->proj, 30.0f, aspect, 0.1f, 100.0f);
     setupCustomShaderLights();
 
@@ -520,7 +531,7 @@
 
     for (int i = 0; i < quadCount; i ++) {
         float startX = 10 * i, startY = 10 * i;
-        float width = rsgGetWidth() - startX, height = rsgGetHeight() - startY;
+        float width = gRenderSurfaceW - startX, height = gRenderSurfaceH - startY;
         rsgDrawQuadTexCoords(startX, startY, 0, 0, 0,
                              startX, startY + height, 0, 0, 1,
                              startX + width, startY + height, 0, 1, 1,
@@ -535,7 +546,7 @@
     gAnisoTime += gDt;
 
     rsgBindProgramVertex(gProgVertex);
-    float aspect = (float)rsgGetWidth() / (float)rsgGetHeight();
+    float aspect = (float)gRenderSurfaceW / (float)gRenderSurfaceH;
     rs_matrix4x4 proj;
     rsMatrixLoadPerspective(&proj, 30.0f, aspect, 0.1f, 100.0f);
     rsgProgramVertexLoadProjectionMatrix(&proj);
@@ -592,10 +603,6 @@
 
     static int countdown = 5;
 
-    if (countdown == 0) {
-        gDt = 0;
-        countdown --;
-    }
     // Perform all the uploads so we only measure rendered time
     if(countdown > 1) {
         displayFontSamples(5);
@@ -612,19 +619,13 @@
         countdown --;
         rsgClearColor(0.2f, 0.2f, 0.2f, 0.0f);
 
-        // Now use text metrics to center the text
-        uint width = rsgGetWidth();
-        uint height = rsgGetHeight();
-        int left = 0, right = 0, top = 0, bottom = 0;
-
         rsgFontColor(0.9f, 0.9f, 0.95f, 1.0f);
         rsgBindFont(gFontSerifBoldItalic);
-
-        const char* text = "Initializing";
-        rsgMeasureText(text, &left, &right, &top, &bottom);
-        int centeredPosX = width / 2 - (right - left) / 2;
-        int centeredPosY = height / 2 - (top - bottom) / 2;
-        rsgDrawText(text, centeredPosX, centeredPosY);
+        if (countdown == 1) {
+            rsgDrawText("Rendering", 50, 50);
+        } else {
+            rsgDrawText("Initializing", 50, 50);
+        }
 
         return false;
     }
@@ -632,70 +633,40 @@
     return true;
 }
 
-static int frameCount = 0;
-static int totalFramesRendered = 0;
 static int benchMode = 0;
 
-#define testTime 5.0f
-static float curTestTime = testTime;
-
 static const char *testNames[] = {
-    "Finished text fill 1",
-    "Finished text fill 2",
-    "Finished text fill 3",
-    "Finished text fill 4",
-    "Finished text fill 5",
-    "Finished 25.6k geo flat color",
-    "Finished 51.2k geo flat color",
-    "Finished 204.8k geo raster load flat color",
-    "Finished 25.6k geo texture",
-    "Finished 51.2k geo texture",
-    "Finished 204.8k geo raster load texture",
-    "Finished full screen mesh 10 by 10",
-    "Finished full screen mesh 100 by 100",
-    "Finished full screen mesh W / 4 by H / 4",
-    "Finished 25.6k geo heavy vertex",
-    "Finished 51.2k geo heavy vertex",
-    "Finished 204.8k geo raster load heavy vertex",
-    "Finished singletexture 5x fill",
-    "Finished 3tex multitexture 5x fill",
-    "Finished blend singletexture 5x fill",
-    "Finished blend 3tex multitexture 5x fill",
-    "Finished 25.6k geo heavy fragment",
-    "Finished 51.2k geo heavy fragment",
-    "Finished 204.8k geo raster load heavy fragment",
-    "Finished 25.6k geo heavy fragment, heavy vertex",
-    "Finished 51.2k geo heavy fragment, heavy vertex",
-    "Finished 204.8k geo raster load heavy fragment, heavy vertex",
+    "Finished text fill 1,",
+    "Finished text fill 2,",
+    "Finished text fill 3,",
+    "Finished text fill 4,",
+    "Finished text fill 5,",
+    "Finished 25.6k geo flat color,",
+    "Finished 51.2k geo flat color,",
+    "Finished 204.8k geo raster load flat color,",
+    "Finished 25.6k geo texture,",
+    "Finished 51.2k geo texture,",
+    "Finished 204.8k geo raster load texture,",
+    "Finished full screen mesh 10 by 10,",
+    "Finished full screen mesh 100 by 100,",
+    "Finished full screen mesh W / 4 by H / 4,",
+    "Finished 25.6k geo heavy vertex,",
+    "Finished 51.2k geo heavy vertex,",
+    "Finished 204.8k geo raster load heavy vertex,",
+    "Finished singletexture 5x fill,",
+    "Finished 3tex multitexture 5x fill,",
+    "Finished blend singletexture 5x fill,",
+    "Finished blend 3tex multitexture 5x fill,",
+    "Finished 25.6k geo heavy fragment,",
+    "Finished 51.2k geo heavy fragment,",
+    "Finished 204.8k geo raster load heavy fragment,",
+    "Finished 25.6k geo heavy fragment heavy vertex,",
+    "Finished 51.2k geo heavy fragment heavy vertex,",
+    "Finished 204.8k geo raster load heavy fragment heavy vertex,",
 };
 
-int root(int launchID) {
-
-    gDt = rsGetDt();
-
-    rsgClearColor(0.2f, 0.2f, 0.2f, 0.0f);
-    rsgClearDepth(1.0f);
-
-    if(!checkInit()) {
-        return 1;
-    }
-
-    curTestTime -= gDt;
-    if(curTestTime < 0.0f) {
-        float fps = (float)(frameCount) / (testTime - curTestTime);
-        rsDebug(testNames[benchMode], fps);
-        benchMode ++;
-        curTestTime = testTime;
-        totalFramesRendered += frameCount;
-        frameCount = 0;
-        gTorusRotation = 0;
-
-        if (benchMode > gMaxModes) {
-            benchMode = 0;
-        }
-    }
-
-    switch (benchMode) {
+static void runTest(int index) {
+    switch (index) {
     case 0:
         displayFontSamples(1);
         break;
@@ -777,10 +748,87 @@
     case 26:
         displayPixelLightSamples(8, true);
         break;
+    }
+}
 
+static void drawOffscreenResult(int posX, int posY, int width, int height) {
+    bindProgramVertexOrtho();
+
+    rs_matrix4x4 matrix;
+    rsMatrixLoadIdentity(&matrix);
+    rsgProgramVertexLoadModelMatrix(&matrix);
+
+    rsgBindProgramFragment(gProgFragmentTexture);
+
+    rsgBindSampler(gProgFragmentTexture, 0, gLinearClamp);
+    rsgBindTexture(gProgFragmentTexture, 0, gRenderBufferColor);
+
+    float startX = posX, startY = posY;
+    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(int launchID) {
+
+    gRenderSurfaceW = rsgGetWidth();
+    gRenderSurfaceH = rsgGetHeight();
+    rsgClearColor(0.2f, 0.2f, 0.2f, 1.0f);
+    rsgClearDepth(1.0f);
+    if(!checkInit()) {
+        return 1;
     }
 
-    frameCount ++;
+    rsgFinish();
+    int64_t start = rsUptimeMillis();
+    rsGetDt();
+
+    int drawPos = 0;
+    int frameCount = 100;
+    for(int i = 0; i < frameCount; i ++) {
+        setupOffscreenTarget();
+        gRenderSurfaceW = rsAllocationGetDimX(gRenderBufferColor);
+        gRenderSurfaceH = rsAllocationGetDimY(gRenderBufferColor);
+        rsgClearColor(0.1f, 0.1f, 0.1f, 1.0f);
+        rsgClearDepth(1.0f);
+
+        runTest(benchMode);
+        rsgClearAllRenderTargets();
+        gRenderSurfaceW = rsgGetWidth();
+        gRenderSurfaceH = rsgGetHeight();
+        int size = 8;
+        drawOffscreenResult((drawPos+=size)%gRenderSurfaceW, (gRenderSurfaceH * 3) / 4, size, size);
+        gDt = rsGetDt();
+    }
+
+    rsgFinish();
+
+    int64_t end = rsUptimeMillis();
+    float fps = (float)(frameCount) / ((float)(end - start)*0.001f);
+    rsDebug(testNames[benchMode], fps);
+
+    drawOffscreenResult(0, 0,
+                        gRenderSurfaceW / 2,
+                        gRenderSurfaceH / 2);
+
+    const char* text = testNames[benchMode];
+    int left = 0, right = 0, top = 0, bottom = 0;
+    uint width = rsgGetWidth();
+    uint height = rsgGetHeight();
+    rsgFontColor(0.9f, 0.9f, 0.95f, 1.0f);
+    rsgBindFont(gFontSerifBoldItalic);
+    rsgMeasureText(text, &left, &right, &top, &bottom);
+    rsgFontColor(1.0f, 1.0f, 1.0f, 1.0f);
+    rsgDrawText(text, 2 -left, height - 2 + bottom);
+
+    benchMode ++;
+
+    gTorusRotation = 0;
+
+    if (benchMode > gMaxModes) {
+        benchMode = 0;
+    }
 
     return 1;
 }