Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
112 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

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
ObjectOps>
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<GLenum target>
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<GLenum target>
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


Contents
  Introduction
  Taking a step in the API independence direction

  Source code
  Printable version
  Discuss this article