Thread Safety of SurfaceTool.Commit() / ArrayMesh.AddSurfaceFromArrays()
I am working on voxel based project in Godot and relying heavily on c# and multthreading in order to generate a continuous world at a decent frame rate. While implementing mesh generation, I ran into some threading issues which i tracked back to SurfaceTool.commit(). My process for generating chunks of my world is as follows:
- pre-generate some chunks around the player at startup and without threading
- create a container object called WorldChunk (derived from Spatial)
- pass the object to a worker thread
- in thread:
a. generate world data
b. generate geometry from world data via SurfaceTool.begin() .. SurfaceTool.commit()
c. notify main thread that the cunk is done by CallDeferred()
- join worker thread, place mutex
- generate collision data for chunk, by calling ArrayMesh.CreateTrimeshShape() and adding that to a StaticBody
- add chunk to list of finished chunks (adding it from a deferred called function directly wasn't reliable)
- on next call of _Process(), add all finished chunks to scene by calling AddChild(), also guarded by a mutex
This basically works, if I skip step 6. Then the world is generate fine as the player floats arount. New chunks get added when player gets close to the edge.
With step 6 enabled, as soon as the first chunk generated from a thread gets added, I get an error message in the output window:
E 0:00:02.032 mesh_surface_get_primitive_type: Index p_surface = 0 is out of bounds (mesh->surfaces.size() = 0).
<C++ Source> drivers/gles3/rasterizer_storage_gles3.cpp:4015 @ mesh_surface_get_primitive_type()
:0 @ Godot.Shape Godot.NativeCalls.godot_icall_0_291(IntPtr , IntPtr )()
Mesh.cs:321 @ Godot.Shape Godot.Mesh.CreateTrimeshShape()()
WorldChunk.cs:68 @ void WorldChunk._Ready()()
:0 @ void Godot.NativeCalls.godot_icall_2_414(IntPtr , IntPtr , IntPtr , Boolean )()
Node.cs:516 @ void Godot.Node.AddChild(Godot.Node , Boolean )()
World.cs:237 @ void World.AddQueuedChunks()()
World.cs:79 @ void World._Process(Single )()
When I move the call to SurfaceTool.commit() out of the worker thread and into the main thread (basically between step 5 and 6, suddenly everything works as expected. This brings me to the conclusion, that SurfaceTool.commit() is not thread safe.
I also tried to ditch SurfaceTool and use ArrayMesh directly, but I get the same behaviour. I assume SurfaceTool.commit() calls ArrayMesh.AddSurfaceFromArrays() internally. So probaly it has something to to with aquiring resource from the GPU or upload vertex data to the GPU?
Although my workaround works, it has a noticable performance impact (around 2ms per chunk and usually around 50-100 chunks are added). So I would be curious to know if there is a way to reliably generate geometry from a worker thread or if I this behaviour is expected?