## Recommended Posts

Stoic    368
Hi all - I've been writing shaders for quite some time, but I'm still a little bit confused about batching and what the driver does when shader parameters change and shaders are (re)bound, etc... (I'm using Cg as my shader solution, on PC) Let me give two relatively simple examples, simple texture change on static meshes, and different instances of animating characters in a vertex shader... The simplest case is that I have two boxes with the same lighting effects and environment, but different textures. (Like a basic "static mesh effect") Let's say that I have my static meshes sorted and stored in a single list or array(and I know they're all opaque, etc). What is the appropriate way to handle rendering?
   BindStaticMeshShader()
foreach (static mesh)
{
SetTextureParam( this mesh's texture )
SubmitMesh
}

Or do I have to do this?
   foreach (static mesh)
{
SetTextureParam( this mesh's texture )
SubmitMesh
}

It seems to be basically the same problem for handling animation in the vertex shader... Let's say that I'm doing a morph-target style Vertex blend in a vertex shader. Like, say I have a "blob" entity in my game that I'm instancing, but each blob is at a different point in the animation (so 't' values would be different for each instance of the entity). For skinning, it would be an even bigger deal since you have that huge array of bone vectors to pass in... Can I just bind the shader a single time, then use the Cg runtime to update the values of the parameters in between submissions? Or do I have to set the parameters, then rebind the shader? I guess I'm confused by not understanding how and where compiled shaders are stored and treated by the driver. What actually happens when you change a parameter? Does it patch it into a compiled version of the shader and upload it to the graphics card, or does something else happen? Can someone try to explain this to me? Thanks for any help!

##### Share on other sites
Ashkan    451
Hi,

Before we go any further, let me give you a little background on how current hardware accelerated rendering APIs (OpenGL and Direct3D) work:

OpenGL and Direct3D are both Finite State Machines (FSM for short). Wikipedia's entry on FSM reads:

Quote:
 Quoted from Wikipedia's entry on FSMs:A finite state machine (FSM) or finite state automaton (plural: automata) or simply a state machine is a model of behavior composed of a finite number of states, transitions between those states, and actions.

Automata theory is a big field of computer science but what that means for us is that OpenGL and Direct3D are both like a big machine with thousands of switches to play with. Some of these switches are binary switches which can be turned on or off, like glEnable()/glDisable() which turn on/off server side switches (I delibarately use the term "Switch" instead of the more common term "State" to avoid later confusions. We'll see the definiation of "State" in a moment). Others can have several values, like setting the number of active lights in a fixed function pipeline setup. Each and every combination of these switches represents a unique state. Consequently, the current state of the pipeline is the current combination of switches and their values. Moving from one state to another is called a transition between states which takes place as a result of undertaking an action.

A nice property of State Machines is that changing the value of a given switch does not reset the values of all other swtiches; the whole system makes a transition to another state with all other switches unchanged and only the newly changed switch having a new value.

That's actually where "Fixed Function Pipeline" stems from. In a fixed function pipeline you don't get to program anything since all you're allowed to do is to play with the available switches in order to make transitions to other states. In a Programmable Function Pipeline on the other hand, you'll have the luxury of manually programming the pipeline, but the term can be misleading, since even a programmable function pipeline is not totally programmable. You're just allowed to program several stages of this pipeline, but it's really far from being totally completely programmable. Other stages of the pipeline (like Primitive Assembly or Setup/Rasterization stage) follow the old paradigm and are just fixed function.

With that said, let's move on to your questions:

Quote:
 Original post by StoicLet me give two relatively simple examples, simple texture change on static meshes, and different instances of animating characters in a vertex shader...The simplest case is that I have two boxes with the same lighting effects and environment, but different textures. (Like a basic "static mesh effect") Let's say that I have my static meshes sorted and stored in a single list or array(and I know they're all opaque, etc). What is the appropriate way to handle rendering? BindStaticMeshShader() foreach (static mesh) { SetTextureParam( this mesh's texture ) SubmitMesh }Or do I have to do this?  foreach (static mesh) { SetTextureParam( this mesh's texture ) BindStaticMeshShader() SubmitMesh }

The first one. That's actually the basic idea behind culling redundant states. You shouldn't be changing states when you don't need to. Why? Because transitions between states do not reset the value of other switches. BindStaticMeshShader() sets a switch and results in a transition to a new state, but calling SetTextureParam() afterwards will not reset the value of other switches, although it too results in a transition to a new state.

Quote:
 I guess I'm confused by not understanding how and where compiled shaders are stored and treated by the driver.

In OpenGL, shaders are compiled by the driver and stored where the driver best sees fit (usually VRAM, but that's totally up to driver writers).

Quote:
 What actually happens when you change a parameter? Does it patch it into a compiled version of the shader and upload it to the graphics card, or does something else happen? Can someone try to explain this to me?

Do you recompile a DLL every single time you pass a different parameter to a function that resides inside that DLL?

That's what we've got variables for, so we can have a piece of code that runs for arbitrary input. The shader code is compiled and loaded into GPU accessible memory. Instructions are fetched by GPU one after another and executed. Each insruction has a number of operands on which it operates. The operands are stored in registers. These operands are what you see as parameters. The net result is that these calls will just set a value inside a register which shader instructions fetch and use. No recompilations is required.

Hope that helps.