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.
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. –