Необходимы ли MTLVertexAttributeDescriptors? Зачем они нужны?
Я изучал Metal для iOS/OSX и начал с того, что следовал учебному пособию Рэя Вендерлиха ( https://www.raywenderlich.com/146414/metal-tutorial-swift-3-part-1-getting-started). Этот учебник работает хорошо, но он не упоминает MTLVertexAttributeDescriptors.
Теперь, когда я разрабатываю свое собственное приложение, у меня появляются странные проблемы, и мне интересно, может ли тот факт, что я не использую MTLVertexAttributeDescriptors, может быть связан с проблемой.
Какую разницу они имеют? Я был в состоянии сделать множество шейдеров с различными структурами вершин, и я даже не знал об этих вещах.
Я знаю, что вы используете их, чтобы описать расположение компонентов вершин для использования в шейдере. Например, шейдер может использовать эту структуру для вершин, и она будет установлена в дескрипторе вершины в функции ниже.
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
}
Но даже без настройки MTLVertexDescriptor шейдер уже может получить доступ к буферу вершин и компонентам position/texCoord вершин в массиве. Просто установив буфер вершин, шейдер получает доступ ко всем компонентам. Так что хорошего делает дескриптор?
1 ответ
Есть, конечно, несколько способов ведения дел. Дескриптор вершины используется только для одного из них.
Например, функция вершины может быть объявлена так:
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
}
Это диктует, что атрибуты вершины должны быть предоставлены в отдельных буферах, каждый из которых имеет определенный макет.
Вы также можете сделать:
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
}
Это диктует, что атрибуты вершины должны быть предоставлены в одном буфере структур, соответствующих макету MyVertexIn
,
Ни один из вышеперечисленных не требует и не использует дескриптор вершины. Это совершенно не имеет значения.
Тем не менее, вы также можете сделать это:
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
}
Обратите внимание на использование attribute(n)
а также stage_in
атрибутов. Это не диктует, как предоставляются атрибуты вершины. Скорее, дескриптор вершины описывает отображение одного или нескольких буферов в атрибуты вершины. Отображение также может выполнять преобразования и расширения. Например, приведенный выше код шейдера указывает, что position
поле является float3
но буферы могут содержать (и быть описаны как содержащие) half3
значения (или различные другие типы) и Металл сделает преобразование автоматически.
Один и тот же шейдер может использоваться с разными дескрипторами вершин и, таким образом, с разным распределением атрибутов вершин по буферам. Это обеспечивает гибкость для разных сценариев, в некоторых из которых атрибуты вершин разделяются на разные буферы (аналогично приведенному мною первому примеру), а в других они чередуются в одном и том же буфере (аналогично второму примеру). И т.п.
Если вам не нужна такая гибкость и дополнительный уровень абстракции, вам не нужно иметь дело с дескрипторами вершин. Они там для тех, кто в них нуждается.