Статьи

1 000 000 частиц на iPad!

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