Memory leak in SpriteShapeController.LateUpdate()

Unity: 2022.1.b07
Version: 8.0.0-pre.5

We use sprite shapes to model water. The surface of the water can wave and for this we update the surface vertices of the sprite shapes on each FixedUpdate().

Everytime the the vertices has been updated the SpriteShapeController.LateUpdate() in the same frame reports GC.Alloc() in the profiler. The amount seems to vary with the number of vertices (based on water size) but is typically around 1kB per frame.

We manipulate the vertices by accessing the spline on the SpriteShapeController and call SetPosition() for the vertices that needs to be updated. We do not perform any baking.

Note that this issue is not new. It has been around since we started using sprite shapes which is more than a year ago. Google tells me there are similar reports to earlier version of the sprite shape framework and Unity claims to have fixed those. Not sure if is related.

Turns out this can be avoided by turning of auto update of the collider. I would however prefer to have this on. Upon further inspection it turns out that SpriteShapeController.OnWillRenderObject() leaks even more data each frame. Why is this and is this something that Unity intend to fix?

I’ve had a similar experience, also for the purpose of dynamic water. A sprite shape doesn’t seem to be runtime optimized under the hood for constant changes, but is more of an editor tool.

I believe every time you call SetPosition on the spline, it rebuilds the entire mesh. A SetPositions function doesn’t exist unfortunately.

I stopped there, but it seemed to me like rebuilding a Mesh every frame would be more efficient. But I’m not sure if using meshes as sprites is possible.

1 Like

Yes, I’ve actually reached this conclusion my self. Made a post earlier today asking about any suggestions on how to proceed in that manner.

Dynamic custom shaped sprites? - Unity Forum

Perhaps you could achieve the same effect with a shader instead. Instead of dynamically repositioning vertices by script, you might instead be able to accomplish something similar with a vertex shader.

Whenever there is a change in SpriteShape (like change in Splines, Properties or SpriteShape profile) we regenerate the geeometry. Geometry is generated in a C# Job (Burst is supported) for optimal performance. For Graphics, SpriteShapeRenderer waits for completion of this Job and updates internal mesh on the same frame.
For Colliders, this happens in the next frame at the MonoBehavior level. As far as we tested, there are no known memory leaks. However please do feel free to submit a bug report with a simple repro project. Thanks.

Thanks for you answer!

This is how the profiler looks when there is water in the scene. We update the splines on each fixed frame. That is what is causing these spikes. P.S. The sum (42.4KB) is due to multiple cameras rendering water, just didn’t fit in the image.

This is how it looks without water. (The small 336B leak) is a bug in Cinemachine which they have promised to fix.

The code that performs the update is very straight forward:

var spline = this.bodySpriteShapeController.spline;

for (var i = 0; i < this.surfacePointCount; i++)
{
    spline.SetPosition(i, new(this.surfaceX[i], this.smoothSurfaceY[i] + this.Size.y));
}

Afaik can tell this bug has already been reported: Unity Issue Tracker - [SpriteShape] SprieShapeController.OnWillRenderObject() generates large GC Allocations every frame (unity3d.com)

The readme states that this bug should have been fixed. But that does not seem to be the case unless I’m doing something wrong.

Also note that the memory leak is not the only issue here. Whatever happens on OnWillRenderObject() uses a lot of CPU time which makes scenes with water much less performant than those without.

You should probably be able to reproduce the issue by simply modifying the spline on each frame.

Hi @johan_olofsson , please note :

Tessellation for the Edge Sprites is always done in C# Job and does not incur any GC cost.

Tessellation of Inner Geometry (Fill Area) may incur GC allocations depending on the option : Fill Tessellation (C# Job) . If this option is enabled, there is no GC cost as Inner Tessellation is also done on C# Job. If its not enabled, the Inner geometry is generated in Main-thread which usually has some GC allocations.

However note enabling this option has some constraints as described here :
Fill Tessellation in C# Job | 2D SpriteShape | 5.1.7 (unity3d.com)

  • The Sprite Shape cannot have any duplicate points in the same location. Duplicate points can occur if you drag a point over another point.
  • The Sprite Shape’s edges cannot intersect or overlap each other.

If the above conditions are met, please go ahead and enable this option for the fastest performance without any GC alloc. However if Spline inputs are non-deterministic (like totally random points with overlaps etc…) then disable this option to use the default generator which is more tolerant with overlaps and invalid input.

Please let us know if you need more Info.

7897855--1005937--upload_2022-2-15_17-54-42.png

Ok, sounds promising! I’ll look into this.

We do however create the SpriteShapeController in script. Do you know how to enable this tesselation job from code? There are no obvious property for it at a quick glance.

Ok, I tried this. The good news are that yes indeed the memory leak goes away. I actually remember trying this checkbox a while back when this was first implemented. However it caused the log to get spammed with the message:

Fill tessellation (C# Job) encountered errors. Please disable it to use default tessellation for fill geometry.

And the sprite shape flickers all the time. (Changing Detail to Low Quality reduces the flickering, but it doesn’t stop)

According to the page you linked, the requirements for the sprite shapes using the Job are:

  • The Sprite Shape cannot have any duplicate points in the same location. Duplicate points can occur if you drag a point over another point.

  • The Sprite Shape’s edges cannot intersect or overlap each other.

I am pretty sure these are fulfilled but I still get the message/flicker.

I created a small sample water object with very few points in order to inspect it. The points are the following:

0: (0, 1.001934)
1: (0.3333333, 0.9962765)
2: (0.6666667, 0.9955561)
3: (1, 0.9955561)
4: (2, 0.9955561)
5: (2, -1)
6: (-1, -1)
7: (-1, 1.001934)

This is a water are with the size 1x1.

The first 4 vertices are the moving surface, and the other 4 are the corners of a “rect” which is like a margin of thickness 1 that is supposed to be behind whatever geometry is around the water fill any gap between the actual water area and the geometry tiles.

Like this:

As far as I can see, this example does not violate any of the two requirements. What could be the issue? Is there any way to get some details on what the Job considers invalid?

Worth mentioning is that it seems to work fine if I update all the points in the surface to the same Y-value. However, as soon as there are some actual waves the flickering starts. I can’t make out any pattern of when the flicking occurs, it appears to be totally random.

Could you please submit a bug report with a simple repro case please? Will take a look asap. Thanks.

Yes, I’ll set it up later today. You should have it by tomorrow since I believe we are on different time zones. Thanks!

Turns out this was super easy to reproduce in the SpriteShape sample project. I just took the Basic 1 sample and reduced the number of points to 4 (applies to more points too) and made it pulsate when running. This will make it flicker randomly.

(see SpriteShapeFlicker.unity and SpriteShapeTest.cs)

I uploaded the file here instead of filing a bug report.

Please let me know if you experience the same. Thanks!

7901953–1006750–SpriteShapeFlicker.zip (790 KB)

Thanks for the Repro project. Yes, I can reproduce the issue. Will take a look and fix it as soon as possible. Will post an update here. Thanks.

1 Like

Excellent, thank you!

Just a thought. Could it be that the flickering occurs when the spline is updated before the previous job has completed?

Btw, while you are at it. Would it be possible to fix the leak implied from having the collider automatically updated too?

No. A bug that only happens in Tessellation in C# Job was causing the issue and hence been fixed now. You can track the issue here :
https://fogbugz.unity3d.com/default.asp?1405282_cmlnhns7

I believe what you are referring is GC again and not memory leak. Since Collider does not have a thread-safe way to update the data yet, Collider is still updated in the subsequent frame. We will consider improving this in a future versions. Thanks.

1 Like

Ok great news! Thanks!

Yes I do of course refer to GC.Alloc() not actual memory leaks as in undeleted raw memory.

Regardless those are unwanted on a repeated basis as they will pile up and cause collects which may in turn cause spikes. I’ve worked very hard to make sure we have no allocation what so ever except when creating new instances and loading levels. It seems reasonable that unity would strive for this in their frameworks as well?

Btw, it would be much appreciated if you would give some thoughts on this thread: Dynamic custom shaped sprites? - Unity Forum , should me or any one else be in need of creating our own custom shapes. Obv. without all the Job/Collider details etc. Just how do you create a “mesh-like-thingy” and make it work as a sprite?

Thanks!