Are MTLVertexAttributeDescriptors necessary? Why are they needed?
Asked Answered
E

1

20

I've been learning Metal for iOS / OSX, and I began by following a Ray Wenderlich tutorial. This tutorial works fine but it makes no mention of MTLVertexAttributeDescriptors.

Now that I'm developing my own app, I'm getting weird glitches and I'm wondering if the fact that I don't use MTLVertexAttributeDescriptors may be related to the problem.

What difference do they make? I've been able to make a variety of shaders with varying vertex structures and I never even knew about these things.

I know you use them to describe the layout of vertex components for use in a shader. For example a shader might use this structure for vertices, and it would be set up in a vertex descriptor in the function below.

typedef struct
{
    float3 position [[attribute(T_VertexAttributePosition)]];
    float2 texCoord [[attribute(T_VertexAttributeTexcoord)]];
} Vertex;

 class func buildMetalVertexDescriptor() -> MTLVertexDescriptor {

    let mtlVertexDescriptor = MTLVertexDescriptor()

    mtlVertexDescriptor.attributes[T_VertexAttribute.position.rawValue].format = MTLVertexFormat.float3
    mtlVertexDescriptor.attributes[T_VertexAttribute.position.rawValue].offset = 0
    mtlVertexDescriptor.attributes[T_VertexAttribute.position.rawValue].bufferIndex = T_BufferIndex.meshPositions.rawValue

    mtlVertexDescriptor.attributes[T_VertexAttribute.texcoord.rawValue].format = MTLVertexFormat.float2
    mtlVertexDescriptor.attributes[T_VertexAttribute.texcoord.rawValue].offset = 0
    mtlVertexDescriptor.attributes[T_VertexAttribute.texcoord.rawValue].bufferIndex = T_BufferIndex.meshGenerics.rawValue

    mtlVertexDescriptor.layouts[T_BufferIndex.meshPositions.rawValue].stride = 12
    mtlVertexDescriptor.layouts[T_BufferIndex.meshPositions.rawValue].stepRate = 1
    mtlVertexDescriptor.layouts[T_BufferIndex.meshPositions.rawValue].stepFunction = MTLVertexStepFunction.perVertex

    mtlVertexDescriptor.layouts[T_BufferIndex.meshGenerics.rawValue].stride = 8
    mtlVertexDescriptor.layouts[T_BufferIndex.meshGenerics.rawValue].stepRate = 1
    mtlVertexDescriptor.layouts[T_BufferIndex.meshGenerics.rawValue].stepFunction = MTLVertexStepFunction.perVertex

    return mtlVertexDescriptor
}

But even without the MTLVertexDescriptor setup, the shader can already access the vertex buffer and the position / texCoord components of vertices in the array. Just by setting the vertex buffer, the shader has access to all of the components. So what good does the descriptor do?

Eufemiaeugen answered 31/10, 2017 at 21:17 Comment(0)
K
37

There are, of course, multiple ways of doing things. The vertex descriptor is only used for one of them.

For example, a vertex function might be declared like this:

vertex MyVertexOut vertex_func(device const float3 *positions [[buffer(0)]],
                               device const float2 *texCoords [[buffer(1)]],
                               uint vid [[vertex_id]])
{
    // use positions[vid] and texCoords[vid] to fill in and return a MyVertexOut structure
}

This dictates that the vertex attributes be supplied in separate buffers, each of a specific layout.

You could also do:

struct MyVertexIn
{
    float3 position;
    float2 texCoord;
};
vertex MyVertexOut vertex_func(device const MyVertexIn *vertexes [[buffer(0)]],
                               uint vid [[vertex_id]])
{
    // use vertexes[vid].position and vertexes[vid].texCoord to fill in and return a MyVertexOut structure
}

This dictates that the vertex attributes be supplied in a single buffer of structs matching the layout of MyVertexIn.

Neither of the above require or make use of the vertex descriptor. It's completely irrelevant.

However, you can also do this:

struct MyVertexIn
{
    float3 position [[attribute(0)]];
    float2 texCoord [[attribute(1)]];
};
vertex MyVertexOut vertex_func(MyVertexIn vertex [[stage_in]])
{
    // use vertex.position and vertex.texCoord to fill in and return a MyVertexOut structure
}

Note the use of the attribute(n) and stage_in attributes. This does not dictate how the vertex attributes are supplied. Rather, the vertex descriptor describes a mapping from one or more buffers to the vertex attributes. The mapping can also perform conversions and expansions. For example, the shader code above specifies that the position field is a float3 but the buffers may contain (and be described as containing) half3 values (or various other types) and Metal will do the conversion automatically.

The same shader can be used with different vertex descriptors and, thus, different distribution of vertex attributes across buffers. That provides flexibility for different scenarios, some where the vertex attributes are separated out into different buffers (similar to the first example I gave) and others where they're interleaved in the same buffer (similar to the second example). Etc.

If you don't need that flexibility and the extra level of abstraction, then you don't need to deal with vertex descriptors. They're there for those who do need them.

Kinna answered 1/11, 2017 at 0:59 Comment(2)
Thanks - I was wondering what about the difference between the [[stage_in]] and [[buffer]] syntax. Unfortunately this also means that the lack of vertex descriptors probably aren't the reason for my glitching.Eufemiaeugen
Just a note - sometimes you may need to use packed_floatN instead of floatN in yours structs depending on how you send up your data.Favoritism

© 2022 - 2024 — McMap. All rights reserved.