Article

Capturing GPU Command Data Programmatically

Invoke Metal’s frame capture from your app under the specific runtime conditions you choose.

Overview

Use MTLCaptureManager to programmatically capture information about commands sent to a specific device object. Depending on the runtime conditions you choose to stop the capture, it enables you to capture a specific frame, just a part of a frame, implement a custom UI to trigger a capture, or programmatically trigger capture when certain conditions occur.

To start a capture, create a MTLCaptureDescriptor object that defines which commands you want to record and what should happen after the capture is complete. By default, when you stop capturing data, Xcode halts your app and presents its results in the Metal frame debugger.

Capture a Device or Command Queue

To start capturing commands for a specific MTLDevice or MTLCommandQueue, set the capture descriptor's captureObject property to point at the specific object to track, and call the startCapture(with:) method. To stop capturing commands, call the stopCapture() method.

func triggerProgrammaticCapture() {
    let captureManager = MTLCaptureManager.shared()
    let captureDescriptor = MTLCaptureDescriptor()
    captureDescriptor.captureObject = self.device
    do {
        try captureManager.startCapture(with: captureDescriptor)
    }
    catch
    {
        fatalError("error when trying to capture: \(error)")
    }
}

func runMetalCommands() {
    let commandBuffer = commandQueue.makeCommandBuffer()!
    // Do Metal work
    commandBuffer.commit()
    let captureManager = MTLCaptureManager.shared()
    captureManager.stopCapture()
}

The capture manager captures commands only within MTLCommandBuffer objects that are created after the capture starts and are committed before the capture stops.

Capture a Custom Scope

To capture commands using a custom scope, create a MTLCaptureScope object and set the capture descriptor's captureObject property to point to it. To define boundaries for the scoped capture, call the MTLCaptureScope object's begin() and end() methods just before and after the commands that you want to capture. The capture stops when your app reaches the corresponding end() method of the given capture scope.

func setupProgrammaticCaptureScope() {
    myCaptureScope = MTLCaptureManager.shared().makeCaptureScope(device: device)
    myCaptureScope?.label = "My Capture Scope"
}

func triggerProgrammaticCaptureScope() {
    guard let captureScope = myCaptureScope else { return }
    let captureManager = MTLCaptureManager.shared()
    let captureDescriptor = MTLCaptureDescriptor()
    captureDescriptor.captureObject = captureScope
    do {
        try captureManager.startCapture(with:captureDescriptor)
    }
    catch
    {
        fatalError("error when trying to capture: \(error)")
    }
}

func runMetalCommands() {
    myCaptureScope?.begin()
    let commandBuffer = commandQueue.makeCommandBuffer()!
    // Do Metal work
    commandBuffer.commit()
    myCaptureScope?.end()
}

The capture scope only captures commands within MTLCommandBuffer objects that are created after the scope begins and are committed before the scope ends.

Capture Multiple Frames

When you capture a frame programmatically, you can capture Metal commands that span multiple frames by using a custom capture scope. For example, by calling begin() at the start of frame 1 and end() after frame 3, the trace will contain command data from all the buffers that were committed in the three frames.

Capture GPU Command Data to a File

Instead of launching the Xcode frame debugger, you can also save GPU command information to a file for later analysis. Test to make sure the feature is available before attempting to record a trace file. Then, set the capture descriptor's destination property to MTLCaptureDestination.gpuTraceDocument and specify the file's destination. The file must have a file extension of .gputrace.

let captureManager = MTLCaptureManager.shared()

guard captureManager.supportsDestination(.gpuTraceDocument) else
{
    print("Capture to a GPU tracefile is not supported")
    return
}

let captureDescriptor = MTLCaptureDescriptor()
captureDescriptor.captureObject = self.device
captureDescriptor.destination = .gpuTraceDocument
captureDescriptor.outputURL = self.traceURL
...

When you stop capturing data, Metal writes the trace to the file, and then continues executing your app. Open a trace file at a later time to launch Xcode and replay the trace.

You can record multiple traces in a single launch of your app, although not at the same time.

See Also

Capturing a Frame Programmatically

Capturing Metal Commands Programmatically

Invoke Metal’s frame capture from your app, then save the resulting GPU trace to a file or view it in Xcode.

Creating a Custom Capture Scope

Use custom capture scopes to control which commands get captured.

class MTLCaptureManager

An object you use to capture Metal command data in your app.

protocol MTLCaptureScope

An object that defines custom boundaries for a GPU frame capture.

class MTLCaptureDescriptor

A configuration for a Metal capture session.

enum MTLCaptureDestination

The kinds of destinations for captured command data.

let MTLCaptureErrorDomain: String

The error domain for capture errors.

enum MTLCaptureError

Errors returned by capture sessions.