This is more of an FYI at this point, but thought I'd let the other devs and our programmers know that I am currently working on a better state management system for jME. Our current approach of using == to determine if a state is current or worse yet, static variables in the individual state classes to determine current gl state, poorly keeps track of state across multiple contexts (pbuffers and later fbo, for examples) and thus causes us to call jni a lot more than we should have to just to ensure our states are still properly set.
So, what I have so far (and it seems to work nicely for the states I've finished adding it to) is a RenderContext class that has an array of StateRecord objects. These RenderContext objects are tracked by the current DisplaySystem, with one being designated the "current context". As you use different contexts (such as by telling a TextureRenderer to render, etc.) the currentContext is set internally automatically. When states apply themselves, they use the record in the current context to decide what jni calls they actually need to make (since opengl is a state machine, states set in one frame are carried on until cancelled or changed, so we end up needing a lot fewer jni calls.)
Really, this has no real effort impact on most jME users, (unless they were having to hack issues with states not properly updating when switching contexts, in which case they should be able to remove those hacks when I am done!) but what it could mean is a bit less jni overhead and better support for future featuresets. In the meantime I am also uncovering existing jME state bugs here and there that I will post here to keep everyone on the same page.
Bug #1: Our stencil state had a single mask field, but we used it for both glStencilMask and glStencilFunc. Unfortunately, the mask parameter in these two functions means two different things, so really we need two mask values. I've updated the class locally to reflect this. If you saved a StencilState to binary, the old mask will now not load in. There was no clean way to load the old mask (if present) AND load the new values. Sorry. The api still has setStencilMask though, which will set both masks to whatever value you send in. As far as I can tell, only my shadow code is making use of stencils internally, and since nobody barked about this limitation yet over the last 3 years, it probably will not affect many people.
Bug #2: This one is a two parter and I'm surprised no one caught it yet. First, AlphaState is missing two source blend constants (SB_DST_ALPHA and SB_ONE_MINUS_DST_ALPHA). We actually have those referenced in LWJGLAplhaState, but not in a very usable way because of the missing constants. Also, SB_SRC_ALPHA_SATURATE had the wrong constant value, so if you were setting an AlphaState with that, LWJGLAlphaState was in reality using GL_DST_ALPHA. Fun, huh? I've fixed this locally, and it should not have any impact on the jME programmer aside from giving you new and correct blending results. (I also think this class should really be called BlendState, since alpha blending is just one possible setting, but hey… a topic for another day.)
When Using JMECanvasImplementor: Not really a bug per se, but due to how we have to delay creation of the renderer in jME until the first time the Canvas is painted, you need to setup your context yourself. The RenderContext should already be created and set as current, you just need to let it know about the renderer you made. If you extend SimpleCanvasImpl or SimplePassCanvasImpl, this is already done for you. Otherwise you should be able to just add one line of code.
In your implementation of JMECanvasImplementor, do something like:
public void doSetup() {
// where you set up the display and renderer......
DisplaySystem display = DisplaySystem.getDisplaySystem();
renderer = new LWJGLRenderer(width, height);
renderer.setHeadless(true);
display.setRenderer(renderer);
display.getCurrentContext().setupRecords(renderer); // <--- THIS IS THE IMPORTANT LINE HERE.
Again, this is not in cvs yet, so it's just a heads-up and discussion point.
Just adding it to the list so I don't forget, the *flippedCulling methods (in CullState) that I added for sfera's Outline pass to work are static. Could be put in or linked to this new RenderContext or replaced by a proper winding (clockwise / counterclockwise) RenderState.
Cool, we are discussing yanking AttributeState and replacing it with something else, so maybe this is that something else.
sounds great! making just about every state setting on every apply is something that costs quite a bit…
Yeah, just spending quality time with the states code is resulting in many performance related revelations…
Bug #3: Perspective correction is a glHint affecting global rendering, but we set it per Texture, which makes no sense. Therefore, I have moved it and the CM_ constants to TextureState. Most of us probably leave it set at what TextureManager sets, (nicest aka CM_PERSPECTIVE) so this won't matter much. I've left in Texture.setCorrection and deprecated it with a message about what to change it to. Your code will still break if it refs Texture.CM_* though.
Bug #4: I love this one. Currently if you set texture's combineFuncAlpha to ACF_DOT3_RGB(A) then our texturestate panics and stops processing textures. Fixed locally.
if (texture.getCombineFuncAlpha() == Texture.ACF_DOT3_RGB
|| texture.getCombineFuncAlpha() == Texture.ACF_DOT3_RGBA) {
GL11.glDisable(GL11.GL_TEXTURE_2D); // <-- Huh? Why disable. :) Guess we forgot to first do a cap check
break; // <-- not only did we disable, but this break kills out of the texture processing loop.
}
that one goes in to the hall of monkey fame
so did the changes affect the final speed or not?
i'm ok with this as long as it gets documented (preferably in the javadocs and maybe in the wiki too).
Well, first of all, the changes were not made to get us faster, they were made to make us correct in situations more complex than our usual simple demos. That said, the changes have boosted fps on the q3 benchmark and should give everything a nice boost.
as i said in the texturepost, great work, and something reallly really needed…
question: what type of usage should we(mostly devs) use for jme objects, like my waterrenderpass for example…use the fastest method with issuing needsrefresh manually i suppose, but then again, some classes might use objects created by the end user which might be a "noob"(said in a friendly way) so they might rely on the old way of doing things…
In general, you shouldn't need to do anything to get good performance or correct results. In fact, even if you directly make changes to an object inside of a state, you are probably ok. The reason is that you're probably going to cycle between that state and another instance of that state type during the rendering of your scene, the next time you apply the changed state, the changes will be picked up on anyway. The only situation where it won't get picked up on is if you only use one instance of a given state type in your scene.
An important (to me, at least) thing comes to my mind releated to state management: I still need textures in four canvases. The old state management wasn't able to handle those four contexts (esp. texture were the problem). Does the new management ease this? What has to be done for textures additionally?
Each canvas will have a context and each context will be able to track applied textures independantly. Where it will fall apart though (I believe) is the singleton TextureManager. We might consider making that a non-singleton and have a reference to an instance of TextureManager in RenderContext class.
I'm not sure why you think the TextureManager would be a problem. The problem is the Texture class, I think. A texture must be able to have multiple texture ids, no? Then TextureManager can also stay a singleton.
And: I forgot to mention that I render a single scene to all the canvases. So the same Texture/TextureState objects would be rendered on all canvases.
Texture ids are shared between different contexes if I'm not mistaken.
According to what I have read, glGenTextures generates texture names that are unique for the currently active GL Context. Textures are only shared if the GLContext is explicitly shared. But maybe that is old or outdated information.
At this point I think we just need a test to use to study the affect of such a situation. shrug