Jump to content

  • Log In with Google      Sign In   
  • Create Account

Like
0Likes
Dislike

Object Abstraction in OpenGL

By James Sharpe | Published May 29 2004 08:41 AM in OpenGL

class texture static gluint void object public
If you find this article contains errors or problems rendering it unreadable (missing images or files, mangled code, improper text formatting, etc) please contact the editor so corrections can be made. Thank you for helping us improve this resource


Introduction
We are beginning to see more and more use of the notion of an object in OpenGL. An object is simply represented by an unsigned integer that is used as the parameter to various state setting functions. In many cases when working with objects in OpenGL you will be performing the same common tasks with these objects: Creation, Binding and Destruction. I realised this when I was implementing the sub-classes Texture1D, Texture2D, Texture3D etc.. and found I was simply writing the same code over and over, but with just a different constant (in this case, although we see that this can be generalised to different entry points) and decided that an abstraction was needed.

My immediate thought was to move this into the base class GLTexture, however I realised I would come across this problem again when implementing vertex and fragment programs(ARB, EXT, NV), vertex buffers(VBO, VAR, VAO). Also note that mechanisms such as ARB_occlusion_query uses a similar notion of an object. The problem with providing a common abstraction is that each of these extensions uses different entry points and/or parameters. The solution to this problem is to use templates and to move the entry point calls into a helper class. So my initial implementation of the Object interface was:

template class Object { public: Object() : ID(ObjectOps::GenerateID()) {} ~Object() { ObjectOps::Destroy(ID); } void Bind() const { ObjectOps::Bind(ID); } protected: GLuint ID; }; Notice how simple this class is. The work is done in the ObjectOps classes. I've opted to use all static member functions for the ObjectOps interface, but you could make it into a class and use the Pimpl idiom, allocating an instance as a private variable, but I choose to avoid the overhead of the allocation of the object, any data needed can be stored as private static variables.

A sample implementation of an ObjectOps class(for ARB_vertex_program):

class ARBVertexProgramOps { public: static GLuint GenerateID() { GLuint ID; glGenProgramsARB(1, &ID); return ID; } static void Destroy(GLuint ID) { glDeleteProgramsARB(1, &ID); } static void Bind(GLuint ID) { if(ID != currentID) { glBindProgramARB(GL_VERTEX_PROGRAM_ARB, ID); currentID = ID; } } private: static GLuint currentID; }; Still implementing individual Ops classes for each of the texture types is still going to be tedious so we can take advantage of templates again to change the constant for us:

template class TexOps { public: static GLuint GenerateID() { GLuint ID; glGenTextures(1, &ID); return ID; } static void Bind(GLuint ID) { glBindTexture(target, ID); } static void Destroy(GLuint ID) { glDeleteTextures(1, &ID); } }; I've omitted the currently bound state memory in this case since a little more work is needed remembering the state for each texture unit if multitexture is used. I'll leave that as an exercise for the reader. You can also do a similar thing with the ARBVertexProgramOps class to support ARB_fragment_program:

template class ARBProgramOps { public: static GLuint GenerateID() { GLuint ID; glGenProgramsARB(1, &ID); return ID; } static void Destroy(GLuint ID) { glDeleteProgramsARB(1, &ID); } static void Bind(GLuint ID) { if(ID != currentID) { glBindProgramARB(target, ID); currentID = ID; } } private: static GLuint currentID; };
Taking a step in the API independence direction
You'll notice that in the above code, the usage to say create a 2D texture class would be:

class Texture2D : public Object< TexOps > { public: /* 2D Texture specific functions */ }; Now this is ok if you know that the texture you are dealing with is a 2D texture, but it is useful to be able to have a base class GLTexture, that you can refer to all textures by. Now this causes us a problem: we would like the base texture class to have a method void Bind() const. However, this method is defined in Object<> so simply using the seemingly obvious solution of multiple inheritance won't work.

The solution comes in the form of a diamond inheritance hierarchy. This formation is normally used as an argument against multiple inheritance, but in this case we can use a handy trick called delegation to a sister class[1] to enable us to achieve our aim. As an indirect side effect of this we also achieve the ability to have an API independent base class for our textures!

Posted Image

This is what our hierarchy will look like. To avoid having two copies of the base texture object we need to make the inheritance of Texture in GLObject<> and GLTexture virtual. Now this poses us a new problem, we've just made GLObject<> dependent upon Texture! Again templates come to the rescue, making the interface now:

class Texture { public: virtual ~Texture() { } virtual void Bind() const = 0; /* Other abstract Texture functions */ }; template class Object: public virtual ObjectBase { public: Object() : ID(ObjectOps::GenerateID()) {} ~Object() { ObjectOps::Destroy(ID); } void Bind() const { ObjectOps::Bind(ID); } protected: GLuint ID; }; class GLTexture : public virtual Texture // Notice the virtual keyword - very important! { public: /*GL specific functions and implementations of pure virtual functions from Texture */ }; class Texture2D : public GLTexture, private GLObject





Comments

Note: Please offer only positive, constructive comments - we are looking to promote a positive atmosphere where collaboration is valued above all else.




PARTNERS