State

Inefficient management of GL state leads to increased CPU load that may limit the amount of useful work the CPU could be doing elsewhere. Reducing the number of times rendering is paused due to GL state change will increase the chance of realizing the potential throughput of the GPU. The main point in this section is: do not modify or query GL state unless absolutely necessarily.

Do not set any state redundantly (S1)

All relevant GL state should be initialized during application initialization and not in the main render loop. For instance, occasionally glClearDepthf, glColorMask, or glViewport finds its way into the application render loop even though the values passed to these functions are always constant. Other times they are set unconditionally in the loop, just in case their values have changed per frame. Only call these functions when the values actually do need to change. Additionally, do not automatically set state back to some predefined value (e.g., the GL defaults). That idiom might be useful while developing your application as it makes it easier to re-order pieces of rendering code, but it should not be done in production code without a very good reason.

Avoid querying any GL state in the render loop (S2)

When a GL context is created, the state is initially well-defined. Every state variable has a default value that is described in the OpenGL ES specification ("State Tables"). Except when compiling shaders, determining available extensions, or the application needs to query implementation specific constants, there should be no need to query any GL state. These queries can almost always be done in initialization. Well-written applications check for GL errors in debug builds. If no errors are reported as a result of changing state, it is assumed that the changes are now part of the new GL state. For these two reasons, the current state is always known and you should almost never need to query any GL state in a loop. If an application frequently calls functions that begin with glIs* or glGet*, these calls should be tracked down and eliminated.

Batch on shared state (S3)

An efficient approach to reduce the number of state changes is batching together all draw calls that use the same state (shaders, blending, textures, etc.). For instance, not batching on the shader changes has the form:

[ UseProgram(21), DrawX1, UseProgram(59), DrawY1, 
UseProgram(21), DrawX2, UseProgram(59), DrawY2 ]

Batching on the shaders leads to an improvement (fewer shader changes):

[ UseProgram(21), DrawX1, DrawX2, 
UseProgram(59), DrawY1, DrawY2 ]

It is quite effective to group draw calls based on the shader programs they use. Reprogramming the GPU by switching between shaders is a relatively costly operation. Within each batch, a further optimization can be made by grouping draw calls that use the same uniforms, texture objects and other state. Generating a log of all function calls into the OpenGL ES API is a good approach for revealing poor batching. A tool such as PerfHUDES can conveniently generate this log without rebuilding the GL application; no change to the source code is necessary.

Do not repeat per object state when binding (S4)

Recall that some state is bound to the object it affects. As that state is stored in the object, you do not need to repeat it when you rebind the object. A very common mistake is setting the texture parameters for filtering and wrapping every time a texture object is bound. Another common mistake is updating uniform variables that have not changed value since the last time the particular shader program was used. In particular, when batching opportunities are limited, repeating per object state generates enormously inefficient GL code that can easily have a measurable impact on framerate.

Enable backface culling whenever possible (S5)

Always enable face culling whenever the back-faces are not visible. Rendering the back-faces of primitives is often not necessary.

Note:

The default GL state has backface culling disabled, so this is one state that should almost always be set during application initialization and be left enabled for the application lifetime.