Effectively copy Swift Array to memory buffer for iOS Metal

I am writing an iOS application using Apple's new metal structure. I have an array of Matrix4 objects (see Ray Wenderlich tutorial ), which I need to pass to the shader via the MTLDevice.newBufferWithLength () method. The Matrix4 object uses Apple GLKit (it contains the GLKMatrix4 object).

I use instancing with GPU calls.

I will later change this to a structure that contains more data per instance (besides the Matrix4 object.

  • How can I efficiently copy an array of [Matrix4] objects into this buffer?

  • Is there a better way to do this? Again, I will expand this to use a structure with more data in the future.

Below is a subset of my code:

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) 

Note: The boxArray [i] .raw () method is defined like this in Objective-C code:

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

You can see that I look at each object in the array and then execute memcpy. I did this because I was having problems processing the array as a continuous set of memory.

Thanks!

+5
ios swift metal swift-array
source share
3 answers

Swift Array promises to be continuous memory, but you need to make sure that it is indeed a Swift Array and not a secret NSArray. If you want to be absolutely sure, use ContiguousArray. This will provide continuous memory, even if the objects in it can be connected to ObjC. If you want even more control over your memory, check out the ManagedBuffer .

In doing so, you should use newBufferWithBytesNoCopy(length:options:deallocator) to create an MTL buffer around existing memory.

+7
source share

I did this with an array of particles, which I pass to the computational shader.

In a nutshell, I define some constants and declare some mutable pointers and a mutable buffer pointer:

 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>! 

When I set up the particles, I fill in the pointers and use posix_memalign () to allocate memory:

  posix_memalign(&particlesMemory, alignment, particlesMemoryByteSize) particlesVoidPtr = COpaquePointer(particlesMemory) particlesParticlePtr = UnsafeMutablePointer<Particle>(particlesVoidPtr) particlesParticleBufferPtr = UnsafeMutableBufferPointer(start: particlesParticlePtr, count: particleCount) 

The cycle for filling particles is slightly different - I now iterate over the buffer pointer:

  for index in particlesParticleBufferPtr.startIndex ..< particlesParticleBufferPtr.endIndex { [...] let particle = Particle(positionX: positionX, positionY: positionY, velocityX: velocityX, velocityY: velocityY) particlesParticleBufferPtr[index] = particle } 

Inside the applyShader () function, I create a copy of the memory that is used both in the input and output buffers:

  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) 

... and after running the shader, I put the shared memory (particleMemory) back in the buffer pointer:

  particlesVoidPtr = COpaquePointer(particlesMemory) particlesParticlePtr = UnsafeMutablePointer(particlesVoidPtr) particlesParticleBufferPtr = UnsafeMutableBufferPointer(start: particlesParticlePtr, count: particleCount) 

There is an updated version of Swift 2.0 of this in my GitHub repository here

+4
source share

Obviously, the point of using shared memory and MTLDevice.makeBuffer(bytesNoCopy:...) is to avoid redundant copies of memory. Therefore, ideally, we are looking for a design that allows us to easily manipulate data after it has already been loaded into the MTLBuffer object.

After some study of this issue, I decided to try to create a semi-general solution that allows to simplify the allocation of page-aligned memory, loading contents into this memory and then managing your items in this shared memory block.

I created an implementation of the Swift array called PageAlignedArray , which corresponds to the interface and functionality of the Swift built-in array, but is always located on the page-aligned memory, so it can be easily done in MTLBuffer . I also added a convenient method for directly converting PageAlignedArray to a metal buffer.

Of course, you can continue to mutate your array, and your updates will automatically be available to the GPU with the kindly provided shared memory architecture. However, keep in mind that you must regenerate your MTLBuffer object whenever the length of the array changes.

Here is a quick code example:

  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 time to generate a Metal buffer: let testMetalBuffer = device?.makeBufferWithPageAlignedArray(alignedArray) 

The example uses matrix_double4x4 , but the array should work for any type of Swift value. Please note that if you use a reference type (for example, any class type), the array will contain pointers to your elements and therefore will not be used with your GPU.

+2
source share

All Articles