2016-11-24 21 views
5

GPU'da görüntü işleme için Swift ve Metal kullanan macOS projesi üzerinde çalışıyorum. Geçtiğimiz hafta yeni 15 inç MacBook Pro'yu (geç 2016) aldım ve kodumda tuhaf bir şey fark ettim: bir dokuya yazması gereken çekirdekler öyle görünmüyordu ...Metal çekirdekler yeni MacBook Pro'da düzgün çalışmıyor (geç 2016) GPU'lar

Çok sonra kazma, problemin, hesaplama yapmak için hangi GPU'nun Metal (AMD Radeon Pro 455 veya Intel (HD) 530 Grafik 5) tarafından kullanıldığıyla ilişkili olduğunu buldum.

Radeon ve Intel GPU'larda (MTLCreateSystemDefaultDevice() Radeon varsayılan aygıtı döndürür iken) temsil eden cihazların bir dizisi MTLDevice kullanılarak MTLCopyAllDevices() döndürür başlatılması. Her durumda, kod Intel GPU ile beklendiği gibi çalışır, ancak Radeon GPU ile durum böyle değildir.

Size bir örnek göstereyim.

:

Bu çekirdek kullanmak sipariş
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); 
    } 

, ben bu kod parçası kullanın:

burada bir çıkış dokusuna bir giriş doku ve kopyalarını rengini alır basit bir çekirdek olduğunu başlatmak için
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))") 
     } 
    } 

Bu kodu let device = devices[0] (Radeon GPU) ile çalıştırdığınızda outTexture hiçbir zaman (varsayım) yazılmaz ve sonuç olarak outData değişmeden kalır. Öte yandan, bu kodu let device = devices[1] (Intel GPU) ile çalıştırırken, her şey beklendiği gibi çalışır ve outData, inData'daki değerler ile güncellenir.

cevap

8

GPU her ne zaman bir doku gibi MTLStorageModeManaged kaynağına yazıyorsa ve bu kaynağı CPU'dan okumak istiyorsanız (ör. getBytes() kullanarak), bir blit kodlayıcı kullanarak senkronize etmeniz gerektiğini düşünüyorum. commandBuffer.commit() çizgisinin üzerinde aşağıdaki koyarak deneyin:

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

GPU kaynak için sistem belleğini kullandığı için entegre bir GPU bu olmadan kaçabilirsin ve senkronize bir şey yok.

+0

Vay, bu eksik parçaydı, çok teşekkür ederim! Swift ve Metal'i geçtiğimiz birkaç ay boyunca paralel olarak öğrenmeye çalışıyorum ve kolay olduğunu söyleyemem. –