I would like to use a metal computational shader to calculate some positions that are then fed into a metal shader. Sounds straightforward, but it's hard for me to get MTLBuffer data in a metal-based SCNProgram.
The core of the calculation is as follows, in this far-fetched example, it takes three three-dimensional vectors (in both buffers).
kernel void doSimple(const device float3 *inVector [[ buffer(0) ]], device float3 *outVector [[ buffer(1) ]], uint id [[ thread_position_in_grid ]]) { float yDisplacement = 0; . . . . outVector[id] = float3( inVector[id].x, inVector[id].y + yDisplacement, inVector[id].z); }
This kernel function runs every frame in the method - renderer:willRenderScene:atTime: my SCNSceneRendererDelegate . There are two buffers, and they switch after each frame.
Buffers are created as follows:
func setupBuffers() { positions = [vector_float3(0,0,0), vector_float3(1,0,0), vector_float3(2,0,0)] let bufferSize = sizeof(vector_float3) * positions.count
And the computational shader starts using the following (in willRenderScene func);
let computeCommandBuffer = commandQueue.commandBuffer() let computeCommandEncoder = computeCommandBuffer.computeCommandEncoder() computeCommandEncoder.setComputePipelineState(pipelineState) computeCommandEncoder.setBuffer(buffer1, offset: 0, atIndex: 0) computeCommandEncoder.setBuffer(buffer2, offset: 0, atIndex: 1) computeCommandEncoder.dispatchThreadgroups(numThreadgroups, threadsPerThreadgroup: threadsPerGroup) computeCommandEncoder.endEncoding() computeCommandBuffer.commit() computeCommandBuffer.waitUntilCompleted() let bufferSize = positions.count*sizeof(vector_float3) var data = NSData(bytesNoCopy: buffer2.contents(), length: bufferSize, freeWhenDone: false) var resultArray = [vector_float3](count: positions.count, repeatedValue: vector_float3(0,0,0)) data.getBytes(&resultArray, length:bufferSize) for outPos in resultArray { print(outPos.x, ", ", outPos.y, ", ", outPos.z) }
This works, and I see that my computational shader updates the y coordinate for each vector in the array.
This scene consists of three spheres evenly distributed. The vertex shader simply takes the position calculated in the computational shader and adds it to each vertex position (well, the y-component anyway). I give each sphere an index, the vertex shader uses this index to pull the corresponding position from my computable array.
The metal vertex function is shown below, it is referenced by a SCNProgram and is installed in the material of each sphere.
vertex SimpleVertex simpleVertex(SimpleVertexInput in [[ stage_in ]], constant SCNSceneBuffer& scn_frame [[buffer(0)]], constant MyNodeBuffer& scn_node [[buffer(1)]], constant MyPositions &myPos [[buffer(2)]], constant uint &index [[buffer(3)]] ) { SimpleVertex vert; float3 posOffset = myPos.positions[index]; float3 pos = float3(in.position.x, in.position.y + posOffset.y, in.position.z); vert.position = scn_node.modelViewProjectionTransform * float4(pos,1.0); return vert; }
MyPositions is a simple structure containing an array of float3s.
struct MyPositions { float3 positions[3]; };
I have no problem transferring data to the vertex shader using the setValue method for each spherical material, as shown below (also in the willRenderScene method). Everything works as expected (three spheres move up).
var i0:UInt32 = 0 let index0 = NSData(bytes: &i0, length: sizeof(UInt32)) sphere1Mat.setValue(index0, forKey: "index") sphere1Mat.setValue(data, forKey: "myPos")
BUT this requires that the data be copied from the GPU to the CPU on the GPU and this is really what I would prefer to avoid. So my question is ... How to pass MTLBuffer to SCNProgram?
Tried the following in willRenderScene but got nothing but EXEC_BAD...
let renderCommandEncoder = renderer.currentRenderCommandEncoder! renderCommandEncoder.setVertexBuffer(buffer2, offset: 0, atIndex: 2) renderCommandEncoder.endEncoding()
Completed an example on GitHub .
Thanks for reading, struggling with this. The workaround is to use MTLTexture instead of MTLBuffer, as I was able to pass them to SCNProgram through a diffuse mat.