Портативный способ создания неоднородного массива данных вершин

В графическом программировании очень распространено работать с форматами вершин. Это описано, например, здесь.

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

Обычный способ сделать это так: сначала объявите формат вашей вершины как структуру.

struct Vertex {
    float x;
    float y;
    uint16_t someData;
    float etc;
};

Затем вы создаете их массив, заполняете и отправляете в свой графический API (например, OpenGL).

Vertex myVerts[100];
myVerts[0].x = 42.0;
// etc.

// when done, send the data along:
graphicsApi_CreateVertexBuffer(&myVerts[0], ...);

(Кроме того: я пропустил ту часть, где вы сообщаете API, что это за формат; мы просто предполагаем, что он знает).

Однако графический API ничего не знает о вашей структуре. Ему просто нужна последовательность значений в памяти, например:

|<---  first vertex   -->||<---  second vertex  -->| ...
[float][float][u16][float][float][float][u16][float] ...

А из-за проблем с упаковкой и центровкой нет гарантии, что myVerts так будут выложены в памяти.

Конечно, таким образом написано множество кода, и он работает, несмотря на то, что его нельзя переносить.

Но есть ли какой-нибудь переносимый способ сделать это, который

1. Inefficient
2. Awkward to write

?

Это в основном проблема сериализации. См. Также: Правильный переносимый способ интерпретации буфера как структуры

Основной известный мне способ, соответствующий стандартам, - это выделить вашу память как char[]. Затем вы просто заполняете все байты именно так, как хотите.

Но чтобы превратиться из struct Vertex представление выше этого char[]представление потребовало бы дополнительной копии (и медленной побайтовой копии). Так что это неэффективно.

В качестве альтернативы вы можете записать данные в char[]представление напрямую, но это крайне неудобно. Гораздо приятнее сказатьverts[5].x = 3.0f чем обращение к байтовому массиву, запись числа с плавающей запятой как 4 байта и т. д.

Есть ли хороший портативный способ сделать это?

1 ответ

Решение

Однако графический API ничего не знает о вашей структуре. Ему просто нужна последовательность значений в памяти, например:

|<---  first vertex   -->||<---  second vertex  -->| ...
[float][float][u16][float][float][float][u16][float] ...

Это неправда. Графический API знает вашу структуру, потому что вы рассказали ему о своей структуре. Вы даже это уже знаете:

(Кроме того: я пропустил ту часть, где вы сообщаете API, что это за формат; мы просто предполагаем, что он знает).

Когда вы сообщаете графическому API, где находится каждое поле в вашей структуре, вы должны использовать sizeof а также offsetofвместо того, чтобы угадывать макет вашей структуры. Тогда ваш код будет работать, даже если компилятор вставит заполнение. Например (в OpenGL):

struct Vertex {
    float position[2];
    uint16_t someData;
    float etc;
};

glVertexAttribPointer(position_index, 2, GL_FLOAT, GL_FALSE, sizeof(struct Vertex), (void*)offsetof(struct Vertex, position));
glVertexAttribIPointer(someData_index, 1, GL_SHORT, sizeof(struct Vertex), (void*)offsetof(struct Vertex, someData));
glVertexAttribPointer(etc_index, 1, GL_FLOAT, GL_FALSE, sizeof(struct Vertex), (void*)offsetof(struct Vertex, etc));

не

glVertexAttribPointer(position_index, 2, GL_FLOAT, GL_FALSE, 14, (void*)0);
glVertexAttribIPointer(someData_index, 1, GL_SHORT, 14, (void*)8);
glVertexAttribPointer(etc_index, 1, GL_FLOAT, GL_FALSE, 14, (void*)10);

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

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