Huge performance loss in Mesh.CreateVBO for dynamic meshes [ IOS ]

This is a follow on from a problem noticed in unity 3.4 which I thought might be fixed in 3.5 as there is this build note

“Optimizations for OpenGL ES 2.0 to avoid performance spikes with dynamic geometry”

A thread talking about a user fix for the problem 3.4 is here ;
THREAD

To be fair the performance spiking (as it was before) is gone but Mesh.CreateVBO is still appearing and taking a large amount of frame time. Leaving the only option to continue implementing one of the solutions noted in the linked thread.

For a visual comparison here is the profiler scene [ iPod 4, IOS5 ] with a number of dynamic meshes updated every frame.
Make sure to note the scale of the graphs.

Ping ponging between two meshes that are updated every frame (i.e double buffering)

Single Mesh updated every frame

CPU changes from 9ms to 17ms and the overall render time increases dramatically. Digging down we can see that MeshDrawVBO remains mostly the same (as expected) but Mesh.CreateVBO goes from 0.39ms to 8.75ms ( Ping Pong method to Single ) which is a huge performance loss per frame.

Would this be classed as a bug?
Or should it simply be a user note that if you want to change a dynamic mesh every frame you need to double buffer it yourself?

Many Thanks,
Steven

Looks like a genuine problem from my point of view.

I’m having this problem as well, but I’m not sure I understand how to ping-pong between two meshes. Could you elaborate how you did this?

In your awake() or start() function create two empty meshes.

meshA and meshB
don’t assign anything to the meshFilter yet.

At the end of each frame ( end of update in a script for example ) assign a different one of the meshes to the mesh filter like so.

bool on = true;
void Update()
{
/////
//// Update your meshes here
////

// Alternate between which mesh you’re using
if( on )
{
meshFilter.sharedMesh = meshA;
on = false;
}
else
{
meshFilter.sharedMesh = meshB;
on = true;
}
}

Thats literally the only different between those two profiler stats.
The first one flips between two meshes like above, the second one only uses meshA with no flipping.

Still not working for me :confused: I’m wondering what you do in this section

/////
//// Update your meshes here
////

It works perfectly for me. I’m doing a ping-pong between two meshes for each mesh that is updated frequently. I use SpriteManager 2, so I changed it in the core of that system to make it easy to use. I did a couple of things:

  • Add a second Mesh instance to SpriteMesh that gets created in tandem with the original, and a bool “ping” to check which is currently being used
  • Add a “dynamic” bool to ISpriteMesh interface, and also add it to the PackedSprite Sprite classes, which then transfers it to its child SpriteMesh instance
  • Added an “isDirty” bool to ISpriteMesh that is set whenever you would have changed set any mesh data (and commented those lines of code)
  • Created a class that keeps a List of dynamic SpriteMesh instances, and updates them in a loop during its LateUpdate function

The actual updating of the SpriteMesh looks like this:

        public void UpdateMesh()
	{
		if ( m_mesh == null || m_secondMesh == null )
		{
			SpriteMeshListener.Unregister( this );
			return;
		}
		
		if ( ping )
		{
			m_secondMesh.vertices = m_vertices;
			if ( m_useUV2 )
				m_secondMesh.uv = m_uvs2;
			else
				m_secondMesh.uv = m_uvs;
			m_secondMesh.colors = m_colors;
			m_secondMesh.triangles = m_faces;
			m_secondMesh.RecalculateBounds();
			#if SPRITE_WANT_NORMALS
				m_secondMesh.RecalculateNormals();
			#endif
			
			meshFilter.mesh = m_secondMesh;
		
			ping = false;
		}
		else
		{
			m_mesh.vertices = m_vertices;
			if ( m_useUV2 )
				m_mesh.uv = m_uvs2;
			else
				m_mesh.uv = m_uvs;
			m_mesh.colors = m_colors;
			m_mesh.triangles = m_faces;
			m_mesh.RecalculateBounds();
			#if SPRITE_WANT_NORMALS
				m_mesh.RecalculateNormals();
			#endif
			
			meshFilter.mesh = m_mesh;
			
			ping = true;
		}
		
		isDirty = false;
	}

Note that when a SpriteMesh instance is NOT dynamic, I simply call the UpdateMesh function directly when I would have otherwise set the isDirty flag.

This affect iOS only or does Android also see this sort of problem?

David

Can someone confirm that this was fixed in 3.5.2?

Still an issue in 3.5.1 (is there a beta of 3.5.2 or something?). I’ve just hacked my SpriteManager to double buffer meshes and it has made a difference, but still not perfect.

3.5.2 has just been publicly released with this in the build notes;

“iOS: Fixed dynamic geometry performance regression in 3.5/3.5.1.”

Hopefully that’s a yes, but I haven’t done a test yet. If anyone else has a go first please post results.

Okay, my results are in…

Slow vertex color submission :Not Fixed
Mesh.CreateVBO :Not Fixed

So I believe the double buffer trick is still the fastest way to render dynamic geometry. Sorry guys. I have a sad face too.

For reference my test is to allocate fixed arrays at startup for 100 quads ( 400 verts, 400 uv0,400 uv1, 400 colors )
Length of arrays does not change ever after startup.

-Each frame change the positions of all the quads ( vertices ) and values of the colors.
-Perform the same operation as above but ping pong between two meshes
-On top of these two tests, alternate between the different vertex colour submission techniques ( Color vs Tangent ) to see what difference that makes

Here is my results [ iPod 4 3rd generation ]

9ms normal. 0.4ms with double buffer

Its only 400 verts too, this must be killing NGUI, EZGUI, QuadUI, 2Dtoolkit guys right?

That’s right. What’s going on?

Have you tried simply setting meshFilter.mesh = null before changing the mesh?

Just tried what you suggested on meshFilter.sharedMesh = null, and then tried again on meshFilter.mesh = null but It didn’t make a difference.

Are you having different results?

I can’t test iOS, so can’t say. I was just curious if de-referencing the mesh would fix it. I guess not! I’ll have to tweak NGUI.

Another question… I noticed in the code posted above the mesh is never cleared: Unity - Scripting API: Mesh.Clear

It specifically states that you should call that function before rebuilding the array. Would adding that function fix the issues? I’m doing that in NGUI, but without a way to test it it makes it difficult to tell if it helps or not.

That’s only true for rebuilding the whole object to prevent a mismatch of triangle versus vertex data. Resubmitting the triangle array every frame is super bad for performance, you ideally want to never change this ( or rarely change it ) after initialisation. Definitely not every frame.

Unitys own example for modifying vertex attributes every frame shows you are not required to call clear (example 2),only doing so if triangle ( or vertex count ) changes.

From the hardware perspective there is no reason why assigning indices would be slow at all. Sure, setting less data would result in better performance, but nothing that would be classified as “super bad”.

I wonder if Unity does this check every time you set the vertex array…

Out of curiosity though, have you tried doing a clear first and setting indices at the end? What performance effect does it have in reality?

Curious, I just gave it a try with a PC build. Frame time dropped from 0.85 to 0.59 by not setting the index buffer. I guess it does affect Unity in a bad way… thanks for the tip!

It doesn’t need to. The mesh index arrays are all properties (not fields actually) and it can mark them as dirty for rebuild immediately without checking so they can be updated at the end of the frame.