Распределение 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)
...
...
}
Мои вопросы -
Можно ли создавать новый буфер каждый раз, используя
device.makeBuffer(..)
или лучше статически распределить несколько буферов и реализовать их повторное использование? Если повторное использование лучше, что мы делаем для синхронизации записи / чтения данных CPU/GPU в этих буферах?Еще один не связанный вопрос, можно ли рисовать
MTKView
результаты по неосновной теме? Или жеMTKView
ничья должна быть только в главном потоке (хотя я читаю, что Металл действительно многопоточный)?
2 ответа
Выделения довольно дороги, поэтому я бы порекомендовал схему буферов многократного использования. Мой предпочтительный способ сделать это - сохранить изменяемый массив (очередь) буферов, поставить в очередь буфер после завершения буфера команд, который его использовал (или в вашем случае, после того, как вы прочитали результаты на ЦП), и выделить новый буфер, когда очередь пуста и вам нужно кодировать больше работы. В установившемся режиме вы обнаружите, что эта схема редко выделяет всего более 2-3 буферов, при условии, что ваши кадры завершаются своевременно. Если вам требуется, чтобы эта схема была поточно-ориентированной, вы можете защитить доступ к очереди с помощью мьютекса (реализованного с помощью
dispatch_semaphore
).Вы можете использовать другой поток для кодирования работы рендеринга, которая рисует в
MTKView
До тех пор, пока вы соблюдаете стандартные меры предосторожности при многопоточности. Помните, что хотя очереди команд являются поточно-ориентированными (в том смысле, что вы можете одновременно создавать и кодировать несколько буферов команд из одной и той же очереди), сами буферы команд, а кодировщики - нет. Я бы посоветовал вам профилировать однопоточный случай и вводить усложнение многопоточности, только если / когда это абсолютно необходимо.
- Если это небольшой объем данных (до 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
Это на самом деле похоже на третью часть руководства, но именно эта часть показывает настройку тройной буферизации в разделе "Повторное использование унифицированных буферов".