Распределение MTLBuffer + синхронизация CPU/GPU

Я использую металлический шейдер производительности (MPSImageHistogram) вычислить что-то в MTLBuffer что я беру, выполняю вычисления, а затем показываю через MTKView, MTLBuffer вывод от шейдера небольшой (~4K байт). Так что я выделяю новый MTLBuffer объект для каждого прохода рендеринга, и существует не менее 30 рендеров в секунду для каждого видеокадра.

calculation = MPSImageHistogram(device: device, histogramInfo: &histogramInfo)
let bufferLength = calculation.histogramSize(forSourceFormat: MTLPixelFormat.bgra8Unorm)
let buffer = device.makeBuffer(length: bufferLength, options: .storageModeShared)
let commandBuffer = commandQueue?.makeCommandBuffer()

calculation.encode(to: commandBuffer!, sourceTexture: metalTexture!, histogram: buffer!, histogramOffset: 0)
commandBuffer?.commit()

commandBuffer?.addCompletedHandler({ (cmdBuffer) in
    let dataPtr = buffer!.contents().assumingMemoryBound(to: UInt32.self)
    ...
    ...

}

Мои вопросы -

  1. Можно ли создавать новый буфер каждый раз, используя device.makeBuffer(..)или лучше статически распределить несколько буферов и реализовать их повторное использование? Если повторное использование лучше, что мы делаем для синхронизации записи / чтения данных CPU/GPU в этих буферах?

  2. Еще один не связанный вопрос, можно ли рисовать MTKView результаты по неосновной теме? Или же MTKView ничья должна быть только в главном потоке (хотя я читаю, что Металл действительно многопоточный)?

2 ответа

Решение
  1. Выделения довольно дороги, поэтому я бы порекомендовал схему буферов многократного использования. Мой предпочтительный способ сделать это - сохранить изменяемый массив (очередь) буферов, поставить в очередь буфер после завершения буфера команд, который его использовал (или в вашем случае, после того, как вы прочитали результаты на ЦП), и выделить новый буфер, когда очередь пуста и вам нужно кодировать больше работы. В установившемся режиме вы обнаружите, что эта схема редко выделяет всего более 2-3 буферов, при условии, что ваши кадры завершаются своевременно. Если вам требуется, чтобы эта схема была поточно-ориентированной, вы можете защитить доступ к очереди с помощью мьютекса (реализованного с помощью dispatch_semaphore).

  2. Вы можете использовать другой поток для кодирования работы рендеринга, которая рисует в MTKViewДо тех пор, пока вы соблюдаете стандартные меры предосторожности при многопоточности. Помните, что хотя очереди команд являются поточно-ориентированными (в том смысле, что вы можете одновременно создавать и кодировать несколько буферов команд из одной и той же очереди), сами буферы команд, а кодировщики - нет. Я бы посоветовал вам профилировать однопоточный случай и вводить усложнение многопоточности, только если / когда это абсолютно необходимо.

  1. Если это небольшой объем данных (до 4K), вы можете использовать setBytes (): https://developer.apple.com/documentation/metal/mtlcomputecommandencoder/1443159-setbytes

Это может быть быстрее / лучше, чем выделять новый буфер каждый кадр. Вы также можете использовать подход с тройной буферизацией, чтобы доступ последовательных кадров к буферу не мешал. https://developer.apple.com/library/content/documentation/3DDrawing/Conceptual/MTLBestPracticesGuide/TripleBuffering.html

Из этого туториала Вы узнаете, как настроить тройную буферизацию для рендеринга: https://www.raywenderlich.com/146418/metal-tutorial-swift-3-part-3-adding-texture

Это на самом деле похоже на третью часть руководства, но именно эта часть показывает настройку тройной буферизации в разделе "Повторное использование унифицированных буферов".

Другие вопросы по тегам