Merge "Balls test app."

This commit is contained in:
Jason Sams
2010-10-29 11:41:03 -07:00
committed by Android (Google) Code Review
14 changed files with 712 additions and 76 deletions

View File

@@ -141,6 +141,13 @@ public class RenderScriptGL extends RenderScript {
nContextSetSurface(w, h, mSurface);
}
public int getHeight() {
return mHeight;
}
public int getWidth() {
return mWidth;
}
void pause() {
validate();

View File

@@ -76,78 +76,5 @@ public class ScriptC extends Script {
rs.nScriptCSetScript(pgm, 0, pgmLength);
return rs.nScriptCCreate();
}
public static class Builder extends Script.Builder {
byte[] mProgram;
int mProgramLength;
public Builder(RenderScript rs) {
super(rs);
}
public void setScript(String s) {
try {
mProgram = s.getBytes("UTF-8");
mProgramLength = mProgram.length;
} catch (java.io.UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
public void setScript(Resources resources, int id) {
InputStream is = resources.openRawResource(id);
try {
try {
setScript(is);
} finally {
is.close();
}
} catch(IOException e) {
throw new Resources.NotFoundException();
}
}
public void setScript(InputStream is) throws IOException {
byte[] buf = new byte[1024];
int currentPos = 0;
while(true) {
int bytesLeft = buf.length - currentPos;
if (bytesLeft == 0) {
byte[] buf2 = new byte[buf.length * 2];
System.arraycopy(buf, 0, buf2, 0, buf.length);
buf = buf2;
bytesLeft = buf.length - currentPos;
}
int bytesRead = is.read(buf, currentPos, bytesLeft);
if (bytesRead <= 0) {
break;
}
currentPos += bytesRead;
}
mProgram = buf;
mProgramLength = currentPos;
}
static synchronized ScriptC internalCreate(Builder b) {
b.mRS.nScriptCBegin();
android.util.Log.e("rs", "len = " + b.mProgramLength);
b.mRS.nScriptCSetScript(b.mProgram, 0, b.mProgramLength);
int id = b.mRS.nScriptCCreate();
ScriptC obj = new ScriptC(id, b.mRS);
return obj;
}
public void addDefine(String name, int value) {}
public void addDefine(String name, float value) {}
public void addDefines(Class cl) {}
public void addDefines(Object o) {}
void addDefines(Field[] fields, int mask, Object o) {}
public ScriptC create() {
return internalCreate(this);
}
}
}

View File

@@ -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

View File

@@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 B

View File

@@ -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);
}
}
}

View File

@@ -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));
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -470,7 +470,7 @@ void * Context::threadProc(void *vrsc)
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);

View File

@@ -217,6 +217,32 @@ static void wc_xy(void *usr, uint32_t idx)
}
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 @@ void ScriptC::runForEach(Context *rsc,
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++) {