Search Unity

Some help with iterating over entities and Compute Shader

Discussion in 'Entity Component System' started by Matt_De_Boss_Developer, Nov 25, 2019.

  1. Matt_De_Boss_Developer

    Matt_De_Boss_Developer

    Joined:
    Oct 17, 2014
    Posts:
    46
    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:

    1. 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.
    2. 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.
    3. 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.
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Unity.Entities;
    5. using Unity.Jobs;
    6. using Unity.Collections;
    7. using UnityEngine.Rendering;
    8. using Unity.Mathematics;
    9. using Unity.Burst;
    10.  
    11. [BurstCompile]
    12. public class VoxelGenerationSystem : ComponentSystem
    13. {
    14.     EntityQuery chunksQuery; //and entity query that will grab all the chunks currently in the world
    15.  
    16.     ComputeShader voxelDataGenerator = Resources.Load<ComputeShader>("GenerateVoxelData"); //the object for the compute shader
    17.     ComputeBuffer voxelBuffer; //the actual compute buffer that we allcoate for the compute shader
    18.  
    19.     AsyncGPUReadbackRequest request; //this is the object for the Async request that will fetch our voxel data from the compute shader
    20.  
    21.     int GenerateVoxelDataKernel; //just the integer ID for the kernel in the compute shader
    22.  
    23.     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
    24.  
    25.     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
    26.  
    27.     NativeArray<Entity> chunks; //keeps an up to date list of all chunks in the world
    28.  
    29.     protected override void OnCreate()
    30.     {
    31.         chunksQuery = World.Active.EntityManager.CreateEntityQuery(typeof(ChunkInfo)); //initialize the chunk query to grab everything with "ChunkInfo" on it
    32.  
    33.         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)
    34.  
    35.         GenerateVoxelDataKernel = voxelDataGenerator.FindKernel("GenerateVoxelData"); //finds the Kernel ID
    36.         voxelDataGenerator.SetBuffer(GenerateVoxelDataKernel, "VoxelData", voxelBuffer); //sets the buffer of the compute shader using our allocated compute buffer
    37.  
    38.     }
    39.  
    40.  
    41.  
    42.  
    43.     protected override void OnUpdate()
    44.     {
    45.  
    46.  
    47.         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
    48.         {
    49.  
    50.             chunks = chunksQuery.ToEntityArray(Allocator.Persistent); //update the current list of all chunks
    51.  
    52.  
    53.             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
    54.             {
    55.                 if (World.Active.EntityManager.GetComponentData<ChunkInfo>(chunks[i]).chunkProgress == 0) //0 means that it has no voxel data
    56.                 {
    57.                     chunkID = i;
    58.                     break;
    59.                 }
    60.                 else
    61.                 {
    62.                     chunkID = -1;
    63.                 }
    64.             }
    65.  
    66.             if (chunkID == -1) //this means that no chunk was found that needed voxel data, so we just deallocate the chunks array
    67.             {
    68.                 chunks.Dispose();
    69.                 return;
    70.             }
    71.  
    72.             Entity chunk = chunks[chunkID]; //grabs the chunk that needs the voxel data
    73.  
    74.  
    75.             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
    76.  
    77.             voxelDataGenerator.SetInt("SizeX", chunkInfo.size.x + 1);
    78.             voxelDataGenerator.SetInt("SizeY", chunkInfo.size.y + 1);
    79.             voxelDataGenerator.SetInt("SizeZ", chunkInfo.size.z + 1);
    80.  
    81.             voxelDataGenerator.SetInt("x", chunkInfo.pos.x);
    82.             voxelDataGenerator.SetInt("y", chunkInfo.pos.y);
    83.             voxelDataGenerator.SetInt("z", chunkInfo.pos.z);
    84.  
    85.  
    86.  
    87.             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));
    88.             hasDispatched = true; //the gpu is now doing its thing, so lets wait for it to finish before going onto the next chunk
    89.             request = AsyncGPUReadback.Request(voxelBuffer); //setting the request in motion, and we can now use this to check if the gpu is done
    90.  
    91.         }
    92.  
    93.  
    94.         if (request.done) //if the gpu is done with its S***, then lets copy the new data into the buffer of the chunk
    95.         {
    96.  
    97.             if (request.hasError == true) //if the request has something wrong with it, then just throw an error
    98.             {
    99.                 throw new System.NotImplementedException();
    100.             }
    101.  
    102.  
    103.  
    104.             DynamicBuffer<float> voxelDynamicBuffer = World.Active.EntityManager.GetBuffer<VoxelData>(chunks[chunkID]).Reinterpret<float>(); //fetches the voxel buffer of the chunk we are copying data into
    105.             voxelDynamicBuffer.CopyFrom(request.GetData<float>()); //copy that S*** from the compute buffer into the buffer of the chunk
    106.  
    107.             ChunkInfo chunkInfo = World.Active.EntityManager.GetComponentData<ChunkInfo>(chunks[chunkID]); //fetch the ChunkInfo from the chunk so that we can change it to 1
    108.             chunkInfo.chunkProgress = 1; //set it to 1
    109.  
    110.             World.Active.EntityManager.SetComponentData(chunks[chunkID], chunkInfo); //set the component
    111.  
    112.             hasDispatched = false;
    113.             chunks.Dispose();
    114.         }
    115.     }
    116. }
    117.  
     
  2. BackgroundMover

    BackgroundMover

    Joined:
    May 9, 2015
    Posts:
    224
    It was a little confusing at first given Unity has its own concept of a Chunk.

    I would think using a component flag is faster than actually getting component data (for if chunkProgress == 0). And besides, the way its set up now, the query is still iterating over completed ChunkInfos anyway. It'd let you ditch lines 50-75 and more, I think.
     
  3. Matt_De_Boss_Developer

    Matt_De_Boss_Developer

    Joined:
    Oct 17, 2014
    Posts:
    46
    Yeah looking at it now, it is pretty confusing. The chunks refer to the voxel chunks that make up a terrain system. And I definitely agree with you. Using a component flag would not only simplify my code by a significant amount, but it would also split my entities into their archetypes (actual ECS chunks) which is sort of the entire point of ECS.