add Nbody that uses ByteBuffer

Change-Id: Ia9cf4126ff1b84e91d22274e72200a6662dc4614
diff --git a/java/tests/RsNbody/Android.mk b/java/tests/RsNbody/Android.mk
new file mode 100644
index 0000000..dae1eac
--- /dev/null
+++ b/java/tests/RsNbody/Android.mk
@@ -0,0 +1,39 @@
+#
+# Copyright (C) 2015 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+                   $(call all-renderscript-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4
+
+LOCAL_PACKAGE_NAME := RsNbody
+LOCAL_RENDERSCRIPT_TARGET_API := 23
+
+LOCAL_RENDERSCRIPT_CC := $(LLVM_RS_CC)
+LOCAL_RENDERSCRIPT_INCLUDES_OVERRIDE := \
+    $(TOPDIR)external/clang/lib/Headers \
+    $(TOPDIR)frameworks/rs/scriptc
+
+#LOCAL_RENDERSCRIPT_FLAGS := -rs-package-name=android.support.v8.renderscript
+
+LOCAL_32_BIT_ONLY := true
+
+include $(BUILD_PACKAGE)
diff --git a/java/tests/RsNbody/AndroidManifest.xml b/java/tests/RsNbody/AndroidManifest.xml
new file mode 100644
index 0000000..a702634
--- /dev/null
+++ b/java/tests/RsNbody/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.rs.nbody_gl" >
+
+    <uses-sdk
+        android:targetSdkVersion="23" />
+
+
+    <application
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:largeHeap="true"
+
+        android:theme="@style/AppTheme" >
+        <activity
+            android:name=".MainActivity"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="image/*" />
+            </intent-filter>
+
+        </activity>
+    </application>
+
+</manifest>
diff --git a/java/tests/RsNbody/res/layout/activity_main.xml b/java/tests/RsNbody/res/layout/activity_main.xml
new file mode 100644
index 0000000..a251887
--- /dev/null
+++ b/java/tests/RsNbody/res/layout/activity_main.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".MainActivity">
+
+    <com.example.android.rs.nbody_gl.BasicGLSurfaceView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:id="@+id/surfaceView"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentRight="true"
+        android:layout_alignParentEnd="true"
+        />
+</RelativeLayout>
diff --git a/java/tests/RsNbody/res/mipmap-hdpi/ic_launcher.png b/java/tests/RsNbody/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
--- /dev/null
+++ b/java/tests/RsNbody/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/java/tests/RsNbody/res/mipmap-mdpi/ic_launcher.png b/java/tests/RsNbody/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
--- /dev/null
+++ b/java/tests/RsNbody/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/java/tests/RsNbody/res/mipmap-xhdpi/ic_launcher.png b/java/tests/RsNbody/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
--- /dev/null
+++ b/java/tests/RsNbody/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/java/tests/RsNbody/res/mipmap-xxhdpi/ic_launcher.png b/java/tests/RsNbody/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
--- /dev/null
+++ b/java/tests/RsNbody/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/java/tests/RsNbody/res/mipmap-xxxhdpi/ic_launcher.png b/java/tests/RsNbody/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..aee44e1
--- /dev/null
+++ b/java/tests/RsNbody/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/java/tests/RsNbody/res/values-w820dp/dimens.xml b/java/tests/RsNbody/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/java/tests/RsNbody/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+<resources>
+    <!-- Example customization of dimensions originally defined in res/values/dimens.xml
+         (such as screen margins) for screens with more than 820dp of available width. This
+         would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
+    <dimen name="activity_horizontal_margin">64dp</dimen>
+</resources>
diff --git a/java/tests/RsNbody/res/values/colors.xml b/java/tests/RsNbody/res/values/colors.xml
new file mode 100644
index 0000000..3ab3e9c
--- /dev/null
+++ b/java/tests/RsNbody/res/values/colors.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="colorPrimary">#3F51B5</color>
+    <color name="colorPrimaryDark">#303F9F</color>
+    <color name="colorAccent">#FF4081</color>
+</resources>
diff --git a/java/tests/RsNbody/res/values/dimens.xml b/java/tests/RsNbody/res/values/dimens.xml
new file mode 100644
index 0000000..47c8224
--- /dev/null
+++ b/java/tests/RsNbody/res/values/dimens.xml
@@ -0,0 +1,5 @@
+<resources>
+    <!-- Default screen margins, per the Android Design guidelines. -->
+    <dimen name="activity_horizontal_margin">16dp</dimen>
+    <dimen name="activity_vertical_margin">16dp</dimen>
+</resources>
diff --git a/java/tests/RsNbody/res/values/strings.xml b/java/tests/RsNbody/res/values/strings.xml
new file mode 100644
index 0000000..016bd38
--- /dev/null
+++ b/java/tests/RsNbody/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">Nbody_gl</string>
+</resources>
diff --git a/java/tests/RsNbody/res/values/styles.xml b/java/tests/RsNbody/res/values/styles.xml
new file mode 100644
index 0000000..3f3debd
--- /dev/null
+++ b/java/tests/RsNbody/res/values/styles.xml
@@ -0,0 +1,9 @@
+<resources>
+
+    <!-- Base application theme. -->
+    <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
+        <!-- Customize your theme here. -->
+        
+    </style>
+
+</resources>
diff --git a/java/tests/RsNbody/src/com/example/android/rs/nbody_gl/BasicGLRenderer.java b/java/tests/RsNbody/src/com/example/android/rs/nbody_gl/BasicGLRenderer.java
new file mode 100644
index 0000000..ca9a5b5
--- /dev/null
+++ b/java/tests/RsNbody/src/com/example/android/rs/nbody_gl/BasicGLRenderer.java
@@ -0,0 +1,118 @@
+package com.example.android.rs.nbody_gl;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+import android.content.Context;
+import android.opengl.GLES20;
+import android.opengl.GLSurfaceView;
+import android.opengl.Matrix;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+public class BasicGLRenderer implements GLSurfaceView.Renderer {
+
+    private static final String TAG = "BasicGLRenderer";
+    private Swarm mSwarm;
+    Context mContext;
+    private final float[] mMVPMatrix = new float[16];
+    private final float[] mProjectionMatrix = new float[16];
+    private final float[] mViewMatrix = new float[16];
+    private final float[] mRotationMatrix = new float[16];
+    private float mAngle;
+
+    GLSurfaceView mGLSurfaceView;
+
+    public BasicGLRenderer(Context context, GLSurfaceView view) {
+        mContext = context;
+        mGLSurfaceView = view;
+    }
+
+    @Override
+    public void onSurfaceCreated(GL10 unused, EGLConfig config) {
+        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+        GLES20.glEnable(GLES20.GL_DEPTH_TEST);
+
+        if (mSwarm != null) {
+            mSwarm.onPause();
+        }
+        mSwarm = new Swarm(mContext, mGLSurfaceView);
+        mSwarm.onSurfaceCreated();
+        mSwarm.onResume();
+    }
+
+    @Override
+    public void onDrawFrame(GL10 unused) {
+        float[] scratch = new float[16];
+
+        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
+        Matrix.setLookAtM(mViewMatrix, 0,
+                0, 0, -3, 0f,
+                0f, 0f, 0f,
+                1.0f, 0.0f);
+        Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);
+
+        mSwarm.draw(mMVPMatrix);
+
+        Matrix.setRotateM(mRotationMatrix, 0, mAngle, 0, 0, 1.0f);
+        Matrix.multiplyMM(scratch, 0, mMVPMatrix, 0, mRotationMatrix, 0);
+
+    }
+
+    @Override
+    public void onSurfaceChanged(GL10 unused, int width, int height) {
+        GLES20.glViewport(0, 0, width, height);
+        float ratio = (float) width / height;
+        Matrix.frustumM(mProjectionMatrix, 0, -ratio / 10, ratio / 10, -.1f, .1f, .1f, 200);
+    }
+
+    public static int loadShader(int type, String shaderCode) {
+        int shader = GLES20.glCreateShader(type);
+        GLES20.glShaderSource(shader, shaderCode);
+        GLES20.glCompileShader(shader);
+
+        return shader;
+    }
+
+    public static void checkGlError(String glOperation) {
+        int error;
+        while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
+            Log.e(TAG, glOperation + ": glError " + error);
+            throw new RuntimeException(glOperation + ": glError " + error);
+        }
+    }
+
+    public float getAngle() {
+        return mAngle;
+    }
+
+    public void setAngle(float angle) {
+        mAngle = angle;
+    }
+
+    public void onResume() {
+        if (mSwarm != null) {
+            mSwarm.onResume();
+        }
+    }
+
+    public void onPause() {
+        if (mSwarm != null) {
+            mSwarm.onPause();
+        }
+    }
+
+    public void onTouchEvent(MotionEvent e) {
+        mSwarm.onTouchEvent(e);
+    }
+
+    public void onJoystick(float dx, float dy) {
+        mSwarm.onJoystick(dx, dy);
+    }
+
+    public void onKeyDown(int keyCode, KeyEvent event) {
+
+        mSwarm.onKeyDown(keyCode, event);
+    }
+}
\ No newline at end of file
diff --git a/java/tests/RsNbody/src/com/example/android/rs/nbody_gl/BasicGLSurfaceView.java b/java/tests/RsNbody/src/com/example/android/rs/nbody_gl/BasicGLSurfaceView.java
new file mode 100644
index 0000000..27c628e
--- /dev/null
+++ b/java/tests/RsNbody/src/com/example/android/rs/nbody_gl/BasicGLSurfaceView.java
@@ -0,0 +1,82 @@
+package com.example.android.rs.nbody_gl;
+
+import android.content.Context;
+import android.opengl.GLSurfaceView;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+
+public class BasicGLSurfaceView extends GLSurfaceView {
+    private static final String TAG = "BasicGLSurfaceView";
+    private final BasicGLRenderer mRenderer;
+
+    public BasicGLSurfaceView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setEGLContextClientVersion(2);
+        mRenderer = new BasicGLRenderer(context, this);
+        setup();
+    }
+
+    public BasicGLSurfaceView(Context context) {
+        super(context);
+        setEGLContextClientVersion(2);
+        mRenderer = new BasicGLRenderer(context, this);
+        setup();
+    }
+
+    private void setup() {
+        setRenderer(mRenderer);
+        setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
+        setFocusable(true);
+        setFocusableInTouchMode(true);
+        requestFocus();
+
+    }
+
+    private final float TOUCH_SCALE_FACTOR = 180.0f / 320;
+    private float mPreviousX;
+    private float mPreviousY;
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        mRenderer.onResume();
+    }
+
+    @Override
+    public void onPause() {
+        mRenderer.onPause();
+        super.onPause();
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent e) {
+        mRenderer.onTouchEvent(e);
+        return true;
+    }
+
+    @Override
+    public boolean onGenericMotionEvent(MotionEvent event) {
+        Log.v(TAG,"onGenericMotionEvent ");
+        if (event.isFromSource(InputDevice.SOURCE_CLASS_JOYSTICK)) {
+            if (event.getAction() == MotionEvent.ACTION_MOVE) {
+                float x = event.getAxisValue(MotionEvent.AXIS_X);
+                float y = event.getAxisValue(MotionEvent.AXIS_Y);
+                mRenderer.onJoystick(x,y);
+                return true;
+            }
+        }
+        return super.onGenericMotionEvent(event);
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        mRenderer.onKeyDown(keyCode, event);
+         return super.onKeyDown(keyCode, event);
+     }
+
+
+}
\ No newline at end of file
diff --git a/java/tests/RsNbody/src/com/example/android/rs/nbody_gl/MainActivity.java b/java/tests/RsNbody/src/com/example/android/rs/nbody_gl/MainActivity.java
new file mode 100644
index 0000000..9261b88
--- /dev/null
+++ b/java/tests/RsNbody/src/com/example/android/rs/nbody_gl/MainActivity.java
@@ -0,0 +1,41 @@
+package com.example.android.rs.nbody_gl;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.Toolbar;
+
+public class MainActivity extends Activity {
+    private static final String TAG = "MainActivity";
+    private BasicGLSurfaceView mGLView;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+        mGLView = (BasicGLSurfaceView) findViewById(R.id.surfaceView);
+        View decorView = getWindow().getDecorView();
+        int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+                | View.SYSTEM_UI_FLAG_FULLSCREEN;
+        decorView.setSystemUiVisibility(uiOptions);
+    }
+
+    @Override
+    protected void onPause() {
+        mGLView.onPause();
+        super.onPause();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mGLView.onResume();
+    }
+
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        Log.v(TAG, "KEY " + keyCode);
+        return true;
+    }
+}
diff --git a/java/tests/RsNbody/src/com/example/android/rs/nbody_gl/Swarm.java b/java/tests/RsNbody/src/com/example/android/rs/nbody_gl/Swarm.java
new file mode 100644
index 0000000..ee7428c
--- /dev/null
+++ b/java/tests/RsNbody/src/com/example/android/rs/nbody_gl/Swarm.java
@@ -0,0 +1,392 @@
+package com.example.android.rs.nbody_gl;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.opengl.GLES20;
+import android.opengl.GLSurfaceView;
+import android.os.AsyncTask;
+import android.renderscript.Allocation;
+import android.renderscript.Element;
+import android.renderscript.Float2;
+import android.renderscript.Float3;
+import android.renderscript.RenderScript;
+import android.renderscript.Type;
+import android.support.v4.view.MotionEventCompat;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+import java.util.Arrays;
+import java.util.Objects;
+
+public class Swarm {
+    private static final String TAG = "Swarm";
+    private static final boolean USE_BB = true;
+    public static final int SWARM_SIZE = 1000; // max is 2730= 2^32/12
+
+    private final String vertexShaderCode =
+            "uniform   mat4 u_MVP;" +
+                    "attribute vec4 a_Position;" +
+                    "attribute vec4 a_Color;" +
+                    "varying   vec4 v_Color;" +
+                    "varying   vec4 v_loc;" +
+                    "varying   vec3 v_pos;" +
+
+                    "void main() {" +
+                    "  v_Color = a_Color;" +
+                    "  gl_Position = u_MVP * a_Position;" +
+                    "v_loc = a_Position;" +
+                    " v_pos = gl_Position.xyz;" +
+                    "}";
+
+    private final String fragmentShaderCode =
+            "precision mediump float;" +
+                    "varying vec4 v_Color;" +
+                    "varying vec4 v_loc;" +
+                    "varying vec3 v_pos;" +
+
+                    "void main() {" +
+
+                    "  gl_FragColor = v_Color ;" +
+                    "}";
+
+    GLSurfaceView mGLSurfaceView;
+    boolean mDrawSwarm = false;
+
+    private volatile FloatBuffer vertexBuffer;
+    private final ShortBuffer drawListBuffer;
+    private final FloatBuffer mColorBuffer;
+    private final int mProgram;
+    private final Context mContext;
+    Background mBackground = new Background();
+    private int mPositionHandle;
+    private int mColorHandle;
+    private int mMVPMatrixHandle;
+    static final int[] sTetrahedronIndex = {0, 1, 2, 0, 2, 3, 0, 3, 1, 1, 2, 3};
+
+    static final int FLOATS_PER_VERTEX = 4;
+    static final int FLOATS_PER_COLOR = 3;
+    static final int VERTEX_PER_TETRAHEDRON = 4;
+    static final int BYTES_PER_FLOAT = 4;
+    volatile float mTriArray[] = new float[SWARM_SIZE * FLOATS_PER_VERTEX * VERTEX_PER_TETRAHEDRON];
+    float mColorArray[] = new float[SWARM_SIZE * FLOATS_PER_COLOR * VERTEX_PER_TETRAHEDRON];
+    short mIndexArray[] = new short[SWARM_SIZE * sTetrahedronIndex.length];
+
+    private final int vertexStride = FLOATS_PER_VERTEX * BYTES_PER_FLOAT; // 4 bytes per vertex
+    private final int colorStride = FLOATS_PER_COLOR * BYTES_PER_FLOAT; // 4 bytes per vertex
+    private boolean mOld = true;
+    private Object lock = new Object();
+
+    public Swarm(Context context, GLSurfaceView view) {
+
+        mContext = context;
+        mGLSurfaceView = view;
+        double arc = 2 * Math.PI / mTriArray.length;
+        for (int i = 0; i < mTriArray.length; i += FLOATS_PER_VERTEX) {
+            int p = i;
+            int v = ((i / FLOATS_PER_VERTEX) % VERTEX_PER_TETRAHEDRON) == 0 ? 0 : 1;
+            int c = (i / FLOATS_PER_VERTEX) / VERTEX_PER_TETRAHEDRON;
+            double s = (2 + c % 7) / 4.;
+            mTriArray[p++] = (float) (s * v * Math.sin(i * arc));
+            mTriArray[p++] = (float) (s * v * Math.cos(i * arc));
+            mTriArray[p++] = (float) ((Math.random() - .5) * 1.8);
+            mTriArray[p] = 0;
+        }
+
+        float[] hsv = new float[]{.9f, .99f, .8f};
+        Arrays.fill(mColorArray, 0, 12, .1f);
+        for (int i = 12; i < mColorArray.length; i += FLOATS_PER_COLOR) {
+            if (((i / 3) % 4) == 0) {
+                mColorArray[i + 2] = mColorArray[i + 1] = mColorArray[i] = 0.8f;
+                continue;
+            }
+            hsv[0] = (float) Math.random() * 360;
+            int color = Color.HSVToColor(hsv);
+            mColorArray[i] = (color & 255) / 255.f;
+            color >>= 8;
+            mColorArray[i + 1] = (color & 255) / 255.f;
+            color >>= 8;
+            mColorArray[i + 2] = (color & 255) / 255.f;
+        }
+        // Build Tetrahedrons
+        for (short i = 0; i < mIndexArray.length; i++) {
+            int tet = i / sTetrahedronIndex.length;
+            int index = i % sTetrahedronIndex.length;
+            int offset = tet * VERTEX_PER_TETRAHEDRON;
+            mIndexArray[i] = (short) (offset + sTetrahedronIndex[index]);
+        }
+
+        ByteBuffer bb = ByteBuffer.allocateDirect(mTriArray.length * 4);   // (# of coordinate values * 4 bytes per float)
+        bb.order(ByteOrder.nativeOrder());
+
+
+        ByteBuffer dlb = ByteBuffer.allocateDirect(mIndexArray.length * 2);
+        dlb.order(ByteOrder.nativeOrder());
+        drawListBuffer = dlb.asShortBuffer();
+        drawListBuffer.put(mIndexArray);
+        drawListBuffer.position(0);
+
+        ByteBuffer colBB = ByteBuffer.allocateDirect(mColorArray.length * 4);
+        colBB.order(ByteOrder.nativeOrder());
+        mColorBuffer = colBB.asFloatBuffer();
+        mColorBuffer.put(mColorArray);
+        mColorBuffer.position(0);
+
+        int vertexShader = BasicGLRenderer.loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
+        int fragmentShader = BasicGLRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
+
+        mProgram = GLES20.glCreateProgram();             // create empty OpenGL Program
+        GLES20.glAttachShader(mProgram, vertexShader);   // add the vertex shader to program
+        GLES20.glAttachShader(mProgram, fragmentShader); // add the fragment shader to program
+        GLES20.glLinkProgram(mProgram);                  // create OpenGL program executables
+    }
+
+    synchronized void setTriangles(FloatBuffer v) {
+        vertexBuffer = v;
+        mGLSurfaceView.requestRender();
+    }
+
+    int count = 0;
+    long time = System.nanoTime();
+
+    public synchronized void draw(float[] mvpMatrix) {
+         if (!mDrawSwarm || vertexBuffer == null) return;
+        mTriArray[2] = 0;
+        synchronized (vertexBuffer) {
+            vertexBuffer.position(0);
+            GLES20.glUseProgram(mProgram);
+
+            mPositionHandle = GLES20.glGetAttribLocation(mProgram, "a_Position");
+            GLES20.glEnableVertexAttribArray(mPositionHandle);
+
+            GLES20.glVertexAttribPointer(mPositionHandle, 3, GLES20.GL_FLOAT, false, vertexStride, vertexBuffer);
+
+            mColorHandle = GLES20.glGetAttribLocation(mProgram, "a_Color");
+            GLES20.glEnableVertexAttribArray(mColorHandle);
+            GLES20.glVertexAttribPointer(mColorHandle, FLOATS_PER_COLOR, GLES20.GL_FLOAT, false, colorStride, mColorBuffer);
+
+            mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "u_MVP");
+            checkGlError("glGetUniformLocation");
+
+            GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);
+            checkGlError("glUniformMatrix4fv");
+
+            GLES20.glDrawElements(
+                    GLES20.GL_TRIANGLES, mIndexArray.length,
+                    GLES20.GL_UNSIGNED_SHORT, drawListBuffer);
+
+            GLES20.glDisableVertexAttribArray(mPositionHandle);
+            GLES20.glFinish();
+        }
+        count++;
+        if (count > 100) {
+            Log.v(TAG, " gl " + count / ((System.nanoTime() - time) * 1E-9) + " fps");
+            count = 0;
+            time = System.nanoTime();
+        }
+    }
+
+    private static void checkGlError(String glOperation) {
+        int error;
+        while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
+            Log.e(TAG, glOperation + ": glError " + error);
+            throw new RuntimeException(glOperation + ": glError " + error);
+        }
+    }
+
+    public void onResume() {
+        mBackground = new Background();
+        mBackground.execute();
+    }
+
+    public void onPause() {
+        mDrawSwarm = false;
+        mBackground.keepRunning = false;
+    }
+
+    public void onSurfaceCreated() {
+        mDrawSwarm = true;
+    }
+
+    public void onTouchEvent(MotionEvent e) {
+        final int action = MotionEventCompat.getActionMasked(e);
+        Log.v(TAG, "action =" + action);
+        switch (action) {
+            case MotionEvent.ACTION_MOVE:
+                if (e.getEventTime() - e.getDownTime() > 100) {
+                    float w = mGLSurfaceView.getWidth();
+                    float h = mGLSurfaceView.getHeight();
+
+                    mDrag_x = (w / 2 - e.getX()) * 4;
+                    mDrag_y = (h / 2 - e.getY()) * 4;
+                    mDrag_z = 0;
+                    mDrag = true;
+                }
+                break;
+            case MotionEvent.ACTION_DOWN:
+                break;
+            case MotionEvent.ACTION_UP:
+                if (e.getEventTime() - e.getDownTime() < 200) {
+                    restartSimulation();
+                }
+
+        }
+    }
+
+    public void onJoystick(float dx, float dy) {
+        if (Math.abs(dx) + Math.abs(dy) > 0.1) {
+            float w = mGLSurfaceView.getWidth();
+            float h = mGLSurfaceView.getHeight();
+            mDrag_x = dx * w * 2;
+            mDrag_y = dy * h * 2;
+            mDrag_z = 0;
+            mDrag = true;
+        }
+    }
+
+
+    float mDrag_x, mDrag_y, mDrag_z;
+    volatile boolean mDrag = false;
+
+    static final int P_CTR_M = 0;
+    static final int P_MAX_M = 1;
+    static final int P_MIN_M = 2;
+    static final int P_INIT_V = 3;
+    static final int P_DIR_X = 4;
+    static final int P_DIR_Y = 5;
+    static final int P_DIR_Z = 6;
+    static final int P_RAD_MIN = 7;
+    static final int P_RAD_MAX = 8;
+    static final float[][] sModes = {
+            {1000, 800, 300, 0, 1, 1, 1, 1300, 1900},
+            {1000000, 8, 3, 1800, 1, 1, 1, 1300, 1900},
+            {1000000, 8, 7, 1800, 0, 0, 1, 1300, 1900},
+            {1000, 800, 300, 0, 1, 1, 1, 1800, 1900},
+            {1000000, 8, 3, 1800, 1, 1, 1, 1800, 1900},
+            {1000000, 8, 7, 1000, 0, 0, 1, 1800, 1900},
+            {1000000, 8, 7, 1800, 0, 1, 0, 1800, 1900},
+    };
+    int mModeNo = 0;
+    volatile float[] mMode = sModes[mModeNo];
+
+    private void restartSimulation() {
+        Log.v(TAG, "restartSimulation " + mModeNo);
+
+        mDrawSwarm = false;
+        mBackground.keepRunning = false;
+        mModeNo = (mModeNo + 1) % sModes.length;
+        mMode = sModes[mModeNo];
+        mBackground = new Background();
+        mBackground.execute();
+        mDrawSwarm = true;
+    }
+
+    public void onKeyDown(int keyCode, KeyEvent event) {
+        restartSimulation();
+    }
+
+
+    class Background extends AsyncTask<Void, FloatBuffer, Void> {
+        boolean keepRunning = true;
+        int count = 0;
+        long time = System.nanoTime();
+
+
+        @Override
+        protected Void doInBackground(Void... params) {
+            RenderScript rs = RenderScript.create(mContext);
+            float[] hsv = new float[3];
+            hsv[1] = 0.8f;
+            hsv[2] = 0.9f;
+
+            Allocation posAlloc = Allocation.createSized(rs, Element.F32_4(rs), SWARM_SIZE);
+            Allocation velAlloc = Allocation.createSized(rs, Element.F32_4(rs), SWARM_SIZE);
+            int triAllocSize = mTriArray.length / 4;
+            Type.Builder builder = new Type.Builder(rs, Element.F32_4(rs));
+
+            Allocation triAlloc1 = Allocation.createSized(rs, Element.F32_4(rs), triAllocSize);
+            Allocation triAlloc2 = triAlloc1;
+            FloatBuffer triBuff1,triBuff2,triBuff;
+            if (USE_BB) {
+                triAlloc2 = Allocation.createSized(rs, Element.F32_4(rs), triAllocSize);
+                triBuff1 = triAlloc1.getByteBuffer().order(ByteOrder.LITTLE_ENDIAN).asFloatBuffer();
+                triBuff2 = triAlloc2.getByteBuffer().order(ByteOrder.LITTLE_ENDIAN).asFloatBuffer();
+            }
+            else {
+                triBuff1 = ByteBuffer.allocateDirect(mTriArray.length *4).order(ByteOrder.nativeOrder()).asFloatBuffer();
+                triBuff2 = ByteBuffer.allocateDirect(mTriArray.length *4).order(ByteOrder.nativeOrder()).asFloatBuffer();
+            }
+
+            ScriptC_nbody nbody = new ScriptC_nbody(rs);
+            nbody.set_positions(posAlloc);
+            nbody.set_velocities(velAlloc);
+            nbody.set_triangles1(triAlloc1);
+            nbody.set_triangles2(triAlloc2);
+            Log.v(TAG, " node = " + mMode + "  " + sModes[mModeNo] + " " + mModeNo);
+            nbody.set_CENTER_MASS(mMode[P_CTR_M]);
+            nbody.set_OBJECT_MAX_MASS(mMode[P_MAX_M]);
+            nbody.set_OBJECT_MIN_MASS(mMode[P_MIN_M]);
+            nbody.set_INITAL_VEL(mMode[P_INIT_V]);
+            nbody.set_INITAL_DIR(new Float3(mMode[P_DIR_X], mMode[P_DIR_Y], mMode[P_DIR_Z]));
+            nbody.set_RADIUS_RANGE(new Float2(mMode[P_RAD_MIN], mMode[P_RAD_MAX]));
+            nbody.forEach_fill_pos(posAlloc);
+            boolean dir = false;
+            try {
+                Thread.sleep(500);
+                int triBufferPos = 0;
+                while (keepRunning) {
+                    count++;
+                    dir = !dir;
+
+                    triBuff = (dir) ? triBuff1 : triBuff2;
+                    long time = System.nanoTime();
+
+                    if (mDrag) {
+                        Log.v(TAG, "Drag =  ... " + mDrag_x + ", " + mDrag_y);
+                        nbody.invoke_setCenterMassPos(mDrag_x, mDrag_y, mDrag_z);
+                        mDrag = false;
+                    }
+                    synchronized (triBuff) {
+                        if (dir)
+                            nbody.forEach_simulate1(posAlloc, posAlloc);
+                        else
+                            nbody.forEach_simulate2(posAlloc, posAlloc);
+
+                        rs.finish();
+                        if (!USE_BB) {
+                            ( (dir) ?triAlloc1:triAlloc1).copyTo(mTriArray);
+                            triBuff.position(0);
+                            triBuff.put(mTriArray);
+                            triBuff.position(0);
+                        }
+                        publishProgress(triBuff);
+                    }
+                }
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+            posAlloc.destroy();
+            velAlloc.destroy();
+            triAlloc1.destroy();
+            if (triAlloc1 != triAlloc2) {
+                triAlloc2.destroy();
+            }
+            nbody.destroy();
+            return null;
+        }
+
+        @Override
+        protected void onProgressUpdate(FloatBuffer... values) {
+            if (count > 100) {
+                Log.v(TAG, " " + count / ((System.nanoTime() - time) * 1E-9) + " fps");
+                count = 0;
+                time = System.nanoTime();
+            }
+            setTriangles(values[0]);
+        }
+    }
+}
diff --git a/java/tests/RsNbody/src/com/example/android/rs/nbody_gl/nbody.rs b/java/tests/RsNbody/src/com/example/android/rs/nbody_gl/nbody.rs
new file mode 100644
index 0000000..e29e954
--- /dev/null
+++ b/java/tests/RsNbody/src/com/example/android/rs/nbody_gl/nbody.rs
@@ -0,0 +1,107 @@
+#pragma version(1)
+#pragma rs java_package_name(com.example.android.rs.nbody_gl)
+#pragma rs_fp_relaxed
+
+float espSqr = .2f;                     // will be constant folded
+float dt = 0.5f;                        // will be constant folded
+float3 half_dt = {0.25f, 0.25f, 0.25f}; // will be constant folded
+
+rs_allocation positions;  // float4
+rs_allocation velocities; // float4
+rs_allocation triangles1;  // float2
+rs_allocation triangles2;  // float2
+
+float CENTER_MASS = 1000;
+float OBJECT_MAX_MASS = 800.f;
+float OBJECT_MIN_MASS = 300.f;
+float INITAL_VEL = 0;
+float3 INITAL_DIR = {1, 1, 1};
+float2 RADIUS_RANGE = {1300.f, 1900.f};
+
+void setCenterMassPos(float x, float y, float z) {
+  float4 ctr_pos = {x, y, z, CENTER_MASS};
+  rsSetElementAt_float4(positions, ctr_pos, 0);
+}
+
+/* create initial conditions */
+float4 __attribute__((kernel)) fill_pos(uint32_t x) {
+  if (x == 0) {
+    float4 out = {0, 0, 0, CENTER_MASS};
+    float4 v = {0, 0, -.000001f, 0};
+    rsSetElementAt_float4(velocities, v, x);
+    return out;
+  }
+  float r = rsRand(RADIUS_RANGE.x, RADIUS_RANGE.y);
+  float3 point = {rsRand(-1000.f, 1000.f),
+                  rsRand(-1000.f, 1000.f),
+                  rsRand(-1000.f, 1000.f)};
+  point = normalize(point);
+  float4 v = {rsRand(-1000.f, 1000.f),
+              rsRand(-1000.f, 1000.f),
+              rsRand(-1000.f, 1000.f), 0};
+  v.xyz *= INITAL_DIR;
+  v = normalize(v);
+  v.xyz = cross(point.xyz, v.xyz);
+  point *= r;
+  float4 out = {point.x, point.y, point.z,
+                rsRand(OBJECT_MIN_MASS, OBJECT_MAX_MASS)};
+
+  v = INITAL_VEL * v * rsqrt(r); // - normalize(out);
+  rsSetElementAt_float4(velocities, v, x);
+  return out;
+}
+
+
+static float4  simulate(float4 in, uint32_t x ,rs_allocation triangles) {
+  float3 force = {0.f, 0.f, 0.f};
+  int dimx = rsAllocationGetDimX(positions);
+  for (int i = 0; i < dimx; i++) {
+    float4 mass = rsGetElementAt_float4(positions, i);
+    float3 dir = mass.xyz - in.xyz;
+    float dist = distance(mass.xyz, in.xyz);
+    dist = mad(dist * dist, dist, espSqr);
+
+    float invDistCubed = 1 / (dist);
+    force += dir * (mass.w * invDistCubed);
+  }
+  float3 acc = force;
+
+  float TSIZE = 0.04f;
+  float4 v = rsGetElementAt_float4(velocities, x);
+  float4 out = in;
+  out.xyz = mad(acc, half_dt, mad(v.xyz, dt, in.xyz));
+  v.xyz += acc;
+  rsSetElementAt_float4(velocities, v, x);
+  float4 normv;
+  float4 up = {0, 0, 1, 0};
+  float4 right;
+  normv.xyz = normalize(v.xyz) * TSIZE * 2.f;
+
+  right = normalize(cross(normv, up));
+  up = normalize(cross(normv, right));
+  right *= TSIZE;
+  up *= TSIZE * 0.5f;
+  float4 p;
+  p.xyz = out.xyz;
+  p *= 0.001f;
+  int colOff = x * 4;
+
+  float4 p1 = p + normv;
+  right *= 0.866f; // sqrt(3)/2
+  rsSetElementAt_float4(triangles, p1, colOff);
+  float4 p2 = p - normv + right + up;
+  rsSetElementAt_float4(triangles, p2, colOff + 1);
+  float4 p3 = p - normv - right + up;
+  rsSetElementAt_float4(triangles, p3, colOff + 2);
+  float4 p4 = p - normv - up * 2;
+  rsSetElementAt_float4(triangles, p4, colOff + 3);
+
+  return out;
+}
+/* one pass of simulation */
+float4 __attribute__((kernel)) simulate1(float4 in, uint32_t x) {
+return simulate(in, x,triangles1);
+}
+float4 __attribute__((kernel)) simulate2(float4 in, uint32_t x) {
+ return simulate(in,x,triangles2);
+}