Search Unity

(Solved) Providing a set of pre-made RenderMesh to a Parallelized Job (IJobForEachWithEntity)

Discussion in 'Graphics for ECS' started by Firewolf34, Apr 25, 2019.

  1. Firewolf34

    Firewolf34

    Joined:
    May 14, 2013
    Posts:
    12
    Hello all! :) Apologies in advance if this question is confusing, as I'm pretty tired.

    I want to provide an ISharedComponentData RenderMesh which has been created on the Main Thread to an IJobForEachWithEntity struct that I have Schedule()'d to Execute() over a set of Entities selected using an EntityQuery. I want to assign this RenderMesh to some of the Entities depending on the info in their IComponentDatas. Ideally I'd like this to be performed in parallel, and as fast as is possible, since I have many Entities to iterate over.

    I am aware of the fact that we are not allowed to access the values of an ISharedComponentData in a threaded Job, because the ISharedComponentData can contain references to Managed memory and this is unsafe to use in a Job thread.

    However I have been doing a bit of research on the forums here and reading some of @5argon's wonderful articles on the topic and I have heard that there is a way to get an "index" to an ISharedComponentData which exists in the ECS system, and is internally used to attach ISharedComponentData to Entity Chunks. I assume the index integer is used internally to allow us to use Entities to retrieve ISharedComponentData values using calls to EntityManager on the main thread (e.g. with GetSharedComponentData<T>(Entity)).

    Since I am not interested in actually accessing the values of the RenderMesh, but merely assigning the pre-existing RenderMesh to a given Entity... Is there any way I can provide this index to a call to EntityCommandBuffer.Concurrent.SetSharedComponent<RenderMesh>(Entity)? All I want to do is attach the pre-created RenderMesh to the Entity, not perform any operations on it's Managed data or read or write to it. But I cannot simply pass the RenderMesh into the Job struct because of:

    Unity.Rendering.RenderMesh used in NativeArray<Unity.Rendering.RenderMesh> must be unmanaged (contain no managed types).


    --------------------------------------------------------------------------------
    Here's the stripped-down for readability version of my current Job code:



    First the IJobForEachWithEntity itself:
    Code (CSharp):
    1. [ExcludeComponent(typeof(BadComponent))]
    2. struct ARenderMeshJob : IJobForEachWithEntity<ComponentOne, ComponentTwo>
    3.  
    4.     [DeallocateOnJobCompletion]
    5.     [ReadOnly] public NativeArray<RenderMesh> renderMeshies;
    6.     public EntityCommandBuffer.Concurrent context;
    7.    
    8.     public void Execute(Entity entity, int index, ref ComponentOne compA, [ReadOnly] ref ComponentTwo compB) {
    9.         int number = compA.numberData; //id no. of desired RenderMesh
    10.        
    11.         ...
    12.        
    13.         //Does it already have a RenderMesh?
    14.         bool hasRenderMesh = ECSMain.mainEntityManager.HasComponent<RenderMesh>(entity);
    15.        
    16.         //Update/set this Entity's RenderMesh:
    17.         RenderMesh meshInstance = renderMeshies[number];
    18.         if (hasRenderMesh) {
    19.             context.SetSharedComponent<RenderMesh>(index, entity, meshInstance);
    20.         } else {
    21.             context.AddSharedComponent<RenderMesh>(index, entity, meshInstance);
    22.         }
    23.     }
    24. }

    When the JobComponentSystem starts it runs:
    Code (CSharp):
    1. protected override void OnCreate() {
    2.     this.Enabled = true;
    3.     barrier = World.Active.GetOrCreateSystem<ARenderMeshCommandBufferSystem>();
    4.     entityQuery = GetEntityQuery(
    5.         ComponentType.ReadWrite<ComponentOne>(),
    6.         ComponentType.ReadOnly<ComponentTwo>(),
    7.         ComponentType.Exclude<BadComponent>()
    8.         );
    9. }

    And here's how the Job is Schedule()'d (on main thread):
    Code (CSharp):
    1. protected override JobHandle OnUpdate(JobHandle inputDeps) {
    2.  
    3.     //Get all the unique Shared RenderMeshDataGroups.
    4.     var uniqueTypes = new List<RenderMeshDataGroup>(10);
    5.     EntityManager.GetAllUniqueSharedComponentData(uniqueTypes);
    6.  
    7.     //Prepare an array for the to-be-scheduled JobHandles.
    8.     JobHandle[] depsArray = new JobHandle[uniqueTypes.Count + 1];
    9.     depsArray[0] = inputDeps;
    10.    
    11.     //Schedule ARenderMeshJob for each unique type.
    12.     for (int sharedIndex = 0; sharedIndex < uniqueTypes.Count; sharedIndex++) {
    13.         RenderMeshDataGroup unique = uniqueTypes[sharedIndex];
    14.  
    15.         //Pre-build RenderMeshes for the Job
    16.         NativeArray<RenderMesh> meshies = new NativeArray<RenderMesh>(5, Allocator.TempJob);
    17.         for (int meshIndex = 0; meshIndex < meshies.Length; meshIndex++) {
    18.            
    19.             meshies[meshIndex] = new RenderMesh
    20.             {
    21.                 mesh = unique.currentMesh,
    22.                 material = unique.currentMaterial,
    23.                 subMesh = 0,
    24.                 castShadows = unique.currentShadowMode,
    25.                 receiveShadows = unique.receiveShadows
    26.             };
    27.         }
    28.  
    29.         //Finally schedule the Job.
    30.         entityQuery.SetFilter(unique);
    31.         ARenderMeshJob job = new ARenderMeshJob()
    32.         {
    33.             renderMeshies = meshies,
    34.             context = barrier.CreateCommandBuffer().ToConcurrent()
    35.         };
    36.         depsArray[sharedIndex + 1] = job.Schedule<ARenderMeshJob>(entityQuery, inputDeps);
    37.     }
    38.    
    39.     //Coalesce scheduled job dependencies.
    40.     var scheduledJobs = new NativeArray<JobHandle>(uniqueTypes.Count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
    41.     scheduledJobs.CopyFrom(depsArray);
    42.     JobHandle combinedDeps = JobHandle.CombineDependencies(scheduledJobs);
    43.     scheduledJobs.Dispose();//be sure to dispose the temporary NativeArray.
    44.     barrier.AddJobHandleForProducer(combinedDeps);//ensure EntityCommandBuffer schedules it's buffer playback correctly
    45.     return combinedDeps;
    46. }
    ---------------------------------------------------------------------------------------

    As you can see, I need the to select the appropriate RenderMesh based on an ID number in the IComponentData for each Entity. However, this code is invalid, and I can't seem to find a way to get the ISharedComponentData into the Job struct. The RenderMesh contains Meshes and Materials that cannot be simply inserted into the struct. I would really like if I could instead of passing the RenderMesh, pass the index to the ISharedComponentData and attach that since an int can be easily copied. Ideally I'd like to avoid using the low-level Chunk Iteration API but if that's the only way, I would appreciate some pointers!

    Any help would be greatly appreciated! Thank you!
     
  2. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    First of all, never do that:
    It's wrong, you shouldn't use EM inside job. For check component existance on entity you should use some thing like that ComponentDataFromEntity<T>.Exists() , ArchetypeChunk.Has(ArchetypeChunkComponentType<T>) etc.

    What about your case. You can't access your RM inside job (actually you can by static instance, but it's not safe and not burstable (anyway your case not urstable too) ).
    Actually you read data anyway.
    And you error start much earlier than job. First wrong point is -
    You can't use unmanaged types in native container.
     
  3. Firewolf34

    Firewolf34

    Joined:
    May 14, 2013
    Posts:
    12
    Thanks @eizenhorn!!

    You're 100% right about me being unable to use the RenderMesh in the NativeArray container. That was non-working code that I wanted to try to achieve similar functionality to. However... I quickly realized that this entire mess of trying to get this managed data into my Parallelized IJobForEach was going to be near impossible... Meshes/Materials just don't work well with Jobs.

    So I rewrote the entire thing and came up with a different approach. Now instead of doing this RenderMesh creation inside a Job, I create the RenderMeshes on the Main Thread, and then assign them to entire chunk archetypes at once using the EntityManager AddSharedComponentData(EntityQuery) API, taking advantage of EntityQuery filters. I'll make another post on this thread explaining how I achieved this in case somebody stumbles upon this thread in the future, ideally.

    However I've run into some issues regarding the Assertion Tests that Unity has added to it's ECS code, specifically some Assertions that fail when I try to use their API. I'm not sure if it's a bug with Unity or I'm just using the API incorrectly (probably the latter) but there's not a lot of documentation about how to use the AddSharedComponentData(EntityQuery) API so I could use all the help I can get.

    Here's the new thread I created for this problem:
    https://forum.unity.com/threads/ass...ch-addsharedcomponentdata-entityquery.674284/

    I can't move ahead on this thread/issue until I can figure out what's causing these Assertion failures.
     
  4. Behaviour

    Behaviour

    Joined:
    Oct 4, 2015
    Posts:
    10
    Any word on this? For my use case I would like to swap between RenderMeshes per entity from a JobComponentSystem or ComponentSystem but I haven't been able to find a way to do it.