我应该多久调用一次OpenGL函数,例如glEnable()
或glEnableClientState()
及其对应的glDisable
对应函数?它们是否应该在应用程序开始时调用一次,还是应该禁用它们并且仅启用我立即需要绘制某些功能的那些功能?是否有性能差异?
答案 0 :(得分:15)
如果您发现经常检查状态变量的值并随后调用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();
}
虽然在drawObject1中设置了GL_LIGHTING
和GL_DEPTH_TEST
,但它们的状态不会保留为drawObject2。在没有glPushAttrib的情况下,情况并非如此。另外 - 请注意,在函数调用结束时无需调用glDisable,glPopAttrib可以完成这项工作。
就性能而言,单个函数调用glEnable / glDisable的开销很小。如果你需要处理许多状态,你可能需要创建自己的状态管理器或对glGetInteger进行多次调用......然后相应地采取行动。增加的机械和控制流程可能使代码更不透明,更难调试,更难以维护。这些问题可能会使其他更有成效的优化变得更加困难。
归因堆栈可以帮助维护抽象层并创建隔离区域。
答案 1 :(得分:12)
“这取决于”。
如果您的整个应用只使用启用/禁用状态的一种组合,那么只需在开头设置它就可以了。
大多数真实世界的应用需要混合,然后您被迫拨打glEnable()
以启用某些特定状态,进行绘制调用,然后再次glDisable()
再次使用它们重新做“清除舞台”。
状态排序,状态跟踪和许多优化方案源于此,因为状态切换有时很昂贵。
答案 2 :(得分:8)
首先,您使用哪个OpenGL版本?您的目标群体拥有哪一代图形硬件?了解这一点可以更容易地给出更正确的答案。我的回答是假设OpenGL 2.1。
OpenGL是一个状态机,意味着每当状态发生变化时,该状态就会变为“当前”,直到程序员使用新的OpenGL API调用再次明确地更改状态。存在此规则的例外情况,例如客户端状态数组调用使当前顶点颜色未定义。但这些是定义规则的例外。
“一旦在应用程序的开头”没有多大意义,因为有时您需要在应用程序仍在运行时销毁您的OpenGL上下文。我假设你的意思是在每个窗口创建之后。这适用于您不需要稍后更改的状态。示例:如果所有绘制调用都使用相同的顶点数组数据,则不需要在之后使用glDisableClientState禁用它们。
有许多与旧的固定功能管道相关联的启用/禁用状态。对此的轻松兑换是:使用着色器!如果您定位的是最近五年的一代卡片,那么无论如何它都可能模仿着色器的固定功能管道。通过使用着色器,您或多或少可以完全控制在变换和光栅化阶段发生的事情,并且您可以使用制服制作自己的“状态”,这些制服的更改/更新非常便宜。
知道OpenGL是一个像我上面所说的状态机应该清楚地表明,只要有可能,就应该努力将状态变化保持在最低限度。但是,最有可能的其他因素会影响性能,而不仅仅是启用/禁用状态调用。如果您想了解它们,请阅读。
与旧的固定功能状态调用相关联的状态不的费用以及不是简单的启用/禁用状态的费用可能在成本上有很大差异。值得注意的是,链接着色器和绑定名称(纹理,程序,缓冲区对象的“名称”)通常相当昂贵。这就是为什么许多游戏和应用程序用于根据纹理对其网格的绘制顺序进行排序的原因。这样,他们就不必两次绑定相同的纹理。然而,现在,这同样适用于着色器程序。如果不必要,您不希望将相同的着色器程序绑定两次。此外,并非特定OpenGL版本中的所有功能都是在所有卡上加速硬件,即使这些卡的供应商声称它们符合OpenGL标准。合规意味着他们遵循规范,而不是他们必须有效地运行所有的功能。在这方面,应该记住GL_ ARB __imaging中的某些函数,如glHistogram和glMinMax。
结论:除非有明显的理由不使用着色器!它可以帮助您避免许多不必要的状态调用,因为您可以使用制服。你知道,OpenGL着色器已经存在了大约六年。此外,启用/禁用状态更改的开销可能是一个问题,但通常还有很多可以从优化其他更昂贵的状态更改中获得,例如glUseProgram,glCompileShader,glLinkprogram,glBindBuffer和glBindTexture。
P.S:OpenGL 3.0删除了客户端状态启用/禁用调用。它们是隐式启用的,因为绘制数组是在此版本中绘制的唯一方法。立即模式已被删除。 gl..Pointer调用也被删除了,因为实际上只需要glVertexAttribPointer。
答案 3 :(得分:0)
我被教导的一条经验法则说,随意启用/禁用而不是检查当前状态并仅在需要时进行更改几乎总是更便宜。
那就是说,马克的回答肯定是有效的。