Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OpenGL State Sets

Tags:

c++

opengl

This is less of an OpenGL question and more of a C++ organizational question.

I'm going to have a simple scene graph (an n-tree) with nodes that (for the sake of this question) will all render some geometry. To be more specific, they each have some OpenGL draw commands inside a draw() method.

For optimization reasons, I would like to batch similar objects together and draw them all at once. For this reason I'd like a way of expressing what I'm calling "state sets" for OpenGL. A state set is just a bunch of OpenGL binds, or commands that get set before draw is called on X objects, and gets unset afterward.

So a state set has at least set() and unset() and will be called by the rendering system before and after draw commands of nodes which use this state set.

My question is how to express said state sets? Sure a bunch of functions could do, but I'd rather be able to name a set and recall it. Like node A has a state set of LIGHTING_AND_SHADOW and node B has CEL_SHADING.

For this reason making an abstract class called stateSet which is essentially an interface for the set() and unset() methods and having each state set inherit from it seems like a good idea. But, it requires the creation of a bunch of objects just to gain a name essentially. it seems like there might be a better way.

Also ideally I would like to have a list of all stateSets that can be easily recalled. For example before rendering begins, it would be nice to be able to sort all of the nodes in the scene graph by their stateSet.

Any ideas?

like image 905
Cody Smith Avatar asked Dec 30 '12 17:12

Cody Smith


People also ask

What is OpenGL state?

State. The OpenGL context contains information used by the rendering system. This information is called State, which has given rise to the saying that OpenGL is a "state machine". A piece of state is simply some value stored in the OpenGL context.

Is OpenGL a state machine?

The OpenGL API is defined as a state machine. Almost all of the OpenGL functions set or retrieve some state in OpenGL. The only functions that do not change state are functions that use the currently set state to cause rendering to happen.

What is OpenGL and how it works?

OpenGL (Open Graphics Library) is a cross-platform, hardware-accelerated, language-independent, industrial standard API for producing 3D (including 2D) graphics. Modern computers have dedicated GPU (Graphics Processing Unit) with its own memory to speed up graphics rendering.


2 Answers

You can implement your states using the singleton pattern. Then, another singleton state tracker class manages the instances of these state classes, and only sets a state when its not already set. See below for a crude implementation. This should give you an idea on how to go about it:

class SingletonStateClass1 {
public:
    static SingletonStateClass1* getInstance() {
        if(! instanceFlag) {
            single = new SingletonStateClass1();
            instanceFlag = true;
            return single;
        }
        else {
            return single;
        }
    }
    void set() {
        // Do setting stuff
    }
    void unset() {
        // Do unsetting stuff
    }
    ~SingletonStateClass1() {
        instanceFlag = false;
    }

private:
    static bool instanceFlag;
    static SingletonStateClass1 *single;
    SingletonStateClass1() {} //private constructor
};
bool SingletonStateClass1::instanceFlag = false; //needs to be set so that the first call to getInstance() works ok.


//ASSUME THERE IS ANOTHER CLASS WITH SIMILAR DESIGN, NAMED: SingletonStateClass2  

class SingletonStateTracker {
public:
    static SingletonStateTracker* getInstance() {
        if(! instanceFlag) {
            single = new SingletonStateTracker();
            state1 = SingletonStateClass1::getInstance();
            state2 = SingletonStateClass2::getInstance();
            instanceFlag = true;
            isSetState1 = false;
            isSetState2 = false;
            return single;
        }
        else {
            return single;
        }
    }

    // Only setting a state unsets the other states
    void set1() {
        if (!isSetState1) {
            if (isSetState2) {
                state2->unset();
                isSetState2 = false;
            }
            state1->set();
            isSetState1 = true;
        }
    }
    void set2() {
        if (!isSetState2) {
            if (isSetState1) {
                state1->unset();
                isSetState1 = false;
            }
            state2->set();
            isSetState2 = true;
        }
    }

private:
    static bool instanceFlag;
    static bool isSetState1;
    static bool isSetState2;
    static SingletonStateTracker *single;
    static SingletonStateClass1 *state1; 
    static SingletonStateClass2 *state2; 
    SingletonStateTracker() {} //private constructor
};
bool SingletonStateTracker::instanceFlag = false; //needs to be set so that the first call to getInstance() works ok.




class DrawableObject1 {
public:
    DrawableObject1() {
        tracker = SingletonStateTracker::getInstance();
    }
    void draw() const
    {
        tracker->set1();
        //DO YOUR OBJECT SPECIFIC OPENGL DRAW STUFF HERE
    }
private:
    SingletonStateTracker* tracker;
};

class DrawableObject2 {
public:
    DrawableObject2() {
        tracker = SingletonStateTracker::getInstance();
    }
    void draw() const
    {
        tracker->set2();
        //DO YOUR OBJECT SPECIFIC OPENGL DRAW STUFF HERE
    }
private:
    SingletonStateTracker* tracker;
};


/* Below two classes show a crude usage of the above design */
class Scene {
    // ... other stuff ...

public:
    DrawableObject1 obj1a;
    DrawableObject1 obj1b;
    DrawableObject2 obj2;
    // ... other stuff ...
};

class Viewer {
    // ... other stuff ...
public:
    void draw() {
        scene->obj1a.draw(); //This call unsets state2, sets state1
        scene->obj1b.draw(); //This call does not set any state since state1 is already set
        scene->obj2.draw();  //This call unsets state1, sets state2
    }
private:
    Scene* scene;
    // ... other stuff ...
};

Note that the above design is very simple. You can have multiple State Classes to inherit a common interface as you noted in your question. The key is to have a Tracker(a.k.a Manager) class through which the objects set states. The job of the Tracker class is to eliminate unnecessary setting of a state if its already set, and unset the other states if they are set currently.

like image 157
meyumer Avatar answered Oct 03 '22 06:10

meyumer


For this reason making an abstract class called stateSet which is essentially an interface for the set() and unset() methods and having each state set inherit from it seems like a good idea. But, it requires the creation of a bunch of objects just to gain a name essentially.

Your hypothetical abstract class implementation works through the virtual function table, which is generally implemented as an array of function pointers. The apparently pointless objects you would instantiate in this case do hold meaningful state - those function pointers.

As an alternative to creating many arrays of two function pointers, perhaps you should create two arrays of function pointers, name indices into the arrays, retain the last used index, and have your stateSet(uint state_name) check to see if the state_name is different before indirecting through the arrays. I would recommend hiding all that global state from other compilation units though the static keyword.

This approach offers less automatic safety - semantically nothing stops you from passing any integer into stateSet(uint) and there's no range check on a raw array unless you put it there yourself.

The two options are otherwise fundamentally quite similar; so give your own idea due consideration.

like image 38
01d55 Avatar answered Oct 03 '22 05:10

01d55