Metal Shader language : float2 screws everything up.

I have hit a brick wall. I cannot explain the behavior I am seeing. What it boils down to is: SIMD2<Float> is incompatible with float2.

I am working through tutorials trying to get a handle on Metal. All the tutorials are woefully out of date, so no answers there. But I did find somewhere that I should be using SIMD4<Float> for my point data. Great. I was able to bridge that gap. but I added uv maps using a float2. and the result was a portion of my vertex data gets lost somewhere. The buffer is smaller, by about 25%.

and I cannot figure it out. help?

in anticipation of pertinent questions:

the vertex struct:
Code Block
struct vertex {
    var position : SIMD4<Float> = SIMD4<Float>(x: 0.0,y: 0.0,z: 0.0,w: 0.0)
    var uv : SIMD2<Float> = SIMD2<Float>(x:0.0 ,y:0.0)
    init(x: Float ,y: Float, z: Float) {
        self.position.x = x
        self.position.y = y
        self.position.z = z
        self.position.w = 1.0
        uv = SIMD2<Float>(0.5 ,0.5)
    }
    init( x: Float, y: Float, z: Float, u: Float, v: Float ){
        position = SIMD4<Float>(x,y,z,1.0)
        uv = SIMD2<Float>(u ,v)
    }
}

if I change the SIMD2 to an SIMD4... the entire thing works.

how I measure the size of my buffer:
Code Block
let matrixSize = MemoryLayout.size(ofValue:vertexField.vertices[0])
        let vertsSize = vertexField.vertices.count * matrixSize
        // now we make the vertex buffers.
        for i in 0..<maxFramesInFlight{
            if let aVertBuff = device.makeBuffer(bytes: vertexField.vertices, length: vertsSize, options: .storageModeManaged){
                aVertBuff.label = "verts buffer \(i)"
                vertexBuffs += [aVertBuff]
            }
        }

vertexField is a struct containing an array of vertices, and an array of offsets and lengths for the various meshes I make from those vertices. Eventually I'll figure out the elegant way of doing that.

the buffer winds up having 64 elements, out of 84. The fragment shader, assumes 84 entries, and winds up setting those values to 0,0,0,0 0,0.

I sent a single SIMD2<Float> variable to the Fragment shader, and examined it. It was correct.

any ideas are welcome. I am up the river without a paddle on this one.



Replies

Can you try changing the calculation of matrixSize as follows?
Code Block
let matrixSize = MemoryLayout.stride(ofValue:vertexField.vertices[0])


Your problem seems to be an alignment issue.
Swift does not guarantee any particular memory layout for structures or arrays, but Metal buffers depend entirely on a given memory layout to feed data to shaders. (OOPer's suggestion would help Metal "understand" the array layout, but not the struct layout.)

To layout memory for a Metal buffer, you should create a struct in a C header and include that in your Objective-C bridging header. Swift will enforce the memory layout as defined by C.

As a bonus, you can also include the C header with the struct in your .metal code and use the struct as a parameter to your shaders, which will guarantee that your layout matches even if you change the struct.


Alignment really can cause issues when setting up buffers. Also, as was mentioned earlier, using a bridging header can give you a lot of control over the sizes of structs that Metal sees and that Swift can also write to.

There is a great Swift sample project from Marius Horga which explores one way to set up some triangle meshes. He recreates a project from Apple which was written in Objective C.

Metal has really developed since then and you can do a phenomenal amount of path tracing with very few pipelines.