2016-11-24 7 views
5

私は、GPUでの画像処理にSwiftとMetalを使用するmacOSプロジェクトに取り組んでいます。先週、私は新しい15インチMacBook Pro(2016年末)を受け取り、コードで奇妙なことに気付きました。テクスチャに書き込むはずのカーネルはそうそうは思わなかった...新しいMacBook Pro(2016年後半)GPUでメタルカーネルが正常に動作しない

この問題は、計算を行うためにMetal(AMD Radeon Pro 455またはIntel(R)HD Graphics 530)がどのGPUを使用するかに関連していることが判明しました。

MTLCopyAllDevices()を使用してMTLDeviceを初期化すると、RadeonおよびIntel GPUを表すデバイスの配列が返されます(MTLCreateSystemDefaultDevice()は、Radeonのデフォルトデバイスを返します)。いずれにしても、コードはIntel GPUでは期待通りに動作しますが、Radeon GPUではそうではありません。

例を示しましょう。ここでは、開始するには

は、出力テクスチャに入力質感とコピーの色を取る簡単なカーネルです:

kernel void passthrough(texture2d<uint, access::read> inTexture [[texture(0)]], 
          texture2d<uint, access::write> outTexture [[texture(1)]], 
          uint2 gid [[thread_position_in_grid]]) 
    { 
     uint4 out = inTexture.read(gid); 
     outTexture.write(out, gid); 
    } 

私はこのカーネルを使用するために、私は、コードのこの部分を使用します

let devices = MTLCopyAllDevices() 
    for device in devices { 
     print(device.name!) // [0] -> "AMD Radeon Pro 455", [1] -> "Intel(R) HD Graphics 530" 
    } 

    let device = devices[0] 
    let library = device.newDefaultLibrary() 
    let commandQueue = device.makeCommandQueue() 

    let passthroughKernelFunction = library!.makeFunction(name: "passthrough") 

    let cps = try! device.makeComputePipelineState(function: passthroughKernelFunction!) 

    let commandBuffer = commandQueue.makeCommandBuffer() 
    let commandEncoder = commandBuffer.makeComputeCommandEncoder() 

    commandEncoder.setComputePipelineState(cps) 

    // Texture setup 
    let width = 16 
    let height = 16 
    let byteCount = height*width*4 
    let bytesPerRow = width*4 
    let region = MTLRegionMake2D(0, 0, width, height) 
    let textureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .rgba8Uint, width: width, height: height, mipmapped: false) 

    // inTexture 
    var inData = [UInt8](repeating: 255, count: Int(byteCount)) 
    let inTexture = device.makeTexture(descriptor: textureDescriptor) 
    inTexture.replace(region: region, mipmapLevel: 0, withBytes: &inData, bytesPerRow: bytesPerRow) 

    // outTexture 
    var outData = [UInt8](repeating: 128, count: Int(byteCount)) 
    let outTexture = device.makeTexture(descriptor: textureDescriptor) 
    outTexture.replace(region: region, mipmapLevel: 0, withBytes: &outData, bytesPerRow: bytesPerRow) 

    commandEncoder.setTexture(inTexture, at: 0) 
    commandEncoder.setTexture(outTexture, at: 1) 
    commandEncoder.dispatchThreadgroups(MTLSize(width: 1,height: 1,depth: 1), threadsPerThreadgroup: MTLSize(width: width, height: height, depth: 1)) 

    commandEncoder.endEncoding() 
    commandBuffer.commit() 
    commandBuffer.waitUntilCompleted() 

    // Get the data back from the GPU 
    outTexture.getBytes(&outData, bytesPerRow: bytesPerRow, from: region , mipmapLevel: 0) 

    // Validation 
    // outData should be exactly the same as inData 
    for (i,outElement) in outData.enumerated() { 
     if outElement != inData[i] { 
      print("Dest: \(outElement) != Src: \(inData[i]) at \(i))") 
     } 
    } 

このコードをlet device = devices[0](Radeon GPU)で実行すると、outTextureは決して(私の仮定に)書き込まれず、結果としてoutDataは変更されません。一方、このコードをlet device = devices[1](インテルGPU)で実行すると、すべてが正常に動作し、outDataがinDataの値で更新されます。

答えて

8

GPUがテクスチャなどのMTLStorageModeManagedリソースに書き込み、そのリソースをCPUから読み取ろうとすると(たとえばgetBytes()を使用)、blitエンコーダを使用して同期する必要があると思います。 GPUは、リソースのためのシステムメモリを使用していると同期するために何もないので、あなたが統合されたGPU上で、このずに逃げること

let blitEncoder = commandBuffer.makeBlitCommandEncoder() 
blitEncoder.synchronize(outTexture) 
blitEncoder.endEncoding() 

commandBuffer.commit()線の上に次のことを入れてみてください。

+0

うわー、それは欠けていた作品だった、ありがとう!私はここ数ヶ月間、スウィフトとメタルを並行して学びたいと思っていました。それは簡単だとは言えません。 –

関連する問題