2016-11-24 65 views
5

Pracuję nad projektem MacOS, który wykorzystuje Swift i Metal do przetwarzania obrazu na GPU. W zeszłym tygodniu otrzymałem nowy 15-calowy MacBook Pro (koniec 2016 r.) I zauważyłem coś dziwnego w moim kodzie: jądra, które miały pisać na fakturze, nie zdawały się tego robić ...Jądra metalu nie zachowują się poprawnie na nowych procesorach graficznych MacBook Pro (koniec 2016 r.)

Po wielu kopanie, stwierdziłem, że problem wiąże się z wykorzystywaniem GPU przez Metal (AMD Radeon Pro 455 lub Intel (R) HD Graphics 530) do wykonywania obliczeń.

Inicjowanie MTLDevice przy użyciu MTLCopyAllDevices() zwraca tablicę urządzeń reprezentujących procesory graficzne Radeon i Intel (podczas gdy MTLCreateSystemDefaultDevice() zwraca domyślne urządzenie, którym jest Radeon). W każdym razie kod działa zgodnie z oczekiwaniami z procesorem Intel GPU, ale nie jest tak w przypadku procesora graficznego Radeon.

Pozwól, że pokażę ci przykład.

Aby rozpocząć, tutaj jest prosty jądro że bierze fakturę wejściowy i kopie jego kolor do tekstury wyjściowa:

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

zamówić używać tego jądra, używam ten kawałek kodu:

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))") 
     } 
    } 

Po uruchomieniu tego kodu z let device = devices[0] (GPU Radeon), outTexture nigdy nie jest zapisywane (moje przypuszczenie), w wyniku czego outData pozostaje niezmieniona. Z drugiej strony, podczas uruchamiania tego kodu z let device = devices[1] (Intel GPU) wszystko działa zgodnie z oczekiwaniami, a dane wyjściowe są aktualizowane wartościami w inData.

Odpowiedz

8

Myślę, że zawsze, gdy GPU zapisuje do zasobu MTLStorageModeManaged, takiego jak tekstura, a następnie chcesz odczytać ten zasób z procesora (na przykład przy użyciu getBytes()), musisz zsynchronizować go za pomocą kodera blit. Spróbuj umieścić następujące powyżej linii commandBuffer.commit():

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

można uciec bez tego na zintegrowanym GPU, ponieważ GPU wykorzystuje pamięć systemową dla zasobu i nie ma nic do synchronizacji.

+0

Wow, to był brakujący element, dziękuję bardzo !!! Od kilku miesięcy próbuję nauczyć się Swift i Metalu i nie mogę powiedzieć, że było to łatwe. –