Должен ли я называть glEnable и glDisable каждый раз, когда я рисую что-то?
Как часто я должен вызывать функции OpenGL, такие как glEnable()
или glEnableClientState()
, и соответствующие им сопоставления glDisable
? Должны ли они быть вызваны один раз в начале приложения, или я должен отключать их и включать только те функции, которые мне нужны для рисования? Есть ли разница в производительности?
Ответы
Ответ 1
"Это зависит".
Если ваше приложение использует только одну комбинацию состояний включения/выключения, то, во всяком случае, просто установите его в начале и идите.
Большинство приложений реального мира необходимо смешивать, а затем вы вызываете glEnable()
, чтобы включить определенные состояния (состояния), выполнить вызовы рисования, затем glDisable()
их снова, когда вы закончите "очистить сцену".
Отслеживание состояния, отслеживание состояния и многие схемы оптимизации проистекают из этого, поскольку переход состояния иногда дорог.
Ответ 2
Если вы обнаружите, что часто проверяете значение переменных состояния и затем вызываете glEnable/glDisable, вы можете немного почистить вещи, используя стек атрибутов (glPushAttrib/glPopAttrib).
Стек атрибута позволяет вам изолировать области вашего кода и такие, что изменения атрибута в одном разделе не влияют на состояние атрибута в других разделах.
void drawObject1(){
glPushAttrib(GL_ENABLE_BIT);
glEnable(GL_DEPTH_TEST);
glEnable(GL_LIGHTING);
/* Isolated Region 1 */
glPopAttrib();
}
void drawObject2(){
glPushAttrib(GL_ENABLE_BIT);
glEnable(GL_FOG);
glEnable(GL_GL_POINT_SMOOTH);
/* Isolated Region 2 */
glPopAttrib();
}
void drawScene(){
drawObject1();
drawObject2();
}
Хотя GL_LIGHTING
и GL_DEPTH_TEST
установлены в drawObject1, их состояние не сохраняется для drawObject2. В отсутствие glPushAttrib это не так. Также - обратите внимание, что нет необходимости вызывать glDisable в конце вызовов функций, glPopAttrib выполняет задание.
Что касается производительности, накладные расходы, вызванные вызовами отдельных функций glEnable/glDisable, минимальны. Если вам нужно обрабатывать много состояний, вам, вероятно, понадобится создать свой собственный менеджер состояний или сделать многочисленные вызовы glGetInteger... и затем действовать соответствующим образом. Добавленный механизм и поток управления могут сделать код менее прозрачным, сложнее отлаживать и более сложно поддерживать. Эти проблемы могут затруднить другие, более плодотворные и оптимизационные оптимизации.
Стек атрибута может помочь поддерживать уровни абстракции и создавать области изоляции.
glPushAttrib manpage
Ответ 3
Прежде всего, какую версию OpenGL вы используете? И какое поколение графического оборудования имеет ваша целевая группа? Зная это, было бы легче дать более правильный ответ. Мой ответ предполагает OpenGL 2.1.
OpenGL - это конечный автомат, что означает, что всякий раз, когда состояние изменяется, это состояние становится "текущим" до тех пор, пока явно не будет изменено программистом с новым вызовом API OpenGL. Исключения из этого правила существуют, например, вызовы массива клиентских состояний, создающие текущий цвет вершины undefined. Но это исключения, которые определяют правило.
"один раз в начале приложения" не имеет большого смысла, потому что есть время, когда вам нужно уничтожить свой контекст OpenGL, пока приложение все еще работает. Я предполагаю, что вы имеете в виду сразу после каждого создания окна. Это работает для состояния, которое вам не нужно менять позже. Пример. Если все ваши призывы рисования используют одни и те же данные массива вершин, вам не нужно отключать их с помощью glDisableClientState.
Существует много состояний включения/выключения, связанных со старым конвейером с фиксированной функцией. Легкое искупление для этого: используйте шейдеры! Если вы ориентируетесь на поколение карт не старше пяти лет, это, вероятно, имитирует конвейер с фиксированной функцией с шейдерами. Используя шейдеры, вы более или менее полностью контролируете, что происходит на этапах преобразования и растеризации, и вы можете создавать свои собственные "состояния" с униформой, которые очень дешевы для изменения/обновления.
Зная, что OpenGL - это конечный автомат, как я сказал выше, должен четко указывать, что нужно стремиться к тому, чтобы изменения состояния были минимальными, насколько это возможно. Однако есть, скорее всего, другие вещи, которые влияют на производительность намного больше, чем включение/отключение государственных вызовов. Если вы хотите узнать о них, прочитайте.
Расход состояния, не связанный со старыми вызовами состояния фиксированной функции и который не является простым условием включения/выключения, может сильно различаться по стоимости. В частности, связывание шейдеров и имен привязок ( "имена" текстур, программ, объектов буфера) обычно довольно дороги. Вот почему множество игр и приложений, используемых для сортировки порядка рисования их сеток в соответствии с текстурой. Таким образом, им не пришлось связывать одну и ту же текстуру дважды. Однако в то же время это относится и к шейдерным программам. Вы не хотите связывать одну и ту же программу шейдеров дважды, если вам это не нужно. Кроме того, не все функции в определенной версии OpenGL аппаратно ускоряются на всех картах, даже если производители этих карт утверждают, что они совместимы с OpenGL. Быть совместимым означает, что они следуют спецификации, а не то, что они обязательно эффективно управляют всеми навыками. Некоторые функции, такие как glHistogram и glMinMax из GL__ ARB __imaging, следует помнить в этом отношении.
Заключение: если нет очевидной причины, чтобы не использовать шейдеры! Это избавит вас от множества бесполезных государственных вызовов, поскольку вместо этого вы можете использовать униформы. Шейдеры OpenGL уже около шести лет вы знаете. Кроме того, издержки на включение/выключение изменений состояния могут быть проблемой, но, как правило, есть намного больше возможностей для оптимизации других более дорогих изменений состояния, таких как glUseProgram, glCompileShader, glLinkprogram, glBindBuffer и glBindTexture.
P.S: OpenGL 3.0 удалил состояние включения/выключения клиента. Они неявно включены, поскольку массивы рисования - единственный способ сделать эту версию. Непосредственный режим был удален. gl..Pointer звонки также были удалены, так как на самом деле просто нужен glVertexAttribPointer.
Ответ 4
Правило большого пальца, которым меня учили, говорит, что почти всегда дешевле просто включать/отключать по своему усмотрению, а не проверять текущее состояние и изменять только при необходимости.
Тем не менее, ответ Marc - это то, что обязательно должно работать.