PDA

View Full Version : How Expensive are redundant State Changes?



HenryR
01-17-2001, 02:15 PM
Title says it all really. Are redundant state changes (i.e. setting state that is already set) caught by the driver before causing a hardware flush? I kind of assume they are, so the only overhead in setting redundant state is the API call. Are there any situations where redundant state changes can be very bad?

Cheers,

Henry

mcraighead
01-17-2001, 04:51 PM
Don't send redundant state changes. Just don't.

Either design your code to avoid them or wrap GL functions to mirror state in your app. Test whether you're doing a redundant state change, and if you are, don't call into GL.

- Matt

Janne
01-17-2001, 05:13 PM
Ok, deleted the previous answer since it was obviosly the wrong one.

Since I'm now forced to write an state mirror layer now http://www.opengl.org/discussion_boards/ubb/wink.gif, I was wondering the reason why. I would quess that the OpenGL states are mirrored in the system memory for fast access, but the api-overhead and maybe some other faces are making the redundant changes noticeable slower than using own state checker?

Well I had a somewhat working prototype for mirroring the states, so I quess it's time to dust it off and put it back to use http://www.opengl.org/discussion_boards/ubb/smile.gif

DJ Tricky S
01-17-2001, 05:15 PM
They can end up being *very* bad, I know from experience. The last game I worked on I was brought in to rewrite the OpenGL engine. When I came on board the game was getting maybe 2 fps on a PIII/500 with GeForce256. When I got done writing the new engine for it the game would stay pegged at 60 fps on this same setup. The biggest thing I did as far as improving upon the old engine in terms of speeding things up was that the old one made redundant state changes up the yang-yang, and my new engine makes no redundant state changes. So yes, they can be *very* bad. Never do that!

HenryR
01-17-2001, 05:40 PM
Thanks for the replies. I'd be interested in knowing, if possible, why they can be so bad.

Thing being, I now may have to litter if..else clauses all around my renderer, or actually write a wrapper for GL, which I don't really want to have to do - adding another level of indirection to API calls doesn't seem like a good idea, but I will if redundant state changes turn out to be really bad.

Cheers,

Henry

Janne
01-17-2001, 06:06 PM
Writing a wrapper for the needed state changes is probably better than just throwing a bunch of if statements around your code. First of all it's easier to read the code and you still must pass the information about the state around in some meaningfull way and a singleton type of class comes really handy for the job, if you use an object oriented language.

As for adding another abstraction layer on top of OpenGL, well I quess it's pretty much inevitable, since in some ways even if you keep track of the state in your code it is no different than an abstraction layer from a functional point of view http://www.opengl.org/discussion_boards/ubb/wink.gif

And obviously you probably don't need to mirror all the OpenGL states, just the ones you really need to keep track on. This should speed up the creation of the wrapper significantly since you can easily add later more states to be mirrored. You could also add functions that handle a group of state changes by once and to possibly reduce the number of case statements.

- Janne

mcraighead
01-17-2001, 06:20 PM
Every time you call what looks like a "simple" OpenGL function like glBlendFunc, the following would happen with a "typical" driver, not necessarily representative of all drivers:

- You call into a function pointer table in opengl32.dll. This table jumps to the actual function in the driver.
- The driver accesses the thread-local state to get the current GL context (GC). This uses direct access to OS data structures; it's cheap (1 instruction) on NT, but a little more expensive on 9x (3 instructions, I think).
- The driver checks whether you are inside a Begin/End.
- The driver checks whether the source blend factor is valid.
- The driver checks whether the destination blend factor is valid.
- The driver copies the src and dst blend factors into shadows inside the GC.
- The driver sets a bit to indicate that some rasterization state has changed.

Later, when you do a Begin, DrawElements, etc.:
- The driver looks at what dirty bits are set.
- If any dirty bits are set, the driver makes sure that it can still render in HW.
- Because the rasterization dirty bit is set, the driver grabs all the rasterization state and munges it into the way the HW expects to be told how to do rasterization (often a packed bitfield or somesuch). The driver sends this to the HW.

Variants include not setting a dirty bit and instead sending the state immediately to the HW, or not shadowing the state in the driver at all, but the summary is that a redundant state change is simply a bad thing.

I haven't even considered the possibility of a stall inside the HW. We do a pretty good job of making sure that such stalls are minimal, but sometimes they still exist.

If you avoid sending redundant states, on a typical driver, you will at avoid at least a check for Begin/End and an error check on each parameter. That by itself is a pretty nice savings.

Most drivers don't actually look for redundant state changes explicitly because this would penalize smart apps that don't send redundant state changes to help dumb apps that do.

- Matt

Nil_z
01-17-2001, 06:24 PM
I am not very clear about the wrapper Janne refered, how can those if-else statement be avoided? I must check the current value to see if I can skip setting it. Can you explain it a bit more?
Does those switch state change cost heavily? something like glEnable*() and glDisable*().

[This message has been edited by Nil_z (edited 01-17-2001).]

mcraighead
01-17-2001, 06:33 PM
Also, when I'm talking about wrappers, it's nothing more complicated than:




void MyBlendFunc(GLenum src, GLenum dst)
{
static GLenum curSrc = GL_ONE;
static GLenum curDst = GL_ZERO;

if ((src == curSrc) && (dst == curDst)) {
return;
}
curSrc = src;
curDst = dst;
glBlendFunc(src, dst);
}


But you can do other clever things too. Here are two examples I can think of:




void MyEnableLights(int n)
{
int i;
static int lastN = 0;

if (n < lastN) {
for (i = n; i < lastN; i++) {
glDisable(GL_LIGHT0 + i);
}
} else if (n > lastN) {
for (i = lastN; i < n; i++) {
glEnable(GL_LIGHT0 + i);
}
}
}

enum BLEND_MODE {
NO_BLENDING,
TRANSPARENCY,
TRANSPARENCY_ADDITIVE,
ADDITIVE,
SRC_TIMES_DST,
BLEND_MODE_COUNT,
};

GLenum blendModeTable[BLEND_MODE_COUNT][2] = {
GL_ONE, GL_ZERO,
GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA,
GL_SRC_ALPHA, GL_ONE,
GL_ONE, GL_ONE,
GL_ZERO, GL_SRC_COLOR,
};

static void MyBlendFunc(BLEND_MODE mode)
{
static BLEND_MODE lastMode = NO_BLENDING;

if (mode == lastMode) {
return;
}

if (mode == NO_BLENDING) {
glDisable(GL_BLEND);
} else {
if (lastMode == NO_BLENDING) {
glEnable(GL_BLEND);
}
glBlendFunc(blendModeTable[mode][0], blendModeTable[mode][1]);
}
lastMode = mode;
}


I take no responsibility for any bugs in this code. http://www.opengl.org/discussion_boards/ubb/smile.gif You get the idea, though. OpenGL is a low-level graphics API. That gives you the freedom to wrap any higher-level API you like on top of it, and that higher-level API can take responsibility for ensuring that there are no redundant state changes.

- Matt

Eric
01-18-2001, 12:11 AM
Originally posted by mcraighead:
I take no responsibility for any bugs in this code.

Got an error on this line:

GLenum blendModeTable[BLEND_MODE_COUNT][2] =

'BLEND_MODE_COUNT': undeclared identifier

http://www.opengl.org/discussion_boards/ubb/smile.gif http://www.opengl.org/discussion_boards/ubb/smile.gif http://www.opengl.org/discussion_boards/ubb/smile.gif http://www.opengl.org/discussion_boards/ubb/smile.gif http://www.opengl.org/discussion_boards/ubb/smile.gif

Eric

mcraighead
01-18-2001, 01:19 AM
Well, I did catch one bug later, in MyEnableLights: it doesn't update lastN.

Not sure why BLEND_MODE_COUNT is undeclared. It's in the enum!

In any case, pay attention to the ideas, not the specific code. http://www.opengl.org/discussion_boards/ubb/smile.gif

- Matt

Eric
01-18-2001, 01:48 AM
My fault, Matt ! I had modified the enum (don't know how...).

Anyway, I understand the principles...

Actually, I had never ever thought about redundant state changes... The only 'optimization' I am doing right now is materials sorting (i.e. glBindTexture + glMaterial).

Do you think it would be worth to check that type of thing for glMaterial as well ? I mean, it happens that when changing material, you switch texture, ambient and specular but not diffuse (sounds stupid actually but that's an example !).

Should I check for (lastDiffuse==newDiffuse) similar to your blend mode ????

I guess I have to start writing OpenMY (hehe, replace all "gl" calls by "my" calls !).

http://www.opengl.org/discussion_boards/ubb/smile.gif http://www.opengl.org/discussion_boards/ubb/smile.gif http://www.opengl.org/discussion_boards/ubb/smile.gif http://www.opengl.org/discussion_boards/ubb/smile.gif

Regards.

Eric

okapota
01-18-2001, 03:37 AM
does it really worth spending the time to find out if state switching should be done or not? i mean, does state changes cost more time than thos if's and for's?

Cab
01-18-2001, 05:42 AM
is it true that I Can leave blending always enabled and when calling my disableBlending function put it to ONE, ZERO?
Is it the same in terms of performance?
Or is it better the Matt example?
In the last pages of the Red Book, when talking about invariance it uses this example but I don't know if the performance will suffer.
I'm using it and it seems to work properly.

coco
01-18-2001, 06:42 AM
Are redundant glEnable, glDisable, glEnableClientState and glDisableClientState so expensive too (besides the dll overhead)?
The thing is I want to have a balance between fast code and well structured code (dont want to have n state variables in my code all over the place).

zed
01-18-2001, 10:33 AM
yes i backup what matt saiz i have found a speedup for doing the checking myself eg

instead of going
glEnable(GL_LIGHTING);
draw car
glEnable(GL_LIGHTING);
draw house

using this will result in a speed increase

if (!GL_STATE_is_the_lighting_enabled)
{ glEnable(GL_LIGHTING);
GL_STATE_is_the_lighting_enabled = true;
}
draw car
if (!GL_STATE_is_the_lighting_enabled)
{ glEnable(GL_LIGHTING);
GL_STATE_is_the_lighting_enabled = true;
}
draw house

i know theres a bit more typing envolved but its typing u can do in braindead mode
i suppose u could make a GL_STATE structure and chuck all the opengl state statuses in there.
but persoanlly i prefer globals http://www.opengl.org/discussion_boards/ubb/smile.gif

mcraighead
01-18-2001, 11:01 AM
Yes, glEnable/glDisable are also slow. Think of how many enable/disable states there are in OpenGL (in unextended GL, there must be about 50 or so), and then consider that they are not contiguous enumerant values. That means you end up with a big switch statement. Not only that, but sending a redundant Enable or Disable will cause us to set dirty bits, etc., causing more work at a later time.

I will repeat what I said before. If you care about performance, follow the advice of my previous post:



Don't send redundant state changes. Just don't.

Either design your code to avoid them or wrap GL functions to mirror state in your app. Test whether you're doing a redundant state change, and if you are, don't call into GL.


Obviously, it is preferable to design your code such that you never send a redundant state change in the first place rather than to use a wrapper.

Any wrapper will have a break-even point. If more than X% of the GL calls it replaces would have been redundant, the wrapper will be a speedup. Depending on the implementation and the wrapper, I'd generally peg X between around 2% to 20%. This is not a scientific estimate. You might want to do your own tests. But if, say, 50% of your Enable or Disable calls are redundant (which could be true if the values were completely random), a wrapper is a CLEAR win.

Note also that you can make wrappers be inline functions, and if you're wrapping Enable/Disable, you should have a separate function for each enable you wrap so you don't need a big switch statement:




int lightingEnabled = 0;

inline static void MyEnableLighting(void)
{
if (!lightingEnabled) {
glEnable(GL_LIGHTING);
lightingEnabled = 1;
}
}

inline static void MyDisableLighting(void)
{
if (lightingEnabled) {
glDisable(GL_LIGHTING);
lightingEnabled = 0;
}
}


Or, alternately, if you prefer:




int lightingEnabled = 0;

// because this is an inline function, the compiler should optimize the case where enabled is a constant value 0 or 1
inline static void MySetEnableLighting(int enabled)
{
if (enabled == lightingEnabled) {
return;
}
if (enabled) {
glEnable(GL_LIGHTING);
} else {
glDisable(GL_LIGHTING);
}
lightingEnabled = enabled;
}


Watch out, if you sometimes bypass your wrapper, your program will get confused. If you wrap a GL state function, ALWAYS use the wrapper.

- Matt

mcraighead
01-18-2001, 11:04 AM
I don't know what my whole obsession with "static" for the functions' declarations was. By no means must those functions be static. http://www.opengl.org/discussion_boards/ubb/smile.gif

- Matt

coco
01-18-2001, 11:40 AM
About "Watch out, if you sometimes bypass your wrapper", that is the kind of things I think can make my code less clean, and more prone to bugs, but I get the point "state changes are expensive".
I have read alot about "minimize state changes" stuff, but really got the idea of how much of a performance hit is in this topic.


[This message has been edited by coco (edited 01-18-2001).]

Michael Steinberg
01-18-2001, 12:13 PM
Would that inline be nothing more than




#define EnableLighting(enabled) ( \
if (enabled != lightingEnabled) \
if(enabled) glEnable(GL_LIGHTING); \
else glDisable(GL_LIGHTING);\
lightingEnabled = enabled;


That should work or not? Does the precompiler do that find and replace faster? I mean, it simply has to cut and paste, where the compiler needs to replace the function call with the actual function. Well, okay...


[This message has been edited by Michael Steinberg (edited 01-18-2001).]

mcraighead
01-18-2001, 12:31 PM
Macros are prone to bugs, as far as I'm concerned. Inline functions and const variables have made #define mostly obsolete, as far as I'm concerned.

But do whatever makes you happy.

- Matt

HenryR
01-18-2001, 01:36 PM
Fair enough. Time to spend a couple of hours wrapping GL - that little portion of it which I use.

If one retains the semantics and types of the original gl* calls, I can't see using a wrapper could be any more error prone, since it would be practically transparent to the renderer (which is in itself wrapping gl at a higher level).

-- Henry

Olive
01-18-2001, 10:26 PM
What about state objects? I'd see those as some sort of specialized display lists for setting the rendering state. I've read something about that in some ARB meeting notes (by the way, why aren't the latest notes released?). I think there's a similar notion in DirectX but I don't know if it really is useful, especially at driver level.

By the way, I do state caching on the application side and it definitely speeds up things. Not as much on a GeForce (about 4~5% on my test scene) as on a FireGL 1000 (around 30%) but it's still worth it.

Cab
01-19-2001, 01:26 AM
Any answer to this question?

Is it the same (in terms of rendering performance) glDisable(GL_BLEND) that glBlendFunc(GL_ONE, GL_ZERO)?
If blending is enabled the card is always reading the framebuffer when rendering or it depends on the BlendFunc selected?

Thanks

Michael Steinberg
01-19-2001, 02:00 AM
Well, I think I have misexplained me, but you aswered me anyway, Matt. I asked wether that inline would do the same as a #define. That isn't described in my c reference.

mcraighead
01-19-2001, 10:05 AM
PLEASE just disable blending.

We absolutely detest apps that think that it's just as good to set a feature to a mode where it is "essentially" off rather than just disabling it.

That goes for blending (ONE, ZERO), line and polygon stipple (0xFFFF and 32 0xFFFFFFFF's), logic op (COPY), etc.

- Matt

Cab
01-20-2001, 03:41 AM
Thank you, Matt.

Siigron
01-20-2001, 10:05 AM
interesting discussion. thank you, mcraighead, for giving me a good explanation of why it's so expensive to call OGL functions. never thought about all that http://www.opengl.org/discussion_boards/ubb/smile.gif
this might be slightly offtopic, but anyway... don't forget glPushAttrib and glPopAttrib.
it can be very useful sometimes. i used it in a general overlay class in Java a few months ago. since the class didn't know of any states, it would have been very inefficient (and ugly) to check for them. this is what i did instead:
glPushAttrib(GL_ENABLE_BIT); /* since we don't want lighting, z-buffering, stencil-buffering etc. to be enabled */
glEnable(GL_TEXTURE_2D); /* we do want texturing */
glEnable(GL_BLEND); /* and blending... */
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
... draw the objects in this overlay
glPopAttrib();

actually, i'm also wondering if this could be done better?

mcraighead
01-20-2001, 01:29 PM
PushAttrib and PopAttrib? Blech, no. If you care about performance, don't use them at all, ever.

If all you need to save and restore is one enable, use IsEnabled (even though I strongly discourage the use of IsEnabled in general).

Better would be to cache the enable state on your side in the app, just like those wrappers do, so IsEnabled isn't necessary.

Better still would be to design your app so you don't need to save and restore state like that.


One of the biggest problems with OpenGL is that it makes it very easy to write braindead apps that use features that turn out to be really hard to implement in an efficient way. PushAttrib/PopAttrib is one example of this -- I wish they would just go away. Another example is immediate mode; I consider immediate mode to be a rather poor API design choice.

- Matt

Siigron
01-21-2001, 02:28 AM
from an old Red Book... appendix H
under OpenGL Performance Tips
"Use glPushAttrib() and glPopAttrib() to save and restore state values. Use query functions only when your application requires the state values for its own computations."
ouch. dunno if it's still there. or maybe i misunderstood. *sigh* gotta rewrite that class, hehe =)
it's really hard to know what functions one should use and which ones to stay away from. perhaps NV could put up a "slow functions list" or something? it could be useful.

and regarding immediate mode... i think it should die die die. it is useful for total newbies since it is easy to understand, but for anything more complicated than drawing a quad, vertex arrays should be used. using glVertex* instead of arrays are probably the most common mistake among new GL programmers.

mcraighead
01-21-2001, 10:44 AM
That's bad advice. On most implementations, PushAttrib and PopAttrib are ***SLOW***.

- Matt

Siigron
01-21-2001, 11:36 AM
>perhaps NV could put up a "slow functions list" or something? it could be useful.

... for things like this.

HenryR
01-21-2001, 12:51 PM
Incidentally, I just finished writing a little wrapper for gl (20mins coding, not as long as I thought) that caches state on the application side. With a few test runs, I saw about 70% redundancy in the state setting I was doing. I haven't seen much of a speed up (because my app. is fill rate limited), but it makes me feel happier. The only annoying thing was putting all the multitexture state in. (Multitexture annoys me anyway, since my shader system is sufficiently general that it's normally pretty unlikely two succesive stages can be collapsed into a single multitexture stage. Things like different alpha testing for each stage, and wanting to use the fragment colour at each stage put paid to this. Sigh)
Anyway, once that was done, it was really trivial to do.

Henry

coco
01-22-2001, 04:18 AM
HenryR:
I did the state wrapper too, also got 70%+ redundancy, and still got almost no performance increase in some tests because they where geometry-limited (150k+ vertices per frame), yet it increased a little bit more in scenes where I have a bunch of different objects and materials/textures.

MarcusL
05-23-2001, 02:57 PM
Writing an OpenGL wrapper seems to be something like reinventing the wheel, i.e. most developers have to do this. Couldn't this be done once and for all?

Likewise, I'd like to see a C++ wrapper that uses overloading instead of having different function names and doing things a bit smarter and more cleanly (I.e. no void-pointers and setting the type by defined varlues, etc). Also, enums should be used, as to get type safety and not plugging the wrong kind of value into a function.

While we are at it, why not skip the gl-prefix and put it all into a namespace?

I'm about to write all this myself soon, but I really think it's something that should be offered or done already. (As an extension of course, I suspect there's plenty of people still writing in pure-C.)

Anyway, if I get around to it, it'll be publically available, but I feel that this is something that suits the open source principle perfectly.

Uh.. got a bit off topic there, perhaps this should be put in a separate thread?

Anyway, any opinions?

ET3D
05-23-2001, 03:33 PM
I've used Magician (a Java OpenGL wrapper), and indeed it's a little nicer to get rid of the suffixes. It didn't handle state changes, though.

Maybe you could make it a little more object oriented, and call it Direct3D? http://www.opengl.org/discussion_boards/ubb/wink.gif

Mud
05-23-2001, 04:49 PM
Leaving blending enabled is usually a bad idea because it may force some hardware to fetch frame buffer data.

pleopard
05-24-2001, 05:26 AM
I have implemented such a state manager and I am willing to make it available to anyone. I am posting the source code including a test program here but ... Email me at Paul_H_Leopard@hotmail.com and I will send you the original project including the source code (with formatting).

// OpenGLStateManager.h

#ifndef OPENGLSTATEMANAGER_INCLUDED
#define OPENGLSTATEMANAGER_INCLUDED

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Description:
//
// Used to shadow the current OpenGL state to reduce the performance
// penalties associated with OpenGL state queries and push/pop operations.
//
// Author: Paul H. Leopard, Paul_H_Leopard@Hotmail.com
//
//-------0---------0---------0---------0---------0---------0---------0---------0

// .................................................. ..........................
// This class declaration requires knowledge of other class interfaces and/or
// declarations contained in the following header files:

// C++ Standard Library includes

#include <map>

// Win32 and OpenGL includes

#ifdef WIN32
#include <windows.h>
#endif
#include <GL/gl.h>

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~
/** Used to shadow the current OpenGL state to reduce the performance
penalties associated with OpenGL state queries and push/pop operations.
@author Paul H. Leopard - Paul_H_Leopard@Hotmail.com
*/
class OpenGLStateManager
{

// __________________________________________________ __________________________
// Public methods.

public:

// __________________________________________________ __________________________
// .................................................. ..........................
// Lifecycle methods

/** Constructor.
*/
OpenGLStateManager();

// .................................................. ..........................
// Accessor methods

/** Determine if a given state is enabled.
@param glStateId The OpenGL state to test.
@return true if the state is enabled, false otherwise.
*/
bool isEnabled(int glStateId) const;

/** Retrieve the active OpenGL matrix (GL_MODELVIEW_MATRIX,
GL_PROJECTION_MATRIX, or GL_TEXTURE_MATRIX).
@return The active OpenGL matrix (GL_MODELVIEW_MATRIX,
GL_PROJECTION_MATRIX, or GL_TEXTURE_MATRIX).
*/
int activeMatrix() const;

// .................................................. ..........................
// Manipulator methods

/** Enable or disable a given state.
@param glStateId The OpenGL state to test.
@param state true to enable the state, false to disable the state.
*/
void enable(int glStateId, bool state);

/** Enable a given state.
@invariant Invalid OpenGL state requested.
*/
void enable(int glStateId);

/** Disable a given state.
@param glStateId The OpenGL state to test.
*/
void disable(int glStateId);

/** Toggle a given state.
@param glStateId The OpenGL state to toggle.
*/
void toggleState(int glStateId);

/** Synchronize this instance with the current OpenGL state.
*/
void glSynchronize();

/** Set the active OpenGL matrix.
@param matrix The desired OpenGL matrix (GL_MODELVIEW_MATRIX,
GL_PROJECTION_MATRIX, or GL_TEXTURE_MATRIX).
*/
void setActiveMatrix(int matrix);

// .................................................. ..........................
// Operators

// __________________________________________________ __________________________
// Private properties.

private:

/// Shadow of the OpenGL enabled states
mutable std::map<int,bool> m_EnableMap;

/// Lazy initialization indicator.
mutable bool m_Initialized;

/// Currently active OpenGL matrix.
mutable int m_CurrentMatrix;

}; // class OpenGLStateManager

// __________________________________________________ __________________________
// Inlined methods.

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~
// Enable a given state.

inline void OpenGLStateManager::enable(int glStateId)
{
enable(glStateId,true);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~
// Disable a given state.

inline void OpenGLStateManager::disable(int glStateId)
{
enable(glStateId,false);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~
// Toggle a given state.

inline void OpenGLStateManager::toggleState(int glStateId)
{
if (isEnabled(glStateId))
{
disable(glStateId);
}
else
{
enable(glStateId);
}
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~
// Retrieve the active OpenGL matrix (GL_MODELVIEW_MATRIX,
// GL_PROJECTION_MATRIX, or GL_TEXTURE_MATRIX).

inline int OpenGLStateManager::activeMatrix() const
{
return m_CurrentMatrix;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~
// Retrieve the active OpenGL matrix (GL_MODELVIEW_MATRIX,
// GL_PROJECTION_MATRIX, or GL_TEXTURE_MATRIX).

inline void OpenGLStateManager::setActiveMatrix(int matrix)
{
if (m_CurrentMatrix!=matrix)
{
glMatrixMode(matrix);
m_CurrentMatrix = matrix;
}
}

#endif // OPENGLSTATEMANAGER_INCLUDED

// EOF: OpenGLStateManager.h


// OpenGLStateManager.cpp

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Description:
//
// Used to shadow the current OpenGL state to reduce the performance
// penalties associated with OpenGL state queries and push/pop operations.
//
// Author: Paul H. Leopard, Paul_H_Leopard@Hotmail.com
//
//-------0---------0---------0---------0---------0---------0---------0---------0

// ================================================== ================== Includes

// C++ Standard Library includes includes

#include <string>

// Local includes

#include "OpenGLStateManager.h"

// ================================================== =========== End of includes

// Imply namespaces

using namespace std;

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~
// Constructor.

OpenGLStateManager::OpenGLStateManager()
: m_Initialized(false),
m_CurrentMatrix(GL_MODELVIEW_MATRIX)
{
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~
// Enable or disable a given state.

void OpenGLStateManager::enable(int glStateId, bool state)
{
if (!m_Initialized)
{
glSynchronize();
m_Initialized = true;
}

if (state!=m_EnableMap[glStateId])
{
m_EnableMap[glStateId] = state;
if (state)
{
glEnable(glStateId);
}
else
{
glDisable(glStateId);
}
}
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~
// Determine if a given state is enabled.

bool OpenGLStateManager::isEnabled(int glStateId) const
{
bool ret(false);

map<int,bool>::iterator it = m_EnableMap.find(glStateId);
if (it==m_EnableMap.end())
{
int state = glIsEnabled(glStateId);
if (state==GL_TRUE)
{
ret = m_EnableMap[glStateId] = true;
}
else if (state==GL_FALSE)
{
ret = m_EnableMap[glStateId] = false;
}
else
{
throw string("Attempted to query OpenGL for an unknown state");
}
}
else
{
ret = (*it).second;
}

return ret;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~
// Synchronize this instance with the current OpenGL state.

void OpenGLStateManager::glSynchronize()
{
// Synch the current matrix.
glGetIntegerv(GL_MATRIX_MODE,&m_CurrentMatrix);

// Synch the enable/disable states
m_EnableMap[GL_FOG] = (glIsEnabled(GL_FOG))?true:false;
m_EnableMap[GL_LIGHTING] = (glIsEnabled(GL_LIGHTING))?true:false;
m_EnableMap[GL_TEXTURE_1D] = (glIsEnabled(GL_TEXTURE_1D))?true:false;
m_EnableMap[GL_TEXTURE_2D] = (glIsEnabled(GL_TEXTURE_2D))?true:false;
m_EnableMap[GL_LINE_STIPPLE] = (glIsEnabled(GL_LINE_STIPPLE))?true:false;
m_EnableMap[GL_POLYGON_STIPPLE] = (glIsEnabled(GL_POLYGON_STIPPLE))?true:false;
m_EnableMap[GL_CULL_FACE] = (glIsEnabled(GL_CULL_FACE))?true:false;
m_EnableMap[GL_ALPHA_TEST] = (glIsEnabled(GL_ALPHA_TEST))?true:false;
m_EnableMap[GL_BLEND] = (glIsEnabled(GL_BLEND))?true:false;
m_EnableMap[GL_INDEX_LOGIC_OP] = (glIsEnabled(GL_INDEX_LOGIC_OP))?true:false;
m_EnableMap[GL_COLOR_LOGIC_OP] = (glIsEnabled(GL_COLOR_LOGIC_OP))?true:false;
m_EnableMap[GL_DITHER] = (glIsEnabled(GL_DITHER))?true:false;
m_EnableMap[GL_STENCIL_TEST] = (glIsEnabled(GL_STENCIL_TEST))?true:false;
m_EnableMap[GL_DEPTH_TEST] = (glIsEnabled(GL_DEPTH_TEST))?true:false;
m_EnableMap[GL_CLIP_PLANE0] = (glIsEnabled(GL_CLIP_PLANE0))?true:false;
m_EnableMap[GL_CLIP_PLANE1] = (glIsEnabled(GL_CLIP_PLANE1))?true:false;
m_EnableMap[GL_CLIP_PLANE2] = (glIsEnabled(GL_CLIP_PLANE2))?true:false;
m_EnableMap[GL_CLIP_PLANE3] = (glIsEnabled(GL_CLIP_PLANE3))?true:false;
m_EnableMap[GL_CLIP_PLANE4] = (glIsEnabled(GL_CLIP_PLANE4))?true:false;
m_EnableMap[GL_CLIP_PLANE5] = (glIsEnabled(GL_CLIP_PLANE5))?true:false;
m_EnableMap[GL_LIGHT0] = (glIsEnabled(GL_LIGHT0))?true:false;
m_EnableMap[GL_LIGHT1] = (glIsEnabled(GL_LIGHT1))?true:false;
m_EnableMap[GL_LIGHT2] = (glIsEnabled(GL_LIGHT2))?true:false;
m_EnableMap[GL_LIGHT3] = (glIsEnabled(GL_LIGHT3))?true:false;
m_EnableMap[GL_LIGHT4] = (glIsEnabled(GL_LIGHT4))?true:false;
m_EnableMap[GL_LIGHT5] = (glIsEnabled(GL_LIGHT5))?true:false;
m_EnableMap[GL_LIGHT6] = (glIsEnabled(GL_LIGHT6))?true:false;
m_EnableMap[GL_LIGHT7] = (glIsEnabled(GL_LIGHT7))?true:false;
m_EnableMap[GL_TEXTURE_GEN_S] = (glIsEnabled(GL_TEXTURE_GEN_S))?true:false;
m_EnableMap[GL_TEXTURE_GEN_T] = (glIsEnabled(GL_TEXTURE_GEN_T))?true:false;
m_EnableMap[GL_TEXTURE_GEN_R] = (glIsEnabled(GL_TEXTURE_GEN_R))?true:false;
m_EnableMap[GL_TEXTURE_GEN_Q] = (glIsEnabled(GL_TEXTURE_GEN_Q))?true:false;
m_EnableMap[GL_MAP1_VERTEX_3] = (glIsEnabled(GL_MAP1_VERTEX_3))?true:false;
m_EnableMap[GL_MAP1_VERTEX_4] = (glIsEnabled(GL_MAP1_VERTEX_4))?true:false;
m_EnableMap[GL_MAP1_COLOR_4] = (glIsEnabled(GL_MAP1_COLOR_4))?true:false;
m_EnableMap[GL_MAP1_INDEX] = (glIsEnabled(GL_MAP1_INDEX))?true:false;
m_EnableMap[GL_MAP1_NORMAL] = (glIsEnabled(GL_MAP1_NORMAL))?true:false;
m_EnableMap[GL_MAP1_TEXTURE_COORD_1] = (glIsEnabled(GL_MAP1_TEXTURE_COORD_1))?true:false;
m_EnableMap[GL_MAP1_TEXTURE_COORD_2] = (glIsEnabled(GL_MAP1_TEXTURE_COORD_2))?true:false;
m_EnableMap[GL_MAP1_TEXTURE_COORD_3] = (glIsEnabled(GL_MAP1_TEXTURE_COORD_3))?true:false;
m_EnableMap[GL_MAP1_TEXTURE_COORD_4] = (glIsEnabled(GL_MAP1_TEXTURE_COORD_4))?true:false;
m_EnableMap[GL_MAP2_VERTEX_3] = (glIsEnabled(GL_MAP2_VERTEX_3))?true:false;
m_EnableMap[GL_MAP2_VERTEX_4] = (glIsEnabled(GL_MAP2_VERTEX_4))?true:false;
m_EnableMap[GL_MAP2_COLOR_4] = (glIsEnabled(GL_MAP2_COLOR_4))?true:false;
m_EnableMap[GL_MAP2_INDEX] = (glIsEnabled(GL_MAP2_INDEX))?true:false;
m_EnableMap[GL_MAP2_NORMAL] = (glIsEnabled(GL_MAP2_NORMAL))?true:false;
m_EnableMap[GL_MAP2_TEXTURE_COORD_1] = (glIsEnabled(GL_MAP2_TEXTURE_COORD_1))?true:false;
m_EnableMap[GL_MAP2_TEXTURE_COORD_2] = (glIsEnabled(GL_MAP2_TEXTURE_COORD_2))?true:false;
m_EnableMap[GL_MAP2_TEXTURE_COORD_3] = (glIsEnabled(GL_MAP2_TEXTURE_COORD_3))?true:false;
m_EnableMap[GL_MAP2_TEXTURE_COORD_4] = (glIsEnabled(GL_MAP2_TEXTURE_COORD_4))?true:false;
m_EnableMap[GL_POINT_SMOOTH] = (glIsEnabled(GL_POINT_SMOOTH))?true:false;
m_EnableMap[GL_LINE_SMOOTH] = (glIsEnabled(GL_LINE_SMOOTH))?true:false;
m_EnableMap[GL_POLYGON_SMOOTH] = (glIsEnabled(GL_POLYGON_SMOOTH))?true:false;
m_EnableMap[GL_SCISSOR_TEST] = (glIsEnabled(GL_SCISSOR_TEST))?true:false;
m_EnableMap[GL_COLOR_MATERIAL] = (glIsEnabled(GL_COLOR_MATERIAL))?true:false;
m_EnableMap[GL_NORMALIZE] = (glIsEnabled(GL_NORMALIZE))?true:false;
m_EnableMap[GL_AUTO_NORMAL] = (glIsEnabled(GL_AUTO_NORMAL))?true:false;
m_EnableMap[GL_VERTEX_ARRAY] = (glIsEnabled(GL_VERTEX_ARRAY))?true:false;
m_EnableMap[GL_NORMAL_ARRAY] = (glIsEnabled(GL_NORMAL_ARRAY))?true:false;
m_EnableMap[GL_COLOR_ARRAY] = (glIsEnabled(GL_COLOR_ARRAY))?true:false;
m_EnableMap[GL_INDEX_ARRAY] = (glIsEnabled(GL_INDEX_ARRAY))?true:false;
m_EnableMap[GL_TEXTURE_COORD_ARRAY] = (glIsEnabled(GL_TEXTURE_COORD_ARRAY))?true:false;
m_EnableMap[GL_EDGE_FLAG_ARRAY] = (glIsEnabled(GL_EDGE_FLAG_ARRAY))?true:false;
m_EnableMap[GL_POLYGON_OFFSET_POINT] = (glIsEnabled(GL_POLYGON_OFFSET_POINT))?true:false;
m_EnableMap[GL_POLYGON_OFFSET_LINE] = (glIsEnabled(GL_POLYGON_OFFSET_LINE))?true:false;
m_EnableMap[GL_POLYGON_OFFSET_FILL] = (glIsEnabled(GL_POLYGON_OFFSET_FILL))?true:false;
}

// EOF: OpenGLStateManager.cpp

// main.cpp

#include <GL/glut.h>
#include <cstdio>
#include <cmath>
#include <iostream>

using namespace std;

#include "OpenGLStateManager.h"

OpenGLStateManager StateMgr;

GLfloat ambientLight[] = { 0.1f, 0.1f, 0.1f, 1.0f };
GLfloat diffuseLight[] = { .5f, 0.5f, 0.5f, 1.0f };
GLfloat specular[] = { 1.0f, 1.0f, 1.0f, 1.0f};
GLfloat lightPos[] = { .0f, .0f, 1.8f, 1.0f };
GLfloat specref[] = { 1.0f, 1.0f, 1.0f, 1.0f };

void display()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glutSolidIcosahedron();
glutSwapBuffers();
}

void idle()
{
glutPostRedisplay();
}

void visible(int vis)
{
if (vis == GLUT_VISIBLE)
{
glutIdleFunc(idle);
}
else
{
glutIdleFunc(NULL);
}
}

void Key(unsigned char key, int x, int y)
{
switch (key)
{
case 27: //ESC key exits
exit(0);
break;

case 'a': // 'a' key rotates
glRotatef(3.0,0.0,0.0,1.0);
glRotatef(3.0,1.0,0.0,0.0);
break;

case 'l':
StateMgr.toggleState(GL_LIGHTING);
break;

case 'c':
StateMgr.toggleState(GL_CULL_FACE);
break;

}
}

int main(int argc, char **argv)
{
glutInitWindowSize(512,512);
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glutCreateWindow("Glut Keyboard");
glutKeyboardFunc(Key);
glutDisplayFunc(display);
glutVisibilityFunc(visible);

cerr << "Commands :\n\n";
cerr << "\ta - Rotate angle\n";
cerr << "\tl - Toggle lighting\n";
cerr << "\td - Toggle depth test\n";
cerr << "\tc - Toggle culling\n";
cerr << "\tm - Toggle color materials\n";

StateMgr.glSynchronize();
StateMgr.enable(GL_DEPTH_TEST);
StateMgr.enable(GL_CULL_FACE);
StateMgr.enable(GL_LIGHTING);
StateMgr.enable(GL_COLOR_MATERIAL);

glFrontFace(GL_CCW);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f );

// Light 0
glLightfv(GL_LIGHT0,GL_AMBIENT,ambientLight);
glLightfv(GL_LIGHT0,GL_DIFFUSE,diffuseLight);
glLightfv(GL_LIGHT0,GL_SPECULAR,specular);
glLightfv(GL_LIGHT0,GL_POSITION,lightPos);
glEnable(GL_LIGHT0);

// Material properties
glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE); //this is default
glColor3f(1.0f, .3f, .7f);

// Specular, Shiny
glMaterialfv(GL_FRONT, GL_SPECULAR,specref);
glMateriali(GL_FRONT,GL_SHININESS,128);

glutMainLoop();
return 0;
}


[This message has been edited by pleopard (edited 05-24-2001).]

Sean
05-24-2001, 09:14 AM
I s'pose I don't need to mention this, but don't forget to resynchronize after you call a display list, unless you know it doesn't change state...

Got bit by that a few times.

pleopard
05-24-2001, 11:44 AM
So Matt, would it be of value for me to add shadowing to the above class for colors (as in glColor3fv())?

Rephrased : Does glColor3fv() and its related functions suffer from the same performance problem? Would it help to shadow the current color?

mcraighead
05-24-2001, 02:43 PM
It's quite excessive (and almost certainly harmful) to shadow per-vertex state.

- Matt

vegark
05-24-2001, 03:07 PM
I'm to lasy to implement a wrapper for everything in OpenGL. Even if it is very clever and efficient to do it.

I think there's a simple way to exploit the enum constant VALUES.

What would we need?

Just an array with the range of enum values used. (The array should be quite small, but I'm no sure about the range of enums.Jon Leech at SGI wrote something about the enum ranges once. I'll try to find it.) The enum could be used on lookup in the array. The value of the array would be the state set.

We would need some methods in the class to fetch the state changes. At least glEnable/glDisable needs to be fetched. I would use standard names. (Is it possible to use function pointers to redirect the calls in a general method.)

And most of this could be fetched simplty by adding the object and scope-operator, to the old code. (wrapper.glEnable(GL_VERTEX_ARRAY))

Could this work?? Have I overlooked something important? Am I mad? http://www.opengl.org/discussion_boards/ubb/smile.gif

Vegar