We've noticed after upgrading our heavy Paper2D project to 4.22 that tilemap performance has been massively degraded compared to previous versions. After some digging we found out that it's due to a fix to a bug that we've had for a while but successfully worked around.

In short: referencing more than one tilemap in a TileLayer would break the rendering of that TileLayer. We worked around this by just adding more layers, always dedicating a layer to a specific tileset.

The 4.22 rendering rewrite seems to have brought this bug over to PC, and so it was I guess officially detected for the first time, and therefore fixed (yay!). However, this fix totally wrecked performance, especially on mobile platforms. We've worked around this by #ifdeffing so the game runs with the old code on mobile platforms but with the new code on other platforms / the editor etc.

The issue is, simply put: The GetNewBatchMeshes function in PaperRenderSceneProxy now loops through all vertices per batch, adding each vertex separately to the DynamicMeshBuilder. This wrecks performance since the VertexBuffer->Vertices TArray in DynamicMeshBuilder is resized once per AddVertex call.

However, I propose a small code change that improves tilemap performance across all platforms. (I think it's still worse than what we had before, but not by much!)

So the simple fix I propose is this:

Add a Vertices.Reserve() call before the for loop. Since DynamicMeshBuilder doesn't expose the vertices array, I propose:

Add the following method to DynamicMeshBuilder:


  1. /** Add a subsection of the given vertex array to the mesh, defined by StartIndex and Length. */
  2. ENGINE_API int32 AddVertices(const TArray<FDynamicMeshVertex> &InVertices, int32 SubSectionStartIndex, int32 SubSectionLength);


  1. /** Add a subsection of the given vertex array to the mesh, defined by StartIndex and Length. Returns start index of verts in the overall array. */
  2. int32 FDynamicMeshBuilder::AddVertices(const TArray<FDynamicMeshVertex> &InVertices, int32 SubSectionStartIndex, int32 SubSectionLength)
  3. {
  4. int32 StartIndex = VertexBuffer->Vertices.Num();
  5. VertexBuffer->Vertices.Reserve(StartIndex + SubSectionLength);
  6. for (int32 i = SubSectionStartIndex; i < SubSectionStartIndex + SubSectionLength; ++i)
  7. {
  8. AddVertex(InVertices[i]);
  9. }
  10. return StartIndex;
  11. }

In GetNewBatchMeshes, change:


  1. FDynamicMeshBuilder DynamicMeshBuilder(View->GetFeatureLevel());
  2. for (int32 i = Batch.VertexOffset; i < Batch.VertexOffset + Batch.NumVertices; ++i)
  3. {
  4. DynamicMeshBuilder.AddVertex(Vertices[i]);
  5. }



  1. FDynamicMeshBuilder DynamicMeshBuilder(View->GetFeatureLevel());
  2. DynamicMeshBuilder.AddVertices(Vertices, Batch.VertexOffset, Batch.NumVertices);

This gets rid of the constant TArray resizing.

Steps to Reproduce

Refer to UDN.

Have Comments or More Details?

There's no existing public thread on this issue, so head over toAnswerHub just mention UE-79052 in the post.

Login to Vote

Target Fix4.24
Fix Commit10122475
Main Commit10122481
Release Commit10122475
CreatedAug 16, 2019
ResolvedNov 13, 2019
UpdatedMar 2, 2020