PDA

View Full Version : Restoring state when exiting a LIBRARY function/method.



pleopard
07-06-2004, 10:01 AM
I am curious to know if you guys address the issue of GL state changes between function calls in a library. For example, if you call a function to render some sort of object and in the process of rendering it makes 5 state changes (glEnable/glDisable, etc...) do you restore the original state when you exit? I would think that this wouldn't matter with special purpose function code in your apps. However, when the function is in a library and the user doesn't necessarily have the source code to see the states you changed then they would not be able to determine the current state without querying all states.

For example ...

Do you do this:


void renderIt()
{
glEnable(GL_TEXTURE_2D);
glDisable(GL_LIGHTING);
drawMyObject();
}or this :

void renderIt()
{
GLInt texEnabled = glIsEnabled(GL_TEXTURE_2D);
GLInt liteEnabled = glIsEnabled(GL_LIGHTING);
glEnable(GL_TEXTURE_2D);
glDisable(GL_LIGHTING);
drawMyObject();
if (!texEnabled)
{
glDisable(GL_TEXTURE_2D);
}
if (liteEnabled)
{
glEnable(GL_LIGHTING);
}
}I understand that the glIsEnabled() calls can get expensive so (on a recommendation from Matt C.) I wrote a state manager that shadows the current GL state. This is effective as I can do the following while minimizing the additional calls:

void renderIt(MgGLStateManager &sMgr)
{
bool texEnabled = sMgr.isEnabled(GL_TEXTURE_2D);
bool liteEnabled = sMgr.isEnabled(GL_LIGHTING);
sMgr.enable(GL_TEXTURE_2D);
sMgr.disable(GL_LIGHTING);
drawMyObject();
sMgr.enable(GL_TEXTURE_2D,texEnabled);
sMgr.enable(GL_LIGHTING,liteEnabled);
}Or this:

void renderIt(MgGLStateManager &sMgr)
{
sMgr.pushState();
drawMyObject();
sMgr.popState();
}In both cases when using the state manager, no additional calls to glAnything() are made if the state was not changed from the state preceeding the function call.

I find the state manager useful however, there are many cases in which I prefer not to use it. I am curious how you guys address these issues.

jwatte
07-06-2004, 10:24 AM
I think the right choice is to make the API a little more explicit than that. Ideally, the interface between the library and the host app should be through some interface, rather than assuming GL is there. (That way, you could port to something else, too).

If you're faced with a library you can't change, then you're screwed. (That's a technical term)

Humus
07-06-2004, 04:53 PM
The way I do things, I simply specify what state I want to be in, rather than what states I want to change. Then my renderer sets up all state changes, and updates the states that have changed. This means that I can totally forget about what state changes other function does, and a change at somewhere else in the code won't disturb what I'm doing right now.

And to make it simple I simply tell what states that differ from the default state, rather than listing specifying all states.

So if I want to bind a texture I do:
renderer->setTexture(myTexture);
renderer->apply();

Or if I want to use a shader with two textures and blending:
renderer->setShader(myShader);
renderer->setTexture("Base", base);
renderer->setTexture("Bump", bump);
renderer->setBlending(ONE, ONE);
renderer->apply();

Later if I don't want to use any textures, shaders or anything I can simply do:
renderer->apply();

which will reset it to default state.

jwatte
07-06-2004, 06:37 PM
Then my renderer sets up all state changes, and updates the states that have changed
I think the original question dealt with the problem of integrating between an application (that has one kind of state management) and a library (that may have another kind of state management).

I agree that your rendering system should keep state changes within its domain (I have a description of what I'd suggest for a light-weight solution at simple-scene.html (http://www.mindcontrol.org/~hplus/graphics/simple-scene.html) ) but the problem begins if you write a library that
can for some reason NOT share state with the hosting application.

Clearly, the best solution is to let the application and the library share the same state manager, perhaps through something like an abstract interface. But it's not always you're so lucky.

If the library is documented as for what states get changed, you can write a function that invalidates the cache of those specific states for your state manager, and call that function after you've called the library.

flo
07-07-2004, 04:48 AM
Why not just use glPushAttrib(STATE_WHICH_IS_CHANGED_IN_LIB) / glPopAttrib()?

lxnyce
07-07-2004, 04:57 AM
I agree with the previous post. You really can't change your architecture at this stage, so just stick with what you have.

glPushAttrib( GL_ALL_ATTRIB_BITS );
CALL_3RD_PARTY_FUNCTION();
glPopAttrib();

GL_ALL_ATTRIB_BITS will give you the most coverage ground, as you don't know whats going on in the other dll. Things like vertex buffer states are not accounted for though, so if you use them, make sure you re-initialize them.

Keep in mind that it is supposedly slower to use glPushAttrib, especially with GL_ALL_ATTRIB_BITS, but in practice there isn't really a noticeable drop in framerate (for applications).

knackered
07-07-2004, 08:55 AM
If you're in a situation where you're dealing with a 3rd party library making GL calls directly with no guarantee it's saving/restoring state internally, then you simply have no option but to use glPushAttrib(GL_ALL_ATTRIB_BITS)/glPopAttib.
Your other option is to not use such crapply written 3rd party libraries of course.

Humus
07-07-2004, 08:09 PM
I agree on that. I wouldn't wanna poke a library that touches GL-states directly with a ten foot pole. :)

pleopard
07-07-2004, 08:50 PM
If you know that you will be using GL because of a cross-platform need then why the concern over the functions 'touching GL' ?

V-man
07-08-2004, 12:50 PM
PushAttrib and PopAttrib are considered expensive.

A third option is documenting the state changes :

OPTION 1

//This function needs you to enable GL_TEXTURE_2D
void SuperTextureFunction();

OPTION 2

//This function will enable GL_TEXTURE_2D when called. You have been warned!
void SuperTextureFunction();

Humus
07-08-2004, 06:18 PM
Originally posted by pleopard:
If you know that you will be using GL because of a cross-platform need then why the concern over the functions 'touching GL' ?Because I don't want them to interfere with what I'm doing myself. I wouldn't use libraries that changes the active working directory either, or any other application wide state for that matter.

pleopard
07-09-2004, 01:28 PM
My whole point in posting this thread was to see how you guys PREVENTED this kind of interference in your own libraries. I am not using a crappily written 3rd party library, I am creating one for (primarily) my own use. I have developed a lot of really useful special effects code over the years that I tend to copy-cut-paste over and over again so I decided to put it in a library. Given that I am going to put it in a library, I am trying to understand the best mechanism for preventing such interference so that I do not run into the problems you guys are alluding to.

Humus
07-09-2004, 04:11 PM
Well, if only your own code, then I'd recommend doing something like I mentioned above, with some kind of state manager class that keeps track of the states. Then use that class in your libraries too.

pkaler
07-09-2004, 05:05 PM
Originally posted by pleopard:
My whole point in posting this thread was to see how you guys PREVENTED this kind of interference in your own libraries.Design-by-contract. Clearly document the preconditions and postconditions for each function. Have asserts that fire off in your debug build if a precondition or postcondition is not satisfied.

If the client or the library breaks the contract all hell breaks loose.

There is nothing wrong with changing state as long as it is clearly documented and enforced.

zed
07-10-2004, 12:36 AM
im doing something similar to humus (ive looked at his code) though mine does differ in one key thing from memory (which i forget at the moment) sample code i had also added a test that would literally check the current gl state with what i expect it to be (error checking)

struct GLculling
{
GLculling() { cullface=GL_BACK; frontface=GL_CCW; }

bool enable_cullface; // default=true;
int cullface; // FRONT BACK FRONT_AND_BACK default=back
int frontface; // GL_CCW GL_CW default=ccw

void Parse_Culling ( Parser &buf );
};

struct GLstate
{
int CALL_glDisableCullFace, CALL_glEnableCullFace, CALL_glCullFace, CALL_glFrontFace;

GLculling current_cullstate;
void setCulling ( const GLculling &material_culltest );

};

my state class hasnt been touched for a couple of years + thus needs glsl added to it (im not 100% sure the best way of doing this, with all the var+uniform stuff anyways)
anyways even though doing this is a piece of piss it does require heaps of typing mainly of the cut+paste variety. (expect at least 5000LOC if youre including error checking)

jwatte
07-12-2004, 10:50 AM
I am not using a crappily written 3rd party library, I am creating one
Well then :-) Abstracting your rendering interface / state manager is a good start.

However, I've found that it's often way too low level to abstract "a device with four texture units that supports anisotropic filtering" -- although you can port between D3D and GL, and track all your state changes, that way, it's not the level at which you can really describe visuals in a scalable way.

Instead, I prefer to describe visuals as a combination of geometry (vertex/index buffers), materials (textures, shaders, fragment processing functions), and state (blending, transform). The interface between the using code (which your library would be one kind of) and the actual renderer would go through something simple that allows you to allocate and configure geometry, material and state objects, and then issue combinations of these on the renderer.