The Improved Terrain Generation Pipeline
The past two weeks I've been working to multithread and optimize the terrain generation in Mini Solar System and it's finally paying off. I left off last week with a mostly optimized but partly functional system, and I'm pleased to say this week I have ironed out most of the wrinkles and the terrain generation is almost ten times faster.
Setup
As previously mentioned, the old system would use one thread per terrain face, but now we are calculating the same face in parallel with multiple threads. I settled on an approach with the task graph system where one task would be launched to calculate the vertices, UVs, and tris for the mesh as well as the normals and tangents. Then that task would be used as a prerequisite for another task back on the game thread that would compile the terrain data from each thread and build the final mesh once all threads were finished. It had to be done in two separate tasks like this because the procedural mesh component can only create the mesh from the game thread.
The task of calculating the verts and tris was already handled two weeks ago by subdividing the faces into several horizontal strips. Problems arose when it started to calculate the normals and tangents. It would complete, and the result looked mostly correct, however it was multiple orders of magnitude slower than the previous version which led me to believing I had done something wrong. I was using the same engine provided function to do the calculation in both scenarios, so I scrutinized the data I was feeding into it. Everything seemed to check out, so I dug deeper.
Debugging
I started debugging into the function UKismetProceduralMeshLibrary::CalculateTangentsForMesh. I added multiple scope cycle counters throughout different sections of the function to try and deduce through Unreal Insights what was taking so long. The first thing I noticed was that some threads would complete their work quite quickly (a couple hundred milliseconds), but most were taking several full seconds to complete. Looking further into the Insights I noticed that the threads that were taking longer were spending more of their time iterating over vertex overlaps (vertices that are at the same coordinate) for each tri it was iterating over. This was a big red flag. Using breakpoints then to look at what exactly the vert overlaps were, I noticed that nearly every vertex was "overlapping" and therefore every vertex that it iterated through was being added to multiple triangles (in the VertToTriMap).
Very confused at this point I checked how it was determining vert overlaps. It was a simple function that iterates through the array of vertices and checks if any vertex vector is equal to the given test vert and if so adds it to an out array. And that's where I saw the sneaky oversight, the vertex iteration does not skip the vertex that we are testing and will always add it to the overlap array.
So I added an exception to ignore the vertex we are testing against and magically all the vertex overlaps went away! Which is what I would expect with the terrain generation algorithm as it would be impossible for it to calculate two points at the same place. I plan to keep looking further into this to see if it is an engine bug that I can report and fix or if I'm just missing something.
Results
So with that fixed, it was time to test the real world speed increase from being able to parallelize all the calculations. I loaded up the default solar system with three terrestrial planets and terrain detail set to high (256 x 256 resolution per face, 6 faces per planet). Here you can see the trace region for the game load "Initial Generate Planets" as well as the individual time per planet in Unreal Insights:
| Parallel Calculations |
As you can see, there was nearly a tenfold decrease in generation time, which I'm quite pleased with. However there are still some wrinkles left in the final product. Due to the individual sections now generating their own normals it is possible to see discrepancies in the normals, particularly at the edges where two sections join.
I plan to work out what final issues I can, but I would very much like to move on from multithreading for a little while and do some more gameplay focused tasks.
Comments
Post a Comment