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

Time:03-22

Problem description

My program uses MTLBuffer to allocate some memory on the GPU and do computations with it. Then I need to copy the result to a specific place on the host. All solutions I found on the internet involve synchronizing the buffer firstly and then copying it to the place I need. Is there a way to copy data from MTLBuffer directly to the host buffer?


Sample code

Current implementation:

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

What I'm looking for:

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

Additional info

  1. The size of the buffer transferred to the CPU is about 80 MB in most cases.
  2. hostBuff points to a piece of 16-byte aligned memory.
  3. hostBuff is a generic buffer. It comes from the outside, so I can't be sure that it was allocated with mmap().

CodePudding user response:

Answering your direct question: there's no way to copy MTLBuffer into a host pointer. I think the reason comes down to how virtual memory is mapped for CPU and GPU. And even if there was one, it would be identical to how managed buffers work, because the command you encode into a command encoder is executed on GPU timeline, which means even after calling that command method, you wouldn't see the result in the host pointer unless you end the encoding on a command encoder and then commit the command buffer and then wait for it to complete. There is however a method for copying between two MTLBuffers: -[MTLBlitCommandEncoder copyFromBuffer:sourceOffset:toBuffer:destinationOffset:size:].

But if you are using managed buffers, there's another way to do it. You can just call -[MTLBlitCommandEncoder synchronizeResource:] to make the changed made on GPU timeline visible and read the contents of it through -[MTLBuffer contents].

Also, if you are always reading it back on the CPU and the amount of data is relatively small, you might just as well be using shared storage mode.

There are two articles that go more in depth on chosing a storage mode: Choosing a Resource Storage Mode in iOS and tvOS and Choosing a Resource Storage Mode in macOS

  • Related