#1465

Support for metal-cpp for Metal GPU Profiling

Openspndaopened 2mo ago

The current tracy/TracyMetal.hmm expects to be used in a Objective-C++ environment. However, Apple has offered a C++ wrapper called metal-cpp which allows applications to use Metal from nearly only C++ for over two years now.

I looked into what it would take to implement this, and I think the main issue would be that the implementation for the functionality would need to be moved into a separate Objective-C++ source file since what people commonly do is just have wrappers. For example, ImGui does it like this (this is not exactly the source, but a modified excerpt to make it clear what happens):

#ifndef __OBJC__
IMGUI_IMPL_API void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data,
                                                   MTL::CommandBuffer* commandBuffer,
                                                   MTL::RenderCommandEncoder* commandEncoder);
#else
IMGUI_IMPL_API void ImGui_ImplMetal_RenderDrawData(ImDrawData* drawData,
                                                   id<MTLCommandBuffer> commandBuffer,
                                                   id<MTLRenderCommandEncoder> commandEncoder);
#endif
void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data,
                                    MTL::CommandBuffer* commandBuffer,
                                    MTL::RenderCommandEncoder* commandEncoder)
{
    ImGui_ImplMetal_RenderDrawData(draw_data,
                                   (__bridge id<MTLCommandBuffer>)(commandBuffer),
                                   (__bridge id<MTLRenderCommandEncoder>)(commandEncoder));
}

void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id<MTLCommandBuffer> commandBuffer, id<MTLRenderCommandEncoder> commandEncoder)
{
...
}

Meaning that the header included into the project's source files is compiled only with the metal-cpp overloads while the Objective-C++ source file compiles with both overloads and redirects the function calls.

My concern here is the current way these wrappers are implemented for the Tracy client and the fact that they are all header-only. Also, since performance is obviously a concern I don't think it is reasonable to add any large overhead to these functions. However, due to compatibility and easy-of-use I think it would still absolutely be worth it to implement this. Is it ok if the implementation is moved to the client directory? If so I'd be happy to implement this.

Furthermore (unrelated to the original issue), with Metal 4 the limitation mentioned in the User Manual is not a thing anymore, I think.

At the moment, the Metal back-end in Tracy operates differently than other GPU back-ends like Vulkan, Direct3D and OpenGL. Specifically, TracyMetalZone(name, encoderDescriptor) must be placed before the site where a command encoder is about to be created. This is because not all Apple hardware supports timestamps at command granularity, and can only provide timestamps around an entire command encoder (this accommodates for all tiers of GPU hardware on Apple platforms).

The Metal 4 command buffer has a writeTimestamp function now, see: https://developer.apple.com/documentation/metal/mtl4commandbuffer/writetimestamp(counterheap:index:). This looks to match the Vulkan behavior.

Activity

7
wolfpld2mo ago

What overhead are we talking about here? How was it measured?

The wrapper you presented looks to me like a prime target for elimination by the compiler. By moving it to a source file compiled to a profiler object you are effectively prohibiting this optimization (with no lto).

Furthermore

@slomp

spnda2mo ago

What overhead are we talking about here? How was it measured?

The fact that the compiler can't eliminate the redirect without LTO, as you said. I didn't measure anything as I haven't implemented anything yet as of now.

spnda2mo ago

I'm also concerned since the code currently includes all source files into TracyClient.cpp. If we were to do that we'd again run into the issue of including Objective-C++ code into an otherwise C++-only source file.

There's of course always the option of having a #define TRACY_METAL_IMPLEMENTATION with a .inl file, but I am never really a fan of that... and I think it would be very hacky to get it working here due to all of the ObjC incompatibilities.

slomp2mo ago

Apple has offered a C++ wrapper called metal-cpp which allows applications to use Metal from nearly only C++ for over two years now.

Metal C++ (metal-cpp) is not distributed as an integrated part of Xcode or the default Apple SDK.

The Metal 4 command buffer has a writeTimestamp

Sure, but as noted in the manual: "not all Apple hardware supports timestamps at command granularity, and can only provide timestamps around an entire command encoder (this accommodates for all tiers of GPU hardware on Apple platforms)".

In fact, all Apple Silicon hardware (A and M series) don't support it. It's a general limitation of Tile-Based Deferred Rendering (TBDR) hardware.

There are already checks in place if you want to check it yourself (if you enable the Tracy Metal debug mode): https://github.com/wolfpld/tracy/blob/8b41c38ed9b0750e2c874ca7c9d82cf5b012a0e1/public/tracy/TracyMetal.hmm#L306-L321

It would not be difficult to add support for writeTimestamp if it becomes generally available and reliable, though.

According to Metal 4 writeTimestamp documentation:

The method ensures that any prior work finishes, but doesn’t delay any subsequent work. You can alter this command’s behavior through the granularity parameter. Pass MTL4TimestampGranularityRelaxed to allow Metal to provide timestamps with minimal impact to runtime performance, but with less detail. For example, the command may group all timestamps for a pass together. Pass MTL4TimestampGranularityPrecise to request that Metal provides timestamps with the most detail. This can affect runtime performance.

So, granularity "relaxed" is how things are right now, it samples the timestamps at more coarse boundaries natural to the hardware. If you want to switch to "precise", Metal will have to break down the encoder into multiple encoders and inject heavy pipeline flushes/barriers to "emulate" the behavior.

slomp2mo ago

Now, back to the Metal C++ support: Even though it's not an officially distributed part of the SDK, we could abstract most calls in TracyMetal.mm with wrapper macros that would either emit the Objective-C++ call, or the Metal C++ call (we'd also have to macrofy the member fields in the various classes).

I am not sure it would add much, really. Is there something preventing you to compile your Metal abstraction with Objective-C++? (AFAIK, Apple's Objective-C++ is a strict superset of C++)

spnda2mo ago

Metal C++ (metal-cpp) is not distributed as an integrated part of Xcode or the default Apple SDK.

But it is an official library from Apple, so what difference does that make? There's also multiple WWDC talks on it, so it is definitely something Apple is pushing.

I am not sure it would add much, really. Is there something preventing you to compile your Metal abstraction with Objective-C++? (AFAIK, Apple's Objective-C++ is a strict superset of C++)

When it is already written in C++ using the wrappers it is extra effort to make it compatible. Also, as I've discovered after writing the original post, using the tracy functionality requires ARC so for one it is incompatible with the ImGui backend I am using at the moment and I can't imagine it'd be simple to integrate into a C++ codebase that does not have ARC. I'd effectively need to rewrite the Metal rendering backend I currently have to be able to use the Tracy functionality... Why does it require ARC, anyway?

It would not be difficult to add support for writeTimestamp if it becomes generally available and reliable, though.

I looked at the documentation again and I didn't see the granularity enum, so that's fair. I think then the point of the API is more so to avoid the flow that currently exists for example in Tracy that makes it more complicated than it needs to be and gives control to the driver to manage it correctly. As far as I understand the API is available everywhere just at the lower granularity.

Looking at the Metal 4 API I also cannot see any sampling buffers available anymore on command encoders, so I would assume that if a user uses Metal 4 (which I also plan on supporting soon) they could just outright not use the current API. That's probably worth an entirely separate issue.

slomp2mo ago

But it is an official library from Apple, so what difference does that make?

It's a larger surface area to maintain and test in the Tracy project.

There's also multiple WWDC talks on it, so it is definitely something Apple is pushing.

And yet, even Apple has not officialize it in their SDK. (Probably for the same public/wide-spread maintainability and testing reasons I mentioned.)

tracy functionality requires ARC (...) Why does it require ARC, anyway?

It was implemented as a minimum-viable prototype, that happened to work quite well to be adopted/merged. It should be possible to lift ARC, as the lifetime of the ObjC resources in TracyMetal are well scoped and delineated.

I would assume that if a user uses Metal 4 (...) they could just outright not use the current API.

If Metal 4 has major incompatibilities, we should consider rolling a "TracyMetal4" back-end to avoid crossing the beams. We can always unify the implementations later once things are reportedly stable.


All that said, I am not opposed to adding metal-cpp support, or even Metal 4 support. Feel free to open a PR with proposed changes and some simple test case and I'd be happy to review it.