Search Unity

Question How to burst jobify mesh and texture creation?

Discussion in 'Entity Component System' started by MNNoxMortem, May 31, 2022.

  1. MNNoxMortem

    MNNoxMortem

    Joined:
    Sep 11, 2016
    Posts:
    723
    Based on Parallel procedural mesh generation with Job System I would like to parallelize and jobify the Mesh and texture creation which is one of my main bottlenecks. I did want to post there because I already have all mesh data and don't want to construct it and now have troubles getting anything to run with Burst.

    Version:
    • Unity 2020.3.34f1
    Code (CSharp):
    1. "com.unity.jobs": "0.8.0-preview.23",
    2. "com.unity.burst": "1.6.4",
    3. "com.unity.entities": "0.17.0-preview.42",
    4. "com.unity.collections": "0.15.0-preview.21",
    I ran into the following problems:
    • Unity complains that I may not use my BlobArray<Vertex> and BlobArray<int> inside a job because it was constructed from UNSAFE POINTER. I use reinterpretation to reinterpret it to a NativeArray
    • All MeshApi seems to be main thread only
    Which essentially left no work left that I was able to run inside the job, as I continously had to move everything back to the main thread until it ran, which is where it is working now, but because I need to run it so often per frame I would like to parallelize and burst jobify as much as possible.

    This is the data I am trying to construct a Mesh and Texture serialized blobs of the following types:
    Code (CSharp):
    1. public struct RenderableBlob
    2.     {
    3.         public MeshBlob    Mesh;
    4.         public TextureBlob Texture;
    5.     }
    6.     public struct MeshBlob
    7.     {
    8.         public AABB      RenderBounds;
    9.         public BlobArray<VertexData> Vertices;
    10.         public BlobArray<int> Indices;
    11.     }
    12.     public struct TextureBlob
    13.     {
    14.         public TextureFormat Format;
    15.         public int MipmapCount;
    16.         public int Width;
    17.         public int Height;
    18.         public BlobArray<byte> TextureData;
    19.     }
    20.     public struct VertexData
    21.     {
    22.        public float3 Position;
    23.        public float3 Normal;
    24.        public float2 UV;
    25.  
    26.        public static readonly VertexAttributeDescriptor[] Layout =
    27.        {
    28.            new VertexAttributeDescriptor { attribute = VertexAttribute.Position, format  = VertexAttributeFormat.Float32, dimension = 3 },
    29.            new VertexAttributeDescriptor { attribute = VertexAttribute.Normal, format    = VertexAttributeFormat.Float32, dimension = 3 },
    30.            new VertexAttributeDescriptor { attribute = VertexAttribute.TexCoord0, format = VertexAttributeFormat.Float32, dimension = 2 },
    31.        };
    32.     }
    33.  
    Which are deserialized as follows
    Code (CSharp):
    1. ublic static unsafe BlobAssetReference<T> DeserializeBlob<T>(string blobFile) where T : struct
    2.         {
    3.             var cmds = new NativeArray<ReadCommand>(1, Allocator.Temp);
    4.             ReadCommand cmd;
    5.             cmd.Offset = 0;
    6.             using (var file = new FileStream(blobFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
    7.             {
    8.                 // new FileInfo: Slow
    9.                 // cmd.Size   = new FileInfo(blobFile).Length;
    10.                 cmd.Size   = file.Length;
    11.                 cmd.Buffer = (byte*) UnsafeUtility.Malloc(cmd.Size, 1024, Allocator.Temp);
    12.                 cmds[0]    = cmd;
    13.                 var readHandle = AsyncReadManager.Read(blobFile, (ReadCommand*) cmds.GetUnsafePtr(), 1);
    14.                 readHandle.JobHandle.Complete();
    15.                 using (var br = new MemoryBinaryReader((byte*) cmd.Buffer))
    16.                 {
    17.                     var result = br.Read<T>();
    18.                     UnsafeUtility.Free(cmd.Buffer, Allocator.Temp);
    19.                     return result;
    20.                 }
    21.             }
    22.         }
    Where br.Read<T>() is using the following implemenation for
    MemoryBinaryReader : BinaryReader
    Code (CSharp):
    1. public void ReadBytes(void* data, int bytes)
    2.         {
    3.             UnsafeUtility.MemCpy(data, content, bytes);
    4.             content += bytes;
    5.         }
    After reading the data, I try to construct both mesh and texture. This works on the main thread, but I fail to jobify it without running into all the restrictions with burst and Native Collections. Whatever I tried to move into the job cause Unity to complain it cannot do so (UNKNOWN DATA; UNSAFE POINTER, ...)
    Code (CSharp):
    1.  
    2. // FLAG
    3. public const MeshUpdateFlags DoNotRecalculate = MeshUpdateFlags.DontRecalculateBounds
    4. | MeshUpdateFlags.DontValidateIndices
    5. | MeshUpdateFlags.DontResetBoneBounds;
    6.  
    7. // Main Thread
    8. var ResultMesh = new Mesh();
    9.  
    10. // PART 1: Mesh Creation
    11. Mesh.MeshDataArray meshDataArray = Mesh.AllocateWritableMeshData(1);
    12. Mesh.MeshData data = meshDataArray[0];
    13.          
    14. NativeArray<VertexData> nativeInputVertices = renderable.Mesh.Vertices.ToNativeArray();
    15. data.SetVertexBufferParams(nativeInputVertices.Length, VertexData.Layout);
    16. NativeArray<VertexData> vertexData = data.GetVertexData<VertexData>();            
    17. vertexData.CopyFrom(nativeInputVertices);
    18.  
    19. NativeArray<int> nativeInputIndices = renderable.Mesh.Indices.ToNativeArray();
    20. data.SetIndexBufferParams(nativeInputIndices.Length, IndexFormat.UInt32);
    21. NativeArray<int> indexData = data.GetIndexData<int>();
    22. indexData.CopyFrom(nativeInputIndices);
    23.  
    24. Mesh.ApplyAndDisposeWritableMeshData(meshDataArray,
    25.   ResultMesh,
    26.   DoNotRecalculate | MeshUpdateFlags.DontNotifyMeshUsers); // no notify
    27.   var bounds = renderable.Mesh.RenderBounds.ToBounds();
    28.   var subMeshDescriptor = new SubMeshDescriptor()
    29.   {
    30.      topology    = MeshTopology.Triangles,
    31.     vertexCount = nativeInputVertices.Length,
    32.     indexCount  = nativeInputIndices.Length,
    33.     bounds      = bounds,
    34.     indexStart = 0,
    35.     baseVertex = 0,
    36.     firstVertex = 0
    37. };
    38. ResultMesh.subMeshCount = 1;
    39. ResultMesh.SetSubMesh(0, subMeshDescriptor, DoNotRecalculate /* and do notify mesh users here */);
    40. ResultMesh.bounds = bounds;
    41.  
    42. // PART 2: Texture Creation
    43. var texture = new Texture2D((int) textureBlob.Width, (int) textureBlob.Height, textureBlob.Format, textureBlob.MipmapCount, false);
    44. texture.LoadRawTextureData(textureBlob.TextureData.ToNativeArray());
    45. if (compress)
    46.   texture.Compress(true);
    47. texture.Apply();
    48.  
    49. Material m = renderable.Texture.Format switch
    50. {
    51.  TextureFormat.Alpha8 => Hierarchy.Example.MaterialAlpha8,
    52.  TextureFormat.RGBA32 => Hierarchy.Example.MaterialRGBA,
    53.  TextureFormat.RGB24  => Hierarchy.Example.MaterialRGB,
    54.  TextureFormat.R8     => Hierarchy.Example.MaterialR8,
    55.   _ => throw new NotImplementedException()
    56. };
    57. var material = new Material(m);
    58. material.SetTexture("_BaseMap", texture);
    The slowest parts are:
    • "Part 1: Mesh Creation":
      • readHandle.JobHandle.Complete(); // Reading via AsyncReadManager
      • MemoryBinaryReader.ReadBytes(); // Reading the into the struct
    • "Part 2: Texture Creation": texture.LoadRawTextureData - and as this already is using the NativeArray I am not sure how this could be sped up.
    Is it possible to parallize anything of this with burst compiled jobs or make it faster any other way?
     
    Last edited: Jun 2, 2022
  2. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    Is there a reason you aren't just passing the entire BlobAssetReference to the job? You usually want to pass the whole reference around and not individual BlobArrays or BlobPtrs since those use relative addressing.
     
    MNNoxMortem likes this.
  3. MNNoxMortem

    MNNoxMortem

    Joined:
    Sep 11, 2016
    Posts:
    723
    That is exactly how I tried to get access to the data in the first palce. However, the BlobArrays still cause problems, because I found no way to convert it in a way that the burst jobs accept:
    • Mesh, SubMeshDescriptor, Texture and Material are reference type and not allowed, which means most of the code cannot be called from within the job in the first place.
    • Vertices.ToNativeArray(); is not allowed because it was created from UNSAFE POINTER and causes an error when called. Vertices is a BlobArray and ToNativeArray the reinterpretation. At one point I need to convert them efficiently because GC is a huge problem (many MB of garbage per frame unloading/loading different data)
     
  4. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    6,005
    Do you call ToNativeArray on the main thread?
    If not it may be worth considering a simple copy to NativeArray and continue with that. I‘m sure there must be some way to convert Blob to Native. Perhaps using [NativeDisablePtrRestriction] attribute (name similar).
     
    Last edited: Jun 1, 2022
  5. MNNoxMortem

    MNNoxMortem

    Joined:
    Sep 11, 2016
    Posts:
    723
    All the code now is on the main thread as I am struggling to move a single useful line to a bursted job without the security system breaking.