Connection between [[stage_in]], MTLVertexDescriptor and MTKMesh

What is the connection between:

  1. Using
    [[stage_in]]
    in a Metal Shader
  2. Using
    MTLVertexDescriptor
  3. Using
    MTKMesh

For example

  1. Is it possible to use
    [[stage_in]]
    without using
    MTLVertexDescriptor
    ?
  2. Is it possible to use
    MTLVertexDescriptor
    without using
    MTKMesh
    , but an array of a custom struct based data structure? Such as
    struct Vertex {...}, Array<Vertex>
    ?
  3. Is it possible to use
    MTKMesh
    without using
    MTLVertexDescriptor
    ? For example using the same struct based data structure?

I didn't find this information on the internet, and the Metal Shading Language Specification doesn't even include the words "descriptor" or "mesh".

Accepted Reply

An MTKMesh is simply a container for vertex buffers that is built by metal kit from an MDLMesh (Model IO Mesh). MetalKit has functionality to build an MTLVertexDescriptor, which describes the layout of vertices fed into a pipeline, from an MDLVertexDescriptor, which describes the layout of vertices of a model contained by an MDLMesh.


To answer your question

1) No, you must use a MTLVertexDescriptor to build a pipelien[[stage_in]] quailifier if you want to use a to describe your vertex layout.


2) Absolutely. Thid MTKMesh is really only useful if you're using ModelIO for your model loading. And you don't actually even need an MTKMesh to render a model loaded with ModelIO, it just makes is easier. If you have your own model loading code or just have hardcoded vertices or something like that you just setup the MTLVertexDescriptor manually.


3) Yes. You would have to create a struct in you metal shader that matches the layout of the vertices in the MTKMesh. This is more error prone as it's easier to create a struct that doesn't match the mesh's layout. There aren't really any advantages to not using a vertex descriptor.

Replies

An MTKMesh is simply a container for vertex buffers that is built by metal kit from an MDLMesh (Model IO Mesh). MetalKit has functionality to build an MTLVertexDescriptor, which describes the layout of vertices fed into a pipeline, from an MDLVertexDescriptor, which describes the layout of vertices of a model contained by an MDLMesh.


To answer your question

1) No, you must use a MTLVertexDescriptor to build a pipelien[[stage_in]] quailifier if you want to use a to describe your vertex layout.


2) Absolutely. Thid MTKMesh is really only useful if you're using ModelIO for your model loading. And you don't actually even need an MTKMesh to render a model loaded with ModelIO, it just makes is easier. If you have your own model loading code or just have hardcoded vertices or something like that you just setup the MTLVertexDescriptor manually.


3) Yes. You would have to create a struct in you metal shader that matches the layout of the vertices in the MTKMesh. This is more error prone as it's easier to create a struct that doesn't match the mesh's layout. There aren't really any advantages to not using a vertex descriptor.

Thanks! Can you explain a bit how can I write code for the 2. point?

I guess this requires me to

- Write a MTLVertexDescriptor, OK

- Mirror that in a typedef struct with attributes for the shader, OK

- But having no MTKMesh, I'd need to also hand-craft a struct for my hand coded vertex type, don't I? But I also need to implement my buffer generation as well, don't I? Then I'm back in where I was before using MTLVertexDescriptor.


struct Vertex {
  var x, y, z: Float // position data
  var r, g, b, a: Float // color data
  var s, t: Float // texture coordinates
  var nX, nY, nZ: Float // normal

  func floatBuffer() -> [Float] {
    return [x, y, z, r, g, b, a, s, t, nX, nY, nZ]
  }
}

Can you explain in more detail what you're trying to do? Are you trying to draw a 3D model? Or are you just trying to draw some hardcoded or generated geometry?

Defining a struct for your vertex layout in Swift can be pretty painful. You'd need to explicitly define the type of each element (because the struct defines the layout of the vertex data in memory). Also you can't include Swift code in Metal shader code so you can't share the struct betwen the shader code and the application code (thus you'd need to define it two places which will increase the likelyhood of errors since the layouts may not match).

If you're using Swift, I recommend using a vertex descriptor and an input with the [[stage_in]] qualifier in your vertex shader. You do have to define a struct for your [[stage_in]] input, but this struct's layout doesn't need to match that of the vertex descriptor. The [[attribute(n)]] qualifier for each element of the struct used for your [[stage_in]] inpute defines the mapping of the element to the attribute in the vertex descriptor. However, the type (or vector size) for each struct does not need to match the vertex descriptor. The types of elements in the [[stage_in]] struct are only use to define how they are handled in the shading code (not how the vertiex data is laid out). For any non vertex buffer data you want to pass into the shader, you should define in an ObjC bridge header which you can include in both Swift and Metal shader code.

If you use Xcode to create a "Game" application and select "Swift" as the Language and "Metal" as the Game Technology, you'll get an example of using swift with Metal.

Thanks for your answer. Actually, I've spent the last week researching and understanding the Swift / Game App / Metal template 🙂


After a week, it's clear how it works with a loaded mesh, which is also the scenario what you've described, if I understand right. My problem is that I do not have any loaded object, everything is generated on-the-fly. Luckily nothing complicated, only points and lines in 3D space.


For example, for drawing lines, here is my current solution:


1. Creating a toBuffer() method on simd types:

extension float2 {
  func toBuffer() -> [Float] {
  return [x, y]
  }
}

extension float3 {
  func toBuffer() -> [Float] {
  return [x, y, z, 0]
  }
}

extension float4 {
  func toBuffer() -> [Float] {
  return [x, y, z, w]
  }
}


2. Shared parts go in Bridge Header:

struct MyPoint {
  vector_float3 position;
  vector_float4 color;
};

struct MyLine {
  struct MyPoint start;
  struct MyPoint end;
};


3. Then extending these types in Swift:

extension MyPoint {
  func toBuffer() -> [Float] {
  return position.toBuffer() + color.toBuffer()
  }
}

extension MyLine {
  func toBuffer() -> [Float] {
  return start.toBuffer() + end.toBuffer()
  }
}


4. Allowing me to generate 3D objects in code and generate the buffer with a [Float] array:

func buildLineArray() {
  let pointA = MyPoint(position: float3(sqrt(8 / 9), 0, -1 / 3), color: float4(1, 1, 1, 1))
  let pointB = MyPoint(position: float3(-sqrt(2 / 9), sqrt(2 / 3), -1 / 3), color: float4(1, 0, 0, 1))
  let pointC = MyPoint(position: float3(-sqrt(2 / 9), -sqrt(2 / 3), -1 / 3), color: float4(0, 1, 0, 1))
  let pointD = MyPoint(position: float3(0, 0, 1), color: float4(0, 0, 1, 1))

  let lineAB = MyLine(start: pointA, end: pointB)
  let lineAC = MyLine(start: pointA, end: pointC)
  let lineAD = MyLine(start: pointA, end: pointD)
  let lineBC = MyLine(start: pointB, end: pointC)
  let lineBD = MyLine(start: pointB, end: pointD)
  let lineCD = MyLine(start: pointC, end: pointD)

  lineArray = [lineAB, lineAC, lineAD, lineBC, lineBD, lineCD]
}

func buildBuffer() {
  var vertexData = [Float]()
  for line in lineArray {
    vertexData += line.toBuffer()
  }

  let dataSize = vertexData.count * MemoryLayout.size(ofValue: vertexData[0])
  vertexBuffer = device.makeBuffer(bytes: vertexData, length: dataSize, options: [])
}


5. And for example do instanced drawing:


renderEncoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4, instanceCount: lineArray!.count)

Now this one isn't perfect, but the bridged header at least solves half the problem. Generally I feel quite efficient in working with this code.


Do you think this could be achieved with VertexDescriptors as well? My problematic part would be step 4., I simply don't see how can I generate vertices for a VertexDescriptor object in code.