Thousends MTLRenderCommandEncoders drop FPS below 4

Hi, we have the following question about the Metal:


During migrating from OpenGL to Metal we have replaced each glDrawArrays() call by creation of the MTLRenderCommandEncoder with seeting of all required parameters and later calling of the drawPrimitives method. As a result if our scene contains more than 2000 MTLRenderCommandEncoders, our FPS falls to 4. At the same time all documentations requirenments were met: https://developer.apple.com/documentation/metal/mtlrendercommandencoder, i.e. reusing of the MTLDepthStencilState and MTLRenderPipelineState, creating of the MTLBuffer with option MTLDepthStorageModelPrivate.


I want to add that I our area we often deal with the 2D data, i.e. with a significant number of small objects (polylines, circles, NURBS etc). Certainly, the large number of glDrawArrays() calls are also bad for OpenGL performance. But from our experince problems begins with dozens or even hundreds of thousands of objects. Here we have a problem already with 2000 of objects. It looks like we misunderstood something, that’s why we want to ask about the right strategy of working with MTLRenderCommandEncoder.

Here is a screeen shoot of our test 2D file. Here we have about 2300 MTLRenderCommandEncoders and the FPS about 3-4.


Many thanks in advance for any assistance.

Replies

Why can't you use the same encoder for the 2000 draw calls?

Hello,

The problem with your approach is that glDrawArrays does not really map to MTLRenderCommandEncoder. A render command encoder represents a pass in your frame, not a single draw. Metal is very explicit in terms of GPU submissions and your use case ends up creating 2000 unique GPU submissions, which is a lot of work for the GPU driver. Try batching as many draws as possible in the same MTLRenderCommandEncoder and your performance will improve significantly.

Hope this helps!
So calling glDrawArrays and creating a MTLRenderCommandEncoders are not equivalent operations. Using a MTLRenderPassDescriptor to create an MTLRenderCommandEncoder is generally used to tell Metal what texture (or CALayer) you want to render to, not what you want to draw. As others have mentioned, you probably want to call drawPrimitives many times for every command encoder you create. If your rendering is simple and you don't need to render to an offscreen texture, then you only really need a single MTLRenderCommandEncoder each frame.

You can check out this sample which draws the same scene using both OpenGL and Metal. This sample actually uses 2 MTLRenderCommandEncoders each frame because it first renders a reflection to a texture offscreen with the first MTLRenderCommandEncoder and then renders to the screen in the second. Hopefully this can give you a better understanding of what the Metal and OpenGL commands are equivalent.