Making OpenGL calls has a performance cost. It is therefore desirable to reduce the amount of calls.
OpenGL has global state. The state is modified by enabling/disabling blending/culling/alpha testing/etc, specifying the blend function, changing the cull face, etc.
A state leak happens when a method leaves OpenGL in a different state after being called. This is generally undesirable as it often breaks expectations about the OpenGL state in other methods.
Modifying the OpenGL state requires OpenGL calls. Minecraft is not particularly optimized for reducing the amount of OpenGL calls, but makes an attempt using GlStateManager.
GlStateManager manages a part of the OpenGL state and maintains a copy of this state. This part of the state covers most of the game's needs.
It manages the OpenGL state and maintains the copy by wrapping all necessary OpenGL calls. Calling the GlStateManager method will modify the copy. If the copy changes, an OpenGL call is made to change the OpenGL state.
It is essential that the OpenGL state and GlStateManager's copy match for GlStateManager to function. It is unfortunately very easy to corrupt GlStateManager's copy of the state. This may result in unexpected behavior for others using GlStateManager after you, and they will probably not know that you are the culprit.
Making direct OpenGL calls is a very simple way to ruin someone's day:
Call | GLSM.boundTexture |
GL11.boundTexture |
---|---|---|
GLSM.bindTexture(1) |
-1 | -1 |
-> if (GLSM.boundTexture != 1) GL11.glBindTexture(1) |
-1 | -1 |
-> GLSM.boundTexture = 1 |
-1 | 1 |
draw1() |
1 | 1 |
GLSM.bindTexture(1) |
1 | 1 |
-> if (GLSM.boundTexture != 1) GL11.glBindTexture(1) |
1 | 1 |
-> GLSM.boundTexture = 1 |
1 | 1 |
draw2() |
1 | 1 |
GL11.bindTexture(2) !!! |
1 | 2 |
draw3() |
1 | 2 |
GLSM.bindTexture(1) |
1 | 2 |
-> if (GLSM.boundTexture != 1) GL11.glBindTexture(1) !!! |
1 | 2 |
-> GLSM.boundTexture = 1 |
1 | 2 |
draw4() !!! |
1 | 2 |
draw4()
expects texture 1 to be bound, but texture 2 is bound.
GL11.glPushAttrib()
is a method that saves the OpenGL state for recovery at a later time using GL11.glPopAttrib()
. The methods are designed to prevent state leaks, but when GlStateManager is present, they actually cause state leaks.
GlStateManager is not designed to deal with pushAttrib
/popAttrib
. The fact that GlStateManager.pushAttrib()
and GlStateManager.popAttrib()
exist should be considered a bug.
Call | GLSM.boundTexture |
GL11.boundTexture |
---|---|---|
GLSM.bindTexture(1) |
-1 | -1 |
-> if (GLSM.boundTexture != 1) GL11.glBindTexture(1) |
-1 | -1 |
-> GLSM.boundTexture = 1 |
-1 | 1 |
draw1() |
1 | 1 |
GLSM.pushAttrib() !!! |
1 | 1 |
GLSM.bindTexture(2) |
1 | 1 [1] |
-> if (GLSM.boundTexture != 2) GL11.glBindTexture(2) |
1 | 1 [1] |
-> GLSM.boundTexture = 2 |
1 | 2 [1] |
draw2() |
2 | 2 [1] |
GLSM.popAttrib() !!! |
2 | 2 [1] |
GLSM.bindTexture(2) |
2 | 1 |
-> if (GLSM.boundTexture != 2) GL11.glBindTexture(2) !!! |
2 | 1 |
-> GLSM.boundTexture = 2 |
2 | 1 |
draw3() !!! |
2 | 1 |
draw3()
expects texture 2 to be bound, but texture 1 is bound.
Then how do I prevent state leaks? Manually. You'll have to carefully monitor what state you change, then change it back.