<!--
{
  "documentType" : "article",
  "framework" : "Metal",
  "identifier" : "/documentation/Metal/synchronizing-a-managed-resource-in-macos",
  "metadataVersion" : "0.1.0",
  "role" : "article",
  "title" : "Synchronizing a managed resource in macOS"
}
-->

# Synchronizing a managed resource in macOS

Manually synchronize memory for a Metal resource in apps.

## Discussion

For Mac computers with Intel or external GPUs, Metal offers *managed resources.* Managed resources are [`MTLResource`](/documentation/Metal/MTLResource) instances, such as an [`MTLTexture`](/documentation/Metal/MTLTexture) or [`MTLBuffer`](/documentation/Metal/MTLBuffer), which use memory that your app can copy between the CPU and GPU. Managed resources use a [`storageMode`](/documentation/Metal/MTLResource/storageMode) of [`MTLStorageMode.managed`](/documentation/Metal/MTLStorageMode/managed).

You need to manually synchronize managed resources, copying changed memory between the CPU and GPU. This is different from Apple family GPUs, which use [`MTLStorageMode.shared`](/documentation/Metal/MTLStorageMode/shared) for resources that the CPU and GPU can both access. Synchronize after your code finishes memory writes. After data synchronizes, you can safely read it in both your app and GPU functions.

As a best practice, try to keep your data synchronization points to a minimum. Even synchronization calls which don’t copy data can result in a small performance hit.

> Note:
> Managed resources are the default memory storage type for Intel and external GPU devices in Metal. For more information about macOS resource storage modes and how to select them, see <doc://com.apple.metal/documentation/Metal/choosing-a-resource-storage-mode-for-intel-and-amd-gpus>.

### Synchronize a managed buffer

First, create an [`MTLBuffer`](/documentation/Metal/MTLBuffer) with the option [`MTLStorageMode.managed`](/documentation/Metal/MTLStorageMode/managed), which tells Metal to reserve managed memory space for the resource:

```swift
// Create a matrix data structure.
struct MatrixData {
    var modelMatrix = matrix_float4x4()
    var viewMatrix = matrix_float4x4()
    var projectionMatrix = matrix_float4x4()
}

// Create a managed buffer.
guard let matrixBuffer = device.makeBuffer(length: MemoryLayout<MatrixData>.size, options: .storageModeManaged) else { return }
```

Next, modify the buffer’s data on the CPU:

```swift
// Modify the managed buffer's data with the CPU.
var matrixData = MatrixData()
matrixData.modelMatrix = updatedModelMatrix
matrixBuffer.contents().storeBytes(of: matrixData, as: MatrixData.self)
```

After completing a CPU modification, call the [`didModifyRange:`](/documentation/Metal/MTLBuffer/didModifyRange:) method. This method updates a specific range of data and keeps the buffer synchronized. Before calling this method, the modified buffer’s data on the GPU is in an undefined state.

```swift
// Synchronize the managed buffer.
matrixBuffer.didModifyRange(0..<MemoryLayout<matrix_float4x4>.size)
```

After encoding a GPU modification, encode a [`synchronize(resource:)`](/documentation/Metal/MTLBlitCommandEncoder/synchronize(resource:)) command. This command updates the entire buffer and keeps it synchronized. Before executing this command, the modified buffer’s data on the CPU is in an undefined state.

```swift
// Create a command buffer for GPU work.
if let commandBuffer = commandQueue.makeCommandBuffer() {
    // Create a compute command encoder.
    guard let computeCommandEncoder =
            commandBuffer.makeComputeCommandEncoder(dispatchType: MTLDispatchType.serial)
    else { return }
    
    // Encode a compute pass to modify the managed buffer's data with the GPU.
    computeCommandEncoder.setComputePipelineState(computePipelineStateObject)
    computeCommandEncoder.setBuffer(matrixBuffer, offset: 0, index: 0)
    computeCommandEncoder.dispatchThreads(gridSize, threadsPerThreadgroup: threadgroupSize)
    computeCommandEncoder.endEncoding()
    
    // Add a completion handler and commit the command buffer.
    let commandBufferHandler: MTLCommandBufferHandler
    commandBuffer.addCompletedHandler(commandBufferHandler)
    commandBuffer.commit()
}
```

### Synchronize a managed texture

First, create an [`MTLTexture`](/documentation/Metal/MTLTexture) in managed memory from an [`MTLTextureDescriptor`](/documentation/Metal/MTLTextureDescriptor) with its storage mode set to [`MTLStorageMode.managed`](/documentation/Metal/MTLStorageMode/managed):

```swift
// Create a texture descriptor.
let textureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .rgba8Unorm,
                                                                 width: textureSize.width,
                                                                 height: textureSize.height,
                                                                 mipmapped: false)

// Set the descriptor's storage mode and usage.
textureDescriptor.storageMode = MTLStorageMode.managed
textureDescriptor.usage = [.shaderRead, .shaderWrite]

// Create a managed texture.
let imageTexture = device.makeTexture(descriptor: textureDescriptor)
```

To perform a CPU modification and simultaneously notify Metal about the change, call the [`replace(region:mipmapLevel:withBytes:bytesPerRow:)`](/documentation/Metal/MTLTexture/replace(region:mipmapLevel:withBytes:bytesPerRow:)) method. This method updates a specific region of data and keeps the texture synchronized. To update a specific texture slice, call the [`replace(region:mipmapLevel:slice:withBytes:bytesPerRow:bytesPerImage:)`](/documentation/Metal/MTLTexture/replace(region:mipmapLevel:slice:withBytes:bytesPerRow:bytesPerImage:)) method instead. Before calling one of these methods, the modified texture’s data on the GPU is in an undefined state.

```swift
// Simultaneously modify and synchronize the managed texture's data with the CPU.
let region = MTLRegionMake2D(textureOrigin.x, textureOrigin.y, textureSize.width, textureSize.height)
let bytesPerRow = pixelSize * textureSize.width
imageTexture.replace(region: region, mipmapLevel: 0, withBytes: textureData, bytesPerRow: bytesPerRow)
```

After encoding a GPU modification, encode a [`synchronize(resource:)`](/documentation/Metal/MTLBlitCommandEncoder/synchronize(resource:)) command. This command updates the entire texture and keeps it synchronized. To update a specific texture slice or mipmap level, encode the [`synchronize(texture:slice:level:)`](/documentation/Metal/MTLBlitCommandEncoder/synchronize(texture:slice:level:)) command instead. Before executing this command, the modified texture’s data on the CPU is in an undefined state.

```swift
// Create a command buffer for GPU work.
if let commandBuffer = commandQueue.makeCommandBuffer() {
    // Create a compute command encoder.
    guard let computeCommandEncoder =
            commandBuffer.makeComputeCommandEncoder(dispatchType: MTLDispatchType.serial)
    else { return }
    
    // Encode a compute pass to modify the managed texture's data with the GPU.
    computeCommandEncoder.setComputePipelineState(computePipelineStateObject)
    computeCommandEncoder.setTexture(imageTexture, index: 0)
    computeCommandEncoder.dispatchThreads(gridSize, threadsPerThreadgroup: threadgroupSize)
    computeCommandEncoder.endEncoding()
    
    // Synchronize the managed texture.
    guard let blitCommandEncoder = commandBuffer.makeBlitCommandEncoder() else { return }
    blitCommandEncoder.synchronize(resource: imageTexture)
    blitCommandEncoder.endEncoding()
    
    // Add a completion handler.
    commandBuffer.addCompletedHandler { commandBuffer in
        // Once the completion handler is called, it's safe to use the managed resource on CPU.
    }

    // Commit the command buffer.
    commandBuffer.commit()
}
```

---

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)