Chapter 5 Starter Project Stride number when setting normals

Hi,

Just looking at Chapter 5 starter project and I am at the point where I am adding a vertex descriptor attribute for normals on page 120. The code is as follows:

vertexDescriptor.attributes[1] =
            MDLVertexAttribute(name: MDLVertexAttributeNormal,
            format: .float3, offset: 12, bufferIndex: 0)

I am a little bit confused by setting the offset for normals. Why is it set to 12? I know the book says that is the length of the position attribute, but should not it be rather set to

MemoryLayout<float3>.stride

since position is of format .float3?

I am confused as to why is it hardcoded like that? Since in the examples before when setting the MTLVertexDescriptor the MemoryLayout was always used for strides.

Then later it says to set the layout to 24.

vertexDescriptor.layouts[0] = MDLVertexBufferLayout(stride: 24)

but would it not be better to do :

vertexDescriptor.layouts[0] = MDLVertexBufferLayout(stride: MemoryLayout<float3>.stride + MemoryLayout<float3>stride)

since position and normal attributes are both of float3 format?

Cheers

3 Likes

@caroline Can you please help with this when you get a chance? Thank you - much appreciated! :]

@henrygorner - yes that would probably be better. I usually do as you suggest - I’m not sure what was going through my head at the time.

2 Likes

Friendly suggestion to update in the new version of the book. I checked MemoryLayout.stride on a playground and received a stride of 16. If the stride is not 12, I don’t think I understand why the code in the book works fine with a stride of 12.

This is a fantastic book FYI.

Rethinking my answer above:

The vertex descriptor describes how we read in the data.

We’re reading in the position as a MTLVertexFormat.float3 , which is a vector with three components, each of which is a 32-bit floating-point type. (See docs -Apple Developer Documentation)

Three floats would be 4 + 4 + 4 = 12 bytes long. This is different from the stride of a float3.

This is how we’re storing it in the Metal buffer. You can print out the length of mdlMesh.vertexBuffers[0] at the end of Model’s init to test this. You can change the stride of the vertex descriptor to 16 / 32 or 32 / 64. You will see that the length of the Metal buffer gets bigger when you demand more space for the data.

Fortunately, with the [[stage_in]] attribute, we don’t have to match the format exactly, as Metal will work its magic to match our packed floats in the Metal buffer with the float4 that the shader expects.

Try this. Replace vertex_main with:

struct VIn {
  float3 position;
  float3 normal;
};

vertex VertexOut vertex_main(device const VIn *vertices [[buffer(0)]],
                             constant Uniforms &uniforms [[ buffer(1) ]],
                             uint vid [[vertex_id]])
{
  VertexOut out;
  float4 position = float4(vertices[vid].position, 1);
  float3 normal = float3(vertices[vid].normal);
  out.position = uniforms.projectionMatrix * uniforms.viewMatrix
  * uniforms.modelMatrix * position;
  out.worldPosition = (uniforms.modelMatrix * position).xyz;
  out.worldNormal = uniforms.normalMatrix * normal;
  return out;
}

This isn’t using the vertex descriptor [[stage_in]] attribute - it’s using the metal buffer directly.

You’ve specified float3s in the Vertex In structure ( Vin ). Now the shader is expecting two float3s with strides of 16 each. When you run the app, the vertices are all stuffed, because the stride in the buffer is 24 (position 12 + normal 12).

Change Vin to:

struct VIn {
  packed_float3 position;
  packed_float3 normal;
};

Now the shader is expecting a packed type that has a stride of 12. When you run the app now, the train will render correctly.

I hope that answers the question?

TL;DR - the data in the buffer is “packed floats” :slight_smile:

1 Like

And thank you, @lducot2 :blush: - I’m glad you’re enjoying the book :slight_smile:

I have been experimenting with raw pointers in Swift after watching the recent WWDC videos on the topic. My experiments reminded me of this post and helped me grasp this a bit better so I thought I would share.

I had changed my code to use MemoryLayout<float3>.stride and while everything worked fine, I have come to realize why the stride of a float or hardcoded 12 is technically correct.

The metal buffers seem to be storing the information for a float3 as three sequential float values and not as a simd type. I’m not sure why the float3 type requires an alignment of 16; I would have thought the alignment should also be three sequential float strides. One of the reasons I struggled with this is because the Metal Shading Language Specification also defines a float3 and a float4 type with an alignment of 16.

I posted some playground code at the end for anyone interested. I used the Toy Drummer model on Apple’s website.

Warning: The code outputs all of the vertex coordinates in the model. I plan to compare to the information in the frame capture debugger later to see if the vertex values are valid. For now, I think the main take away is that the number of vertices the code calculates and the vertex count in the buffer itself agree. I also tested with an index buffer and a uv buffer and found everything still agrees.

var vd = MDLVertexDescriptor()
var position = MDLVertexAttribute(name: MDLVertexAttributePosition,
                                  format: .float3,
                                  offset: 0,
                                  bufferIndex: 0)

var layout1 = MDLVertexBufferLayout(stride: 12)

vd.attributes = [position]
vd.layouts = [layout1]

// Note: Do not use MDLAsset(url:vertexDescriptor:bufferAllocator:) --- the resulting buffer seems to have zero'd out coordinate values even though the calculated vertex values still agree with those generated by the API

let asset = MDLAsset(url: source)
let meshes = asset.childObjects(of: MDLMesh.self) as! [MDLMesh]

for mesh in meshes {
  mesh.vertexDescriptor = vd
  print1(mesh)
}

func print1(_ mesh: MDLMesh) {
  let buffer = mesh.vertexBuffers[0]
  var ptr = buffer.map().bytes

// Get the first memory address and the last memory address

  let start = ptr
  let stop = start + buffer.length
 
// Calculate the number of (x, y, z) coordinates that could be stored with a stride of 4 between the start and stop memory address then divide the number of coordinates by 3 to get the number of vertices

  let coordCount = (stop - start)/4
  let vtxCount = coordCount/3
  
print("::::::::::", mesh.name, "::::::::::")
  print("# coordinates:     ", coordCount)

// vtxCount and mesh.vertexCount should always be equal if my assumptions are correct
  
  print("# vertices:        ", vtxCount)
  print("modelio vtx count: ", mesh.vertexCount)

  var i = 0
  for _ in i..<buffer.length {
    let x = ptr.load(as: Float.self)
    ptr += 4
    let y = ptr.load(as: Float.self)
    ptr += 4
    let z = ptr.load(as: Float.self)
    ptr += 4
    print(x, y, z)

    i += 12
  }

  print("end at:", i)
}
1 Like

@lducot2 Thank you for sharing your solution - much appreciated! :]