6

我正在使用 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。我这样做是因为我在将数组视为一组连续的内存时遇到了问题。

谢谢!

4

3 回答 3

8

Swift Array 承诺是连续内存,但您需要确保它确实是 Swift Array 而不是秘密的 NSArray。如果您想完全确定,请使用 ContiguousArray。即使其中的对象可以桥接到 ObjC,这也将确保连续的内存。如果您想更好地控制内存,请查看ManagedBuffer.

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

于 2015-08-31T19:21:48.917 回答
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)

在我的 GitHub 存储库中有一个最新的 Swift 2.0 版本

于 2015-09-04T10:45:15.773 回答
2

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

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

我创建了一个 Swift 数组实现PageAlignedArray,称为匹配内置 Swift 数组的接口和功能,但始终驻留在页面对齐的内存中,因此可以很容易地制作成MTLBuffer. 我还添加了一种方便的方法来直接转换PageAlignedArray为 Metal 缓冲区。

当然,您可以在之后继续改变您的阵列,并且您的更新将自动提供给 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 值类型。请注意,如果您使用引用类型(例如任何类型的class),该数组将包含指向您的元素的指针,因此无法从您的 GPU 代码中使用。

于 2016-10-15T04:57:17.257 回答