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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With