有效地将Swift Array复制到iOS Metal的内存缓冲区

我正在使用Apple的新Metal框架编写iOS应用程序。 我有一个Matrix4对象的数组(请参阅Ray Wenderlich的教程 ),我需要通过MTLDevice.newBufferWithLength()方法将其传递给着色器。 Matrix4对象利用了Apple的GLKit(它包含一个GLKMatrix4对象)。

我正在利用GPU调用的实例。

稍后我会将其更改为一个包含每个实例更多数据的结构(超出了Matrix4对象的范围)。

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

  2. 有一个更好的方法吗? 再次,我将展开这个使用未来更多数据的结构。

以下是我的代码的一个子集:

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。 我之所以这么做,是因为我遇到了将arrays视为一组连续内存的问题。

谢谢!

一个Swift数组被承诺是连续的内存,但是你需要确定它确实是一个Swift数组而不是暗中的NSArray。 如果你想完全确定,使用连续数组。 这将确保连续的内存,即使其中的对象可桥接到ObjC。 如果你想更多的控制内存,看看ManagedBuffer

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

我已经用一系列的粒子来完成这个工作,我将这些粒子传递给一个计算着色器。

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

 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()函数内部,我创build了用作input和输出缓冲区的内存副本:

  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) 

有一个最新的Swift 2.0版本在我的GitHub回购这里

显然使用共享内存和MTLDevice.makeBuffer(bytesNoCopy:...)是为了避免多余的内存拷贝。 因此,理想情况下,我们寻找一种devise,使得我们可以在数据加载到MTLBuffer对象后方便地操作数据。

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

我创build了一个称为PageAlignedArray的Swift数组实现,它匹配内置Swift数组的接口和function,但总是驻留在页面alignment的内存中,因此可以很容易地制作成MTLBuffer 。 我还添加了一个便利的方法,直接将PageAlignedArray转换为金属缓冲区。

当然,之后你可以继续改变你的数组,你的更新将自动提供给GPU的共享内存体系结构。 但是,请记住, 只要数组长度发生更改就必须重新生成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) 

该示例使用了matrix_double4x4 ,但该数组应适用于任何Swift值types。 请注意,如果您使用引用types(例如任何class ),则数组将包含指向您的元素的指针,因此将无法在您的GPU代码中使用。