Skip to content

Instantly share code, notes, and snippets.

@JamiesWhiteShirt
Last active July 7, 2024 02:26
Show Gist options
  • Save JamiesWhiteShirt/ff2521936a83ebc10fd6893e206a6770 to your computer and use it in GitHub Desktop.
Save JamiesWhiteShirt/ff2521936a83ebc10fd6893e206a6770 to your computer and use it in GitHub Desktop.
Why you should never use OpenGL directly or GlStateManager.pushAttrib/popAttrib

OpenGL calls, OpenGL state, and state leaks

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

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.

How to corrupt GlStateManager

Direct OpenGL calls

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.

pushAttrib/popAttrib

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment