Page 1 of 1

[Solved][2.1][Metal] Memory leaks when setting PSO

Posted: Wed Apr 24, 2019 10:00 pm
by Nucleartree

I recently ported the imgui implementation found here viewtopic.php?t=89081 to metal on macOS as part of my work there.

Graphically everything looks fine, and the gui renders. I thought the job was done, but I've recently noticed that it's leaking a lot of memory (about 10MB a second!). The leak only occurs if imgui is rendering, so something to do with my port must be up. Much of the port was done by others, but I did piece together bits of the psoCaching to get it to work with the newer ogre 2.1. The thing with imgui is that the vertices and indices are written to buffers each frame, so the pso also gets altered each frame.

I ran the code through the xcode memory profiler and I found that calls to MetalRenderSystem::_setPipelineStateObject() and subsequently MetalRenderSystem::createRenderEncoder() were causing the leaks. I'm 100% sure this is a problem with vertex data being allocated and not cleaned up.

The data gets written to the buffer like this:

Code: Select all

Ogre::v1::VertexBufferBinding* bind = mRenderOp.vertexData->vertexBufferBinding;

if (bind->getBindings().empty() || mVertexBufferSize != vtxCount)
	mVertexBufferSize = vtxCount;

	bind->setBinding(0, Ogre::v1::HardwareBufferManager::getSingleton().createVertexBuffer(sizeof(ImDrawVert), mVertexBufferSize, Ogre::v1::HardwareBuffer::HBU_WRITE_ONLY));
if (mRenderOp.indexData->indexBuffer.isNull() || mIndexBufferSize != idxCount)
	mIndexBufferSize = idxCount;

	mRenderOp.indexData->indexBuffer =
		Ogre::v1::HardwareBufferManager::getSingleton().createIndexBuffer(Ogre::v1::HardwareIndexBuffer::IT_16BIT, mIndexBufferSize, Ogre::v1::HardwareBuffer::HBU_WRITE_ONLY);

// Copy all vertices
ImDrawVert* vtxDst = (ImDrawVert*)(bind->getBuffer(0)->lock(Ogre::v1::HardwareBuffer::HBL_DISCARD));
ImDrawIdx* idxDst = (ImDrawIdx*)(mRenderOp.indexData->indexBuffer->lock(Ogre::v1::HardwareBuffer::HBL_DISCARD));

memcpy(vtxDst, vtxBuf, mVertexBufferSize * sizeof(ImDrawVert));
memcpy(idxDst, idxBuf, mIndexBufferSize * sizeof(ImDrawIdx));

mRenderOp.vertexData->vertexStart = 0;
mRenderOp.vertexData->vertexCount = vtxCount;
mRenderOp.indexData->indexStart = 0;
mRenderOp.indexData->indexCount = idxCount;

From the debugger I can see that these buffer unlock calls are causing MetalRenderSystem::_notifyActiveEncoderEnded to eventually be called. This resets the render encoder and seems to be the reason why createRenderEncoder() is called so much.

But that's about as far as I was able to get with it.
Am I just using the api in completely the wrong way?
Is there something I should be calling to cleanup the buffers or memory?
I'd really appreciate some direction as to what I should be trying.

This is not happening on OpenGL under Linux. I haven't tried it on D3D and Windows yet.

You can see my full implementation here ... /imguiOgre

I'm quite new to all this, and some guidance on what I've done wrong would be appreciated.

Re: [2.1][Metal] Memory leaks when setting PSO

Posted: Thu Apr 25, 2019 12:14 am
by dark_sylinc
I suspect this problem could go away if you enclose your ImguiManager::render call inside a @autoreleasepool{} block (it's an Obj-C extension).

Re: [2.1][Metal] Memory leaks when setting PSO

Posted: Thu Apr 25, 2019 10:27 am
by Nucleartree

Thanks for your answer Dark_sylinc. I forgot to mention that I did try autoreleasepools, but trying it again today fixed the problem. I think the problem was when I first tried it, I wrapped the autoreleasepool around the highest level of my render loop. This would have travelled through pure c++ files before reaching the objective c stuff. Maybe that cancelled it out?

Either way there are no memory leaks now!

Incase its of any use to someone, before you can use @autoreleasepool {} you need to tell xcode to compile your cpp file as objective-c++.
You can do this by either renaming the file to .mm instead of .cpp, or specifying that in something like CMake.

I do:

Code: Select all

	 PROPERTIES COMPILE_FLAGS "-x objective-c++")
just to target a few files, as renaming them .mm would be bad for other platforms.