Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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".

like image 895
hyperknot Avatar asked Dec 18 '22 01:12

hyperknot


1 Answers

  1. No. If you try to create a render pipeline state from a pipeline descriptor without a vertex descriptor, and the corresponding vertex function has a [[stage_in]] parameter, the pipeline state creation call will fail.

  2. Yes. After all, when you draw an MTKMesh, you're still obligated to call setVertexBuffer(...) with the buffers wrapped by the mesh's constituent MTKMeshBuffers. You could just as readily create an MTLBuffer yourself and copy your custom vertex structs into it.

  3. Yes. Instead of having a [[stage_in]] parameter, you'd have a parameter attributed with [[buffer(0)]] (assuming all of the vertex data is interleaved in a single vertex buffer) of type MyVertexType *, as well as a [[vertex_id]] parameter that tells you where to index into that buffer.

Here's an example of setting the vertex buffers from an MTKMesh on a render command encoder:

for (index, vertexBuffer) in mesh.vertexBuffers.enumerated() {
    commandEncoder.setVertexBuffer(vertexBuffer.buffer, offset: vertexBuffer.offset, index: index)
}

vertexBuffer is of type MTKMeshBuffer, while its buffer property is of type MTLBuffer; I mention this because it can be confusing.

Here is one way in which you might create a vertex descriptor to tell Model I/O and MetalKit to lay out the mesh data you're loading:

let mdlVertexDescriptor = MDLVertexDescriptor()
mdlVertexDescriptor.attributes[0] = MDLVertexAttribute(name: MDLVertexAttributePosition, format: MDLVertexFormat.float3, offset: 0, bufferIndex: 0)
mdlVertexDescriptor.attributes[1] = MDLVertexAttribute(name: MDLVertexAttributeNormal, format: MDLVertexFormat.float3, offset: 12, bufferIndex: 0)
mdlVertexDescriptor.attributes[2] = MDLVertexAttribute(name: MDLVertexAttributeTextureCoordinate, format: MDLVertexFormat.float2, offset: 24, bufferIndex: 0)
mdlVertexDescriptor.layouts[0] = MDLVertexBufferLayout(stride: 32)

You can create a corresponding MTLVertexDescriptor in order to create a render pipeline state suitable for rendering such a mesh:

let vertexDescriptor = MTKMetalVertexDescriptorFromModelIO(mdlVertexDescriptor)!

Here's a vertex struct that matches that layout:

struct VertexIn {
    float3 position  [[attribute(0)]];
    float3 normal    [[attribute(1)]];
    float2 texCoords [[attribute(2)]];
};

Here's a stub vertex function that consumes one of these vertices:

vertex VertexOut vertex_main(VertexIn in [[stage_in]])
{
}

And finally, here's a vertex struct and vertex function you could use to render the exact same mesh data without a vertex descriptor:

struct VertexIn {
    packed_float3 position;
    packed_float3 normal;
    packed_float2 texCoords;
};

vertex VertexOut vertex_main(device VertexIn *vertices [[buffer(0)]],
                             uint vid [[vertex_id]])
{
    VertexIn in = vertices[vid];
}

Note that in this last case, I need to mark the struct members as packed since by default, Metal's simd types are padded for alignment purposes (specifically, the stride of a float3 is 16 bytes, not 12 as we requested in our vertex descriptor).

like image 54
warrenm Avatar answered Apr 28 '23 20:04

warrenm