вторник, 21 февраля 2012 г.

Основы VBO в OpenGL. Часть шестая, VAO.


  1. Часть первая, первый квадрат
  2. Часть вторая, добавляем атрибуты
  3. Часть третья, индексный буфер
  4. Часть четвертая, динамика
  5. Часть пятая, дополнительные возможности
  6. Часть шестая, VAO
  7. Ссылки по теме

В предыдущих уроках я уже упоминал о такой технологии как VAO, позволяющей волшебным образом сократить количество биндов буферов VBO, упростив эту процедуру и сделав код быстрее, давайте теперь подробно разберем что это за зверь и с чем его едят.

В OpenGL 2.1, в далеком 2008 году, появилось расширение ARB_vertex_array_object, которое и ввело понятие VAO, расшифровываемое как Vertex Array Object (массив вершинных объектов).
Чтоб понять для чего он нужен - рассмотрим пример из предыдущего урока:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Procedure VBODraw(Count:integer);
Begin
  glEnableClientState( GL_VERTEX_ARRAY );
  glEnableClientState( GL_COLOR_ARRAY );
  glBindBuffer( GL_ARRAY_BUFFER, cId );
  glColorPointer( 3, GL_UNSIGNED_BYTE, 0, nil );
  glEnableClientState( GL_NORMAL_ARRAY );
  glBindBuffer( GL_ARRAY_BUFFER, nId );
  glNormalPointer(GL_FLOAT, 0, nil );
  glEnableClientState( GL_TEXTURE_COORD_ARRAY );
  glBindBuffer( GL_ARRAY_BUFFER, tId );
  glTexCoordPointer(2, GL_FLOAT, 0, nil );
  glBindBuffer( GL_ARRAY_BUFFER, vId );
  glVertexPointer( 3, GL_FLOAT, 0, nil );
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, iId);  
  glDrawElements(GL_TRIANGLES, Count, GL_UNSIGNED_BYTE, nil);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
  glBindBuffer( GL_ARRAY_BUFFER, 0 ); 
  glDisableClientState(GL_VERTEX_ARRAY);
  glDisableClientState(GL_COLOR_ARRAY);
  glDisableClientState(GL_NORMAL_ARRAY);
  glDisableClientState(GL_TEXTURE_COORD_ARRAY);
End;

Итого, нам потребовалось выполнить целых 20 команд, чтоб нарисовать какой-то квадрат. Если мы захотим использовать еще несколько вершинных атрибутов, то код еще более разрастется. Проблема в том, что каждый бинд буфера или активация клиентского стэйта требует некоторого времени. При выводе одного квадрата это не имеет значение, но если вы попытаетесь вывести тысячу таких квадратов, то падение производительности будет уже очень существенным. В предыдущей статье мы эту проблему решали за счет упаковки нескольких атрибутов в один буфер, но такой подход не всегда удобен. Вот тут нам на помощь и приходит VAO, позволяющий, по аналогии с дисплейным списком, всю эту процедуру инициализации буфера скомпилировать в один массив.
На практике это реализуется таким образом:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function GenVAO: cardinal;
begin
  glGenVertexArrays(1, @Result);
  glBindVertexArray(Result);

  glEnableClientState( GL_VERTEX_ARRAY );
  glBindBuffer(GL_ARRAY_BUFFER, vId );
  glVertexPointer( 3, GL_FLOAT, 0, nil );

  glEnableClientState( GL_COLOR_ARRAY );
  glBindBuffer(GL_ARRAY_BUFFER, cId );
  glColorPointer( 3, GL_UNSIGNED_BYTE, 0, nil );

  glEnableClientState( GL_NORMAL_ARRAY );
  glBindBuffer(GL_ARRAY_BUFFER, nId );
  glNormalPointer(GL_FLOAT, 0, nil );

  glEnableClientState( GL_TEXTURE_COORD_ARRAY );
  glBindBuffer(GL_ARRAY_BUFFER, tId );
  glTexCoordPointer(2, GL_FLOAT, 0, nil );

  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, iId);

  glBindVertexArray(0);

  glBindBuffer(GL_ARRAY_BUFFER, 0);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
end;
Как видите, весь код, расположенный до "glDrawElements" мы оформили в виде отдельной процедуры, в начале которой стоит пару новых команд:
    glGenVertexArrays(1, @Result);
    glBindVertexArray(Result);
Команда  glGenVertexArrays, по аналогии с "glGenBuffers", генерирует указатель для нового буфера VAO, команда glBindVertexArray делает этот массив активным. Все последующие операции, вплоть до glBindVertexArray(0) (строка 24), будут скомпилированы в этот массив. Генерацию этого массива необходимо производить уже после того как буферы VBO будут проинициализированы. Так же перед генерацией VAO есть смысл проверить поддержку этого расширения:
  if (GL_ARB_vertex_array_object) then vao:=GenVAO;
Это исключит проблемы со старым железом.

Теперь процедура рисования упростится до безобразия:
1
2
3
4
5
6
Procedure VBODraw(Count: integer);
Begin
  glBindVertexArray(vao);
  glDrawElements(GL_TRIANGLES, Count, GL_UNSIGNED_BYTE, nil);
  glBindVertexArray(0);
End;
Думаю данный код не нуждается в комментариях.

После завершения работы объект vao нужно удалить, делается это по аналогии с вершинными буферами, но командой glDeleteVertexArrays:
  glDeleteVertexArrays(1,@vao);


Бинарник с исходными кодами проекта можно взять здесь: Demo9

Ну и в завершение, привожу из спецификации полный список состояний ОГЛ, устанавливаемых при биндинге VAO (или, другими словами, что можно туда запихнуть):
Table 6.6. Vertex Array Data



Table 6.7. Vertex Array Data (cont.)

Table 6.8. Vertex Array Data (cont.)

4 комментария:

  1. спасибо за интересные статьи. вот просто поделиться со своими колупаниями насчет VAO. У меня на ноуте Intel HD видео, это одна из последних интеловских. следующая за GMA 1400 вроде. Твк вот. в этой видюхе (хотя, может быть в дравах под линукс - нет винды не компе схожего ноута чтобы проверить) чертов VertexArray не запоминает бинды буферов, а только их комбинации с ****pointer, т.е. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, iId); - ему пофигу и поэтому для рендера не достаточно только glDrawElements(GL_TRIANGLES, Count, GL_UNSIGNED_BYTE, nil);, нужно еще индексный буфер перед этим биндить. вот такие пироги :(

    ОтветитьУдалить
  2. Спасибо за замечание, без соответствующего железа такие моменты очень сложно отловить, потому я особенно благодарен за любую информацию об ошибках.

    О VAO - в описании расширения ARB_vertex_array_object четко написано:
    Vertex-array state variables are qualified by the value of VERTEX_ARRAY_BINDING to determine which vertex array object is queried. Tables 6.6, 6.7, and 6.8 define the set of state stored in a vertex array object.

    В таблицах 6.6, 6.7 и 6.8 находится полный список состояний ОГЛ, которые могут быть помещены в VAO, в частности, в таблице 6.8 присутствует заветный "ELEMENT ARRAY BUFFER BINDING", потому, следуя спецификации, можно помещать в VAO биндинг индексного буфера. У меня была мысль прикрепить к статье этот список, но поленился :)

    По поводу интела - гугл нашел обсуждение этой проблемы:
    http://stackoverflow.com/questions/8973690/vao-and-element-array-buffer-state
    Там народ имеет те же проблемы и доводы те же :)

    Что касается Интеловского видео - они упорно отказываются следовать спецификации, то и дело выкидывая какие-то расширения, присутствующие в ядре десяток лет, вдобавок даже то что есть и то не всегда работает, потому я отказался от поддержки этого видео, так как пришлось бы ставить сотню проверок буквально на каждую команду.

    У меня уже был опыт как на интеловской видеокарте, с заявленной поддержкой ОГЛ 2.1, в ядре вообще отсутствовала поддержка VBO (официально присутствует в ядре начиная с ОГЛ 1.5). Казалось бы мелочь, но поддержка этой видеокарты потребовала бы дописывания ко всем командам работы с VBO префикса "ARB". Другая видеокарта, тоже с "полной" поддержкой ОГЛ 2.1 отказывалась работать с FBO, которое хоть и не входит в ядро ОГЛ 2.1, но поддерживается нормальными видеокартами с 2004 года.

    Потому тут все просто - работает - повезло, не работает - выделите из бюджета 40 баксов на нормальное видео :)

    Я сам сижу на NVidia GT520 стоимость 40 баксов, отличная видеокарта, полная поддержка ОГЛ 4.1, включая аппаратную тесселяцию, CUDA, OpenCL 2.0, производительность просто несравнима с производительностью видеокарт интел. И это сегодняшний день, а что будет завтра? Есть ли смысл заморачиваться на вчерашнее железо?

    Я понимаю что есть такое понятие как казуальные игры и офисные компьютеры, под кого это и делается, но под это дело однозначно лучше выбрать DirectX, сэкономив кучу времени и нервов, ну или разрабатывать под ОГЛ 1.1 :)

    ОтветитьУдалить
  3. тут не в стоимости видях даже дело. у меня это ноут для работы. для которого мне важнее чтобы он 4 часа минимум без электросети мог работать. то есть, энергосбережение более важный показатель. но, так как он у меня всегда под рукой, то иногда и в 3D что нибудь на нем пишу. но... всегда приходится с чем-то бороться :) так как, никогда не знаешь что всплывет. а еще у меня и винды нету... :)
    а комнет я для информации написал, так как сам несколько часов убил на выяснения трабла. ну и конечно, в сердцах,высказаться о том, что я вообще думаю про этот интел :) причем, эту видеокарту сравнивают с 8600GT (если я правильно помню), но! не верте никто! даже по производительности (не говоря уже о поддержке расширений) у меня, на реальном проекте, старенькая 5600GT показывала результаты раза в 3 лучшие при рендере статики, чем эта современная от интела.
    а! еще! вспомнил. там у тебя было написано что максимальное кол-во индексов от 4К, так вот, у этого интела 3000 :)

    ОтветитьУдалить
  4. Сочувствую :)
    Мне проще, я хотя бы могу отказаться от поддержки интела :)

    Ну а если только для того, чтоб что-то написать, то рекомендую MesaGL, там реальная поддержка ОГЛ 2.1 (последняя версия поддерживает ОГЛ 3.0), даже шейдера работают :)
    Вполне достаточно для разработки и отладки приложений.

    ОтветитьУдалить