Reproducing DX11 SDK's nBodyGravity example

New here - and to DirectX 11.

I’m trying to reproduce an example from the DX11 SDK called NBodyGravityCS11. The SDK example runs fine on my hardware, now I’m just trying to figure out how to do the same through Unity 4.1.2

The only error I get is: “DestroyBuffer can only be called from the main thread.
Constructors and field initializers will be executed from the loading thread when loading a scene.
Don’t use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function”

I don’t exactly understand this error. However, I’m not sure this is the main issue either, since I already had this error in a DX11/Unity project that worked just fine.

What I get when I play the scene, appears to be all particles located at the center of the screen, but no movement whatsoever.

If there is any kind soul out there, who has the time to take a look at the code and know how to get it running properly, I’d very much appreciate any help. Thank you.

Script attached to camera:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

using System.Runtime.InteropServices;

public class GPUParticles : MonoBehaviour {

    struct PosVelo
    {
        public Vector3 position;
        public Vector3 velocity;
    };

	public int size = 1024;
	public ComputeShader computeShader;

	public float damping = 0.999f;

	public Material material;

    private ComputeBuffer oldPosVelo;
    private ComputeBuffer newPosVelo;

	private int blockSize = 128;	// threads per block, should be multiple of 32 (warp size)
    private int posVeloSizeOf;
    private int dimx;
	
	// Use this for initialization
	void Start () {
        PosVelo pv = new PosVelo();

        posVeloSizeOf = Marshal.SizeOf(pv);

		// create compute buffers
        oldPosVelo = new ComputeBuffer(size, posVeloSizeOf);
        newPosVelo = new ComputeBuffer(size, posVeloSizeOf);

        PosVelo[] pvInitBuffer = new PosVelo;

        for (int i = 0; i < size; i++)
        {
            pv = new PosVelo();
            pv.position = Random.insideUnitSphere * 5;
            pv.velocity = Random.insideUnitSphere;

            pvInitBuffer *= pv;*

}

oldPosVelo.SetData(pvInitBuffer);
newPosVelo.SetData(pvInitBuffer);

  • }*

  • // Update is called once per frame*

  • void Update () {*

dimx = (size + (blockSize - 1)) / blockSize;

  •  if (computeShader) {*
    
  •  	// run compute shader*
    

computeShader.SetBuffer(0, “oldPosVelo”, oldPosVelo);
computeShader.SetBuffer(0, “newPosVelo”, newPosVelo);

  •  	computeShader.SetFloat("dt", Time.deltaTime);*
    
  •  	computeShader.SetFloat("damping", damping);*
    
  •  	computeShader.SetInts("numParticles", size);*
    

computeShader.SetInts(“dimx”, dimx);

computeShader.Dispatch(0, dimx, 1, 1);

  •  }*
    
  • }*

  • // called if script attached to the camera, after all regular rendering is done*

  • void OnPostRender () {*

  •  if (material) {*
    

material.SetBuffer(“oldPosVelo”, oldPosVelo);
material.SetBuffer(“newPosVelo”, newPosVelo);

  •  	material.SetPass (0);*
    
  •  	Graphics.DrawProcedural (MeshTopology.Points, size, 1);*
    
  •  }*
    
  • }*

void OnDisable()
{
//oldPosVelo.Release();
//newPosVelo.Release();
}
}
ComputeShader:
#pragma kernel CSUpdate

struct PosVelo
{
float3 position;
float3 velocity;
};

RWStructuredBuffer oldPosVelo;
RWStructuredBuffer newPosVelo;

float dt;
float damping;
uint numParticles;
uint dimx;

static float softeningSquared = 0.0012500000*0.0012500000;
static float fG = 6.67300e-11f * 10000.0f;
static float fParticleMass = fG*10000.0f * 10000.0f;

#define blockSize 128
groupshared float3 sharedPos[blockSize];

// Body to body interaction, acceleration of the particle at position bi is updated
void bodyBodyInteraction(inout float3 ai, float3 bj, float3 bi, float mass, int particles )
{
float3 r = bj.xyz - bi.xyz;

float distSqr = dot(r, r);
distSqr += softeningSquared;

float invDist = 1.0f / sqrt(distSqr);
_ float invDistCube = invDist * invDist * invDist;_

float s = mass * invDistCube * particles;

ai += r * s;
}

[numthreads(blockSize,1,1)]
void CSUpdate( uint3 Gid : SV_GroupID, uint3 DTid : SV_DispatchThreadID, uint3 GTid : SV_GroupThreadID, uint GI : SV_GroupIndex )
{
// Each thread of the CS updates one of the particles

float3 pos = oldPosVelo[DTid.x].position;
float3 vel = oldPosVelo[DTid.x].velocity;
float3 accel = 0;
float mass = fParticleMass;

// Update current particle using all other particles
[loop]
for (uint tile = 0; tile < dimx; tile++)
{
// Cache a tile of particles unto shared memory to increase IO efficiency
sharedPos[GI] = oldPosVelo[tile * blockSize + GI].position;

GroupMemoryBarrierWithGroupSync();

[unroll]
for (uint counter = 0; counter < blockSize; counter+=8 )
{
bodyBodyInteraction(accel, sharedPos[counter], pos, mass, 1);
bodyBodyInteraction(accel, sharedPos[counter+1], pos, mass, 1);
bodyBodyInteraction(accel, sharedPos[counter+2], pos, mass, 1);
bodyBodyInteraction(accel, sharedPos[counter+3], pos, mass, 1);
bodyBodyInteraction(accel, sharedPos[counter+4], pos, mass, 1);
bodyBodyInteraction(accel, sharedPos[counter+5], pos, mass, 1);
bodyBodyInteraction(accel, sharedPos[counter+6], pos, mass, 1);
bodyBodyInteraction(accel, sharedPos[counter+7], pos, mass, 1);
}

GroupMemoryBarrierWithGroupSync();
}

// numParticles is the number of our particles, however this number might not be an exact multiple of the tile size.
// In such cases, out of bound reads occur in the process above, which means there will be
// tooManyParticles “phantom” particles generating false gravity at position (0, 0, 0), so we have to substract them here.
// NOTE, out of bound reads always return 0 in CS
const uint tooManyParticles = dimx * blockSize - numParticles;
bodyBodyInteraction(accel, float3(0, 0, 0), pos, mass, -tooManyParticles);

// Update the velocity and position of current particle using the acceleration computed above
vel.xyz += accel.xyz * dt; //deltaTime;
vel.xyz *= damping; //damping;
pos.xyz += vel.xyz * dt; //deltaTime;

if ( DTid.x < numParticles )
{
newPosVelo[DTid.x].position = pos;
newPosVelo[DTid.x].velocity = vel;
}
}

I just wanted to say that I'm getting "DestroyBuffer can only be called from the main thread." as well. I'm doing a bunch of compute work for texture generation in the editor, and I'm doing 'using(new ComputeBuffer())' to dispose of the buffers.

Turns out I was missing a couple of diposes (facepalm). No more errors now.

1 Answer

1

I think the error is that you don’t clean up your buffers when the script is terminated. As far as I know there is no garbage collection on the GPU?

Try placing something like this in OnDisable:

if(SomeComputeBuffer != null)
	SomeComputeBuffer .Dispose();
SomeComputeBuffer = null;

You should do this for all your computebuffers and rendertextures.

As to why your code doesn’t work, it is because you never transfer the newPosVelo-information to oldPosVelo. I’ve implemented a similar version to yours, where I have another kernel that transfers the data from newPosVelo to oldPosVelo. After my examns I will post a short tutorial on compute shaders on my blog. I’ll comment here when its up.

Also, you only need to call these functions once per kernel. You should also probably use computeShader.FindKernel(“kernelName”)) instead of setting the kernel ID manually.

computeShader.SetBuffer(0, "oldPosVelo", oldPosVelo);
computeShader.SetBuffer(0, "newPosVelo", newPosVelo);

A destroyBuffer appears to be called implicitly when the runtime terminates if a buffer has been created but not released properly (Some sort of last-ditch attempt to prevent resource leakage). Since the cleanup thread isn't the same as the UI thread, the exception mentioned here is thrown. Note that you don't have to do a full Dispose(). A simple Release() is usually sufficient.