「这是我参与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 同步托管资源内容时需要注意的事项,并且分别给出来了创建托管缓冲区和创建托管纹理的部分代码。