#c #opengl #opengl-es #vertex-buffer #vertex-array-object
#c #opengl #opengl-es #вершинный буфер #вершина-массив-объект
Вопрос:
Я не понимаю, какова цель точек привязки (например, GL_ARRAY_BUFFER
) в OpenGL. Насколько я понимаю glGenBuffers()
, создает своего рода указатель на объект буфера вершин, расположенный где-то в памяти графического процессора.
Итак:
glGenBuffers(1, amp;bufferID)
означает, что теперь у меня есть дескриптор bufferID для 1 вершинного объекта на видеокарте. Теперь я знаю, что следующим шагом будет привязка bufferID к точке привязки
glBindBuffer(GL_ARRAY_BUFFER, bufferID)
чтобы я мог использовать эту точку привязки для отправки данных с помощью glBufferData()
функции следующим образом:
glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW)
Но почему я не мог просто использовать bufferID, чтобы указать, куда я хочу отправить данные вместо этого? Что-то вроде:
glBufferData(bufferID, sizeof(data), data, GL_STATIC_DRAW)
Затем при вызове функции рисования я бы также просто вставил идентификатор which ever в тот VBO, который я хочу, чтобы функция рисования рисовала. Что-то вроде:
glDrawArrays(bufferID, GL_TRIANGLES, 0, 3)
Зачем нам нужен дополнительный шаг косвенного обращения с glBindBuffers
?
Комментарии:
1. » означает, что теперь у меня есть дескриптор, bufferID, для 1 вершинного объекта » Для одного буферного объекта . Не существует такого понятия, как «вершинный объект».
2. «почему я не мог просто использовать bufferID, чтобы указать, куда я хочу отправить данные вместо этого?»…
ARB_direct_state_access
?
Ответ №1:
OpenGL использует точки привязки объектов для двух целей: для обозначения объекта, который будет использоваться как часть процесса рендеринга, и для возможности изменять объект.
Почему он использует их для первого, просто: OpenGL требует много объектов для рендеринга.
Рассмотрим ваш чрезмерно упрощенный пример:
glDrawArrays(bufferID, GL_TRIANGLES, 0, 3)
Этот API не позволяет мне получать отдельные атрибуты вершин из отдельных буферов. Конечно, вы могли бы тогда предложить glDrawArrays(GLint count, GLuint *object_array, ...)
. Но как вы подключаете конкретный объект буфера к определенному атрибуту вершины? Или как у вас есть 2 атрибута из буфера 0 и третий атрибут из буфера 1? Это то, что я могу сделать прямо сейчас с текущим API. Но предложенный вами вариант не может с этим справиться.
И даже это откладывает в сторону множество других объектов, необходимых для рендеринга: объекты program / pipeline, объекты текстур, UBO, SSBO, объекты обратной связи преобразования, объекты запросов и т.д. Наличие всех необходимых объектов, указанных в одной команде, было бы принципиально неработоспособным (и это оставляет в стороне затраты на производительность).
И каждый раз, когда API потребуется добавить новый тип объекта, вам придется добавлять новые варианты glDraw*
функций. И прямо сейчас существует более десятка таких функций. Ваш способ дал бы нам сотни.
Поэтому вместо этого OpenGL определяет для вас способы сказать: «в следующий раз, когда я буду визуализировать, используйте этот объект таким образом для этого процесса». Вот что означает привязка объекта для использования.
Но почему я не мог просто использовать bufferID, чтобы указать, куда я хочу отправить данные вместо этого?
Речь идет о привязке объекта с целью изменения объекта, а не о том, что он будет использоваться. Это … другое дело.
Очевидный ответ: «Вы не можете этого сделать, потому что OpenGL API (до 4.5) не имеет функции, позволяющей вам это делать». Но я скорее подозреваю, что вопрос в том, почему на самом деле в OpenGL нет таких API (до 4.5, где glNamedBufferStorage
и такие существуют).
Действительно, тот факт, что 4.5 имеет такие функции, доказывает, что нет никаких технических причин для API привязки объекта к модификации OpenGL до 4.5. Это действительно было «решение», которое появилось в результате эволюции OpenGL API с версии 1.0, благодаря следованию по пути наименьшего сопротивления. Неоднократно.
Действительно, почти каждое неправильное решение, принятое OpenGL, можно проследить до выбора пути наименьшего сопротивления в API. Но я отвлекся.
В OpenGL 1.0 существовал только один вид объектов: объекты списка отображения. Это означает, что даже текстуры не были сохранены в объектах. Поэтому каждый раз, когда вы переключали текстуры, вам приходилось заново указывать всю текстуру glTexImage*D
. Это означает повторную загрузку. Теперь вы могли (и люди сделали) обернуть создание каждой текстуры в список отображения, что позволило вам переключать текстуры, выполняя этот список отображения. И, надеюсь, драйвер поймет, что вы это делаете, и вместо этого соответствующим образом выделит видеопамять и так далее.
Итак, когда появилась версия 1.1, ARB OpenGL понял, насколько это было глупо. Итак, они создали объекты текстуры, которые инкапсулируют как хранение текстуры в памяти, так и различные состояния внутри. Когда вы хотели использовать текстуру, вы ее привязали. Но была загвоздка. А именно, как его изменить.
Видите ли, в 1.0 была куча уже существующих функций, таких как glTexImage*D
, glTexParamter
и тому подобное. Они изменяют состояние текстуры. Теперь ARB мог бы добавить новые функции, которые делают то же самое, но принимают объекты текстуры в качестве параметров.
Но это означало бы разделение всех пользователей OpenGL на 2 лагеря: те, кто использовал текстурные объекты, и те, кто этого не делал. Это означало, что если вы хотите использовать текстурные объекты, вам нужно переписать весь существующий код, который изменял текстуры. Если бы у вас была какая-то функция, которая выполняла множество glTexParameter
вызовов для текущей текстуры, вам пришлось бы изменить эту функцию, чтобы вызвать новую функцию объекта текстуры. Но вам также придется изменить вашу функцию, которая ее вызывает, чтобы она принимала в качестве параметра объект текстуры, с которым она работает.
И если эта функция вам не принадлежала (потому что она была частью используемой вами библиотеки), то вы даже этого не смогли бы сделать.
Итак, ARB решил сохранить эти старые функции и просто заставить их вести себя по-разному в зависимости от того, была ли текстура привязана к контексту или нет. Если один из них был привязан, то glTexParameter
/etc изменил бы связанную текстуру, а не обычную текстуру контекста.
Это единственное решение установило общую парадигму, разделяемую почти всеми объектами OpenGL.
ARB_vertex_buffer_object использовал эту парадигму по той же причине. Обратите внимание, как различные gl*Pointer
функции ( glVertexAttribPointer
и тому подобное) работают по отношению к буферам. Вам нужно привязать буфер GL_ARRAY_BUFFER
, а затем вызвать одну из этих функций, чтобы настроить массив атрибутов. Когда буфер привязан к этому слоту, функция распознает это и обрабатывает указатель как смещение в буфер, который был привязан во время *Pointer
вызова функции.
Почему? По той же причине: простота совместимости (или для поощрения лени, в зависимости от того, как вы хотите это видеть). ATI_vertex_array_object пришлось создавать новые аналоги gl*Pointer
функций. В то время как ARB_vertex_buffer_object просто скопировал существующие точки входа.
Пользователям не нужно было переходить от использования glVertexPointer
к glVertexBufferOffset
или какой-либо другой функции. Все, что им нужно было сделать, это привязать буфер перед вызовом функции, которая настраивает информацию о вершинах (и, конечно, изменяет указатели на смещения байтов).
Это также означает, что им не нужно было добавлять кучу glDrawElementsWithBuffer
функций типа для рендеринга с индексами, которые поступают из буферных объектов.
Так что это была неплохая идея в краткосрочной перспективе. Но, как и при принятии большинства краткосрочных решений, со временем это становится менее разумным.
Конечно, если у вас есть доступ к GL 4.5/ ARB_direct_state_access, вы можете делать вещи так, как они должны были быть сделаны изначально.
Комментарии:
1. Спасибо за это объяснение, основанное на историческом процессе. Вся чрезмерно запутанная, многословная, сложная бессмыслица, которую вы должны переварить при начале работы с OpenGL, наконец, начинает иметь смысл, когда объясняется часть рациональности дизайна. Это не делает код более привлекательным для просмотра, но помогает мне понять, как перемещаться по API.