Article

Synchronizing a Managed Resource

Synchronize the contents of a managed resource for the CPU or GPU.

Overview

Resources with a MTLStorageModeManaged storage mode are accessible to both the CPU and the GPU. Metal optimizes managed resources for each processor, but requires that you explicitly synchronize a given resource after modifying any part of its contents. After you synchronize a managed resource, the CPU and the GPU both have access to the updated resource data.

After you modify any part of a managed resource with the CPU or the GPU, you must synchronize the resource before you modify any part of it with the other processor. You must follow this rule even if the CPU-modified part and the GPU-modified part of the resource don’t overlap. When you synchronize a managed resource, Metal may synchronize more than the specified part if it needs to. Additionally, Metal may automatically copy resource data between the CPU and the GPU if your app or system is constrained by memory.

Synchronize a Managed Buffer

First, create a managed buffer.

// Matrix buffer and matrix data structure.
id <MTLBuffer> _matrixBuffer;
typedef struct
{
    matrix_float4x4 modelMatrix;
    matrix_float4x4 viewMatrix;
    matrix_float4x4 projectionMatrix;
} MatrixData;

// Create a managed buffer.
_matrixBuffer = [_device newBufferWithLength:sizeof(MatrixData)
                                     options:MTLResourceStorageModeManaged];

Next, modify the buffer's data with the CPU.

// Modify the managed buffer's data with the CPU.
MatrixData *matrixData = (MatrixData*)_matrixBuffer.contents;
matrixData->modelMatrix = updatedModelMatrix;

After completing a CPU modification, call the didModifyRange: method. This method updates a specific range of data and keeps the buffer synchronized. You must call this method to update the buffer’s data for the GPU; otherwise, the data is undefined for the GPU.

// Synchronize the managed buffer.
[_matrixBuffer didModifyRange:NSMakeRange(0, sizeof(matrixData->modelMatrix))];

After encoding a GPU modification, encode a synchronizeResource: command. This command updates the entire buffer and keeps it synchronized. You must execute this command to update the buffer’s data for the CPU; otherwise, the data is undefined for the CPU.

// Create a command buffer for GPU work.
id <MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];

// Encode a compute pass to modify the managed buffer's data with the GPU.
id <MTLComputeCommandEncoder> computeCommandEncoder = [commandBuffer computeCommandEncoderWithDispatchType:MTLDispatchTypeSerial];
[computeCommandEncoder setComputePipelineState:computePipelineStateObject];
[computeCommandEncoder setBuffer:_matrixBuffer
                          offset:0
                         atIndex:0];
[computeCommandEncoder dispatchThreads:gridSize
                 threadsPerThreadgroup:threadgroupSize];
[computeCommandEncoder endEncoding];

// Synchronize the managed buffer.
id <MTLBlitCommandEncoder> blitCommandEncoder = [commandBuffer blitCommandEncoder];
[blitCommandEncoder synchronizeResource:_matrixBuffer];
[blitCommandEncoder endEncoding];

// Add a completion handler and commit the command buffer.
[commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> cb) {
    // Managed buffer is updated and synchronized.
}];
[commandBuffer commit];

Synchronize a Managed Texture

First, create a managed texture.

// Create a managed texture.
id <MTLTexture> _imageTexture;
MTLTextureDescriptor *textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm
                                                                                             width:textureSize.width
                                                                                            height:textureSize.height
                                                                                         mipmapped:NO];
textureDescriptor.storageMode = MTLStorageModeManaged;
textureDescriptor.usage = MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite;
_imageTexture = [_device newTextureWithDescriptor:textureDescriptor];

To perform a CPU modification and simultaneously notify Metal about the change, call the replaceRegion: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 replaceRegion:mipmapLevel:slice:withBytes:bytesPerRow:bytesPerImage: method instead. You must call one of these methods to update the texture’s data for the GPU; otherwise, the data is undefined for the GPU.

// Simultaneously modify and synchronize the managed texture's data with the CPU.
[_imageTexture replaceRegion:MTLRegionMake2D(textureOrigin.x, textureOrigin.y, textureSize.width, textureSize.height)
                 mipmapLevel:0
                   withBytes:textureData
                 bytesPerRow:pixelSize*textureSize.width];

After encoding a GPU modification, encode a synchronizeResource: command. This command updates the entire texture and keeps it synchronized. To update a specific texture slice or mipmap level, encode the synchronizeTexture:slice:level: command instead. You must execute one of these commands to update the texture’s data for the CPU; otherwise, the data is undefined for the CPU.

// Create a command buffer for GPU work.
id <MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];

// Encode a compute pass to modify the managed texture's data with the GPU.
id <MTLComputeCommandEncoder> computeCommandEncoder = [commandBuffer computeCommandEncoderWithDispatchType:MTLDispatchTypeSerial];
[computeCommandEncoder setComputePipelineState:computePipelineStateObject];
[computeCommandEncoder setTexture:_imageTexture
                          atIndex:0];
[computeCommandEncoder dispatchThreads:gridSize
                 threadsPerThreadgroup:threadgroupSize];
[computeCommandEncoder endEncoding];

// Synchronize the managed texture.
id <MTLBlitCommandEncoder> blitCommandEncoder = [commandBuffer blitCommandEncoder];
[blitCommandEncoder synchronizeResource:_imageTexture];
[blitCommandEncoder endEncoding];

// Add a completion handler and commit the command buffer.
[commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> cb) {
    // Managed texture is updated and synchronized.
}];
[commandBuffer commit];

See Also

Resources

Setting Resource Storage Modes

Set a storage mode that defines the memory location and access permissions of a resource.

Copying Data to a Private Resource

Use a blit command encoder to copy buffer or texture data to a private resource.

Transferring Data Between Connected GPUs

Use high-speed connections between GPUs to transfer data quickly.

Reducing the Memory Footprint of Metal Apps

Learn best practices for using memory efficiently in iOS and tvOS.

MTLResource

An allocation of memory that is accessible to a GPU.

MTLBlitCommandEncoder

An encoder that encodes memory copying, filtering, and fill commands.

MTLResourceStateCommandEncoder

An encoder that encodes commands that modify resource configurations.

Buffers

Create and manipulate unstructured GPU resources.

Textures

Create and manipulate structured GPU resources.

Indirect Command Buffers

Recoup encoding time by reusing commands, or create a GPU-driven rendering pipeline by generating commands on the GPU.

Heaps

Create a single allocation of memory from which you can suballocate resources.

Synchronization

Manage access to resources in your app to avoid data hazards.