<!--
{
  "documentType" : "article",
  "framework" : "Metal",
  "identifier" : "/documentation/Metal/converting-a-gpus-counter-data-into-a-readable-format",
  "metadataVersion" : "0.1.0",
  "role" : "article",
  "title" : "Converting a GPU’s counter data into a readable format"
}
-->

# Converting a GPU’s counter data into a readable format

Inspect and use the data within a GPU’s counter sample buffer by resolving it into a standard format.

## Discussion

To use the data a GPU driver stores in an [`MTLCounterSampleBuffer`](/documentation/Metal/MTLCounterSampleBuffer) instance (see [Sampling GPU data into counter sample buffers](/documentation/Metal/sampling-gpu-data-into-counter-sample-buffers)), your app needs to *resolve* it. Resolving the data converts the counter data from the GPU’s internal data structure into a common format that Metal defines.

You can resolve the data in a counter sample buffer by creating a blit pass that converts the data as it copies it to an [`MTLBuffer`](/documentation/Metal/MTLBuffer). If the CPU can access a counter sample buffer, you can also resolve the data after the GPU finishes running a command buffer. See [Creating a counter sample buffer to store a GPU’s counter data during a pass](/documentation/Metal/creating-a-counter-sample-buffer-to-store-a-gpus-counter-data-during-a-pass) for information about making a CPU-accessible counter sample buffer.

### Resolve the counter sample buffer with the CPU

For an [`MTLCounterSampleBuffer`](/documentation/Metal/MTLCounterSampleBuffer) instance that you create with shared memory (see [`storageMode`](/documentation/Metal/MTLCounterSampleBufferDescriptor/storageMode) and [`MTLStorageMode.shared`](/documentation/Metal/MTLStorageMode/shared)), you can resolve the data by calling its [`resolveCounterRange(_:)`](/documentation/Metal/MTLCounterSampleBuffer/resolveCounterRange(_:)) method.

```objective-c
/// Converts the contents of the counter sample buffer into an array of result timestamps.
- (void) resolveSampleBuffer
{
    /// Represents the size of the counter sample buffer.
    NSRange range = NSMakeRange(0, self.sampleCount);

    // Convert the contents of the counter sample buffer into the standard data format.
    NSData* data = [self.counterSampleBuffer resolveCounterRange:range];
    if (nil == data) {
        return;
    }
    ...
}
```

You can resolve a sample counter buffer with the CPU at any time after the GPU finishes running the pass that retrieves the counter’s data. To access the data as soon as possible (with the CPU), add a completion handler to the pass’s command buffer by calling its [`addCompletedHandler(_:)`](/documentation/Metal/MTLCommandBuffer/addCompletedHandler(_:)) method.

```objective-c
[commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> _Nonnull commandBuffer) {
    [self resolveSampleBuffer];
    ...
}];
```

### Resolve the counter sample buffer with a blit pass on the GPU

You can also resolve an [`MTLCounterSampleBuffer`](/documentation/Metal/MTLCounterSampleBuffer) instance’s data into an [`MTLBuffer`](/documentation/Metal/MTLBuffer) by running a blit pass on the GPU. For some GPUs, this technique is the only way to resolve a counter sample buffer that uses private storage (see [`storageMode`](/documentation/Metal/MTLCounterSampleBufferDescriptor/storageMode) and [`MTLStorageMode.private`](/documentation/Metal/MTLStorageMode/private)).

To resolve a sample counter buffer in a blit pass, create an [`MTLBlitCommandEncoder`](/documentation/Metal/MTLBlitCommandEncoder) instance and call its [`resolveCounters(_:range:destinationBuffer:destinationOffset:)`](/documentation/Metal/MTLBlitCommandEncoder/resolveCounters(_:range:destinationBuffer:destinationOffset:)) method.

```objective-c
(id<MTLBuffer>) resolveSampleBuffer:(id<MTLCounterSampleBuffer>)sampleBuffer
                      withBlitEncoder:(id<MTLBlitCommandEncoder>)blitEncoder
              toBufferWithStorageMode:(MTLResourceOptions)storageMode
{
    NSUInteger counterBufferLength = self.sampleCount * sizeof(MTLCounterResultTimestamp);
    id<MTLBuffer> counterDataBuffer = [sampleBuffer.device newBufferWithLength: counterBufferLength
                                                                       options: storageMode];

    if (nil == counterDataBuffer) {
        return nil;
    }

    NSRange range = NSMakeRange(0, self.sampleCount);

    [blitEncoder resolveCounters:sampleBuffer
                         inRange:range
               destinationBuffer:counterDataBuffer
               destinationOffset:0];


    if (storageMode & MTLStorageModeManaged) {
        [blitEncoder synchronizeResource:counterDataBuffer];
    }

    return counterDataBuffer;
}
```

### Cast the counter sample’s data to a result type

Your app can inspect and use the resolved data by casting it to the result type that corresponds to the counter set.

|Counter set names                                                                 |Counter result types                                                          |
|----------------------------------------------------------------------------------|------------------------------------------------------------------------------|
|``doc://com.apple.metal/documentation/Metal/MTLCommonCounterSet/timestamp``       |``doc://com.apple.metal/documentation/Metal/MTLCounterResultTimestamp``       |
|``doc://com.apple.metal/documentation/Metal/MTLCommonCounterSet/stageUtilization``|``doc://com.apple.metal/documentation/Metal/MTLCounterResultStageUtilization``|
|``doc://com.apple.metal/documentation/Metal/MTLCommonCounterSet/statistic``       |``doc://com.apple.metal/documentation/Metal/MTLCounterResultStatistic``       |

For example, your app can cast the data it resolves from a [`timestamp`](/documentation/Metal/MTLCommonCounterSet/timestamp) counter set as an [`MTLCounterResultTimestamp`](/documentation/Metal/MTLCounterResultTimestamp) array.

```objective-c
/// Converts the contents of the counter sample buffer into an array of result timestamps.
- (void) resolveSampleBuffer
    ...
 
    // Convert the contents of the counter sample buffer into the standard data format.
    NSData* data = [self.counterSampleBuffer resolveCounterRange:range];
    ...

    NSUInteger resolvedSampleCount = data.length / sizeof(MTLCounterResultTimestamp);
    if (resolvedSampleCount < sampleCount) {
        printf("Only %lui out of %ui timestamps resolved.", resolvedSampleCount, sampleCount);
        return;
    }

    // Cast the data's bytes property to the counter's result type.
    MTLCounterResultTimestamp* timestamps = (MTLCounterResultTimestamp *)(data.bytes);
    ...
}
```

The code example above also checks whether the result type array has the correct number of elements of the counter set for the app.

### Inspect the information and check for error values

You can also use the result type instances to check whether the GPU stores any error values. The following code example determines whether any of the timestamp samples are equal to `0` or a sentinel error value:

```objective-c
/// Converts the contents of the counter sample buffer into an array of result timestamps.
- (void) resolveSampleBuffer
    ...
 
    // Cast the data's bytes property to the counter's result type.
    MTLCounterResultTimestamp* timestamps = (MTLCounterResultTimestamp *)(data.bytes);

    // Check for invalid values within the (resolved) data from the counter sample buffer.
    for (int index = 0; index < resolvedSampleCount; index++) {
        MTLTimestamp timestamp = timestamps[index].timestamp;

        if (timestamp == MTLCounterErrorValue) {
            printf("Timestamp sample #%di (of %ui) has an error value.", index + 1, sampleCount);
            return;
        }

        if (timestamp == 0) {
            printf("Timestamp sample #%di (of %ui) has a value of zero.", index + 1, sampleCount);
            return;
        }
    }

    ...
}
```

Any time the GPU encounters a runtime error while sampling a counter, it sets the counter datum to the sentinel value [`MTLCounterErrorValue`](/documentation/Metal/MTLCounterErrorValue).

> Note:
> A GPU typically stores timestamp values from its internal clock. You can convert those timestamps into more meaningful time values, in nanoseconds, with ``doc://com.apple.metal/documentation/Metal/MTLDevice/sampleTimestamps()`` — see <doc://com.apple.metal/documentation/Metal/converting-gpu-timestamps-into-cpu-time>.

---

Copyright &copy; 2026 Apple Inc. All rights reserved. | [Terms of Use](https://www.apple.com/legal/internet-services/terms/site.html) | [Privacy Policy](https://www.apple.com/privacy/privacy-policy)