Using Metal Frame Capture with MoltenVK

Using Metal Frame Capture with MoltenVK

Why?

For debugging 3D rendering code that's written using Metal, Apple offers Metal Frame Capture. When debugging some code from the scenery rendering toolkit (which uses Metal via MoltenVK) recently, I was quite keen on using it, as the issue at hand was macOS-specific. Otherwise – with the amazing Renderdoc unfortunately not available on macOS – I was flying blind.

scenery itself is written in Kotlin for the Java Virtual Machine, so launching scenery's examples from Xcode directly was not possible: Attaching to the running process was leading to SIGBUS crashes, as was launching the Java binary directly.

I spent some time looking on teh interwebz if MoltenVK offers some internal facilities for triggering a capture, and as it turns out – it does! Downside here is, the documentation is lacking. The only documentation I found was this issue in the MoltenVK Github repository (it turned out later that searching for one of the environment variables mentioned there, MVK_CONFIG_AUTO_GPU_CAPTURE_SCOPE, Google yields a whopping three results 😂, so it's really lightly documented).

Configuring Metal Frame Capture with MoltenVK

Enough of the intro – what needs to be done to actually get a capture file out of your MoltenVK application? It's embarrassingly simple, only three environment variables needs to be set to create a capture. I'd also like to mention here that this works independently of the host language you're using – this even works when somehow wrapping MoltenVK, such as done by lwjgl for Java, or ash-molten for Rust.

  • METAL_CAPTURE_ENABLED – enables the capturing infrastructure in MoltenVK. If this is set, the next environment variable also needs to be non-zero, otherwise it'll be an error.
  • MVK_CONFIG_AUTO_GPU_CAPTURE_SCOPE – controls the scope of the capture, the options are:
    • 0 – captures nothing
    • 1 – captures everything in the lifecycle of a `VkDevice` in the applications, aka, all the frames rendering between the creation of the device, and closure of the application
    • 2 – captures only the first frame rendered
  • MVK_CONFIG_AUTO_GPU_CAPTURE_OUTPUT_FILE – controls where the file will be dumped, e.g. set to /Users/l33th4x0r/myamazingapplication.gputrace. Make sure it does not exist yet. If the file actually should already exist, MoltenVK refuses to overwrite it and will also spit out an error.

The resulting file can then simply be opened with Xcode, and inspected for any weirdness, or just simply admired.

Behind the scenes

After actually getting this to work, I was curious if MoltenVK does anything special to get this going. It turns out this is not the case:

MTLCaptureManager *captureMgr = [MTLCaptureManager sharedCaptureManager];

// Before macOS 10.15 and iOS 13.0, captureDesc will just be nil
MTLCaptureDescriptor *captureDesc = [[MTLCaptureDescriptor new] autorelease];
captureDesc.captureObject = mtlCaptureObject;
captureDesc.destination = MTLCaptureDestinationDeveloperTools;

// ...
// file existance checks omitted here 🙃
		
if ([captureMgr respondsToSelector: @selector(startCaptureWithDescriptor:error:)] ) {
  NSError *err = nil;
  if ( ![captureMgr startCaptureWithDescriptor: captureDesc error: &err] ) {
	reportError(VK_ERROR_INITIALIZATION_FAILED, "Failed to automatically start GPU capture session (Error code %li): %s", (long)err.code, err.localizedDescription.UTF8String);
  }
} else if ([mtlCaptureObject conformsToProtocol:@protocol(MTLCommandQueue)]) {
  [captureMgr startCaptureWithCommandQueue: mtlCaptureObject];
} else if ([mtlCaptureObject conformsToProtocol:@protocol(MTLDevice)]) {
  [captureMgr startCaptureWithDevice: mtlCaptureObject];
}

So apart from checking for the trace file's existence, there's nothing special happening here, only what Apple's documentation linked above is already suggesting.

Hope this was helpful 👍

Update: I've filed a Pull Request with the MoltenVK repository now that adds this information to their README too, so it's more easily discoverable int the future 🚀