После моего недавнего эксперимента с системой частиц на основе GPU на основе Metal Framework я продвинулся на шаг вперед и смог получить систему с миллионами частиц, работающую со скоростью 20 кадров в секунду (или более 30 кадров в секунду, если я отключаю составной шейдер свечения) на iPad Air 2 На самом деле, поскольку новый метод требует наборов данных длины степени двух, у меня на самом деле есть система из 1 048 576 частиц, но что за сорок восемь тысяч между друзьями?
Вот как выглядит миллион красных, зеленых и синих частиц. Это запись в реальном времени с моего iPad Air 2 в реальном времени:
Техника , которую я уже использовал исходит от этого удивительно занижен блоге от memkite.com . В нем Amund Tveit обсуждает способ обмена данными между процессором и графическим процессором. Используя эту технику, я больше не записываю данные частиц из металла в Swift, что дает значительное улучшение скорости.
В двух словах, я определяю некоторые константы и объявляю несколько изменяемых указателей и указатель изменяемого буфера:
let particleCount: Int = 1048576
var particlesMemory:UnsafeMutablePointer<Void> = nil
let alignment:UInt = 0x4000
let particlesMemoryByteSize:UInt = UInt(1048576) * UInt(sizeof(Particle))
var particlesVoidPtr: COpaquePointer!
var particlesParticlePtr: UnsafeMutablePointer<Particle>!
var particlesParticleBufferPtr: UnsafeMutableBufferPointer<Particle>!
Когда я настраиваю частицы, я заполняю указатели и использую posix_memalign () для выделения памяти:
posix_memalign(&particlesMemory, alignment, particlesMemoryByteSize)
particlesVoidPtr = COpaquePointer(particlesMemory)
particlesParticlePtr = UnsafeMutablePointer<Particle>(particlesVoidPtr)
particlesParticleBufferPtr = UnsafeMutableBufferPointer(start: particlesParticlePtr, count: particleCount)
Цикл для заполнения частиц немного отличается — я теперь зацикливаюсь на указателе буфера:
for index in particlesParticleBufferPtr.startIndex ..< particlesParticleBufferPtr.endIndex
{
[...]
let particle = Particle(positionX: positionX, positionY: positionY, velocityX: velocityX, velocityY: velocityY)
particlesParticleBufferPtr[index] = particle
}
Внутри функции applyShader () я создаю копию памяти, которая используется в качестве буфера ввода и вывода:
let particlesBufferNoCopy = device.newBufferWithBytesNoCopy(particlesMemory, length: Int(particlesMemoryByteSize),
options: nil, deallocator: nil)
commandEncoder.setBuffer(particlesBufferNoCopy, offset: 0, atIndex: 0)
commandEncoder.setBuffer(particlesBufferNoCopy, offset: 0, atIndex: 1)
… и после запуска шейдера я помещаю разделяемую память ( частицMemory) обратно в указатель буфера:
particlesVoidPtr = COpaquePointer(particlesMemory)
particlesParticlePtr = UnsafeMutablePointer(particlesVoidPtr)
particlesParticleBufferPtr = UnsafeMutableBufferPointer(start: particlesParticlePtr, count: particleCount)
Для лучшего объяснения я бы посоветовал взглянуть на оригинальное сообщение в блоге на memkite.com .
Я создал новую ветку, которая использует эту технику , доступ к которой вы можете получить здесь . Оригинальная ветвь , которая использует простой массив по — прежнему доступна для сравнения и контраста.
Невероятно, но эта симуляция работает на скорости почти 17 кадров в секунду на моем iPhone 6 и показывает потенциал Metal Framework в сочетании с Swift не только для игр, но и для довольно серьезной работы по симуляции.