Capturing a Metal workload programmatically

To get a better view of the performance of our game, we would like to run a Metal Capture while running on iOS. For this, we would like to use the system documented by Apple here https://developer.apple.com/documentation/xcode/capturing\-a\-metal\-workload\-programmatically.

Over the past couple of weeks, I have attempted to translate the Swift code to work within the Objective C Unreal Engine 4.27 configuration.

Below is the code that I am using for this. When the metal capture starts, the framerate gets a lot worse as you would expect when performing such an intensive profiling run. The code also successfully generates a .gputrace file, but when I try to open it in Xcode, I get an error saying “Xcode failed to open the gputrace document. The index file does not exist. The capture may be incomplete or corrupt. (2)”.

I believe that this is because I do not commit the command buffer before stopping the capture. In StopMetalCapture, I have commented out the code that does so, because running that code crashes the game. What is the correct code to commit the Unreal Metal Command Buffer?

#if PLATFORM_IOS
void StartMetalCapture()
{
    FMetalContext& Context = GetMetalDeviceContext();

    mtlpp::Device MetalDevice = Context.GetDevice();

    id<MTLDevice> device = MetalDevice.GetPtr();
    
    // Get Documents directory path
    NSArray<NSURL *> *documentsDirectories = [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];

    NSURL* documentsDirectory = [documentsDirectories firstObject];
    NSURL* outputURL = [documentsDirectory URLByAppendingPathComponent : @"MyCapture.gputrace"];

    MTLCaptureManager *captureManager = [MTLCaptureManager sharedCaptureManager];
    MTLCaptureDescriptor *captureDescriptor = [[MTLCaptureDescriptor alloc] init];
    captureDescriptor.captureObject = device;
    captureDescriptor.destination = MTLCaptureDestinationGPUTraceDocument;
    captureDescriptor.outputURL = outputURL;

    if (![captureManager supportsDestination:MTLCaptureDestinationGPUTraceDocument])
    {
        NSLog(@"Capturing to a GPU trace file isn't supported.");
        return;
    }

    NSError *error = nil;
    if (![captureManager startCaptureWithDescriptor:captureDescriptor error:&error])
    {
        NSLog([Content removed] error);
    }
    else
    {
        NSLog([Content removed] outputURL);
    }
}

void StopMetalCapture()
{
    /*FMetalContext& Context = GetMetalDeviceContext();
    mtlpp::CommandBuffer& CommandBuffer = Context.GetCurrentCommandBuffer();
    id<MTLCommandBuffer> NativeCommandBuffer = CommandBuffer.GetPtr();

    [NativeCommandBuffer commit];*/

    MTLCaptureManager *captureManager = [MTLCaptureManager sharedCaptureManager];
    [captureManager stopCapture];

    NSLog(@"Finished metal capture");
}
#endif

Steps to Reproduce

Hi Jimmy,

Looking at the implementation of capture from the XcodeGPUDebuggerPlugin, it appears that a full Submit and flush is happening before programmatic capture.

FRHICommandListExecutor::GetImmediateCommandList().SubmitCommandsAndFlushGPU();Let us know if this doesn’t resolve on your end.

Best regards.