So basically, I have a ComponentSystem that takes all chunks within the world that have a component called “ChunkInfo”, then I grab the first one that needs data from a compute shader (this means that its chunkProgress value is 0). After this, I run a compute shader with a few arguments, and wait for the compute shader to complete by not running another iteration until the request for data is finished. CODE BELOW
So here are my question with how I have implemented this:
- Would it be better to add a component flag instead of having a value within a component that I check? My thinking for this is that then the entityQuery wouldn’t have to iterate over chunks that already have their compute shader run.
- Is there a better way to run compute shaders and wait for them? Because thus far, it looks like a freaking mess how it is.
- Am I changing the component data for each Entity in the most efficient way? So far, I have been creating a new component that I then use SetComponentData on.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Entities;
using Unity.Jobs;
using Unity.Collections;
using UnityEngine.Rendering;
using Unity.Mathematics;
using Unity.Burst;
[BurstCompile]
public class VoxelGenerationSystem : ComponentSystem
{
EntityQuery chunksQuery; //and entity query that will grab all the chunks currently in the world
ComputeShader voxelDataGenerator = Resources.Load<ComputeShader>("GenerateVoxelData"); //the object for the compute shader
ComputeBuffer voxelBuffer; //the actual compute buffer that we allcoate for the compute shader
AsyncGPUReadbackRequest request; //this is the object for the Async request that will fetch our voxel data from the compute shader
int GenerateVoxelDataKernel; //just the integer ID for the kernel in the compute shader
bool hasDispatched = false; //checks whether the compute shader is currently creating voxel data for a chunk, it prevents more than one compute shader running at the same time
int chunkID = -1; //used to save the index to the chunk we are grabbing voxel data for so that whenever the next time the system is run, it can access it
NativeArray<Entity> chunks; //keeps an up to date list of all chunks in the world
protected override void OnCreate()
{
chunksQuery = World.Active.EntityManager.CreateEntityQuery(typeof(ChunkInfo)); //initialize the chunk query to grab everything with "ChunkInfo" on it
voxelBuffer = new ComputeBuffer(17 * 17 * 17, sizeof(float)); //intialize the compute buffer with a size that is +1 in each direction (more info on this later)
GenerateVoxelDataKernel = voxelDataGenerator.FindKernel("GenerateVoxelData"); //finds the Kernel ID
voxelDataGenerator.SetBuffer(GenerateVoxelDataKernel, "VoxelData", voxelBuffer); //sets the buffer of the compute shader using our allocated compute buffer
}
protected override void OnUpdate()
{
if (hasDispatched == false) //only ryn this step if the compute shader is done so that the next chunk can be provided with its voxel data
{
chunks = chunksQuery.ToEntityArray(Allocator.Persistent); //update the current list of all chunks
for (int i = 0; i < chunks.Length; i++) //iterate over all chunks so that we are only grabbing chunks that dont already have their voxel data created by the compute shader
{
if (World.Active.EntityManager.GetComponentData<ChunkInfo>(chunks[i]).chunkProgress == 0) //0 means that it has no voxel data
{
chunkID = i;
break;
}
else
{
chunkID = -1;
}
}
if (chunkID == -1) //this means that no chunk was found that needed voxel data, so we just deallocate the chunks array
{
chunks.Dispose();
return;
}
Entity chunk = chunks[chunkID]; //grabs the chunk that needs the voxel data
ChunkInfo chunkInfo = World.Active.EntityManager.GetComponentData<ChunkInfo>(chunk); //grabs the chunk info so that we can read the dimensions of the chunk, the location of the chunks, and can also change the chunk status
voxelDataGenerator.SetInt("SizeX", chunkInfo.size.x + 1);
voxelDataGenerator.SetInt("SizeY", chunkInfo.size.y + 1);
voxelDataGenerator.SetInt("SizeZ", chunkInfo.size.z + 1);
voxelDataGenerator.SetInt("x", chunkInfo.pos.x);
voxelDataGenerator.SetInt("y", chunkInfo.pos.y);
voxelDataGenerator.SetInt("z", chunkInfo.pos.z);
voxelDataGenerator.Dispatch(GenerateVoxelDataKernel, (int)math.ceil((chunkInfo.size.x + 1) / 1), (int)math.ceil((chunkInfo.size.y + 1) / 1), (int)math.ceil((chunkInfo.size.z + 1) / 1));
hasDispatched = true; //the gpu is now doing its thing, so lets wait for it to finish before going onto the next chunk
request = AsyncGPUReadback.Request(voxelBuffer); //setting the request in motion, and we can now use this to check if the gpu is done
}
if (request.done) //if the gpu is done with its shit, then lets copy the new data into the buffer of the chunk
{
if (request.hasError == true) //if the request has something wrong with it, then just throw an error
{
throw new System.NotImplementedException();
}
DynamicBuffer<float> voxelDynamicBuffer = World.Active.EntityManager.GetBuffer<VoxelData>(chunks[chunkID]).Reinterpret<float>(); //fetches the voxel buffer of the chunk we are copying data into
voxelDynamicBuffer.CopyFrom(request.GetData<float>()); //copy that shit from the compute buffer into the buffer of the chunk
ChunkInfo chunkInfo = World.Active.EntityManager.GetComponentData<ChunkInfo>(chunks[chunkID]); //fetch the ChunkInfo from the chunk so that we can change it to 1
chunkInfo.chunkProgress = 1; //set it to 1
World.Active.EntityManager.SetComponentData(chunks[chunkID], chunkInfo); //set the component
hasDispatched = false;
chunks.Dispose();
}
}
}