The metal-cpp wrapper released end of last year wraps the Metal 2 API, and by extension the basic NS foundation, in C++ code. From looking through the code, metal-cpp uses the objc/runtime.h header and uses objc_msgSend and sel_registerName to fetch pointers and call methods as if it were Objective-C code. This mostly works, however I've come across an issue where the renderCommandEncoderWithDescriptor: selector fails to load through sel_registerName.
My application creates the default metal device, a command queue, a CAMetalLayer and I can present the drawable through a command buffer and all of it works. However, if I want to start actually rasterising any geometry, I need a MTLRenderCommandEncoder. To create one, I need to call renderCommandEncoderWithDescriptor: on a MTLCommandBuffer, which causes a segfault because the selector is 0x0 and the Objective-C runtime code tries to read from it.
This short snippet is where the segfault occurs. Calling MTL::CommandBuffer::renderCommandEncoder will call the objc_msgSend with a 0x0 selector.
auto buffer = queue->commandBuffer();
auto encoder = buffer->renderCommandEncoder(renderPass);
MTLCommandBuffer buffer = [queue commandBuffer];
MTLRenderCommandEncoder encoder = [buffer renderCommmandEncoderWithDescriptor:renderPass];
And this would be the equivalent in raw Objective-C code, which does in fact work.
I have uploaded a small test example to GitHub: https://github.com/spnda/metal-cpp-bug. This example uses GLFW to create the window and I use a small Objective-C++ file to modify the NSWindow and CAMetalLayer, as the metal-cpp wrapper curiously does not provide wrappers for these classes.
I am personally on macOS Monterey 12.3 with a M1 iMac 24", it would be interesting to see if this is an issue with everybody, or just specific to my setup. Not being able to create a MTL::RenderCommandEncoder makes Apple's official Metal C++ wrappers absolutely useless for any rendering, and I would much appreciate any help to fix this issue.
Hi spnda,
Thank you for your reproducer, it was very helpful in reviewing the root cause of the issue.
Looking into the main.cpp file, we noticed that on line 50, while you do allocate memory for the render pass descriptor, you are not initializing the object.
This makes the descriptor invalid (it's an uninitialized block of memory), which is causing the segmentation fault when Metal attempts to create a render command encoder from it.
I have verified that after changing in your program:
MTL::RenderPassDescriptor* mainPass = MTL::RenderPassDescriptor::alloc();
to:
MTL::RenderPassDescriptor* mainPass = MTL::RenderPassDescriptor::alloc()->init();
The program now behaves as expected.
Please try it on your end and let us know how it goes.