Thread Safety of SurfaceTool.Commit() / ArrayMesh.AddSurfaceFromArrays()

komposter64komposter64 Posts: 3Member

Hi,
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:

  1. pre-generate some chunks around the player at startup and without threading
  2. create a container object called WorldChunk (derived from Spatial)
  3. pass the object to a worker thread
  4. 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()
  5. join worker thread, place mutex
  6. generate collision data for chunk, by calling ArrayMesh.CreateTrimeshShape() and adding that to a StaticBody
  7. add chunk to list of finished chunks (adding it from a deferred called function directly wasn't reliable)
  8. 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?


Tags :

Best Answer

  • komposter64komposter64 Posts: 3
    edited August 2 Accepted Answer

    After shuffling some code around, I can now use SurfaceTool.commit() inside my worker threads without crashing. Not sure if I did something stupid which I later fixed by accident or if one of the bugfix releases for Godot 3.2 fixed something in the background.

Answers

  • komposter64komposter64 Posts: 3Member
    edited August 2 Accepted Answer

    After shuffling some code around, I can now use SurfaceTool.commit() inside my worker threads without crashing. Not sure if I did something stupid which I later fixed by accident or if one of the bugfix releases for Godot 3.2 fixed something in the background.

Leave a Comment

BoldItalicStrikethroughOrdered listUnordered list
Emoji
Image
Align leftAlign centerAlign rightToggle HTML viewToggle full pageToggle lights
Drop image/file