有效地将Swift数据复制到iOS Metal

时间:2015-08-31 18:26:37

标签: ios swift metal swift-array

我正在使用Apple的新Metal框架编写iOS应用程序。我有一个Matrix4对象数组(参见Ray Wenderlich's tutorial),我需要通过MTLDevice.newBufferWithLength()方法传入着色器。 Matrix4对象正在利用Apple的GLKit(它包含一个GLKMatrix4对象)。

我正在利用GPU调用来实现实例化。

我稍后会将其更改为一个结构,其中包含每个实例的更多数据(仅限于Matrix4对象。

  1. 如何有效地将[Matrix4]对象数组复制到此缓冲区中?

  2. 有更好的方法吗?同样,我将扩展它以使用未来更多数据的结构。

  3. 以下是我的代码的一部分:

    let sizeofMatrix4 = sizeof(Float) * Matrix4.numberofElements()
    
    // This returns an array of [Matrix4] objects.
    let boxArray = createBoxArray(parentModelViewMatrix)
    
    let sizeOfUniformBuffer = boxArray.count * sizeOfMatrix4
    var uniformBuffer = device.newBufferWithLength(sizeofUniformBuffer, options: .CPUCacheModeDefaultCache)
    let bufferPointer = uniformBuffer?.contents()
    
    // Ouch - way too slow.  How can I optimize?
    for i in 0..<boxArray.count
    {
        memcpy(bufferPointer! + (i * sizeOfMatrix4), boxArray[i].raw(), sizeOfMatrix4)
    }
    
    renderEncoder.setVertexBuffer(uniformBuffer, offset: 0, atIndex: 2)
    

    注意: boxArray [i] .raw()方法在Objective-C代码中定义为:

    - (void *)raw {
        return glkMatrix.m;
    }
    

    你可以看到我循环遍历每个数组对象,然后执行memcpy。我这样做是因为我遇到了将数组视为连续内存集的问题。

    谢谢!

3 个答案:

答案 0 :(得分:8)

Swift数组被认为是连续的内存,但你需要确保它真的是一个Swift数组而不是秘密的NSArray。如果您想完全确定,请使用ContiguousArray。这将确保连续的内存,即使其中的对象可以桥接到ObjC。如果您想要更好地控制内存,请查看ManagedBuffer

这样,您应该使用newBufferWithBytesNoCopy(length:options:deallocator)在现有内存周围创建一个MTL缓冲区。

答案 1 :(得分:4)

我使用一系列粒子将其传递给计算着色器。

简而言之,我定义了一些常量并声明了一些可变指针和一个可变缓冲区指针:

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)

...在着色器运行后,我将共享内存(particlesMemory)放回缓冲区指针:

    particlesVoidPtr = COpaquePointer(particlesMemory)
    particlesParticlePtr = UnsafeMutablePointer(particlesVoidPtr)

    particlesParticleBufferPtr = UnsafeMutableBufferPointer(start: particlesParticlePtr, count: particleCount)

这是at my GitHub repo here

的最新Swift 2.0版本

答案 2 :(得分:2)

显然,使用共享内存和MTLDevice.makeBuffer(bytesNoCopy:...)的目的是避免冗余内存副本。因此,理想情况下,我们会寻找一种设计,使我们可以在数据加载到MTLBuffer对象后轻松操作数据。

经过一段时间的研究后,我决定尝试创建一个半通用解决方案,以便简化页面对齐内存的分配,将内容加载到内存中,然后在共享内存中操作项目块。

我创建了一个名为PageAlignedArray的Swift数组实现,它与内置Swift数组的接口和功能相匹配,但总是驻留在页面对齐的内存中,因此可以很容易地成为{ {1}}。我还添加了一种方便的方法来直接将MTLBuffer转换为Metal缓冲区。

当然,您可以继续改变您的阵列,并且您的更新将自动提供给GPU共享内存架构。但是,请记住只要数组的长度发生变化,就必须重新生成PageAlignedArray对象。

以下是一个快速代码示例:

MTLBuffer

示例使用 var alignedArray : PageAlignedContiguousArray<matrix_double4x4> = [matrixTest, matrixTest] alignedArray.append(item) alignedArray.removeFirst() // Behaves just like a built-in array, with all convenience methods // When it's time to generate a Metal buffer: let testMetalBuffer = device?.makeBufferWithPageAlignedArray(alignedArray) ,但该数组应适用于任何Swift值类型。请注意,如果使用引用类型(例如任何类型的matrix_double4x4),则数组将包含指向元素的指针,因此无法从GPU代码中使用。