Surprisingly high Memory Allocation - Simple use case (URP)

Avatar
  • updated
  • Fixed

Hi there, I set it as a bug though it may not be one (however, that would mean we can't use Shapes for what we intended to unfortunately : ( ).

I just wrote a simple script which draws many discs and it works perfectly fine. My class is inheriting from Immediate Mode Shape Drawer and I'm simply calling Draw.Disc in a loop inside a using block for Draw.Command as recommended in the documentation.

I had a look at the profiler to see the impact of doing that before working more on making cool things with Shapes but I noticed that for around 100 discs, it allocates around 21kb per frame, and it linearly increases the more calls to Draw.Disc I make.

I don't know if it only happens in URP but I wouldn't be surprised since the GC.Alloc calls are all made in RenderPipelineManager::DoRenderLoop_Internal(). Of course I quickly tested disabling my script using Shapes to make sure it comes from it, and all allocation disappear if I don't run my Shapes stuff : (

I also tested in a standalone build and I have the same results.

Any idea if it could be improved or if it's due to the design and will stay that way, please?

Edit: Additional information, if I disable the ShapesRendererFeature in the renderer settings for URP, it doesn't draw the shapes anymore (as expected), but it still allocate the same amount of memory.

Thanks,

Clem

Reporting a bug? please specify Unity version:
2020.1.13
Reporting a bug? please specify Shapes version:
3.0.0
Reporting a bug? please specify Render Pipeline:
URP
Pinned replies
Avatar
Freya Holmér creator
  • Answer
  • Fixed

this has now been fixed in 3.1.0 across all render pipelines!

note that Draw.Text will still allocate for, silly reasons, that I can hopefully fix in the future!

Avatar
Clement Capart

We had a chat with Freya on Discord about it here's a summary of what we found out:


The allocations come from 3 things:

- (around 15%) Material.GetShaderKeywords() -> Can be reduced but it requires quite a big refactoring of the code for it

- (around 50%) "new IMDrawer" allocation (in DiscCore in my case) -> Making IMDrawer a struct instead of a class solve that one!

- (around 35%) matrices.ToArray() in MetaMbp::ExtractDrawCall() -> Going to have a look at what I can do with this one and I'll post it here if I find something

I may have missed some stuff so don't take this as the answer to the whole issue but I thought it could help people who need to solve that issue too.

Avatar
Clement Capart

I finally got to 0 memory allocation instead of 96KB/frame with the same setup. I had to change quite a few things and some may or may not break some stuff, and I only made things work for our use case with URP. Don't assume that it's "easy" for Freya to just do the same and ship it, as she has to deal with all render pipelines and make it a lot more robust than my use case!

 

Per shape drawn:

  • I now cache shader keywords using a dictionary <Material, string[]> to not retrieve them again everytime since Materials are re-used a lot.
  • IMDrawer is a struct
  • I use a Matrix4x4[] pool instead of List<Matrix4x4>.ToArray() in MetaMbp::ExtractDrawCall() to avoid copying them and allocating every time.

Per Draw.Command:

  • I've changed the ShapesRenderFeature and ShapesRenderPass so that they don't create many passes every frame but instead reuse the ones from previous frame. I think an even better solution would be to have only one ShapesRenderPass with multiple command buffers. Also, CommandBuffer is using CommandBufferPool instead of creating one every frame.
  • I've made DrawCalls pooled and ShapeDrawCall a struct with a bool to say if it's using instancing instead of it being sub classes, that avoid more allocations.
Avatar
Freya Holmér creator
  • Answer
  • Fixed

this has now been fixed in 3.1.0 across all render pipelines!

note that Draw.Text will still allocate for, silly reasons, that I can hopefully fix in the future!