После моего недавнего эксперимента с системой частиц на основе 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 не только для игр, но и для довольно серьезной работы по симуляции.