I don’t use MeshData at all. I build the vertices and indices as NativeLists (resizable) in jobs, then apply them to the mesh in one go. As long as you’re using NativeList/NativeArray, it won’t allocate anything in Unity (driver might, and of course they need to be stored somewhere on the GPU). NativeList is resizable, although you may wish to preallocate some sensible defaults: Struct NativeList<T> | Collections | 0.11.0-preview.17
For example:
struct MyVertex {
float3 position;
float3 normal;
float2 uv; }
static readonly VertexAttributeDescriptor[] vertexLayout = {
new VertexAttributeDescriptor { attribute = VertexAttribute.Position, format = VertexAttributeFormat.Float32, dimension = 3 },
new VertexAttributeDescriptor { attribute = VertexAttribute.Normal, format = VertexAttributeFormat.Float32, dimension = 3 },
new VertexAttributeDescriptor { attribute = VertexAttribute.TexCoord0, format = VertexAttributeFormat.Float32, dimension = 2 } };
struct MyJob : IJob {
NativeList<MyVertex> vertices;
NativeList<int> indices;
void Execute() { /* fill up the vertices and indices lists using .Add() */ } }
// somewhere in your initialization code
Mesh mesh = new Mesh();
mesh.hideFlags = HideFlags.DontSaveInBuild | HideFlags.DontSaveInEditor;
mesh.MarkDynamic(); // or not, depends on your use case
// somewhere on the main thread at the beginning of the frame...
MyJob job = new MyJob();
job.vertices = new NativeList<MyVertex>(512 /* preallocated size, but can grow beyond this */, Allocator.TempJob);
job.indices = new NativeList<int>(512 /* preallocated size, but can grow beyond this */, Allocator.TempJob);
JobHandle handle = job.Schedule();
JobHandle.ScheduleBatchedJobs(); // depends on your threading pattern whether you want this or not
// somewhere else on the main thread at the end of the frame or maybe the next frame...
handle.Complete();
mesh.Clear(true);
mesh.SetVertexBufferParams(job.vertices.Length, vertexLayout);
mesh.SetVertexBufferData<MyVertex>(job.vertices, 0, 0, job.vertices.Length, 0, 0);
mesh.SetIndexBufferParams(job.indices.Length, IndexFormat.UInt32 /* or 16, and use ushort instead of int */);
mesh.SetIndexBufferData<int>(job.indices, 0, 0, job.indices.Length, 0);
mesh.subMeshCount = 1;
SubMeshDescriptor smd = new SubMeshDescriptor {
topology = MeshTopology.Triangles,
vertexCount = job.vertices.Length,
indexCount = job.indices.Length };
mesh.SetSubMesh(0, smd, 0);
mesh.UploadMeshData(/* true or false depending on your use case */);
// don't forget these like I did when originally writing this post!
job.vertices.Dispose();
job.indices.Dispose();
Here’s my code (messy, and the comments are highly trained in stealth ninjitsu, so you might not see them ):
https://gitlab.com/burningmime/easybuilding/-/blob/master/Packages/building/src/jobs/MeshBuilder.cs
There are various ways to optimize this (eg different MeshUpdateFlags, and calculating the bounds yourself in the job). Your call as to how far you want to go with those; investigate the mesh API.