glIsEnabled() and performance question

Started by
9 comments, last by v_d_d 21 years, 1 month ago
From the NVIDIA Opengl SDK performance FAQ

quote:
39. How expensive are glEnable and glIsEnabled commands?

It depends on the enable how expensive a given enable is, but there are some general rules.

The one expensive thing about glEnable/glDisable is that they end up being quite large switch statements. The compiler makes the switch statements into a big binary decision tree. Since you (hopefully) don''t re-enable something multiple times, the branches in the binary decision tree tend to be mispredicted. This is just an unfortunate part of OpenGL''s design. At some point, NVIDIA might change our driver implementation to a perfect hash table instead of binary decision tree, but that is a low priority.

Both glEnable and glIsEnabled both have large switch statements.

In general, the glEnable/glDisable call itself is cheap since usually it just sets a "dirty" bit and returns.

At the next rendering call (glBegin, glDrawPixels, etc), the dirty bits are tested. If any are set, the driver goes through a validation process based on what bits have been set. This can be fairly expensive.

Apps can benefit from inline routines such as:

static void INLINE
myEnableLightingTest(__MYcontext *mystate)
{
if (mystate->depthTest) {
// already enabled
} else {
glEnable(GL_LIGHTING);
mystate->lighting = 1;
}
}

static void INLINE
myDisableLightingTest(__MYcontext *mystate)
{
if (mystate->lighting ) {
glDisable(GL_LIGHTING);
mystate->lighting = 0;
} else {
// already disabled
}
}

These custom routines are definitely better than using:

static void INLINE
myEnableLightingTest(void)
{
if (glIsEnabled(GL_DEPTH_TEST)) {
// already enabled
} else {
glEnable(GL_LIGHTING);
}
}

The above code has to determine two different large binary decision trees if lighting is currently disabled! Plus both glIsEnabled and glEnable both test if you are in a begin/end. Recording the last state yourself is a better approach. The other problem with determining two large binary decision trees is that this tends to overwhelm the CPU''s internal branch prediction logic and thereby increases the mispredicted branches in other code.

It may also be interesting to have another inline routine for when you definitely know that the state is being set different:

static void INLINE
myForceEnableLightingTest(__MYcontext *mystate)
{
assert(mystate->lighting == 0);
glEnable(GL_LIGHTING);
mystate->lighting = 1;
}

The same practice applies for myForceDisableLightingTest.

Because of the dirty bits, redundant glDisable/glEnable calls are not particularly expensive:

glDisable(GL_LIGHTING);
glEnable(GL_LIGHTING); // silly but not particularly expensive
glDisable(GL_LIGHTING); // silly but not particularly expensive
glBegin(...); // a single validation occurs at glBegin

Though the following code is very expensive:

glEnable(GL_LIGHTING);
glDrawArrays(...);
glEnable(GL_LIGHTING); // forces a useless validation
glDrawArrays(...);

This advice generally applies to all OpenGL state.

As a rule, the driver does not "second guess" any OpenGL calls that you make to check if the state is already being set as the state is currently. The rationale is that applications can always "short-circuit" null state changes more efficiently than the driver.

Plus there is typically always at least one check about whether the driver is in a begin/end or not that must be performed. There is also often a complex switch statement that must be decided (as in the glEnable/glDisable case).

Note that the begin/end check cannot be short-circuited by the driver in advance of checking if the state is being set to the existing state.

Consider:

glEnable(GL_LIGHTING);
glBegin(...);
glEnable(GL_LIGHTING); // even though redundant, should generate an error

This topic is closed to new replies.

Advertisement