In pretty much every Metal tutorial out there, people use MTLVertexDescriptor like this: they create a struct like
struct Vertex {
var position: float3
var color: float3
}
then a vertex array and buffer:
let vertices: [Vertex] = ...
guard let vertexBuffer = device.makeBuffer(bytes: vertices,
length: MemoryLayout<Vertex>.stride * vertices.count,
options: []) else { ... }
This is all good, we have a buffer with interleaved position and color data. The problem is, when creating a vertex descriptor, they use MemoryLayout<float3>.stride as the offset for the second attribute:
let vertexDescriptor = MTLVertexDescriptor()
vertexDescriptor.attributes[0].format = .float3
vertexDescriptor.attributes[0].offset = 0
vertexDescriptor.attributes[0].bufferIndex = 0
vertexDescriptor.attributes[1].format = .float3
vertexDescriptor.attributes[1].offset = MemoryLayout<float3>.stride // <-- here!
vertexDescriptor.attributes[1].bufferIndex = 0
vertexDescriptor.layouts[0].stride = MemoryLayout<Vertex>.stride
This does not look correct to me. The code happens to work only because the stride of SIMD3<Float> (a.k.a. float3) matches the alignment of the fields in this particular struct.
But if we have something like this:
struct Vertex {
var attr0: Float
var attr1: Float
var attr2: SIMD3<Float>
}
then the naive approach of using stride won't work. Because of padding, attr2 does not start right after the two floats, at offset 2 * MemoryLayout<Float>.stride but at offset = 16.
So it seems to me that the only correct and robust way to set the vertex descriptor's offset is to use offset(of:), like this:
vertexDescriptor.attributes[2].offset = MemoryLayout<Vertex>.offset(of: \.attr2)!
Yet, I'm not able to find a single code example that does this. Am I missing something, or is everybody else just being careless with their offsets?