Binding shaders can be more expensive. As I recall, there’s a penalty for instruction cache. If you’re constantly swapping shaders, you’re probably incurring major cache thrashing… Because most GPUs (Shader Model 2.0 or older) have relatively small instruction caches. This may have changed in newer GPUs, I can’t say for sure? But there’s also potentially complicated pipeline setup whenever you change shaders. So you may want to consider batching your surfaces sorted by shader first, then texture, then individual state changes…
Also, I’d suggest encapsulating all of that stuff into a backend. It’s simpler than it sounds actually. For starters you can simply have it keep track of what objects are bound and encapsulate the bind/unbind operations. When you make the backend call to bind something and it’s already bound, you can simply return.
The concept really covers a much more broad basis though, for instance in my engine the backend encapsulates the matrix/attrib stacks, texture states (per-texture unit), number of requested state changes (grouped by state type: raster, frame buffer, polygon, texture, etc…) and the number of actual states changed (that is, the requested state change wasn’t redundant and something really did change), primitive count, vertex count and in special build configurations you can selectively time individual API calls or types of API calls, etc…
Since it encapsulates most of the OpenGL API, it’s really easy to track down batch generation that’s not very pipeline friendly (i.e. in your case, binding the shader constantly). And redundant pipeline changes are virtually cost free, since they don’t make it farther than the backend which determines there’s no need to notify the API. A good driver should be smart enough to do this on its own, but I find it’s safer to assume the driver is brain dead
If you start out simple and grow, eventually the concept of encapsulating the API becomes first nature. It opens up a whole world of debugging and optimization possibilities once you get into the groove
Here’s a typical backend interface for binding something… L3D_RequestStateChange_Texture and L3D_FinishStateChange_Texture are macros, they count the number of requested state changes and the number of actual state changes respectively. In certain build configs they may also perform performance timings. You can see how if the texture’s already bound, the backend bails out before any API calls are made… Which makes redundant state changes fairly inexpensive (all that happens is the request count increases and a couple of conditionals are evaluated - all inline).
INLINE
void
l3dRenderBackend_GL::L3D_BindTextureCube (int tex_id)
{
L3D_RequestStateChange_Texture ();
// If no texture unit is selected, default to 0...
if (gl_state.current_tex_unit == -1)
L3D_SelectTextureUnit (GL_TEXTURE0_ARB);
if (gl_state.current_textures [gl_state.current_tex_unit].handle == tex_id)
return;
gl_state.current_textures [gl_state.current_tex_unit].handle = tex_id;
gl_state.current_textures [gl_state.current_tex_unit].type = GL_TEXTURE_CUBE_MAP_ARB;
glBindTexture (GL_TEXTURE_CUBE_MAP_ARB, tex_id);
L3D_FinishStateChange_Texture ();
}
It works great if all of your code uses the backend instead of directly making OpenGL API calls. If you have code that can’t be changed in your project, make sure to clear the backend states before using the offending code