Search Unity

Building a mesh with the job system - filling an array larger than arrayLength

Discussion in 'C# Job System' started by sSaltyDog, Oct 23, 2018.

  1. sSaltyDog

    sSaltyDog

    Joined:
    Mar 4, 2017
    Posts:
    37
    Apologies if this is a silly question.

    I want to use IJobParallelFor to fill a NativeArray that is larger than the number of iterations I schedule in arrayLegth. For example, schedule an IJobParallelFor loop for an array of length 100 and add 2 items to an array of length 200 in the Execute() method.

    As far as I can tell this is fundamentally not possible with IJobParallelFor, for a good reason that I don't understand. I get the following error:

    IndexOutOfRangeException: Index 12000 is out of restricted IJobParallelFor range [6000...6000] in ReadWriteBuffer.
    ReadWriteBuffers are restricted to only read & write the element at the job index. You can use double buffering strategies to avoid race conditions due to reading & writing in parallel to the same elements from a job.

    Which makes sense. Is there a usual alternative or workaround?

    Context:

    I'm trying to build a randomly generated voxel mesh (cubes), each cube has 1-6 faces and each face has 4 vertices and 6 triangle indices. Not all faces are drawn but I do know the exact number of faces, vertices and triangles I will need in total.

    Mesh generation is one per-face but ideally I want to generate one cube each time job.Execute() runs
     
  2. slim_trunks

    slim_trunks

    Joined:
    Dec 31, 2017
    Posts:
    41
    I actualy did the exact same thing, so maybe I can help you there.
    You can put the [NativeDisableParallelForRestriction] attribute on your NativeArray, so you can write to multiple elements from one Execution kernel.

    Keep in mind that you now have to deal with possible race conditions, so make sure to modify the index as to not overwrite elements that are written to already or worse things.

    In your example, you would multiply the index by two so you have room for setting one additional element.
     
    sSaltyDog likes this.
  3. sSaltyDog

    sSaltyDog

    Joined:
    Mar 4, 2017
    Posts:
    37
    Thank you, I should have posted here earlier that's just what I need.

    Unfortunately my actual project isn't as simple as my example. However I do know how many faces per voxel and how many verts/tris per face so I should be able to calculate deterministic indices for my vertex and triangle arrays to avoid race conditions.
     
  4. slim_trunks

    slim_trunks

    Joined:
    Dec 31, 2017
    Posts:
    41
    I'm glad I could help!

    And yes you're right. When I did this thing and realized that I just needed to precalculate the number of faces needed, it really clicked for me. The index calculation becomes pretty simple after that, when you do it per face.
     
  5. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    You can use BufferArrays to create dynamic meshes where you don't know the number of faces
     
  6. slim_trunks

    slim_trunks

    Joined:
    Dec 31, 2017
    Posts:
    41
    Yes, but you can partition the process to achieve even more parallelism.

    My setup was generally something like this:
    Use NativeLists in an IJob to set up the vertex, index etc. buffers. This can only be done on one thread.
    Then, use ToDeferredJobArray() to fix the lists length and process them further in an IJobParallelFor, so you have the benefit of the jobs going wide.
     
  7. sSaltyDog

    sSaltyDog

    Joined:
    Mar 4, 2017
    Posts:
    37
    This sounds like what I'm planning to do. I store the exposed edges and total count of exposed edges per block prior to this process, in this struct. I can count the number of exposed blocks and create and store each block's start index in the total count of faces.


    Thanks I'll definitely take a look at that for later. I actually haven't got that far into ECS yet. Currently I'm generating meshes just using the Job system then applying them to entities in the main thread:


    entityManager.AddSharedComponentData(meshObject, MakeMesh(mesh, material));


    I was unable to add a MeshInstanceRenderer to an entity using the job system as it contains non blittable types but I need to revisit that.
     
    Last edited: Oct 24, 2018
  8. sSaltyDog

    sSaltyDog

    Joined:
    Mar 4, 2017
    Posts:
    37
    Here's the finished code, it seems to work well.

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. using Unity.Jobs;
    4. using Unity.Collections;
    5. using Unity.Mathematics;
    6.  
    7. class GenerateMeshJobSystem
    8. {
    9.     struct VertJob : IJobParallelFor
    10.     {
    11.         [NativeDisableParallelForRestriction] public NativeArray<float3> vertices;
    12.         [NativeDisableParallelForRestriction] public NativeArray<int> triangles;
    13.        
    14.         [ReadOnly] public NativeArray<Faces> faces;
    15.  
    16.         [ReadOnly] public MeshGenerator meshGenerator;
    17.         [ReadOnly] public JobUtil util;
    18.         [ReadOnly] public int chunkSize;
    19.  
    20.         //    Vertices for given side
    21.         int GetVerts(int side, float3 position, int index)
    22.         {
    23.             NativeArray<float3> verts = new NativeArray<float3>(4, Allocator.Temp);
    24.             verts.CopyFrom(meshGenerator.Vertices(side, position));
    25.  
    26.             for(int v = 0; v < 4; v++)
    27.                 vertices[index+v] =  verts[v];
    28.  
    29.             verts.Dispose();
    30.             return 4;
    31.         }
    32.  
    33.         //    Triangles are always the same set, offset to vertex index
    34.         int GetTris(int index, int vertIndex)
    35.         {
    36.             NativeArray<int> tris = new NativeArray<int>(6, Allocator.Temp);
    37.             tris.CopyFrom(meshGenerator.Triangles(vertIndex));
    38.  
    39.             for(int t = 0; t < 6; t++)
    40.                 triangles[index+t] =  tris[t];
    41.  
    42.             tris.Dispose();
    43.             return 6;
    44.         }
    45.  
    46.         public void Execute(int i)
    47.         {
    48.             //    Skip blocks that aren't exposed
    49.             if(faces[i].count == 0) return;
    50.  
    51.             //    Get block position for vertex offset
    52.             float3 pos = util.Unflatten(i, chunkSize);
    53.  
    54.             //    Current local indices
    55.             int vertIndex = 0;
    56.             int triIndex = 0;
    57.  
    58.             //    Block starting indices
    59.             int vertOffset = faces[i].faceIndex * 4;
    60.             int triOffset = faces[i].faceIndex * 6;
    61.  
    62.             //    Vertices and Triangles for exposed sides
    63.             if(faces[i].right == 1)
    64.             {
    65.                 triIndex += GetTris(triIndex+triOffset, vertIndex+vertOffset);
    66.                 vertIndex += GetVerts(0, pos, vertIndex+vertOffset);
    67.             }
    68.             if(faces[i].left == 1)
    69.             {
    70.                 triIndex += GetTris(triIndex+triOffset, vertIndex+vertOffset);
    71.                 vertIndex += GetVerts(1, pos, vertIndex+vertOffset);
    72.             }
    73.             if(faces[i].up == 1)
    74.             {
    75.                 triIndex += GetTris(triIndex+triOffset, vertIndex+vertOffset);
    76.                 vertIndex += GetVerts(2, pos, vertIndex+vertOffset);
    77.             }
    78.             if(faces[i].down == 1)
    79.             {
    80.                 triIndex += GetTris(triIndex+triOffset, vertIndex+vertOffset);
    81.                 vertIndex += GetVerts(3, pos, vertIndex+vertOffset);
    82.             }
    83.             if(faces[i].forward == 1)
    84.             {
    85.                 triIndex += GetTris(triIndex+triOffset, vertIndex+vertOffset);
    86.                 vertIndex += GetVerts(4, pos, vertIndex+vertOffset);
    87.             }
    88.             if(faces[i].back == 1)
    89.             {
    90.                 triIndex += GetTris(triIndex+triOffset, vertIndex+vertOffset);
    91.                 vertIndex += GetVerts(5, pos, vertIndex+vertOffset);
    92.             }  
    93.         }
    94.     }
    95.  
    96.     struct MeshGenerator
    97.     {
    98.         //    Cube corners
    99.         float3 v0;
    100.         float3 v2;
    101.         float3 v3;
    102.         float3 v1;
    103.         float3 v4;
    104.         float3 v5;
    105.         float3 v6;
    106.         float3 v7;
    107.  
    108.         public MeshGenerator(byte constParam)
    109.         {
    110.             v0 = new float3(     -0.5f, -0.5f,     0.5f );    //    left bottom front
    111.             v2 = new float3(      0.5f, -0.5f,    -0.5f );    //    right bottom back
    112.             v3 = new float3(     -0.5f, -0.5f,    -0.5f );     //    left bottom back
    113.             v1 = new float3(      0.5f, -0.5f,     0.5f );    //    right bottom front
    114.             v4 = new float3(     -0.5f,  0.5f,     0.5f );    //    left top front
    115.             v5 = new float3(      0.5f,  0.5f,     0.5f );    //    right top front
    116.             v6 = new float3(      0.5f,  0.5f,    -0.5f );    //    right top back
    117.             v7 = new float3(     -0.5f,  0.5f,    -0.5f );    //    left top back
    118.         }
    119.  
    120.         public enum FacesEnum { RIGHT, LEFT, UP, DOWN, FORWARD, BACK };
    121.  
    122.        
    123.         public float3[] Vertices(int faceInt, float3 offset)
    124.         {  
    125.             FacesEnum face = (FacesEnum)faceInt;
    126.             switch(face)
    127.             {
    128.                 case FacesEnum.RIGHT:     return new float3[] {v5+offset, v6+offset, v2+offset, v1+offset};
    129.                 case FacesEnum.LEFT:     return new float3[] {v7+offset, v4+offset, v0+offset, v3+offset};
    130.                 case FacesEnum.UP:         return new float3[] {v7+offset, v6+offset, v5+offset, v4+offset};
    131.                 case FacesEnum.DOWN:     return new float3[] {v0+offset, v1+offset, v2+offset, v3+offset};
    132.                 case FacesEnum.FORWARD: return new float3[] {v4+offset, v5+offset, v1+offset, v0+offset};
    133.                 case FacesEnum.BACK:     return new float3[] {v6+offset, v7+offset, v3+offset, v2+offset};
    134.                 default:                 return null;
    135.             }      
    136.         }
    137.  
    138.         public int[] Triangles(int offset)
    139.         {
    140.             return new int[] {3+offset, 1+offset, 0+offset, 3+offset, 2+offset, 1+offset};
    141.         }
    142.     }
    143.  
    144.     public Mesh GetMesh(Faces[] exposedFaces, int faceCount)
    145.     {
    146.         int chunkSize = ChunkManager.chunkSize;
    147.  
    148.         //    Determine vertex and triangle arrays using face count
    149.         NativeArray<float3> vertices = new NativeArray<float3>(faceCount * 4, Allocator.TempJob);
    150.         NativeArray<int> triangles = new NativeArray<int>(faceCount * 6, Allocator.TempJob);
    151.  
    152.         NativeArray<Faces> faces = new NativeArray<Faces>(exposedFaces.Length, Allocator.TempJob);
    153.         faces.CopyFrom(exposedFaces);
    154.  
    155.         var job = new VertJob()
    156.         {
    157.             vertices = vertices,
    158.             triangles = triangles,
    159.             faces = faces,
    160.  
    161.             util = new JobUtil(),
    162.             meshGenerator = new MeshGenerator(0),
    163.             chunkSize = chunkSize
    164.         };
    165.  
    166.         //    Run job
    167.         JobHandle handle = job.Schedule(faces.Length, 1);
    168.         handle.Complete();
    169.  
    170.         //    Vert (float3) native array to (Vector3) array
    171.         Vector3[] verticesArray = new Vector3[vertices.Length];
    172.         for(int i = 0; i < vertices.Length; i++)
    173.             verticesArray[i] = vertices[i];
    174.  
    175.         //    Tri native array to array
    176.         int[] trianglesArray = new int[triangles.Length];
    177.         triangles.CopyTo(trianglesArray);
    178.        
    179.  
    180.         vertices.Dispose();
    181.         triangles.Dispose();
    182.         faces.Dispose();
    183.  
    184.         return MakeMesh(verticesArray, trianglesArray);
    185.     }
    186.  
    187.     Mesh MakeMesh(Vector3[] vertices, int[] triangles)
    188.     {
    189.         Mesh mesh = new Mesh();
    190.         mesh.vertices = vertices;
    191.         mesh.SetTriangles(triangles, 0);
    192.         mesh.RecalculateNormals();
    193.         UnityEditor.MeshUtility.Optimize(mesh);
    194.  
    195.         return mesh;
    196.     }
    197. }
    198.  
     
    lorisleiva and T-Zee like this.