Необходимы ли 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 значения (или различные другие типы) и Металл сделает преобразование автоматически.

Один и тот же шейдер может использоваться с разными дескрипторами вершин и, таким образом, с разным распределением атрибутов вершин по буферам. Это обеспечивает гибкость для разных сценариев, в некоторых из которых атрибуты вершин разделяются на разные буферы (аналогично приведенному мною первому примеру), а в других они чередуются в одном и том же буфере (аналогично второму примеру). И т.п.

Если вам не нужна такая гибкость и дополнительный уровень абстракции, вам не нужно иметь дело с дескрипторами вершин. Они там для тех, кто в них нуждается.

Другие вопросы по тегам