MTLBuffer 将数据直接从设备复制到主机上的特定缓冲区

MTLBuffer copy data to a specific buffer on host directly from the device

问题描述

我的程序使用 MTLBuffer 在 GPU 上分配一些内存并用它进行计算。然后我需要将结果复制到主机上的特定位置。我在互联网上找到的所有解决方案都是先同步缓冲区,然后将其复制到我需要的地方。有没有办法将数据从 MTLBuffer 直接复制到主机缓冲区?


示例代码

当前实施:

void ComputeOnGPU(void* hostBuff, size_t buffSize)
{
    id<MTLBuffer> gpuBuff = [device newBufferWithLength: buffSize
                                    options: MTLResourceStorageModeManaged];
                                    
    //
    // Do stuff with the GPU buffer
    //

    id<MTLCommandQueue> commandQueue = [device newCommandQueue];
    id<MTLCommandBuffer> commandBuffer = [commandQueue commandBuffer];
    id<MTLBlitCommandEncoder> blitEncoder = [commandBuffer blitCommandEncoder];
    [blitEncoder synchronizeResource: gpuBuff];
    [blitEncoder endEncoding];
    [commandBuffer commit];
    [commandBuffer waitUntilCompleted];

    std::memcpy(hostBuff, [gpuBuff contents], buffSize);

    [gpuBuff setPurgeableState: MTLPurgeableStateEmpty];
    [gpuBuff release];
}

我在找什么:

void ComputeOnGPU(void* hostBuff, size_t buffSize)
{
    id<MTLBuffer> gpuBuff = [device newBufferWithLength: buffSize
                                    options: MTLResourceStorageModeManaged];
                                    
    //
    // Do stuff with the GPU buffer
    //

    id<MTLCommandQueue> commandQueue = [device newCommandQueue];
    id<MTLCommandBuffer> commandBuffer = [commandQueue commandBuffer];
    id<MTLBlitCommandEncoder> blitEncoder = [commandBuffer blitCommandEncoder];

    // Is there something like that?
    [blitEncoder copyMemoryFromBuffer: gpuBuff
                 toHost: hostBuff];

    [blitEncoder endEncoding];
    [commandBuffer commit];
    [commandBuffer waitUntilCompleted];

    [gpuBuff setPurgeableState: MTLPurgeableStateEmpty];
    [gpuBuff release];
}

附加信息

  1. 传输到 CPU 的缓冲区大小超过 4KB
  2. hostBuff 是一个通用缓冲区。它来自外部,所以我不能确定它是用 mmap().
  3. 分配的

回答您的直接问题:无法将 MTLBuffer 复制到主机指针中。我认为原因归结为如何为 CPU 和 GPU 映射虚拟内存。即使有一个,它也与托管缓冲区的工作方式相同,因为您编码到命令编码器中的命令是在 GPU 时间轴上执行的,这意味着即使在调用该命令方法之后,您也不会在主机指针,除非您在命令编码器上结束编码,然后 commit 命令缓冲区,然后等待它完成。然而,有一种在两个 MTLBuffer 之间复制的方法:-[MTLBlitCommandEncoder copyFromBuffer:sourceOffset:toBuffer:destinationOffset:size:].

但是如果您使用托管缓冲区,还有另一种方法可以做到这一点。您只需调用 -[MTLBlitCommandEncoder synchronizeResource:] 即可使 GPU 时间轴上的更改可见,并通过 -[MTLBuffer contents].

读取其中的内容

另外,如果你总是在CPU上回读,而且数据量比较小,不妨使用共享存储模式。

有两篇文章更深入地介绍了选择存储模式:在 iOS 和 tvOS 中选择资源存储模式 在 macOS 中选择资源存储模式