2016-11-24 14 views
5

Tôi đang làm việc trên dự án macOS sử dụng Swift and Metal để xử lý hình ảnh trên GPU. Tuần trước, tôi đã nhận được MacBook Pro 15 inch mới (cuối năm 2016) và nhận thấy có điều gì đó lạ với mã của tôi: hạt nhân được cho là viết lên một kết cấu dường như không ...Hạt nhân kim loại không hoạt động đúng trên MacBook Pro mới (cuối năm 2016) GPU

Sau rất nhiều đào, tôi thấy rằng vấn đề có liên quan đến GPU nào được sử dụng bởi Metal (AMD Radeon Pro 455 hoặc Intel (R) HD Graphics 530) để thực hiện tính toán.

Khởi tạo MTLDevice sử dụng MTLCopyAllDevices() trả về một mảng thiết bị đại diện cho Radeon và GPU Intel (trong khi MTLCreateSystemDefaultDevice() trả về thiết bị mặc định là Radeon). Trong mọi trường hợp, mã hoạt động như mong đợi với GPU Intel nhưng đó không phải là trường hợp với GPU Radeon.

Hãy để tôi chỉ cho bạn một ví dụ.

Để bắt đầu, đây là một hạt nhân đơn giản mà phải mất một kết cấu đầu vào và bản màu sắc của nó đến một kết cấu sản lượng:

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

Tôi ra lệnh sử dụng hạt nhân này, tôi sử dụng đoạn mã này:

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

Khi chạy mã này với let device = devices[0] (Radeon GPU), outTexture không bao giờ được ghi vào (giả định của tôi) và kết quả là Dữ liệu ngoài không thay đổi. Mặt khác, khi chạy mã này với let device = devices[1] (Intel GPU), mọi thứ hoạt động như mong đợi và outData được cập nhật với các giá trị trong inData.

Trả lời

8

Tôi nghĩ rằng bất cứ khi nào GPU ghi vào tài nguyên MTLStorageModeManaged chẳng hạn như kết cấu và sau đó bạn muốn đọc tài nguyên đó từ CPU (ví dụ: sử dụng getBytes()), bạn cần phải đồng bộ hóa nó bằng bộ mã hóa blit. Hãy thử đặt sau trên đường commandBuffer.commit():

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

Bạn có thể lấy đi mà không này trên một GPU tích hợp vì GPU đang sử dụng bộ nhớ hệ thống cho tài nguyên và không có gì để đồng bộ hóa là.

+0

Wow, đó là phần còn thiếu, cảm ơn bạn rất nhiều !!! Tôi đã cố gắng học Swift và Metal song song trong vài tháng qua, và tôi không thể nói nó dễ dàng. –

Các vấn đề liên quan