Metal 框架之同步托管资源

140 阅读3分钟

「这是我参与11月更文挑战的第21天,活动详情查看:2021最后一次更文挑战

概述

托管存储(MTLStorageMode.managed)模式只在 macOS 下有效,非 macOS 下无此存储模式,请移步阅读《 Metal 框架之资源存储模式 》

该模式下的资源,CPU 和 GPU 都可以访问。 Metal 为每个处理器优化托管资源,但给定的资源内容有更改时,需要同步这些资源。同步托管资源后,CPU 和 GPU 都可以访问更新后的资源数据。

使用 CPU 或 GPU 修改托管资源后,必须先同步该资源,然后其他处理器才可以修改该资源。即使资源的 CPU 修改部分和 GPU 修改部分不重叠,也必须遵循此规则。当同步托管资源时,Metal 可能会根据需要同步比指定部分更多的资源。此外,如果 App 或系统受内存限制,Metal 可能会自动在 CPU 和 GPU 之间复制资源数据。


注释:

在离散内存模型中,同步速度受 PCIe 带宽限制。在统一内存模型中,Metal 可能会完全忽略同步调用,因为它只为资源创建单个内存分配。
有关 macOS 内存模型和托管资源的更多信息,请参阅 《 Metal 框架之资源存储模式 》 中的 “在 macOS 中选择资源存储模式“ 部分章节。

同步托管缓冲区

首先创建一个托管缓冲区


// Matrix buffer and matrix data structure.

id <MTLBuffer> _matrixBuffer;

typedef struct

{

    matrix_float4x4 modelMatrix;

    matrix_float4x4 viewMatrix;

    matrix_float4x4 projectionMatrix;

} MatrixData;

接下来,通过 CPU 修改缓冲区的数据。


// Modify the managed buffer's data with the CPU.

MatrixData *matrixData = (MatrixData*)_matrixBuffer.contents;

matrixData->modelMatrix = updatedModelMatrix;

完成一次 CPU 修改后,调用 didModifyRange: 方法,更新特定范围的数据并保持缓冲区同步。必须调用此方法来更新 GPU 的缓冲区数据;否则,数据对于 GPU 来说是未定义的。


// Synchronize the managed buffer.

[_matrixBuffer didModifyRange:NSMakeRange(0, sizeof(matrixData->modelMatrix))];

对 GPU 修改编码后,需要调用 synchronize: 命令,来更新整个缓冲区并保持同步。必须执行此命令才能为 CPU 更新缓冲区数据;否则,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];

同步托管纹理

首先创建一个托管纹理


// 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];

要执行 CPU 修改并同时将更改通知 Metal,需要调用 replaceRegion:mipmapLevel:withBytes:bytesPerRow: 方法,更新特定区域的数据并保持纹理同步。要更新特定的纹理切片,需调用 replaceRegion:mipmapLevel:slice:withBytes:bytesPerRow:bytesPerImage: 方法。必须调用这些方法之一来更新 GPU 的纹理数据;否则,数据对于 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];

对 GPU 修改编码后, 需要调用 synchronize: 命令,来更新整个纹理并保持同步。要更新特定的纹理切片或 mipmap 级别,需要调用 synchronizeTexture:slice:level: 命令。必须执行这些命令之一来更新 CPU 的纹理数据;否则,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];

总结

本文介绍了使用 CPU 或 GPU 同步托管资源内容时需要注意的事项,并且分别给出来了创建托管缓冲区和创建托管纹理的部分代码。