/*
Copyright (c) 2012 Advanced Micro Devices, Inc.  

This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose, 
including commercial applications, and to alter it and redistribute it freely, 
subject to the following restrictions:

1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
//Originally written by Takahiro Harada

#include "b3Solver.h"

///useNewBatchingKernel  is a rewritten kernel using just a single thread of the warp, for experiments
bool useNewBatchingKernel = true;
bool gConvertConstraintOnCpu = false;

#define B3_SOLVER_SETUP_KERNEL_PATH "src/Bullet3OpenCL/RigidBody/kernels/solverSetup.cl"
#define B3_SOLVER_SETUP2_KERNEL_PATH "src/Bullet3OpenCL/RigidBody/kernels/solverSetup2.cl"
#define B3_SOLVER_CONTACT_KERNEL_PATH "src/Bullet3OpenCL/RigidBody/kernels/solveContact.cl"
#define B3_SOLVER_FRICTION_KERNEL_PATH "src/Bullet3OpenCL/RigidBody/kernels/solveFriction.cl"
#define B3_BATCHING_PATH "src/Bullet3OpenCL/RigidBody/kernels/batchingKernels.cl"
#define B3_BATCHING_NEW_PATH "src/Bullet3OpenCL/RigidBody/kernels/batchingKernelsNew.cl"

#include "Bullet3Dynamics/shared/b3ConvertConstraint4.h"

#include "kernels/solverSetup.h"
#include "kernels/solverSetup2.h"

#include "kernels/solveContact.h"
#include "kernels/solveFriction.h"

#include "kernels/batchingKernels.h"
#include "kernels/batchingKernelsNew.h"

#include "Bullet3OpenCL/ParallelPrimitives/b3LauncherCL.h"
#include "Bullet3Common/b3Vector3.h"

struct SolverDebugInfo
{
	int m_valInt0;
	int m_valInt1;
	int m_valInt2;
	int m_valInt3;

	int m_valInt4;
	int m_valInt5;
	int m_valInt6;
	int m_valInt7;

	int m_valInt8;
	int m_valInt9;
	int m_valInt10;
	int m_valInt11;

	int m_valInt12;
	int m_valInt13;
	int m_valInt14;
	int m_valInt15;

	float m_val0;
	float m_val1;
	float m_val2;
	float m_val3;
};

class SolverDeviceInl
{
public:
	struct ParallelSolveData
	{
		b3OpenCLArray<unsigned int>* m_numConstraints;
		b3OpenCLArray<unsigned int>* m_offsets;
	};
};

b3Solver::b3Solver(cl_context ctx, cl_device_id device, cl_command_queue queue, int pairCapacity)
	: m_context(ctx),
	  m_device(device),
	  m_queue(queue),
	  m_batchSizes(ctx, queue),
	  m_nIterations(4)
{
	m_sort32 = new b3RadixSort32CL(ctx, device, queue);
	m_scan = new b3PrefixScanCL(ctx, device, queue, B3_SOLVER_N_CELLS);
	m_search = new b3BoundSearchCL(ctx, device, queue, B3_SOLVER_N_CELLS);

	const int sortSize = B3NEXTMULTIPLEOF(pairCapacity, 512);

	m_sortDataBuffer = new b3OpenCLArray<b3SortData>(ctx, queue, sortSize);
	m_contactBuffer2 = new b3OpenCLArray<b3Contact4>(ctx, queue);

	m_numConstraints = new b3OpenCLArray<unsigned int>(ctx, queue, B3_SOLVER_N_CELLS);
	m_numConstraints->resize(B3_SOLVER_N_CELLS);

	m_offsets = new b3OpenCLArray<unsigned int>(ctx, queue, B3_SOLVER_N_CELLS);
	m_offsets->resize(B3_SOLVER_N_CELLS);
	const char* additionalMacros = "";
	//	const char* srcFileNameForCaching="";

	cl_int pErrNum;
	const char* batchKernelSource = batchingKernelsCL;
	const char* batchKernelNewSource = batchingKernelsNewCL;

	const char* solverSetupSource = solverSetupCL;
	const char* solverSetup2Source = solverSetup2CL;
	const char* solveContactSource = solveContactCL;
	const char* solveFrictionSource = solveFrictionCL;

	{
		cl_program solveContactProg = b3OpenCLUtils::compileCLProgramFromString(ctx, device, solveContactSource, &pErrNum, additionalMacros, B3_SOLVER_CONTACT_KERNEL_PATH);
		b3Assert(solveContactProg);

		cl_program solveFrictionProg = b3OpenCLUtils::compileCLProgramFromString(ctx, device, solveFrictionSource, &pErrNum, additionalMacros, B3_SOLVER_FRICTION_KERNEL_PATH);
		b3Assert(solveFrictionProg);

		cl_program solverSetup2Prog = b3OpenCLUtils::compileCLProgramFromString(ctx, device, solverSetup2Source, &pErrNum, additionalMacros, B3_SOLVER_SETUP2_KERNEL_PATH);
		b3Assert(solverSetup2Prog);

		cl_program solverSetupProg = b3OpenCLUtils::compileCLProgramFromString(ctx, device, solverSetupSource, &pErrNum, additionalMacros, B3_SOLVER_SETUP_KERNEL_PATH);
		b3Assert(solverSetupProg);

		m_solveFrictionKernel = b3OpenCLUtils::compileCLKernelFromString(ctx, device, solveFrictionSource, "BatchSolveKernelFriction", &pErrNum, solveFrictionProg, additionalMacros);
		b3Assert(m_solveFrictionKernel);

		m_solveContactKernel = b3OpenCLUtils::compileCLKernelFromString(ctx, device, solveContactSource, "BatchSolveKernelContact", &pErrNum, solveContactProg, additionalMacros);
		b3Assert(m_solveContactKernel);

		m_contactToConstraintKernel = b3OpenCLUtils::compileCLKernelFromString(ctx, device, solverSetupSource, "ContactToConstraintKernel", &pErrNum, solverSetupProg, additionalMacros);
		b3Assert(m_contactToConstraintKernel);

		m_setSortDataKernel = b3OpenCLUtils::compileCLKernelFromString(ctx, device, solverSetup2Source, "SetSortDataKernel", &pErrNum, solverSetup2Prog, additionalMacros);
		b3Assert(m_setSortDataKernel);

		m_reorderContactKernel = b3OpenCLUtils::compileCLKernelFromString(ctx, device, solverSetup2Source, "ReorderContactKernel", &pErrNum, solverSetup2Prog, additionalMacros);
		b3Assert(m_reorderContactKernel);

		m_copyConstraintKernel = b3OpenCLUtils::compileCLKernelFromString(ctx, device, solverSetup2Source, "CopyConstraintKernel", &pErrNum, solverSetup2Prog, additionalMacros);
		b3Assert(m_copyConstraintKernel);
	}

	{
		cl_program batchingProg = b3OpenCLUtils::compileCLProgramFromString(ctx, device, batchKernelSource, &pErrNum, additionalMacros, B3_BATCHING_PATH);
		//cl_program batchingProg = b3OpenCLUtils::compileCLProgramFromString( ctx, device, 0, &pErrNum,additionalMacros, B3_BATCHING_PATH,true);
		b3Assert(batchingProg);

		m_batchingKernel = b3OpenCLUtils::compileCLKernelFromString(ctx, device, batchKernelSource, "CreateBatches", &pErrNum, batchingProg, additionalMacros);
		b3Assert(m_batchingKernel);
	}
	{
		cl_program batchingNewProg = b3OpenCLUtils::compileCLProgramFromString(ctx, device, batchKernelNewSource, &pErrNum, additionalMacros, B3_BATCHING_NEW_PATH);
		b3Assert(batchingNewProg);

		m_batchingKernelNew = b3OpenCLUtils::compileCLKernelFromString(ctx, device, batchKernelNewSource, "CreateBatchesNew", &pErrNum, batchingNewProg, additionalMacros);
		//m_batchingKernelNew = b3OpenCLUtils::compileCLKernelFromString( ctx, device, batchKernelNewSource, "CreateBatchesBruteForce", &pErrNum, batchingNewProg,additionalMacros );
		b3Assert(m_batchingKernelNew);
	}
}

b3Solver::~b3Solver()
{
	delete m_offsets;
	delete m_numConstraints;
	delete m_sortDataBuffer;
	delete m_contactBuffer2;

	delete m_sort32;
	delete m_scan;
	delete m_search;

	clReleaseKernel(m_batchingKernel);
	clReleaseKernel(m_batchingKernelNew);

	clReleaseKernel(m_solveContactKernel);
	clReleaseKernel(m_solveFrictionKernel);

	clReleaseKernel(m_contactToConstraintKernel);
	clReleaseKernel(m_setSortDataKernel);
	clReleaseKernel(m_reorderContactKernel);
	clReleaseKernel(m_copyConstraintKernel);
}

template <bool JACOBI>
static __inline void solveContact(b3GpuConstraint4& cs,
								  const b3Vector3& posA, b3Vector3& linVelA, b3Vector3& angVelA, float invMassA, const b3Matrix3x3& invInertiaA,
								  const b3Vector3& posB, b3Vector3& linVelB, b3Vector3& angVelB, float invMassB, const b3Matrix3x3& invInertiaB,
								  float maxRambdaDt[4], float minRambdaDt[4])
{
	b3Vector3 dLinVelA;
	dLinVelA.setZero();
	b3Vector3 dAngVelA;
	dAngVelA.setZero();
	b3Vector3 dLinVelB;
	dLinVelB.setZero();
	b3Vector3 dAngVelB;
	dAngVelB.setZero();

	for (int ic = 0; ic < 4; ic++)
	{
		//	dont necessary because this makes change to 0
		if (cs.m_jacCoeffInv[ic] == 0.f) continue;

		{
			b3Vector3 angular0, angular1, linear;
			b3Vector3 r0 = cs.m_worldPos[ic] - (b3Vector3&)posA;
			b3Vector3 r1 = cs.m_worldPos[ic] - (b3Vector3&)posB;
			setLinearAndAngular((const b3Vector3&)cs.m_linear, (const b3Vector3&)r0, (const b3Vector3&)r1, &linear, &angular0, &angular1);

			float rambdaDt = calcRelVel((const b3Vector3&)cs.m_linear, (const b3Vector3&)-cs.m_linear, angular0, angular1,
										linVelA, angVelA, linVelB, angVelB) +
							 cs.m_b[ic];
			rambdaDt *= cs.m_jacCoeffInv[ic];

			{
				float prevSum = cs.m_appliedRambdaDt[ic];
				float updated = prevSum;
				updated += rambdaDt;
				updated = b3Max(updated, minRambdaDt[ic]);
				updated = b3Min(updated, maxRambdaDt[ic]);
				rambdaDt = updated - prevSum;
				cs.m_appliedRambdaDt[ic] = updated;
			}

			b3Vector3 linImp0 = invMassA * linear * rambdaDt;
			b3Vector3 linImp1 = invMassB * (-linear) * rambdaDt;
			b3Vector3 angImp0 = (invInertiaA * angular0) * rambdaDt;
			b3Vector3 angImp1 = (invInertiaB * angular1) * rambdaDt;
#ifdef _WIN32
			b3Assert(_finite(linImp0.getX()));
			b3Assert(_finite(linImp1.getX()));
#endif
			if (JACOBI)
			{
				dLinVelA += linImp0;
				dAngVelA += angImp0;
				dLinVelB += linImp1;
				dAngVelB += angImp1;
			}
			else
			{
				linVelA += linImp0;
				angVelA += angImp0;
				linVelB += linImp1;
				angVelB += angImp1;
			}
		}
	}

	if (JACOBI)
	{
		linVelA += dLinVelA;
		angVelA += dAngVelA;
		linVelB += dLinVelB;
		angVelB += dAngVelB;
	}
}

static __inline void solveFriction(b3GpuConstraint4& cs,
								   const b3Vector3& posA, b3Vector3& linVelA, b3Vector3& angVelA, float invMassA, const b3Matrix3x3& invInertiaA,
								   const b3Vector3& posB, b3Vector3& linVelB, b3Vector3& angVelB, float invMassB, const b3Matrix3x3& invInertiaB,
								   float maxRambdaDt[4], float minRambdaDt[4])
{
	if (cs.m_fJacCoeffInv[0] == 0 && cs.m_fJacCoeffInv[0] == 0) return;
	const b3Vector3& center = (const b3Vector3&)cs.m_center;

	b3Vector3 n = -(const b3Vector3&)cs.m_linear;

	b3Vector3 tangent[2];
#if 1
	b3PlaneSpace1(n, tangent[0], tangent[1]);
#else
	b3Vector3 r = cs.m_worldPos[0] - center;
	tangent[0] = cross3(n, r);
	tangent[1] = cross3(tangent[0], n);
	tangent[0] = normalize3(tangent[0]);
	tangent[1] = normalize3(tangent[1]);
#endif

	b3Vector3 angular0, angular1, linear;
	b3Vector3 r0 = center - posA;
	b3Vector3 r1 = center - posB;
	for (int i = 0; i < 2; i++)
	{
		setLinearAndAngular(tangent[i], r0, r1, &linear, &angular0, &angular1);
		float rambdaDt = calcRelVel(linear, -linear, angular0, angular1,
									linVelA, angVelA, linVelB, angVelB);
		rambdaDt *= cs.m_fJacCoeffInv[i];

		{
			float prevSum = cs.m_fAppliedRambdaDt[i];
			float updated = prevSum;
			updated += rambdaDt;
			updated = b3Max(updated, minRambdaDt[i]);
			updated = b3Min(updated, maxRambdaDt[i]);
			rambdaDt = updated - prevSum;
			cs.m_fAppliedRambdaDt[i] = updated;
		}

		b3Vector3 linImp0 = invMassA * linear * rambdaDt;
		b3Vector3 linImp1 = invMassB * (-linear) * rambdaDt;
		b3Vector3 angImp0 = (invInertiaA * angular0) * rambdaDt;
		b3Vector3 angImp1 = (invInertiaB * angular1) * rambdaDt;
#ifdef _WIN32
		b3Assert(_finite(linImp0.getX()));
		b3Assert(_finite(linImp1.getX()));
#endif
		linVelA += linImp0;
		angVelA += angImp0;
		linVelB += linImp1;
		angVelB += angImp1;
	}

	{  //	angular damping for point constraint
		b3Vector3 ab = (posB - posA).normalized();
		b3Vector3 ac = (center - posA).normalized();
		if (b3Dot(ab, ac) > 0.95f || (invMassA == 0.f || invMassB == 0.f))
		{
			float angNA = b3Dot(n, angVelA);
			float angNB = b3Dot(n, angVelB);

			angVelA -= (angNA * 0.1f) * n;
			angVelB -= (angNB * 0.1f) * n;
		}
	}
}
/*
 b3AlignedObjectArray<b3RigidBodyData>& m_bodies;
	b3AlignedObjectArray<b3InertiaData>& m_shapes;
	b3AlignedObjectArray<b3GpuConstraint4>& m_constraints;
	b3AlignedObjectArray<int>* m_batchSizes;
	int m_cellIndex;
	int m_curWgidx;
	int m_start;
	int m_nConstraints;
	bool m_solveFriction;
	int m_maxNumBatches;
 */

struct SolveTask  // : public ThreadPool::Task
{
	SolveTask(b3AlignedObjectArray<b3RigidBodyData>& bodies, b3AlignedObjectArray<b3InertiaData>& shapes, b3AlignedObjectArray<b3GpuConstraint4>& constraints,
			  int start, int nConstraints, int maxNumBatches, b3AlignedObjectArray<int>* wgUsedBodies, int curWgidx, b3AlignedObjectArray<int>* batchSizes, int cellIndex)
		: m_bodies(bodies), m_shapes(shapes), m_constraints(constraints), m_batchSizes(batchSizes), m_cellIndex(cellIndex), m_curWgidx(curWgidx), m_start(start), m_nConstraints(nConstraints), m_solveFriction(true), m_maxNumBatches(maxNumBatches)
	{
	}

	unsigned short int getType() { return 0; }

	void run(int tIdx)
	{
		int offset = 0;
		for (int ii = 0; ii < B3_MAX_NUM_BATCHES; ii++)
		{
			int numInBatch = m_batchSizes->at(m_cellIndex * B3_MAX_NUM_BATCHES + ii);
			if (!numInBatch)
				break;

			for (int jj = 0; jj < numInBatch; jj++)
			{
				int i = m_start + offset + jj;
				int batchId = m_constraints[i].m_batchIdx;
				b3Assert(batchId == ii);
				float frictionCoeff = m_constraints[i].getFrictionCoeff();
				int aIdx = (int)m_constraints[i].m_bodyA;
				int bIdx = (int)m_constraints[i].m_bodyB;
				//				int localBatch = m_constraints[i].m_batchIdx;
				b3RigidBodyData& bodyA = m_bodies[aIdx];
				b3RigidBodyData& bodyB = m_bodies[bIdx];

				if (!m_solveFriction)
				{
					float maxRambdaDt[4] = {FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX};
					float minRambdaDt[4] = {0.f, 0.f, 0.f, 0.f};

					solveContact<false>(m_constraints[i], (b3Vector3&)bodyA.m_pos, (b3Vector3&)bodyA.m_linVel, (b3Vector3&)bodyA.m_angVel, bodyA.m_invMass, (const b3Matrix3x3&)m_shapes[aIdx].m_invInertiaWorld,
										(b3Vector3&)bodyB.m_pos, (b3Vector3&)bodyB.m_linVel, (b3Vector3&)bodyB.m_angVel, bodyB.m_invMass, (const b3Matrix3x3&)m_shapes[bIdx].m_invInertiaWorld,
										maxRambdaDt, minRambdaDt);
				}
				else
				{
					float maxRambdaDt[4] = {FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX};
					float minRambdaDt[4] = {0.f, 0.f, 0.f, 0.f};
					float sum = 0;
					for (int j = 0; j < 4; j++)
					{
						sum += m_constraints[i].m_appliedRambdaDt[j];
					}
					frictionCoeff = 0.7f;
					for (int j = 0; j < 4; j++)
					{
						maxRambdaDt[j] = frictionCoeff * sum;
						minRambdaDt[j] = -maxRambdaDt[j];
					}
					solveFriction(m_constraints[i], (b3Vector3&)bodyA.m_pos, (b3Vector3&)bodyA.m_linVel, (b3Vector3&)bodyA.m_angVel, bodyA.m_invMass, (const b3Matrix3x3&)m_shapes[aIdx].m_invInertiaWorld,
								  (b3Vector3&)bodyB.m_pos, (b3Vector3&)bodyB.m_linVel, (b3Vector3&)bodyB.m_angVel, bodyB.m_invMass, (const b3Matrix3x3&)m_shapes[bIdx].m_invInertiaWorld,
								  maxRambdaDt, minRambdaDt);
				}
			}
			offset += numInBatch;
		}
		/*		for (int bb=0;bb<m_maxNumBatches;bb++)
		{
			//for(int ic=m_nConstraints-1; ic>=0; ic--)
			for(int ic=0; ic<m_nConstraints; ic++)
			{
				
				int i = m_start + ic;
				if (m_constraints[i].m_batchIdx != bb)
					continue;

				float frictionCoeff = m_constraints[i].getFrictionCoeff();
				int aIdx = (int)m_constraints[i].m_bodyA;
				int bIdx = (int)m_constraints[i].m_bodyB;
				int localBatch = m_constraints[i].m_batchIdx;
				b3RigidBodyData& bodyA = m_bodies[aIdx];
				b3RigidBodyData& bodyB = m_bodies[bIdx];

				if( !m_solveFriction )
				{
					float maxRambdaDt[4] = {FLT_MAX,FLT_MAX,FLT_MAX,FLT_MAX};
					float minRambdaDt[4] = {0.f,0.f,0.f,0.f};

					solveContact<false>( m_constraints[i], (b3Vector3&)bodyA.m_pos, (b3Vector3&)bodyA.m_linVel, (b3Vector3&)bodyA.m_angVel, bodyA.m_invMass, (const b3Matrix3x3 &)m_shapes[aIdx].m_invInertiaWorld, 
							(b3Vector3&)bodyB.m_pos, (b3Vector3&)bodyB.m_linVel, (b3Vector3&)bodyB.m_angVel, bodyB.m_invMass, (const b3Matrix3x3 &)m_shapes[bIdx].m_invInertiaWorld,
						maxRambdaDt, minRambdaDt );
				}
				else
				{
					float maxRambdaDt[4] = {FLT_MAX,FLT_MAX,FLT_MAX,FLT_MAX};
					float minRambdaDt[4] = {0.f,0.f,0.f,0.f};
					float sum = 0;
					for(int j=0; j<4; j++)
					{
						sum +=m_constraints[i].m_appliedRambdaDt[j];
					}
					frictionCoeff = 0.7f;
					for(int j=0; j<4; j++)
					{
						maxRambdaDt[j] = frictionCoeff*sum;
						minRambdaDt[j] = -maxRambdaDt[j];
					}
					solveFriction( m_constraints[i], (b3Vector3&)bodyA.m_pos, (b3Vector3&)bodyA.m_linVel, (b3Vector3&)bodyA.m_angVel, bodyA.m_invMass,(const b3Matrix3x3 &) m_shapes[aIdx].m_invInertiaWorld, 
						(b3Vector3&)bodyB.m_pos, (b3Vector3&)bodyB.m_linVel, (b3Vector3&)bodyB.m_angVel, bodyB.m_invMass,(const b3Matrix3x3 &) m_shapes[bIdx].m_invInertiaWorld,
						maxRambdaDt, minRambdaDt );
			
				}
			}
		}
		*/
	}

	b3AlignedObjectArray<b3RigidBodyData>& m_bodies;
	b3AlignedObjectArray<b3InertiaData>& m_shapes;
	b3AlignedObjectArray<b3GpuConstraint4>& m_constraints;
	b3AlignedObjectArray<int>* m_batchSizes;
	int m_cellIndex;
	int m_curWgidx;
	int m_start;
	int m_nConstraints;
	bool m_solveFriction;
	int m_maxNumBatches;
};

void b3Solver::solveContactConstraintHost(b3OpenCLArray<b3RigidBodyData>* bodyBuf, b3OpenCLArray<b3InertiaData>* shapeBuf,
										  b3OpenCLArray<b3GpuConstraint4>* constraint, void* additionalData, int n, int maxNumBatches, b3AlignedObjectArray<int>* batchSizes)
{
#if 0
	{	
		int nSplitX = B3_SOLVER_N_SPLIT_X;
		int nSplitY = B3_SOLVER_N_SPLIT_Y;
		int numWorkgroups = B3_SOLVER_N_CELLS/B3_SOLVER_N_BATCHES;
		for (int z=0;z<4;z++)
		{
			for (int y=0;y<4;y++)
			{
				for (int x=0;x<4;x++)
				{
					int newIndex = (x+y*nSplitX+z*nSplitX*nSplitY);
				//	printf("newIndex=%d\n",newIndex);

					int zIdx = newIndex/(nSplitX*nSplitY);
					int remain = newIndex%(nSplitX*nSplitY);
					int yIdx = remain/nSplitX;
					int xIdx = remain%nSplitX;
				//	printf("newIndex=%d\n",newIndex);
				}
			}
		}

		//for (int wgIdx=numWorkgroups-1;wgIdx>=0;wgIdx--)
		for (int cellBatch=0;cellBatch<B3_SOLVER_N_BATCHES;cellBatch++)
		{
			for (int wgIdx=0;wgIdx<numWorkgroups;wgIdx++)
			{
				int zIdx = (wgIdx/((nSplitX*nSplitY)/4))*2+((cellBatch&4)>>2);
				int remain= (wgIdx%((nSplitX*nSplitY)/4));
				int yIdx = (remain/(nSplitX/2))*2 + ((cellBatch&2)>>1);
				int xIdx = (remain%(nSplitX/2))*2 + (cellBatch&1);
				
				/*int zIdx = newIndex/(nSplitX*nSplitY);
				int remain = newIndex%(nSplitX*nSplitY);
				int yIdx = remain/nSplitX;
				int xIdx = remain%nSplitX;
				*/
				int cellIdx = xIdx+yIdx*nSplitX+zIdx*(nSplitX*nSplitY);
			//	printf("wgIdx %d: xIdx=%d, yIdx=%d, zIdx=%d, cellIdx=%d, cell Batch %d\n",wgIdx,xIdx,yIdx,zIdx,cellIdx,cellBatch);
			}
		}
	}
#endif

	b3AlignedObjectArray<b3RigidBodyData> bodyNative;
	bodyBuf->copyToHost(bodyNative);
	b3AlignedObjectArray<b3InertiaData> shapeNative;
	shapeBuf->copyToHost(shapeNative);
	b3AlignedObjectArray<b3GpuConstraint4> constraintNative;
	constraint->copyToHost(constraintNative);

	b3AlignedObjectArray<unsigned int> numConstraintsHost;
	m_numConstraints->copyToHost(numConstraintsHost);

	//printf("------------------------\n");
	b3AlignedObjectArray<unsigned int> offsetsHost;
	m_offsets->copyToHost(offsetsHost);
	static int frame = 0;
	bool useBatches = true;
	if (useBatches)
	{
		for (int iter = 0; iter < m_nIterations; iter++)
		{
			for (int cellBatch = 0; cellBatch < B3_SOLVER_N_BATCHES; cellBatch++)
			{
				int nSplitX = B3_SOLVER_N_SPLIT_X;
				int nSplitY = B3_SOLVER_N_SPLIT_Y;
				int numWorkgroups = B3_SOLVER_N_CELLS / B3_SOLVER_N_BATCHES;
				//printf("cell Batch %d\n",cellBatch);
				b3AlignedObjectArray<int> usedBodies[B3_SOLVER_N_CELLS];
				for (int i = 0; i < B3_SOLVER_N_CELLS; i++)
				{
					usedBodies[i].resize(0);
				}

				//for (int wgIdx=numWorkgroups-1;wgIdx>=0;wgIdx--)
				for (int wgIdx = 0; wgIdx < numWorkgroups; wgIdx++)
				{
					int zIdx = (wgIdx / ((nSplitX * nSplitY) / 4)) * 2 + ((cellBatch & 4) >> 2);
					int remain = (wgIdx % ((nSplitX * nSplitY) / 4));
					int yIdx = (remain / (nSplitX / 2)) * 2 + ((cellBatch & 2) >> 1);
					int xIdx = (remain % (nSplitX / 2)) * 2 + (cellBatch & 1);
					int cellIdx = xIdx + yIdx * nSplitX + zIdx * (nSplitX * nSplitY);

					if (numConstraintsHost[cellIdx] == 0)
						continue;

					//printf("wgIdx %d: xIdx=%d, yIdx=%d, zIdx=%d, cellIdx=%d, cell Batch %d\n",wgIdx,xIdx,yIdx,zIdx,cellIdx,cellBatch);
					//printf("cell %d has %d constraints\n", cellIdx,numConstraintsHost[cellIdx]);
					if (zIdx)
					{
						//printf("?\n");
					}

					if (iter == 0)
					{
						//printf("frame=%d, Cell xIdx=%x, yIdx=%d ",frame, xIdx,yIdx);
						//printf("cellBatch=%d, wgIdx=%d, #constraints in cell=%d\n",cellBatch,wgIdx,numConstraintsHost[cellIdx]);
					}
					const int start = offsetsHost[cellIdx];
					int numConstraintsInCell = numConstraintsHost[cellIdx];
					//				const int end = start + numConstraintsInCell;

					SolveTask task(bodyNative, shapeNative, constraintNative, start, numConstraintsInCell, maxNumBatches, usedBodies, wgIdx, batchSizes, cellIdx);
					task.m_solveFriction = false;
					task.run(0);
				}
			}
		}

		for (int iter = 0; iter < m_nIterations; iter++)
		{
			for (int cellBatch = 0; cellBatch < B3_SOLVER_N_BATCHES; cellBatch++)
			{
				int nSplitX = B3_SOLVER_N_SPLIT_X;
				int nSplitY = B3_SOLVER_N_SPLIT_Y;

				int numWorkgroups = B3_SOLVER_N_CELLS / B3_SOLVER_N_BATCHES;

				for (int wgIdx = 0; wgIdx < numWorkgroups; wgIdx++)
				{
					int zIdx = (wgIdx / ((nSplitX * nSplitY) / 4)) * 2 + ((cellBatch & 4) >> 2);
					int remain = (wgIdx % ((nSplitX * nSplitY) / 4));
					int yIdx = (remain / (nSplitX / 2)) * 2 + ((cellBatch & 2) >> 1);
					int xIdx = (remain % (nSplitX / 2)) * 2 + (cellBatch & 1);

					int cellIdx = xIdx + yIdx * nSplitX + zIdx * (nSplitX * nSplitY);

					if (numConstraintsHost[cellIdx] == 0)
						continue;

					//printf("yIdx=%d\n",yIdx);

					const int start = offsetsHost[cellIdx];
					int numConstraintsInCell = numConstraintsHost[cellIdx];
					//				const int end = start + numConstraintsInCell;

					SolveTask task(bodyNative, shapeNative, constraintNative, start, numConstraintsInCell, maxNumBatches, 0, 0, batchSizes, cellIdx);
					task.m_solveFriction = true;
					task.run(0);
				}
			}
		}
	}
	else
	{
		for (int iter = 0; iter < m_nIterations; iter++)
		{
			SolveTask task(bodyNative, shapeNative, constraintNative, 0, n, maxNumBatches, 0, 0, 0, 0);
			task.m_solveFriction = false;
			task.run(0);
		}

		for (int iter = 0; iter < m_nIterations; iter++)
		{
			SolveTask task(bodyNative, shapeNative, constraintNative, 0, n, maxNumBatches, 0, 0, 0, 0);
			task.m_solveFriction = true;
			task.run(0);
		}
	}

	bodyBuf->copyFromHost(bodyNative);
	shapeBuf->copyFromHost(shapeNative);
	constraint->copyFromHost(constraintNative);
	frame++;
}

void checkConstraintBatch(const b3OpenCLArray<b3RigidBodyData>* bodyBuf,
						  const b3OpenCLArray<b3InertiaData>* shapeBuf,
						  b3OpenCLArray<b3GpuConstraint4>* constraint,
						  b3OpenCLArray<unsigned int>* m_numConstraints,
						  b3OpenCLArray<unsigned int>* m_offsets,
						  int batchId)
{
	//						b3BufferInfoCL( m_numConstraints->getBufferCL() ),
	//						b3BufferInfoCL( m_offsets->getBufferCL() )

	int cellBatch = batchId;
	const int nn = B3_SOLVER_N_CELLS;
	//	int numWorkItems = 64*nn/B3_SOLVER_N_BATCHES;

	b3AlignedObjectArray<unsigned int> gN;
	m_numConstraints->copyToHost(gN);
	b3AlignedObjectArray<unsigned int> gOffsets;
	m_offsets->copyToHost(gOffsets);
	int nSplitX = B3_SOLVER_N_SPLIT_X;
	int nSplitY = B3_SOLVER_N_SPLIT_Y;

	//	int bIdx = batchId;

	b3AlignedObjectArray<b3GpuConstraint4> cpuConstraints;
	constraint->copyToHost(cpuConstraints);

	printf("batch = %d\n", batchId);

	int numWorkgroups = nn / B3_SOLVER_N_BATCHES;
	b3AlignedObjectArray<int> usedBodies;

	for (int wgIdx = 0; wgIdx < numWorkgroups; wgIdx++)
	{
		printf("wgIdx = %d           ", wgIdx);

		int zIdx = (wgIdx / ((nSplitX * nSplitY)) / 2) * 2 + ((cellBatch & 4) >> 2);
		int remain = wgIdx % ((nSplitX * nSplitY));
		int yIdx = (remain % (nSplitX / 2)) * 2 + ((cellBatch & 2) >> 1);
		int xIdx = (remain / (nSplitX / 2)) * 2 + (cellBatch & 1);

		int cellIdx = xIdx + yIdx * nSplitX + zIdx * (nSplitX * nSplitY);
		printf("cellIdx=%d\n", cellIdx);
		if (gN[cellIdx] == 0)
			continue;

		const int start = gOffsets[cellIdx];
		const int end = start + gN[cellIdx];

		for (int c = start; c < end; c++)
		{
			b3GpuConstraint4& constraint = cpuConstraints[c];
			//printf("constraint (%d,%d)\n", constraint.m_bodyA,constraint.m_bodyB);
			if (usedBodies.findLinearSearch(constraint.m_bodyA) < usedBodies.size())
			{
				printf("error?\n");
			}
			if (usedBodies.findLinearSearch(constraint.m_bodyB) < usedBodies.size())
			{
				printf("error?\n");
			}
		}

		for (int c = start; c < end; c++)
		{
			b3GpuConstraint4& constraint = cpuConstraints[c];
			usedBodies.push_back(constraint.m_bodyA);
			usedBodies.push_back(constraint.m_bodyB);
		}
	}
}

static bool verify = false;

void b3Solver::solveContactConstraint(const b3OpenCLArray<b3RigidBodyData>* bodyBuf, const b3OpenCLArray<b3InertiaData>* shapeBuf,
									  b3OpenCLArray<b3GpuConstraint4>* constraint, void* additionalData, int n, int maxNumBatches)
{
	b3Int4 cdata = b3MakeInt4(n, 0, 0, 0);
	{
		const int nn = B3_SOLVER_N_CELLS;

		cdata.x = 0;
		cdata.y = maxNumBatches;  //250;

		int numWorkItems = 64 * nn / B3_SOLVER_N_BATCHES;
#ifdef DEBUG_ME
		SolverDebugInfo* debugInfo = new SolverDebugInfo[numWorkItems];
		adl::b3OpenCLArray<SolverDebugInfo> gpuDebugInfo(data->m_device, numWorkItems);
#endif

		{
			B3_PROFILE("m_batchSolveKernel iterations");
			for (int iter = 0; iter < m_nIterations; iter++)
			{
				for (int ib = 0; ib < B3_SOLVER_N_BATCHES; ib++)
				{
					if (verify)
					{
						checkConstraintBatch(bodyBuf, shapeBuf, constraint, m_numConstraints, m_offsets, ib);
					}

#ifdef DEBUG_ME
					memset(debugInfo, 0, sizeof(SolverDebugInfo) * numWorkItems);
					gpuDebugInfo.write(debugInfo, numWorkItems);
#endif

					cdata.z = ib;

					b3LauncherCL launcher(m_queue, m_solveContactKernel, "m_solveContactKernel");
#if 1

					b3BufferInfoCL bInfo[] = {

						b3BufferInfoCL(bodyBuf->getBufferCL()),
						b3BufferInfoCL(shapeBuf->getBufferCL()),
						b3BufferInfoCL(constraint->getBufferCL()),
						b3BufferInfoCL(m_numConstraints->getBufferCL()),
						b3BufferInfoCL(m_offsets->getBufferCL())
#ifdef DEBUG_ME
							,
						b3BufferInfoCL(&gpuDebugInfo)
#endif
					};

					launcher.setBuffers(bInfo, sizeof(bInfo) / sizeof(b3BufferInfoCL));
					//launcher.setConst(  cdata.x );
					launcher.setConst(cdata.y);
					launcher.setConst(cdata.z);
					b3Int4 nSplit;
					nSplit.x = B3_SOLVER_N_SPLIT_X;
					nSplit.y = B3_SOLVER_N_SPLIT_Y;
					nSplit.z = B3_SOLVER_N_SPLIT_Z;

					launcher.setConst(nSplit);
					launcher.launch1D(numWorkItems, 64);

#else
					const char* fileName = "m_batchSolveKernel.bin";
					FILE* f = fopen(fileName, "rb");
					if (f)
					{
						int sizeInBytes = 0;
						if (fseek(f, 0, SEEK_END) || (sizeInBytes = ftell(f)) == EOF || fseek(f, 0, SEEK_SET))
						{
							printf("error, cannot get file size\n");
							exit(0);
						}

						unsigned char* buf = (unsigned char*)malloc(sizeInBytes);
						fread(buf, sizeInBytes, 1, f);
						int serializedBytes = launcher.deserializeArgs(buf, sizeInBytes, m_context);
						int num = *(int*)&buf[serializedBytes];

						launcher.launch1D(num);

						//this clFinish is for testing on errors
						clFinish(m_queue);
					}

#endif

#ifdef DEBUG_ME
					clFinish(m_queue);
					gpuDebugInfo.read(debugInfo, numWorkItems);
					clFinish(m_queue);
					for (int i = 0; i < numWorkItems; i++)
					{
						if (debugInfo[i].m_valInt2 > 0)
						{
							printf("debugInfo[i].m_valInt2 = %d\n", i, debugInfo[i].m_valInt2);
						}

						if (debugInfo[i].m_valInt3 > 0)
						{
							printf("debugInfo[i].m_valInt3 = %d\n", i, debugInfo[i].m_valInt3);
						}
					}
#endif  //DEBUG_ME
				}
			}

			clFinish(m_queue);
		}

		cdata.x = 1;
		bool applyFriction = true;
		if (applyFriction)
		{
			B3_PROFILE("m_batchSolveKernel iterations2");
			for (int iter = 0; iter < m_nIterations; iter++)
			{
				for (int ib = 0; ib < B3_SOLVER_N_BATCHES; ib++)
				{
					cdata.z = ib;

					b3BufferInfoCL bInfo[] = {
						b3BufferInfoCL(bodyBuf->getBufferCL()),
						b3BufferInfoCL(shapeBuf->getBufferCL()),
						b3BufferInfoCL(constraint->getBufferCL()),
						b3BufferInfoCL(m_numConstraints->getBufferCL()),
						b3BufferInfoCL(m_offsets->getBufferCL())
#ifdef DEBUG_ME
							,
						b3BufferInfoCL(&gpuDebugInfo)
#endif  //DEBUG_ME
					};
					b3LauncherCL launcher(m_queue, m_solveFrictionKernel, "m_solveFrictionKernel");
					launcher.setBuffers(bInfo, sizeof(bInfo) / sizeof(b3BufferInfoCL));
					//launcher.setConst(  cdata.x );
					launcher.setConst(cdata.y);
					launcher.setConst(cdata.z);
					b3Int4 nSplit;
					nSplit.x = B3_SOLVER_N_SPLIT_X;
					nSplit.y = B3_SOLVER_N_SPLIT_Y;
					nSplit.z = B3_SOLVER_N_SPLIT_Z;

					launcher.setConst(nSplit);

					launcher.launch1D(64 * nn / B3_SOLVER_N_BATCHES, 64);
				}
			}
			clFinish(m_queue);
		}
#ifdef DEBUG_ME
		delete[] debugInfo;
#endif  //DEBUG_ME
	}
}

void b3Solver::convertToConstraints(const b3OpenCLArray<b3RigidBodyData>* bodyBuf,
									const b3OpenCLArray<b3InertiaData>* shapeBuf,
									b3OpenCLArray<b3Contact4>* contactsIn, b3OpenCLArray<b3GpuConstraint4>* contactCOut, void* additionalData,
									int nContacts, const ConstraintCfg& cfg)
{
	//	b3OpenCLArray<b3GpuConstraint4>* constraintNative =0;
	contactCOut->resize(nContacts);
	struct CB
	{
		int m_nContacts;
		float m_dt;
		float m_positionDrift;
		float m_positionConstraintCoeff;
	};

	{
		CB cdata;
		cdata.m_nContacts = nContacts;
		cdata.m_dt = cfg.m_dt;
		cdata.m_positionDrift = cfg.m_positionDrift;
		cdata.m_positionConstraintCoeff = cfg.m_positionConstraintCoeff;

		if (gConvertConstraintOnCpu)
		{
			b3AlignedObjectArray<b3RigidBodyData> gBodies;
			bodyBuf->copyToHost(gBodies);

			b3AlignedObjectArray<b3Contact4> gContact;
			contactsIn->copyToHost(gContact);

			b3AlignedObjectArray<b3InertiaData> gShapes;
			shapeBuf->copyToHost(gShapes);

			b3AlignedObjectArray<b3GpuConstraint4> gConstraintOut;
			gConstraintOut.resize(nContacts);

			B3_PROFILE("cpu contactToConstraintKernel");
			for (int gIdx = 0; gIdx < nContacts; gIdx++)
			{
				int aIdx = abs(gContact[gIdx].m_bodyAPtrAndSignBit);
				int bIdx = abs(gContact[gIdx].m_bodyBPtrAndSignBit);

				b3Float4 posA = gBodies[aIdx].m_pos;
				b3Float4 linVelA = gBodies[aIdx].m_linVel;
				b3Float4 angVelA = gBodies[aIdx].m_angVel;
				float invMassA = gBodies[aIdx].m_invMass;
				b3Mat3x3 invInertiaA = gShapes[aIdx].m_initInvInertia;

				b3Float4 posB = gBodies[bIdx].m_pos;
				b3Float4 linVelB = gBodies[bIdx].m_linVel;
				b3Float4 angVelB = gBodies[bIdx].m_angVel;
				float invMassB = gBodies[bIdx].m_invMass;
				b3Mat3x3 invInertiaB = gShapes[bIdx].m_initInvInertia;

				b3ContactConstraint4_t cs;

				setConstraint4(posA, linVelA, angVelA, invMassA, invInertiaA, posB, linVelB, angVelB, invMassB, invInertiaB,
							   &gContact[gIdx], cdata.m_dt, cdata.m_positionDrift, cdata.m_positionConstraintCoeff,
							   &cs);

				cs.m_batchIdx = gContact[gIdx].m_batchIdx;

				gConstraintOut[gIdx] = (b3GpuConstraint4&)cs;
			}

			contactCOut->copyFromHost(gConstraintOut);
		}
		else
		{
			B3_PROFILE("gpu m_contactToConstraintKernel");

			b3BufferInfoCL bInfo[] = {b3BufferInfoCL(contactsIn->getBufferCL()), b3BufferInfoCL(bodyBuf->getBufferCL()), b3BufferInfoCL(shapeBuf->getBufferCL()),
									  b3BufferInfoCL(contactCOut->getBufferCL())};
			b3LauncherCL launcher(m_queue, m_contactToConstraintKernel, "m_contactToConstraintKernel");
			launcher.setBuffers(bInfo, sizeof(bInfo) / sizeof(b3BufferInfoCL));
			//launcher.setConst(  cdata );

			launcher.setConst(cdata.m_nContacts);
			launcher.setConst(cdata.m_dt);
			launcher.setConst(cdata.m_positionDrift);
			launcher.setConst(cdata.m_positionConstraintCoeff);

			launcher.launch1D(nContacts, 64);
			clFinish(m_queue);
		}
	}
}

/*
void b3Solver::sortContacts(  const b3OpenCLArray<b3RigidBodyData>* bodyBuf, 
			b3OpenCLArray<b3Contact4>* contactsIn, void* additionalData, 
			int nContacts, const b3Solver::ConstraintCfg& cfg )
{
	
	

	const int sortAlignment = 512; // todo. get this out of sort
	if( cfg.m_enableParallelSolve )
	{
		

		int sortSize = NEXTMULTIPLEOF( nContacts, sortAlignment );

		b3OpenCLArray<unsigned int>* countsNative = m_numConstraints;//BufferUtils::map<TYPE_CL, false>( data->m_device, &countsHost );
		b3OpenCLArray<unsigned int>* offsetsNative = m_offsets;//BufferUtils::map<TYPE_CL, false>( data->m_device, &offsetsHost );

		{	//	2. set cell idx
			struct CB
			{
				int m_nContacts;
				int m_staticIdx;
				float m_scale;
				int m_nSplit;
			};

			b3Assert( sortSize%64 == 0 );
			CB cdata;
			cdata.m_nContacts = nContacts;
			cdata.m_staticIdx = cfg.m_staticIdx;
			cdata.m_scale = 1.f/(N_OBJ_PER_SPLIT*cfg.m_averageExtent);
			cdata.m_nSplit = B3_SOLVER_N_SPLIT;

			
			b3BufferInfoCL bInfo[] = { b3BufferInfoCL( contactsIn->getBufferCL() ), b3BufferInfoCL( bodyBuf->getBufferCL() ), b3BufferInfoCL( m_sortDataBuffer->getBufferCL() ) };
			b3LauncherCL launcher( m_queue, m_setSortDataKernel );
			launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(b3BufferInfoCL) );
			launcher.setConst(  cdata );
			launcher.launch1D( sortSize, 64 );
		}

		{	//	3. sort by cell idx
			int n = B3_SOLVER_N_SPLIT*B3_SOLVER_N_SPLIT;
			int sortBit = 32;
			//if( n <= 0xffff ) sortBit = 16;
			//if( n <= 0xff ) sortBit = 8;
			m_sort32->execute(*m_sortDataBuffer,sortSize);
		}
		{	//	4. find entries
			m_search->execute( *m_sortDataBuffer, nContacts, *countsNative, B3_SOLVER_N_SPLIT*B3_SOLVER_N_SPLIT, b3BoundSearchCL::COUNT);

			m_scan->execute( *countsNative, *offsetsNative, B3_SOLVER_N_SPLIT*B3_SOLVER_N_SPLIT );
		}

		{	//	5. sort constraints by cellIdx
			//	todo. preallocate this
//			b3Assert( contactsIn->getType() == TYPE_HOST );
//			b3OpenCLArray<b3Contact4>* out = BufferUtils::map<TYPE_CL, false>( data->m_device, contactsIn );	//	copying contacts to this buffer

			{
				

				b3Int4 cdata; cdata.x = nContacts;
				b3BufferInfoCL bInfo[] = { b3BufferInfoCL( contactsIn->getBufferCL() ), b3BufferInfoCL( m_contactBuffer->getBufferCL() ), b3BufferInfoCL( m_sortDataBuffer->getBufferCL() ) };
				b3LauncherCL launcher( m_queue, m_reorderContactKernel );
				launcher.setBuffers( bInfo, sizeof(bInfo)/sizeof(b3BufferInfoCL) );
				launcher.setConst(  cdata );
				launcher.launch1D( nContacts, 64 );
			}
//			BufferUtils::unmap<true>( out, contactsIn, nContacts );
		}
	}

	
}

*/
void b3Solver::batchContacts(b3OpenCLArray<b3Contact4>* contacts, int nContacts, b3OpenCLArray<unsigned int>* nNative, b3OpenCLArray<unsigned int>* offsetsNative, int staticIdx)
{
	int numWorkItems = 64 * B3_SOLVER_N_CELLS;
	{
		B3_PROFILE("batch generation");

		b3Int4 cdata;
		cdata.x = nContacts;
		cdata.y = 0;
		cdata.z = staticIdx;

#ifdef BATCH_DEBUG
		SolverDebugInfo* debugInfo = new SolverDebugInfo[numWorkItems];
		adl::b3OpenCLArray<SolverDebugInfo> gpuDebugInfo(data->m_device, numWorkItems);
		memset(debugInfo, 0, sizeof(SolverDebugInfo) * numWorkItems);
		gpuDebugInfo.write(debugInfo, numWorkItems);
#endif

#if 0
		b3BufferInfoCL bInfo[] = { 
			b3BufferInfoCL( contacts->getBufferCL() ), 
			b3BufferInfoCL(  m_contactBuffer2->getBufferCL()),
			b3BufferInfoCL( nNative->getBufferCL() ), 
			b3BufferInfoCL( offsetsNative->getBufferCL() ),
#ifdef BATCH_DEBUG
			,	b3BufferInfoCL(&gpuDebugInfo)
#endif
		};
#endif

		{
			m_batchSizes.resize(nNative->size());
			B3_PROFILE("batchingKernel");
			//b3LauncherCL launcher( m_queue, m_batchingKernel);
			cl_kernel k = useNewBatchingKernel ? m_batchingKernelNew : m_batchingKernel;

			b3LauncherCL launcher(m_queue, k, "*batchingKernel");
			if (!useNewBatchingKernel)
			{
				launcher.setBuffer(contacts->getBufferCL());
			}
			launcher.setBuffer(m_contactBuffer2->getBufferCL());
			launcher.setBuffer(nNative->getBufferCL());
			launcher.setBuffer(offsetsNative->getBufferCL());

			launcher.setBuffer(m_batchSizes.getBufferCL());

			//launcher.setConst(  cdata );
			launcher.setConst(staticIdx);

			launcher.launch1D(numWorkItems, 64);
			//clFinish(m_queue);
			//b3AlignedObjectArray<int> batchSizesCPU;
			//m_batchSizes.copyToHost(batchSizesCPU);
			//printf(".\n");
		}

#ifdef BATCH_DEBUG
		aaaa
			b3Contact4* hostContacts = new b3Contact4[nContacts];
		m_contactBuffer->read(hostContacts, nContacts);
		clFinish(m_queue);

		gpuDebugInfo.read(debugInfo, numWorkItems);
		clFinish(m_queue);

		for (int i = 0; i < numWorkItems; i++)
		{
			if (debugInfo[i].m_valInt1 > 0)
			{
				printf("catch\n");
			}
			if (debugInfo[i].m_valInt2 > 0)
			{
				printf("catch22\n");
			}

			if (debugInfo[i].m_valInt3 > 0)
			{
				printf("catch666\n");
			}

			if (debugInfo[i].m_valInt4 > 0)
			{
				printf("catch777\n");
			}
		}
		delete[] debugInfo;
#endif  //BATCH_DEBUG
	}

	//	copy buffer to buffer
	//b3Assert(m_contactBuffer->size()==nContacts);
	//contacts->copyFromOpenCLArray( *m_contactBuffer);
	//clFinish(m_queue);//needed?
}
