我正在使用Apple的新Metal框架编写iOS应用程序。我有一个Matrix4对象数组(参见Ray Wenderlich's tutorial),我需要通过MTLDevice.newBufferWithLength()方法传入着色器。 Matrix4对象正在利用Apple的GLKit(它包含一个GLKMatrix4对象)。
我正在利用GPU调用来实现实例化。
我稍后会将其更改为一个结构,其中包含每个实例的更多数据(仅限于Matrix4对象。
如何有效地将[Matrix4]对象数组复制到此缓冲区中?
有更好的方法吗?同样,我将扩展它以使用未来更多数据的结构。
以下是我的代码的一部分:
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。我这样做是因为我遇到了将数组视为连续内存集的问题。
谢谢!
答案 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)
的最新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代码中使用。