Search Unity

C# Job System: Jagged NativeArray, Modifying mutilple meshes

Discussion in 'C# Job System' started by Bordeaux_Fox, Jan 17, 2019.

  1. Bordeaux_Fox

    Bordeaux_Fox

    Joined:
    Nov 14, 2018
    Posts:
    589
    Hello everyone,

    I'm trying to write C# script that used the Job system to modifiy the vertices of a lot of different meshes, not just one mesh as in examples on Unity is shown, on multiple CPU cores. I read the Unity Manual about C# jobs and looked at the C# job cockbook on GitHub. But I found no examples for multiple meshes. At the moment, the script can be feed with multiple meshes, but can only modifiy one mesh in a parallel job. I tried to make the NativeContainer to be a jagged array, so it can hold the vertices of the different meshes als seperate arrays but Unity just throws exceptions at me. Or I did not understand to write it correct.

    Here is my code that is working at the moment. Of course there are parts left, that do not for loop
    the amount of meshes, just because I cannot make jagged arrays work in a C# job.
    So, how can I pass the data of different meshes through a C# job?

    Also I was thinking about to spread the workload, so my system has 4 cores. I did not see the performence yet, but to handle a lot of meshes just on one another core seems to be too much work to hold the framerate.

    Code (CSharp):
    1. using UnityEngine;
    2. using Unity.Collections;
    3. using Unity.Jobs;
    4. using Unity.Burst;
    5.  
    6. [BurstCompile]
    7.  
    8. public class LeafAnimation : MonoBehaviour
    9. {
    10.     [Header("Animation Settings")]
    11.     public float waveScale = 0.1f;
    12.     public float waveSpeed = 1.0f;
    13.     public float animationTime = 1.0f;
    14.     [Header("Data Arrays")]
    15.     public int gameObjectsAmount;
    16.     public GameObject[] gameObjects;
    17.     public Mesh[] meshes;
    18.     public MeshFilter[] meshFilters;
    19.     public Renderer[] meshRenderers;
    20.  
    21.     private Vector3[] meshVertices;
    22.     private int meshVerticesLength;
    23.  
    24.     // Job
    25.  
    26.     [ReadOnly]
    27.     CalculateJob m_CalculateJob;
    28.     JobHandle m_JobHandle;
    29.  
    30.     NativeArray<Vector3> m_Vertices;
    31.     Vector3[] m_ModifiedVertices;
    32.     Mesh[] m_Mesh;
    33.  
    34.     struct CalculateJob : IJobParallelFor
    35.     {
    36.         public NativeArray<Vector3> vertices;
    37.         public float timeWaveSpeed;
    38.         public float waveScale;
    39.         public float maxWaveScale;
    40.  
    41.         private Vector3 vertex;
    42.  
    43.         public void Execute(int i)
    44.         {
    45.             vertex = vertices[i];
    46.  
    47.             vertex.x = (timeWaveSpeed * Mathf.Clamp(Mathf.Exp(vertex.y - 12.0f) * waveScale, 0.0f, maxWaveScale) + vertex.x);
    48.  
    49.             vertices[i] = vertex;
    50.         }
    51.     }
    52.  
    53.     private void Awake()
    54.     {
    55.         gameObjectsAmount = gameObjects.Length;
    56.         meshFilters = new MeshFilter[gameObjectsAmount];
    57.         meshRenderers = new MeshRenderer[gameObjectsAmount];
    58.         meshes = new Mesh[gameObjectsAmount];
    59.  
    60.         for (int i = 0; i < gameObjectsAmount; i++)
    61.         {
    62.             meshFilters[i] = gameObjects[i].GetComponent<MeshFilter>();
    63.             meshRenderers[i] = gameObjects[i].GetComponent<Renderer>();
    64.             meshes[i] = meshFilters[i].mesh;
    65.         }
    66.     }
    67.  
    68.     private void Start()
    69.     {
    70.         m_Mesh = new Mesh[gameObjectsAmount];
    71.  
    72.         for (int i = 0; i < gameObjectsAmount; i++)
    73.         {
    74.             if (meshRenderers[i].enabled == true)
    75.             {
    76.                 meshVertices = meshes[i].vertices;
    77.                 meshVerticesLength = meshVertices.Length;
    78.             }
    79.  
    80.             m_Mesh[i] = meshes[i];
    81.             m_Mesh[i].MarkDynamic();
    82.             m_Vertices = new NativeArray<Vector3>(m_Mesh[i].vertices, Allocator.Persistent);
    83.             m_ModifiedVertices = new Vector3[m_Vertices.Length];
    84.         }
    85.     }
    86.  
    87.     private void Update()
    88.     {
    89.         animationTime = Mathf.Sin(Time.time * waveSpeed) * 0.01f;
    90.  
    91.         m_CalculateJob = new CalculateJob()
    92.         {
    93.             vertices = m_Vertices,
    94.             timeWaveSpeed = animationTime,
    95.             waveScale = waveScale,
    96.             maxWaveScale = waveScale * 10,
    97.         };
    98.  
    99.         m_JobHandle = m_CalculateJob.Schedule(meshVerticesLength, 64);
    100.     }
    101.  
    102.     private void LateUpdate()
    103.     {
    104.         m_JobHandle.Complete();
    105.  
    106.         m_CalculateJob.vertices.CopyTo(m_ModifiedVertices);
    107.  
    108.         for (int i = 0; i < gameObjectsAmount; i++)
    109.         {
    110.             m_Mesh[i].vertices = m_ModifiedVertices;
    111.             m_Mesh[i].RecalculateNormals();
    112.         }
    113.     }
    114.  
    115.     private void OnDisable()
    116.     {
    117.         if (m_Vertices.IsCreated)
    118.         {
    119.             m_Vertices.Dispose();
    120.         }
    121.     }
    122.  
    123.     private void OnDestroy()
    124.     {
    125.         if (m_Vertices.IsCreated)
    126.         {
    127.             m_Vertices.Dispose();
    128.         }
    129.     }
    130. }
    131.  
     
    Last edited: Jan 17, 2019
  2. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,778
    How many.meshes you are talking about?
    You could.potentially have group of meshes per thread, rather than vertices of all meshes in one array, to be multithreaded. It may be hard to tell threads, where is one mesh starting and ending. Specially, if these are different meshes, with mix count of vertices.
     
  3. Bordeaux_Fox

    Bordeaux_Fox

    Joined:
    Nov 14, 2018
    Posts:
    589
    My plan is it to modify only the first few meshes in the cameras view. So maybe about ten meshes. They are rather simple billboards, about 400 vertices. But if everything works I may also included more the more detailed branches meshes. So I have to come up with another job that do the raycasts and bounds to update the list of meshes to modify.
    Another problem may be also that the mesh of LOD1 also have to be changed. Because it would be noticeable if the mesh changes from LOD0 to LOD1 and suddenly the positions are different.
    So for a game object, the different LOD meshes have to wave synchronously.
    I do not know if that group method works with the NativeContainer. It should be rather dynamic with the amount of meshes. The question is how to transfer multiple items to one job. I am also curios how to deal with when the job cannot finish the workload in time. But last time I checked, a highly dense ocean mesh worked very well with a job.
     

    Attached Files:

    Last edited: Jan 17, 2019
  4. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,778
    Your issue with overrun job duration will become an issue, if you go beyond a frame duration. Typically up to 4 times frame duration. But 400 vertices should not be a big issue, unless you have heavy algorithms in job.

    400 verts, that means 100 bilboards right? What I would do, is utilise entities of ECS per each bilboard. Then you can nicely run on threads your job.

    But without entities, I am not sure, if there is other easy way, without copying billboards data to array, compute in job, then copy results back to billboards from array, every frame.
     
  5. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    would you not want to run this directly on the GPU?

    you could schedule many parallel jobs in parallel...
    1. for each mesh -> schedule parallel job (store job handle in a native array)
    2. after loop -> combine dependencies

    Let me know if you need this better explained...
     
  6. Bordeaux_Fox

    Bordeaux_Fox

    Joined:
    Nov 14, 2018
    Posts:
    589
    I do not want to use a shader because GPU is already very busy and a lot of CPU power is left, every additional core is left. Also doing math like exp and pow seems to be too much of workload for the GPU. This should run on decent mobile platform. Already uses Lightweight RP and the rest of power is reserved for dynamic light effects because light is some major gameplay element. Not to speaken about alpha blend and alpha cutout textures that are really hungry. But that is the problem with vegetation on mobile platform.

    Nevertheless, I would appreriate an example of multiple jobs, already tried that but it did not work.
     
    Last edited: Jan 17, 2019
  7. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    formatting is messed up, because I copied your code into a code block = sorry, but I think you get the idea....


    Code (CSharp):
    1.  
    2. [LIST=1]
    3. [*]    private void Update()
    4. [*]    {
    5. [*]        animationTime = Mathf.Sin(Time.time * waveSpeed) * 0.01f;
    6. [/LIST]
    7. int forLoopCount = ?? // length of the list, where you store your meshes...
    8. m_JobHandle = default(JobHandle);
    9. NativeArray<JobHandle> jobHandleArray = new NativeArray<JobHandle>(forLoopCount, Allocator.TempJob);
    10.       for (int i = 0; i < forLoopCount; i++)
    11.         {
    12. [LIST=1]
    13. [*]        m_CalculateJob = [URL='http://www.google.com/search?q=new+msdn.microsoft.com']new[/URL] CalculateJob()
    14. [*]        {
    15. [*]            vertices = m_Vertices,
    16. [*]            timeWaveSpeed = animationTime,
    17. [*]            waveScale = waveScale,
    18. [*]            maxWaveScale = waveScale * 10,
    19. [*]        };
    20. [*]
    21.  
    22. [*]        jobHandleArray[i] = m_CalculateJob.Schedule(meshVerticesLength, 64);  // store in the array
    23. [*]    }
    24. [/LIST]
    25.          }
    26.  
    27. m_JobHandle = JobHandle.CombineDependencies(jobHandleArray);
    28. jobHandleArray.Dispose();