Balls test app.

Change-Id: I842f43e37145f8112120e2bd49925f81c588c40c
diff --git a/java/Balls/Android.mk b/java/Balls/Android.mk
new file mode 100644
index 0000000..5b65628
--- /dev/null
+++ b/java/Balls/Android.mk
@@ -0,0 +1,31 @@
+#
+# 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_STATIC_JAVA_LIBRARIES := android.renderscript
+
+LOCAL_PACKAGE_NAME := Balls
+
+include $(BUILD_PACKAGE)
+
+endif
diff --git a/java/Balls/AndroidManifest.xml b/java/Balls/AndroidManifest.xml
new file mode 100644
index 0000000..2fffc5f
--- /dev/null
+++ b/java/Balls/AndroidManifest.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.balls">
+    <application 
+        android:label="Balls"
+        android:icon="@drawable/test_pattern">
+        <activity android:name="Balls"
+                  android:screenOrientation="landscape">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/java/Balls/res/drawable/flares.png b/java/Balls/res/drawable/flares.png
new file mode 100644
index 0000000..3a5c970
--- /dev/null
+++ b/java/Balls/res/drawable/flares.png
Binary files differ
diff --git a/java/Balls/res/drawable/test_pattern.png b/java/Balls/res/drawable/test_pattern.png
new file mode 100644
index 0000000..e7d1455
--- /dev/null
+++ b/java/Balls/res/drawable/test_pattern.png
Binary files differ
diff --git a/java/Balls/src/com/android/balls/Balls.java b/java/Balls/src/com/android/balls/Balls.java
new file mode 100644
index 0000000..5957c94
--- /dev/null
+++ b/java/Balls/src/com/android/balls/Balls.java
@@ -0,0 +1,128 @@
+/*
+ * 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.balls;
+
+import android.renderscript.RSSurfaceView;
+import android.renderscript.RenderScript;
+
+import android.app.Activity;
+import android.content.res.Configuration;
+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.Window;
+import android.widget.Button;
+import android.widget.ListView;
+
+import java.lang.Runtime;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.View;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+
+public class Balls extends Activity implements SensorEventListener {
+    //EventListener mListener = new EventListener();
+
+    private static final String LOG_TAG = "libRS_jni";
+    private static final boolean DEBUG  = false;
+    private static final boolean LOG_ENABLED = DEBUG ? Config.LOGD : Config.LOGV;
+
+    private BallsView mView;
+    private SensorManager mSensorManager;
+
+    // get the current looper (from your Activity UI thread for instance
+
+
+    public void onSensorChanged(SensorEvent event) {
+        //android.util.Log.d("rs", "sensor: " + event.sensor + ", x: " + event.values[0] + ", y: " + event.values[1] + ", z: " + event.values[2]);
+        synchronized (this) {
+            if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
+                if(mView != null) {
+                    mView.setAccel(event.values[0], event.values[1], event.values[2]);
+                }
+            }
+        }
+    }
+
+    public void onAccuracyChanged(Sensor sensor, int accuracy) {
+    }
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
+
+        // Create our Preview view and set it as the content of our
+        // Activity
+        mView = new BallsView(this);
+        setContentView(mView);
+    }
+
+    @Override
+    protected void onResume() {
+        mSensorManager.registerListener(this,
+                                        mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
+                                        SensorManager.SENSOR_DELAY_FASTEST);
+
+        // Ideally a game should implement onResume() and onPause()
+        // to take appropriate action when the activity looses focus
+        super.onResume();
+        mView.onResume();
+    }
+
+    @Override
+    protected void onPause() {
+        Log.e("rs", "onPause");
+
+        // Ideally a game should implement onResume() and onPause()
+        // to take appropriate action when the activity looses focus
+        super.onPause();
+        mView.onPause();
+
+
+
+        //Runtime.getRuntime().exit(0);
+    }
+
+    @Override
+    protected void onStop() {
+        mSensorManager.unregisterListener(this);
+        super.onStop();
+    }
+
+    static void log(String message) {
+        if (LOG_ENABLED) {
+            Log.v(LOG_TAG, message);
+        }
+    }
+
+
+}
+
diff --git a/java/Balls/src/com/android/balls/BallsRS.java b/java/Balls/src/com/android/balls/BallsRS.java
new file mode 100644
index 0000000..f76a011
--- /dev/null
+++ b/java/Balls/src/com/android/balls/BallsRS.java
@@ -0,0 +1,146 @@
+/*
+ * 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.balls;
+
+import android.content.res.Resources;
+import android.renderscript.*;
+import android.util.Log;
+
+public class BallsRS {
+    public static final int PART_COUNT = 800;
+
+    public BallsRS() {
+    }
+
+    private Resources mRes;
+    private RenderScriptGL mRS;
+    private ScriptC_balls mScript;
+    private ScriptC_ball_physics mPhysicsScript;
+    private ProgramFragment mPF;
+    private ProgramVertex mPV;
+    private ProgramRaster mPR;
+    private ProgramStore mPS;
+    private ScriptField_Point mPoints;
+    private ScriptField_Point mArcs;
+    private ScriptField_VpConsts mVpConsts;
+
+    void updateProjectionMatrices() {
+        mVpConsts = new ScriptField_VpConsts(mRS, 1);
+        ScriptField_VpConsts.Item i = new ScriptField_VpConsts.Item();
+        Matrix4f mvp = new Matrix4f();
+        mvp.loadOrtho(0, mRS.getWidth(), mRS.getHeight(), 0, -1, 1);
+        i.MVP = mvp;
+        mVpConsts.set(i, 0, true);
+    }
+
+    private void createProgramRaster() {
+        ProgramRaster.Builder b = new ProgramRaster.Builder(mRS);
+        mPR = b.create();
+        mScript.set_gPR(mPR);
+    }
+
+    private void createProgramVertex() {
+        updateProjectionMatrices();
+
+        ProgramVertex.ShaderBuilder sb = new ProgramVertex.ShaderBuilder(mRS);
+        String t =  "varying vec4 varColor;\n" +
+                    "void main() {\n" +
+                    "  vec4 pos = vec4(0.0, 0.0, 0.0, 1.0);\n" +
+                    "  pos.xy = ATTRIB_position;\n" +
+                    "  gl_Position = UNI_MVP * pos;\n" +
+                    "  varColor = ATTRIB_color;\n" +
+                    "  gl_PointSize = ATTRIB_size;\n" +
+                    "}\n";
+        sb.setShader(t);
+        sb.addConstant(mVpConsts.getType());
+        sb.addInput(mPoints.getElement());
+        ProgramVertex pvs = sb.create();
+        pvs.bindConstants(mVpConsts.getAllocation(), 0);
+        mScript.set_gPV(pvs);
+    }
+
+    private Allocation loadTexture(int id) {
+        final Allocation allocation = Allocation.createFromBitmapResource(mRS, mRes,
+                id, Element.RGB_565(mRS), false);
+        allocation.uploadToTexture(0);
+        return allocation;
+    }
+
+    public void init(RenderScriptGL rs, Resources res, int width, int height) {
+        mRS = rs;
+        mRes = res;
+
+        ProgramFragment.Builder pfb = new ProgramFragment.Builder(rs);
+        pfb.setPointSpriteTexCoordinateReplacement(true);
+        pfb.setTexture(ProgramFragment.Builder.EnvMode.MODULATE,
+                           ProgramFragment.Builder.Format.RGBA, 0);
+        pfb.setVaryingColor(true);
+        mPF = pfb.create();
+        rs.contextBindProgramFragment(mPF);
+
+        mPF.bindTexture(loadTexture(R.drawable.flares), 0);
+
+        mPoints = new ScriptField_Point(mRS, PART_COUNT);
+        mArcs = new ScriptField_Point(mRS, PART_COUNT * 2);
+
+        Mesh.AllocationBuilder smb = new Mesh.AllocationBuilder(mRS);
+        smb.addVertexAllocation(mPoints.getAllocation());
+        smb.addIndexType(Primitive.POINT);
+        Mesh smP = smb.create();
+
+        smb = new Mesh.AllocationBuilder(mRS);
+        smb.addVertexAllocation(mArcs.getAllocation());
+        smb.addIndexType(Primitive.LINE);
+        Mesh smA = smb.create();
+
+        mPhysicsScript = new ScriptC_ball_physics(mRS, mRes, R.raw.ball_physics, true);
+
+        mScript = new ScriptC_balls(mRS, mRes, R.raw.balls, true);
+        mScript.set_partMesh(smP);
+        mScript.set_arcMesh(smA);
+        mScript.set_physics_script(mPhysicsScript);
+        mScript.bind_point(mPoints);
+        mScript.bind_arc(mArcs);
+        mScript.bind_balls1(new ScriptField_Ball(mRS, PART_COUNT));
+        mScript.bind_balls2(new ScriptField_Ball(mRS, PART_COUNT));
+
+        mScript.set_gPF(mPF);
+        createProgramVertex();
+        createProgramRaster();
+
+        mPS = ProgramStore.BLEND_ADD_DEPTH_NO_DEPTH(mRS);
+        mScript.set_gPS(mPS);
+
+        mPhysicsScript.set_gMinPos(new Float2(5, 5));
+        mPhysicsScript.set_gMaxPos(new Float2(width - 5, height - 5));
+
+        mScript.invoke_initParts(width, height);
+
+        mRS.contextBindRootScript(mScript);
+    }
+
+    public void newTouchPosition(float x, float y, float pressure, int id) {
+        mPhysicsScript.set_touchX(x);
+        mPhysicsScript.set_touchY(y);
+        mPhysicsScript.set_touchPressure(pressure);
+    }
+
+    public void setAccel(float x, float y) {
+        mPhysicsScript.set_gGravityVector(new Float2(x, y));
+    }
+
+}
diff --git a/java/Balls/src/com/android/balls/BallsView.java b/java/Balls/src/com/android/balls/BallsView.java
new file mode 100644
index 0000000..635dac9
--- /dev/null
+++ b/java/Balls/src/com/android/balls/BallsView.java
@@ -0,0 +1,115 @@
+/*
+ * 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.balls;
+
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.concurrent.Semaphore;
+
+import android.renderscript.RSSurfaceView;
+import android.renderscript.RenderScript;
+import android.renderscript.RenderScriptGL;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+public class BallsView extends RSSurfaceView {
+
+    public BallsView(Context context) {
+        super(context);
+        //setFocusable(true);
+    }
+
+    private RenderScriptGL mRS;
+    private BallsRS mRender;
+
+    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
+        super.surfaceChanged(holder, format, w, h);
+        if (mRS == null) {
+            RenderScriptGL.SurfaceConfig sc = new RenderScriptGL.SurfaceConfig();
+            mRS = createRenderScript(sc);
+            mRS.contextSetSurface(w, h, holder.getSurface());
+            mRender = new BallsRS();
+            mRender.init(mRS, getResources(), w, h);
+        }
+        mRender.updateProjectionMatrices();
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        if(mRS != null) {
+            mRS = null;
+            destroyRenderScript();
+        }
+    }
+
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev)
+    {
+        int act = ev.getActionMasked();
+        if (act == ev.ACTION_UP) {
+            mRender.newTouchPosition(0, 0, 0, ev.getPointerId(0));
+            return false;
+        } else if (act == MotionEvent.ACTION_POINTER_UP) {
+            // only one pointer going up, we can get the index like this
+            int pointerIndex = ev.getActionIndex();
+            int pointerId = ev.getPointerId(pointerIndex);
+            mRender.newTouchPosition(0, 0, 0, pointerId);
+        }
+        int count = ev.getHistorySize();
+        int pcount = ev.getPointerCount();
+
+        for (int p=0; p < pcount; p++) {
+            int id = ev.getPointerId(p);
+            mRender.newTouchPosition(ev.getX(p),
+                                     ev.getY(p),
+                                     ev.getPressure(p),
+                                     id);
+
+            for (int i=0; i < count; i++) {
+                mRender.newTouchPosition(ev.getHistoricalX(p, i),
+                                         ev.getHistoricalY(p, i),
+                                         ev.getHistoricalPressure(p, i),
+                                         id);
+            }
+        }
+        return true;
+    }
+
+    void setAccel(float x, float y, float z) {
+        if (mRender == null) {
+            return;
+        }
+        mRender.setAccel(x, -y);
+    }
+
+}
+
+
diff --git a/java/Balls/src/com/android/balls/ball_physics.rs b/java/Balls/src/com/android/balls/ball_physics.rs
new file mode 100644
index 0000000..b5f149c
--- /dev/null
+++ b/java/Balls/src/com/android/balls/ball_physics.rs
@@ -0,0 +1,116 @@
+#pragma version(1)
+#pragma rs java_package_name(com.android.balls)
+
+#include "balls.rsh"
+
+float2 gGravityVector = {0.f, 9.8f};
+
+#pragma rs export_func(setGamma);
+
+float2 gMinPos = {0.f, 0.f};
+float2 gMaxPos = {1280.f, 700.f};
+
+float touchX;
+float touchY;
+float touchPressure = 0.f;
+
+void setGamma(float g) {
+}
+
+
+void root(const Ball_t *ballIn, Ball_t *ballOut, const BallControl_t *ctl, uint32_t x) {
+    float2 fv = {0, 0};
+    float2 pos = ballIn->position;
+    //rsDebug("physics pos in", pos);
+
+    int arcID = -1;
+    float arcInvStr = 100000;
+
+    const Ball_t * bPtr = rsGetElementAt(ctl->ain, 0);
+    for (uint32_t xin = 0; xin < ctl->dimX; xin++) {
+        float2 vec = bPtr[xin].position - pos;
+        float2 vec2 = vec * vec;
+        float len2 = vec2.x + vec2.y;
+
+        if (len2 < 1000) {
+            if (len2 > (4*4)) {
+                // Repulsion
+                float len = sqrt(len2);
+                if (len < arcInvStr) {
+                    arcInvStr = len;
+                    arcID = xin;
+                }
+                fv -= (vec / (len * len * len)) * 20000.f;
+            } else {
+                if (len2 < 0.1) {
+                    continue;
+                }
+                // Collision
+                float2 axis = normalize(vec);
+                float e1 = dot(axis, ballIn->delta);
+                float e2 = dot(axis, bPtr[xin].delta);
+                float e = (e1 - e2) * 0.45f;
+                if (e1 > 0) {
+                    fv -= axis * e;
+                } else {
+                    fv += axis * e;
+                }
+            }
+        }
+    }
+
+    fv -= gGravityVector;
+    fv *= ctl->dt;
+
+    {
+        float2 tp = {touchX, touchY};
+        float2 vec = tp - ballIn->position;
+        float2 vec2 = vec * vec;
+        float len2 = vec2.x + vec2.y;
+
+        if (len2 > 0.2) {
+            float len = sqrt(len2);
+            fv -= (vec / (len * len)) * touchPressure * 1000.f;
+        }
+    }
+
+    ballOut->delta = ballIn->delta * 0.998f;
+    ballOut->position = ballIn->position;
+
+    ballOut->delta += fv;
+    ballOut->position += ballOut->delta * ctl->dt;
+
+    if (ballOut->position.x > gMaxPos.x) {
+        if (ballOut->delta.x > 0) {
+            ballOut->delta.x *= -0.7;
+        }
+        ballOut->position.x = gMaxPos.x;
+    }
+    if (ballOut->position.y > gMaxPos.y) {
+        if (ballOut->delta.y > 0) {
+            ballOut->delta.y *= -0.7;
+        }
+        ballOut->position.y = gMaxPos.y - 1.f;
+    }
+    if (ballOut->position.x < gMinPos.x) {
+        if (ballOut->delta.x < 0) {
+            ballOut->delta.x *= -0.7;
+        }
+        ballOut->position.x = gMinPos.x + 1.f;
+    }
+    if (ballOut->position.y < gMinPos.y) {
+        if (ballOut->delta.y < 0) {
+            ballOut->delta.y *= -0.7;
+        }
+        ballOut->position.y = gMinPos.y + 1.f;
+    }
+
+    ballOut->color.b = 1.f;
+    ballOut->color.r = min(sqrt(length(ballOut->delta)) * 0.1f, 1.f);
+    ballOut->color.g = min(sqrt(length(fv) * 0.1f), 1.f);
+    ballOut->arcID = arcID;
+    ballOut->arcStr = 8 / arcInvStr;
+
+    //rsDebug("physics pos out", ballOut->position);
+}
+
diff --git a/java/Balls/src/com/android/balls/balls.rs b/java/Balls/src/com/android/balls/balls.rs
new file mode 100644
index 0000000..9d3f30b
--- /dev/null
+++ b/java/Balls/src/com/android/balls/balls.rs
@@ -0,0 +1,104 @@
+#pragma version(1)
+#pragma rs java_package_name(com.android.balls)
+#include "rs_graphics.rsh"
+
+#include "balls.rsh"
+
+#pragma stateFragment(parent)
+
+rs_program_fragment gPF;
+rs_program_vertex gPV;
+rs_program_raster gPR;
+rs_program_store gPS;
+rs_mesh partMesh;
+rs_mesh arcMesh;
+
+typedef struct __attribute__((packed, aligned(4))) Point {
+    float2 position;
+    uchar4 color;
+    float size;
+} Point_t;
+Point_t *point;
+Point_t *arc;
+
+typedef struct VpConsts {
+    //rs_matrix4x4 Proj;
+    rs_matrix4x4 MVP;
+} VpConsts_t;
+VpConsts_t *vpConstants;
+
+
+#pragma rs export_func(initParts)
+
+rs_script physics_script;
+
+Ball_t *balls1;
+Ball_t *balls2;
+
+static int frame = 0;
+
+void initParts(int w, int h)
+{
+    uint32_t dimX = rsAllocationGetDimX(rsGetAllocation(balls1));
+
+    for (uint32_t ct=0; ct < dimX; ct++) {
+        balls1[ct].position.x = rsRand(0.f, (float)w);
+        balls1[ct].position.y = rsRand(0.f, (float)h);
+        balls1[ct].delta.x = 0.f;
+        balls1[ct].delta.y = 0.f;
+        balls1[ct].arcID = -1;
+        balls1[ct].color = 0.f;
+    }
+}
+
+
+
+int root() {
+    rsgClearColor(0.f, 0.f, 0.f, 1.f);
+
+    BallControl_t bc;
+    Ball_t *bout;
+
+    if (frame & 1) {
+        bc.ain = rsGetAllocation(balls2);
+        bc.aout = rsGetAllocation(balls1);
+        bout = balls2;
+    } else {
+        bc.ain = rsGetAllocation(balls1);
+        bc.aout = rsGetAllocation(balls2);
+        bout = balls1;
+    }
+
+    bc.dimX = rsAllocationGetDimX(bc.ain);
+    bc.dt = 1.f / 30.f;
+
+    rsForEach(physics_script, bc.ain, bc.aout, &bc);
+
+    uint32_t arcIdx = 0;
+    for (uint32_t ct=0; ct < bc.dimX; ct++) {
+        point[ct].position = bout[ct].position;
+        point[ct].color = rsPackColorTo8888(bout[ct].color);
+        point[ct].size = 6.f + bout[ct].color.g * 6.f;
+
+        if (bout[ct].arcID >= 0) {
+            arc[arcIdx].position = bout[ct].position;
+            arc[arcIdx].color.r = min(bout[ct].arcStr, 1.f) * 0xff;
+            arc[arcIdx].color.g = 0;
+            arc[arcIdx].color.b = 0;
+            arc[arcIdx].color.a = 0xff;
+            arc[arcIdx+1].position = bout[bout[ct].arcID].position;
+            arc[arcIdx+1].color = arc[arcIdx].color;
+            arcIdx += 2;
+        }
+    }
+
+    frame++;
+    rsgBindProgramFragment(gPF);
+    rsgBindProgramVertex(gPV);
+    rsgBindProgramRaster(gPR);
+    rsgBindProgramStore(gPS);
+    rsgDrawMesh(arcMesh, 0, 0, arcIdx);
+    rsgDrawMesh(partMesh);
+    return 1;
+}
+
diff --git a/java/Balls/src/com/android/balls/balls.rsh b/java/Balls/src/com/android/balls/balls.rsh
new file mode 100644
index 0000000..ed3c31a
--- /dev/null
+++ b/java/Balls/src/com/android/balls/balls.rsh
@@ -0,0 +1,17 @@
+
+typedef struct __attribute__((packed, aligned(4))) Ball {
+    float2 delta;
+    float2 position;
+    float3 color;
+    int arcID;
+    float arcStr;
+} Ball_t;
+Ball_t *balls;
+
+
+typedef struct BallControl {
+    uint32_t dimX;
+    rs_allocation ain;
+    rs_allocation aout;
+    float dt;
+} BallControl_t;
diff --git a/rsContext.cpp b/rsContext.cpp
index 3f04585..0241455 100644
--- a/rsContext.cpp
+++ b/rsContext.cpp
@@ -470,7 +470,7 @@
              rsc->timerPrint();
              rsc->timerReset();
          }
-         if (rsc->mThreadPriority > 0 && targetTime) {
+         if (targetTime > 1) {
              int32_t t = (targetTime - (int32_t)(rsc->mTimeMSLastScript + rsc->mTimeMSLastSwap)) * 1000;
              if (t > 0) {
                  usleep(t);
diff --git a/rsScriptC.cpp b/rsScriptC.cpp
index 9dce158..1f5ed7c 100644
--- a/rsScriptC.cpp
+++ b/rsScriptC.cpp
@@ -217,6 +217,32 @@
 
 }
 
+static void wc_x(void *usr, uint32_t idx)
+{
+    MTLaunchStruct *mtls = (MTLaunchStruct *)usr;
+
+    while (1) {
+        uint32_t slice = (uint32_t)android_atomic_inc(&mtls->mSliceNum);
+        uint32_t xStart = mtls->xStart + slice * mtls->mSliceSize;
+        uint32_t xEnd = xStart + mtls->mSliceSize;
+        xEnd = rsMin(xEnd, mtls->xEnd);
+        if (xEnd <= xStart) {
+            return;
+        }
+
+        //LOGE("usr idx %i, x %i,%i  y %i,%i", idx, mtls->xStart, mtls->xEnd, yStart, yEnd);
+        //LOGE("usr ptr in %p,  out %p", mtls->ptrIn, mtls->ptrOut);
+        uint8_t *xPtrOut = mtls->ptrOut + (mtls->eStrideOut * xStart);
+        const uint8_t *xPtrIn = mtls->ptrIn + (mtls->eStrideIn * xStart);
+        for (uint32_t x = xStart; x < xEnd; x++) {
+            ((rs_t)mtls->script->mProgram.mRoot) (xPtrIn, xPtrOut, mtls->usr, x, 0, 0, 0);
+            xPtrIn += mtls->eStrideIn;
+            xPtrOut += mtls->eStrideOut;
+        }
+    }
+
+}
+
 void ScriptC::runForEach(Context *rsc,
                          const Allocation * ain,
                          Allocation * aout,
@@ -296,10 +322,14 @@
         mtls.eStrideOut = aout->getType()->getElementSizeBytes();
     }
 
-    if ((rsc->getWorkerPoolSize() > 1) && mEnviroment.mIsThreadable && (mtls.dimY > 1)) {
+    if ((rsc->getWorkerPoolSize() > 1) && mEnviroment.mIsThreadable) {
+        if (mtls.dimY > 1) {
+            rsc->launchThreads(wc_xy, &mtls);
+        } else {
+            rsc->launchThreads(wc_x, &mtls);
+        }
 
         //LOGE("launch 1");
-        rsc->launchThreads(wc_xy, &mtls);
     } else {
         //LOGE("launch 3");
         for (uint32_t ar = mtls.arrayStart; ar < mtls.arrayEnd; ar++) {