Thursday 14 August 2008

GLState Snippet

Nothing particularly big and clever, just something that seems to work quite well for slightly wrapping up OpenGL state in Scala (complete with manual and probably incomplete syntax highlighting):

trait Bound {
  def unbind: Unit
}
trait Bind {
  protected def bind: Bound
  def boundWith( p: =>Unit ) = {
    val b = bind
    p
    b.unbind
  }
  def &&&(other: Bind) = new Bind {
     class CompositeBound( a:Bound, b:Bound ) extends Bound {
       def unbind = {
        a.unbind
        b.unbind
      }
    }
    protected def bind = {
      new CompositeBound( Bind.this.bind, other.bind )
    }
  }
}

And an implementation for those wonderful glEnable/glDisable chains:

object GLUnchanged extends Bound{ def unbind = () }
object GLStateUtil {
  def glSet( s:Int, v:Boolean ) = if(v) GL11.glEnable(s) else GL11.glDisable(s)
}
case class GLEnabledBound( state:Int, value:Boolean ) extends Bound {
  def unbind = GLStateUtil.glSet(state,value)
}
case class GLSetEnabled( state:Int, value:Boolean ) extends Bind {
  protected def bind = if( GL11.glIsEnabled(state) == value ) {
    GLUnchanged
  } else {
    GLStateUtil.glSet( state, value )
    GLEnabledBound( state, !value )
  }
}


And finally a stupid example of usage (I'm refactoring a lot of the rendering code, so I don't have a real example to hand right now):

val playerState = GLSetEnabled( GL13.GL_TEXTURE_CUBE_MAP, false ) &&&
    GLSetEnabled( GL11.GL_TEXTURE_2D, false ) &&&
    GLSetEnabled( GL11.GL_LIGHTING, false )

playerState boundWith {
  playerModel.render
}

I suspect there's a better way to handle the Bound class returned such that it's not sat in the open, and the terminology may change quite a bit (bind/unbind came from various C++ implementations of similar stuff, none this nice to use). I quite like the way state changes can be composed, although it doesn't even attempt to perform any optimisation on the combined set.

I think it could also be made less verbose to good effect, and possibly a way to skip the redundant state checks would also be nice in case the state of the GL changes due to things outside the scope of these classes - especially for debugging.

At some point I think I'll combine all the resources into implementations of this class.

Anyway, back to playing with code...

4 comments:

Enne Walker said...

I'm not really familiar with Scala, but am I correct in interpreting that what you have posted is essentially a scoping mechanism for GL state? And, one that checks against the current state before setting it?

I've always wondered about the necessity of the CPU-side check for GL state. (I'm only thinking about performance here. If somebody wants to change render state behind my back, then they should be responsible for changing it back.) It's partially that in my experience it's usually quicker to always set render state rather than to test and set. However, that may be my experience with other APIs/platforms, where state gets sent in blocks and so it's nearly free to set state. Do you know how OpenGL drivers handle this?

Snut said...

Exactly right on the scoped state. Scala's a garbage collected language, so this is the kind of thing I'd usually use RAII for and it was interesting to approach it in a different way. This is a fairly idiomatic way to handle it in Scala, from what I've seen, the only change I made is the addition of the Bound class which lets you encapsulate anything required to reset state.

And very true on the check-before-set approach being inefficient, too. I'm pretty sure a lot of this stuff can be disposed of or at least made a bit smarter, but it was helpful for thinking about the code as I wrote it so I left it in. Plus it's the kind of thing I've occasionally found handy in the past when debugging DirectX apps, so having the option there might be useful eventually.

Thinking about it, as the concept is fairly general I think it's possible some use cases may have far greater overhead associated with unchecked, potentially redundant state setting operations. Something like binding render targets, or possibly shaders if they need to be validated, although even in these cases I'd rather profile first. Certainly the little boolean state example was poorly chosen in any case :)

Enne Walker said...

I had thought about doing something similar for Crawl, but there wasn't any way to place the rendering scope such that I wasn't setting state, unsetting it, and then setting it again repeatedly.

I think you're definitely right that not all state change operations have the same cost. Changing shaders, vertex declarations, and render targets are all disproportionately costly. I'd want to profile it, but I still wonder if the driver wouldn't make that check implicitly before going ahead with an operation that would flush the pipeline.

Snut said...

To be honest I've found such simple code as the original snippet mostly useful for prototyping and testing. In a more mature application most state changes seem to just end up being folded into the batching, and you also tend to end up setting such big chunks of state for each batch that there's little point even trying to do the unset/minimal set dance...

The tile support in SS 0.5 looks excellent, by the way!