A group dedicated to indie and standalone game development.

Report article RSS Feed Writing a Scene Graph for 2D Games

Covers writing a basic Scene Graph for a 2D Engine, which essentially handles depth sorting and creates a tight loop for rendering a scene. A big advantage of using a Scene Graph is that rendering becomes an event driven system instead of a "polling" system of sorts. This tutorial was written in C using OpenGL, but the concepts should work for C# and DirectX.

Posted by Xienen on Jan 6th, 2011
Intermediate Client Side Coding.

NOTE: All Ampersands in the below code have turned into their & form and I have not been able to fix it.  So please replace them in the code below if you're going to use it.

This tutorial covers writing a basic Scene Graph for a 2D Engine, which essentially handles depth sorting and creates a tight loop for rendering a scene. A big advantage of using a Scene Graph is that rendering becomes an event driven system instead of a "polling" system of sorts.

This tutorial was written in C using OpenGL, but the concepts should work for C# and DirectX.

While this may be considered common knowledge to many, I always wished that I could find a tutorial on Scene Graphs when I was getting started.

So, lets jump right in and define some structures that we're going to need to store the Vertex, Texture, and Shader Data:

c code:
typedef struct xRenderDataTexture;
typedef struct xRenderDataShader;

struct xRenderDataVerts
{
  xVector        m_xRotation;
  xColor         m_xColor;
  xVector       *m_xVectorArray;
  int          m_iVectorArrayLength;
  xVector2D     *m_xTextureCoordinatesArray;
  bool         m_bRender;
  float        m_fDepth;
  xRenderDataTexture  *m_pTextureData;
  xRenderDataShader *m_pShaderData;

  xRenderDataVerts  *m_pNext;
  xRenderDataVerts  *m_pPrev;
};

struct xRenderDataTexture
{
  GLenum         m_uiRenderType;
  xColor        *m_xShaderParameterArray;
  GLuint        *m_uiTextureIndexArray;
  int          m_iTextureIndexArrayLength;
  float        m_fMinDepth;
  float        m_fMaxDepth;
  xRenderDataVerts   m_xVertDataList;
  bool         m_bRender;
  bool         m_bForcedUnique;

  xRenderDataTexture  *m_pNext;
  xRenderDataTexture  *m_pPrev;
};

struct xRenderDataShader
{
  int         m_iBlendMode;
  float       m_fMinDepth;
  float       m_fMaxDepth;
  xRenderDataTexture  m_xTextureDataList;
  bool        m_bRender;

  xRenderDataShader *m_pNext;
  xRenderDataShader *m_pPrev;
};

And just for possible future expansion, we create a currently minuscule xSceneGraph struct:

c code:
struct xSceneGraph
{
  xRenderDataShader m_xRenderData;
}

These structures are mostly self explanatory, but this is the hierarchical break down of the the Scene Graph:

Scene Graph contains a linked list of xRenderDataShader objects, which contains a linked list of xRenderDataTexture objects, which in turn contains a linked list of xRenderDataVert objects(which each represent a single renderable object in the game, or Game Object. By modifying a bit of code, though, you could have it represent multiple objects in the game, which may be a significant optimization depending on the number of duplicate objects in your game, it just isn't for mine).

So, the basic idea is that an xRenderDataShader object is selected for each Game Object by its Blend Mode(None, Alpha Transparent, or any type of shader used in your game) and desired Depth, or a new xRenderDataShader object is created if we cannot find one that fits the desired settings. Similarly, within this xRenderDataShader object, we select or create an xRenderDataTexture object by the Render Type(Points, Lines, Triangles, Quads, etc.), Texture Count and Indicies, and desired Depth. Lastly within this xRenderDataTexture object we create a new xRenderDataVert object and place it in the correct place within the linked list based on the desired Depth(this is the part that would need to be modified if you want to have multiple Game Objects within a single xRenderDataVert object).

You'll notice that I have also not concerned myself with the m_xShaderParameterArray in the xRenderDataTexture object. This is for simplicity within my engine, since this could be a pain to maintain and I hardly use shaders within Break Blocks. Instead, I set the m_bForcedUnique as needed, when I want to have custom shader parameters for a particular Game/xRenderDataVert object. I also found the m_bForcedUnique variable very useful when I know that I will be changing the texture of an object frequently, such as a button's background in my UI. You'll see more about these details later.

You will also notice that xRenderDataVerts keeps a reference to an xRenderDataTexture object and an xRenderDataShader object. At first, I was storing a reference to these objects inside of my Game Object itself, but I later realized that the Scene Graph may need to move the xRenderDataVerts object into a new xRenderDataTexture and/or xRenderDataShader object, so it's best to keep the references in the xRenderDataVerts object itself to allow the Scene Graph direct access to modify them with updating the Game Object itself.

These data structures need some inline initialization functions(to reduce copy/paste errors):

c code:
inline void xRenderDataVertsInit(xRenderDataVerts *pThis)
{
  xColorInit(&pThis->m_xColor, 1.0, 1.0, 1.0, 1.0);
  xVectorInit(&pThis->m_xRotation, 0.0, 0.0, 0.0);

  pThis->m_xVectorArray = 0;
  pThis->m_iVectorArrayLength = 0;
  pThis->m_xTextureCoordinatesArray = 0;
  pThis->m_bRender = 1;
  pThis->m_fDepth = -99999999.0f;

  pThis->m_pTextureData = 0;
  pThis->m_pShaderData  = 0;

  pThis->m_pNext = 0;
  pThis->m_pPrev = 0;
}

inline void xRenderDataTextureInit(xRenderDataTexture *pThis)
{
  xRenderDataVertsInit(&pThis->m_xVertDataList);

  pThis->m_uiRenderType = 0;
  pThis->m_xShaderParameterArray = 0;
  pThis->m_uiTextureIndexArray = 0;
  pThis->m_iTextureIndexArrayLength = 0;
  pThis->m_fMinDepth =  99999999.0f;
  pThis->m_fMaxDepth = -99999999.0f;
  pThis->m_bRender = 1;
  pThis->m_bForcedUnique = 0;

  pThis->m_pNext = 0;
  pThis->m_pPrev = 0;
}

inline void xRenderDataTextureInit(xRenderDataTexture *pThis, int iBlendMode, GLuint uiRenderType, int iTextureCount, GLuint *pTextureIDs, float fDepth, bool bForceUnique)
{
  pThis->m_uiRenderType = uiRenderType;
  pThis->m_fMinDepth = fDepth;
  pThis->m_fMaxDepth = fDepth;
  pThis->m_bForcedUnique = bForceUnique;

  pThis->m_iTextureIndexArrayLength = iTextureCount;
  pThis->m_uiTextureIndexArray = (GLuint*)malloc(sizeof(GLuint) * iTextureCount);
  pThis->m_uiTextureIndexArray[0] = pTextureIDs[0];

  if(iTextureCount > 1)
    pThis->m_uiTextureIndexArray[1] = pTextureIDs[1];

  switch(iBlendMode)
  {
    case PE_BLEND_MODE_MASKED:
      pThis->m_xShaderParameterArray = (xColor*)malloc(sizeof(xColor) * 2);
      break;
    case PE_BLEND_MODE_2COLOR:
      pThis->m_xShaderParameterArray = (xColor*)malloc(sizeof(xColor) * 2);
      break;
  }
}

inline void xRenderDataShaderInit(xRenderDataShader *pThis)
{
  xRenderDataTextureInit(&pThis->m_xTextureDataList);

  pThis->m_bIs3D = 0;
  pThis->m_iBlendMode = 0;
  pThis->m_fMinDepth =  99999999.0f;
  pThis->m_fMaxDepth = -99999999.0f;
  pThis->m_bRender = 1;

  pThis->m_pNext = 0;
  pThis->m_pPrev = 0;
}

inline void xSceneGraphInit(xSceneGraph *pThis)
{
  xRenderDataShaderInit(&pThis->m_xRenderData);
}

Lets start looking at the Scene Graph management code by checking out the xRenderDataShader selection/creation:

c code:
xRenderDataShader* xSceneGraphGetRenderDataShader(xSceneGraph *pThis, float fDepth, int iBlendMode)
{
  xRenderDataShader *object, *temp;

  if(pThis->m_xRenderData.m_pNext)
  {
    object = pThis->m_xRenderData.m_pNext;
    while(1)
    {
      if(fDepth < object->m_fMinDepth || fDepth <= object->m_fMaxDepth)
      {
        if(iBlendMode == object->m_iBlendMode)
        {
          if(fDepth < object->m_fMinDepth)
            object->m_fMinDepth = fDepth;
        }
        else if(fDepth < object->m_fMinDepth &amp;&amp; object->m_pPrev &amp;&amp; object->m_pPrev->m_pPrev &amp;&amp; iBlendMode == object->m_pPrev->m_iBlendMode)
        {
          object = object->m_pPrev;
          object->m_fMaxDepth = fDepth;
        }
        else
        {
          if(fDepth < object->m_fMinDepth)
          {
            temp = object->m_pPrev;
            object->m_pPrev = (xRenderDataShader*)malloc(sizeof(xRenderDataShader));
            xRenderDataShaderInit(object->m_pPrev);

            object->m_pPrev->m_pPrev = temp;
            object->m_pPrev->m_pNext = object;
            object = object->m_pPrev;
            temp->m_pNext = object;
          }
          else
            object = xSceneGraphInsertRenderDataShader(object, fDepth);

          object->m_iBlendMode = iBlendMode;
          object->m_fMinDepth = fDepth;
          object->m_fMaxDepth = fDepth;
        }

        return object;
      }

      if(!object->m_pNext)
      {
        if(iBlendMode == object->m_iBlendMode)
        {
          object->m_fMaxDepth = fDepth;
          return object;
        }

        object->m_pNext = (xRenderDataShader*)malloc(sizeof(xRenderDataShader));
        xRenderDataShaderInit(object->m_pNext);

        object->m_pNext->m_pPrev = object;
        object = object->m_pNext;

        object->m_iBlendMode = iBlendMode;
        object->m_fMinDepth = fDepth;
        object->m_fMaxDepth = fDepth;

        return object;
      }

      object = object->m_pNext;
    }
  }
  else
  {
    object = (xRenderDataShader*)malloc(sizeof(xRenderDataShader));
    xRenderDataShaderInit(object);

    pThis->m_xRenderData.m_pNext = object;
    object->m_pPrev = &amp;pThis->m_xRenderData;

    object->m_iBlendMode = iBlendMode;
    object->m_fMinDepth = fDepth;
    object->m_fMaxDepth = fDepth;
  }

  return object;
}

There's nothing too complicated in this, it's mostly just linked list management with the small caveat of finding an xRenderDataShader object that can contain the desired Depth that has the same Blend Mode requested.

There is one key function call in there, though, xSceneGraphInsertRenderDataShader, which splits an xRenderDataShader around the specified Depth because an existing xRenderDataShader object contains Game/xRenderDataVert objects that have both smaller and larger Depths than the requested Depth:

c code:
xRenderDataShader* xSceneGraphInsertRenderDataShader(xRenderDataShader *pData, float fDepth)
{
  xRenderDataShader *ret, *duplicate;
  xRenderDataTexture *texdata, *duptexdata;
  xRenderDataVerts *vertdata;

  ret = (xRenderDataShader*)malloc(sizeof(xRenderDataShader));
  duplicate = (xRenderDataShader*)malloc(sizeof(xRenderDataShader));

  xRenderDataShaderInit(ret);
  xRenderDataShaderInit(duplicate);

  duplicate->m_iBlendMode = pData->m_iBlendMode;
  duplicate->m_fMinDepth = pData->m_fMinDepth;
  duplicate->m_pNext = ret;
  duplicate->m_pPrev = pData->m_pPrev;

  ret->m_pNext = pData;
  ret->m_pPrev = duplicate;

  pData->m_pPrev->m_pNext = duplicate;
  pData->m_pPrev = ret;

  texdata = pData->m_xTextureDataList.m_pNext;
  duptexdata = &amp;duplicate->m_xTextureDataList;
  while(1)
  {
    if(texdata->m_fMinDepth > fDepth)
    {
      pData->m_fMinDepth = texdata->m_fMinDepth;
      break;
    }

    // NOTE: There is an unhandled case here, where we may need to split an xRenderDataTexture object into multiple pieces due to the requested Depth
/*    if(texdata->m_fMaxDepth > fDepth)
    {
      duptexdata->m_pNext = xSceneGraphSplitRenderDataTexture(texdata, fDepth);
      break;
    }
*/

    texdata->m_pPrev = duptexdata;
    duptexdata->m_pNext = texdata;
    duptexdata = texdata;

    duplicate->m_fMaxDepth = duptexdata->m_fMaxDepth;

    vertdata = duptexdata->m_xVertDataList.m_pNext;
    while(vertdata)
    {
      vertdata->m_pShaderData = duplicate;
      vertdata->m_pTextureData = duptexdata;
      vertdata = vertdata->m_pNext;
    }
   
    texdata = texdata->m_pNext;

    duptexdata->m_pNext = 0;
  }

  texdata->m_pPrev = &amp;pData->m_xTextureDataList;
  pData->m_xTextureDataList.m_pNext = texdata;

  return ret;
}  
 

Once again, this function is mostly self explanatory and is all about managing the linked lists. One thing to note here, though, is that this function is incomplete due to an unhandled possibility. I will leave it to you to write this function, if you need it, because I haven't needed it in my game yet.

Similarly, the xRenderDataTexture functions follow:

c code:
xRenderDataTexture* xSceneGraphGetRenderDataTexture(xSceneGraph *pThis, xRenderDataShader *pShaderData, float fDepth, bool bForceUniqueTextureData, GLenum iRenderType, int iNumTextures, GLuint *pTextureIDs)
{
  xRenderDataTexture *object, *temp;

  if(pShaderData->m_xTextureDataList.m_pNext)
  {
    object = pShaderData->m_xTextureDataList.m_pNext;
    while(1)
    {
      if(fDepth < object->m_fMinDepth || fDepth <= object->m_fMaxDepth)
      {
        if(!bForceUniqueTextureData &amp;&amp; !object->m_bForcedUnique &amp;&amp; iRenderType == object->m_uiRenderType &amp;&amp; iNumTextures == object->m_iTextureIndexArrayLength &amp;&amp; pTextureIDs[0] == object->m_uiTextureIndexArray[0])
        {
          if(fDepth < object->m_fMinDepth)
            object->m_fMinDepth = fDepth;
        }
        else if(!bForceUniqueTextureData &amp;&amp; !object->m_bForcedUnique &amp;&amp; fDepth < object->m_fMinDepth &amp;&amp; object->m_pPrev &amp;&amp; object->m_pPrev->m_pPrev &amp;&amp; iRenderType == object->m_pPrev->m_uiRenderType &amp;&amp; iNumTextures == object->m_pPrev->m_iTextureIndexArrayLength &amp;&amp; pTextureIDs[0] == object->m_pPrev->m_uiTextureIndexArray[0])
        {
          object = object->m_pPrev;
          object->m_fMaxDepth = fDepth;
        }
        else
        {
          if(!bForceUniqueTextureData &amp;&amp; fDepth == object->m_fMaxDepth)
          {
            temp = object->m_pNext;
            while(temp &amp;&amp; fDepth == temp->m_fMaxDepth)
            {
              if(iRenderType == temp->m_uiRenderType &amp;&amp; iNumTextures == temp->m_iTextureIndexArrayLength &amp;&amp; pTextureIDs[0] == temp->m_uiTextureIndexArray[0])
                return temp;

              temp = temp->m_pNext;
            }
          }

          if(fDepth <= object->m_fMinDepth)
          {
            temp = object->m_pPrev;
            object->m_pPrev = (xRenderDataTexture*)malloc(sizeof(xRenderDataTexture));
            xRenderDataTextureInit(object->m_pPrev);

            object->m_pPrev->m_pPrev = temp;
            object->m_pPrev->m_pNext = object;
            object = object->m_pPrev;
            temp->m_pNext = object;
          }
          else
            object = xSceneGraphInsertRenderDataTexture(object, fDepth);

          xRenderDataTextureInit(object, pShaderData->m_iBlendMode, iRenderType, iNumTextures, pTextureIDs, fDepth, bForceUniqueTextureData);
        }

        return object;
      }

      if(!object->m_pNext)
      {
        if(!bForceUniqueTextureData &amp;&amp; !object->m_bForcedUnique &amp;&amp; iRenderType == object->m_uiRenderType &amp;&amp; iNumTextures == object->m_iTextureIndexArrayLength &amp;&amp; pTextureIDs[0] == object->m_uiTextureIndexArray[0])
        {
          object->m_fMaxDepth = fDepth;
          return object;
        }

        object->m_pNext = (xRenderDataTexture*)malloc(sizeof(xRenderDataTexture));
        xRenderDataTextureInit(object->m_pNext);

        object->m_pNext->m_pPrev = object;
        object = object->m_pNext;

        xRenderDataTextureInit(object, pShaderData->m_iBlendMode, iRenderType, iNumTextures, pTextureIDs, fDepth, bForceUniqueTextureData);

        return object;
      }

      object = object->m_pNext;
    }
  }
  else
  {
    object = (xRenderDataTexture*)malloc(sizeof(xRenderDataTexture));
    xRenderDataTextureInit(object);

    pShaderData->m_xTextureDataList.m_pNext = object;
    object->m_pPrev = &amp;pShaderData->m_xTextureDataList;

    xRenderDataTextureInit(object, pShaderData->m_iBlendMode, iRenderType, iNumTextures, pTextureIDs, fDepth, bForceUniqueTextureData);
  }

  return object;
}

xRenderDataTexture* xSceneGraphInsertRenderDataTexture(xRenderDataTexture *pData, float fDepth)
{
  xRenderDataTexture *ret, *duplicate;
  xRenderDataVerts *vertdata, *dupvertdata;

  ret = (xRenderDataTexture*)malloc(sizeof(xRenderDataTexture));
  duplicate = (xRenderDataTexture*)malloc(sizeof(xRenderDataTexture));

  xRenderDataTextureInit(ret);
  xRenderDataTextureInit(duplicate);

  duplicate->m_pNext = ret;
  duplicate->m_pPrev = pData->m_pPrev;

  duplicate->m_uiRenderType = pData->m_uiRenderType;
  duplicate->m_iTextureIndexArrayLength = pData->m_iTextureIndexArrayLength;
  duplicate->m_uiTextureIndexArray = (GLuint*)malloc(sizeof(GLuint) * pData->m_iTextureIndexArrayLength);
  duplicate->m_uiTextureIndexArray[0] = pData->m_uiTextureIndexArray[0];

  if(pData->m_iTextureIndexArrayLength > 1)
    duplicate->m_uiTextureIndexArray[1] = pData->m_uiTextureIndexArray[1];

  ret->m_pNext = pData;
  ret->m_pPrev = duplicate;

  vertdata = pData->m_xVertDataList.m_pNext;
  dupvertdata = &amp;duplicate->m_xVertDataList;
  while(1)
  {
    if(vertdata->m_fDepth > fDepth)
      break;

    vertdata->m_pPrev = dupvertdata;
    dupvertdata->m_pNext = vertdata;
    dupvertdata = vertdata;
   
    dupvertdata->m_pTextureData = duplicate;
   
    dupvertdata->m_pNext = 0;

    vertdata = vertdata->m_pNext;
  }

  vertdata->m_pPrev = &amp;pData->m_xVertDataList;
  pData->m_xVertDataList.m_pNext = vertdata;

  return ret;
}

As stated earlier, the xRenderDataVert function is quite simple:

c code:
xRenderDataVerts* xSceneGraphGetRenderDataVerts(xSceneGraph *pThis, xRenderDataTexture *pData, float fDepth)
{
  xRenderDataVerts *temp;
  xRenderDataVerts *current = &amp;pData->m_xVertDataList;

  while(current->m_pNext)
  {
    if(fDepth <= current->m_fDepth)
    {
      if(current->m_pPrev)
        current = current->m_pPrev;

      break;
    }

    current = current->m_pNext;
  }

  temp = current->m_pNext;
  current->m_pNext = (xRenderDataVerts*)malloc(sizeof(xRenderDataVerts));

  xRenderDataVertsInit(current->m_pNext);

  current->m_pNext->m_pPrev = current;
  current->m_pNext->m_pNext = temp;
  current->m_pNext->m_fDepth = fDepth;

  if(temp)
    temp->m_pPrev = current->m_pNext;

  return current->m_pNext;
}

At this point, we have the base functions for inserting an object into our Scene Graph. Lets take a look at an example of how these functions could be used to add a Game Object to the scene:

c code:
xRenderDataVerts* xSceneGraphInsertQuad(xSceneGraph *pThis, float fDepth, int iBlendMode, int iTextureCount, GLuint *pTextureIDs, bool bForceUniqueTextureData)
{
  xRenderDataShader *shaderdata;
  xRenderDataTexture *texturedata;
  xRenderDataVerts *vertdata;
 
  shaderdata = xSceneGraphGetRenderDataShader(pThis, fDepth, iBlendMode, 0);

  texturedata = xSceneGraphGetRenderDataTexture(pThis, shaderdata, fDepth, bForceUniqueTextureData, GL_QUADS, iTextureCount, pTextureIDs);

  vertdata = xSceneGraphGetRenderDataVerts(pThis, texturedata, fDepth);

  vertdata->m_iVectorArrayLength = 4;
  vertdata->m_xVectorArray = (xVector*)malloc(sizeof(xVector) * vertdata->m_iVectorArrayLength);
  memset(vertdata->m_xVectorArray, 0, sizeof(xVector) * vertdata->m_iVectorArrayLength);

  vertdata->m_xTextureCoordinatesArray = (xVector2D*)malloc(sizeof(xVector2D) * vertdata->m_iVectorArrayLength);
  memset(vertdata->m_xTextureCoordinatesArray, 0, sizeof(xVector2D) * vertdata->m_iVectorArrayLength);

  vertdata->m_fDepth = fDepth;
  vertdata->m_pShaderData = shaderdata;
  vertdata->m_pTextureData = texturedata;

  return vertdata;
}

So, the game object simply has to store the reference to the xRenderDataVerts object that has been created, update the Vertex and Texture Coordinates to have an object that's rendering to the screen, such as this:

c code:
void xGameObjectUpdate(xRenderDataVerts *pVertData)
{
  //Top Left Vertex
  pVertData->m_xVectorArray[0].x = 0.0;
  pVertData->m_xVectorArray[0].y = 0.0;

  //Bottom Left Vertex
  pVertData->m_xVectorArray[1].x = 0.0;
  pVertData->m_xVectorArray[1].y = 128.0;

  //Bottom Right Vertex
  pVertData->m_xVectorArray[2].x = 128.0;
  pVertData->m_xVectorArray[2].y = 128.0;

  //Top Right Vertex
  pVertData->m_xVectorArray[3].x = 128.0;
  pVertData->m_xVectorArray[3].y = 0.0;

  //Top Left Vertex
  pVertData->m_xTextureCoordinatesArray[0].x = 0.0;
  pVertData->m_xTextureCoordinatesArray[0].y = 1.0;

  //Bottom Left Vertex
  pVertData->m_xTextureCoordinatesArray[1].x = 0.0;
  pVertData->m_xTextureCoordinatesArray[1].y = 0.0;

  //Bottom Right Vertex
  pVertData->m_xTextureCoordinatesArray[2].x = 1.0;
  pVertData->m_xTextureCoordinatesArray[2].y = 0.0;

  //Top Right Vertex
  pVertData->m_xTextureCoordinatesArray[3].x = 1.0;
  pVertData->m_xTextureCoordinatesArray[3].y = 1.0;
}

Finally, we just need our Render Loop to go over the data and make the correct OpenGL calls to render everything to the render target(which is the back buffer in most games):

c code:
void xGraphicsUpdate()
{
  xRenderDataShader *shaderdata;
  xRenderDataTexture *texturedata;
  xRenderDataVerts *vertdata;

  shaderdata = g_xGraphics.m_pSceneGraph->m_xRenderData.m_pNext;

  glEnable(GL_BLEND);
  glEnableClientState(GL_VERTEX_ARRAY);

  while(shaderdata)
  {
    if(shaderdata->m_bRender)
    {
      switch(shaderdata->m_iBlendMode)
      {
        case 0: // None
          glDisable(GL_BLEND);
          break;

        case 1: // Alpha
          glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
          break;
      }

      texturedata = shaderdata->m_xTextureDataList.m_pNext;

      while(texturedata)
      {
        if(texturedata->m_bRender)
        {
          // TODO: Set Shader Parameters here using texturedata->m_xShaderParameterArray

          if(texturedata->m_iTextureIndexArrayLength > 0)
          {
            glEnable(GL_TEXTURE_2D);
            glBindTexture(GL_TEXTURE_2D, texturedata->m_uiTextureIndexArray[0]);
            glEnableClientState(GL_TEXTURE_COORD_ARRAY);
          }
          else
          {
            glDisable(GL_TEXTURE_2D);
            glDisableClientState(GL_TEXTURE_COORD_ARRAY);
          }

          vertdata = texturedata->m_xVertDataList.m_pNext;

          while(vertdata)
          {
            if(vertdata->m_bRender)
            {
              glColor4f(vertdata->m_xColor.R, vertdata->m_xColor.G, vertdata->m_xColor.B, vertdata->m_xColor.A);

              if(texturedata->m_iTextureIndexArrayLength > 0)
                glTexCoordPointer(2, GL_FLOAT, 0, vertdata->m_xTextureCoordinatesArray);

              glVertexPointer(3, GL_FLOAT, 0, vertdata->m_xVectorArray);

              glDrawArrays(texturedata->m_uiRenderType, 0, vertdata->m_iVectorArrayLength);

              if(vertdata->m_xRotation.x)
                glPopMatrix();
            }

            vertdata = vertdata->m_pNext;
          }
        }

        texturedata = texturedata->m_pNext;
      }

      switch(shaderdata->m_iBlendMode)
      {
        case 0:
          glEnable(GL_BLEND);
          break;
      }
    }

    shaderdata = shaderdata->m_pNext;
  }

  glColor4f(1.0, 1.0, 1.0, 1.0);
  glDisableClientState(GL_VERTEX_ARRAY);
  glDisable(GL_BLEND);
}

Obviously, for this to all work properly you have to initialize OpenGL, clear the back buffer before calling xGraphicsUpdate, and then call SwapBuffers after the call. You also, if your game is 2D, should already be in Ortho mode. If you are having trouble getting all these details to work, please let me know and I might add the details to this tutorial to help you guys out.

Also, in case you're trying to just copy and paste the code in this tutorial, here are the xVector and xColor data structures, along with their init and copy code:

c code:
struct xVector
{
  float x, y, z;
};

struct xVector2D
{
  float x, y;
};

inline void xVectorInit(xVector *pThis)
{
  pThis->x = 0.0;
  pThis->y = 0.0;
  pThis->z = 0.0;
}

inline void xVectorInit(xVector *pThis, float fX, float fY, float fZ)
{
  pThis->x = fX;
  pThis->y = fY;
  pThis->z = fZ;
}

inline void xVectorCopy(xVector *pDest, const xVector *pSource)
{
  pDest->x = pSource->x;
  pDest->y = pSource->y;
  pDest->z = pSource->z;
}

inline void xVector2DInit(xVector2D *pThis, float fX, float fY)
{
  pThis->x = fX;
  pThis->y = fY;
}

struct xColor
{
  float R, G, B, A;
};

inline void xColorInit(xColor *pThis)
{
  pThis->R = 1.0;
  pThis->G = 1.0;
  pThis->B = 1.0;
  pThis->A = 1.0;
}

inline xColor* xColorInit(xColor *pThis, float fR, float fG, float fB, float fA)
{
  pThis->R = fR;
  pThis->G = fG;
  pThis->B = fB;
  pThis->A = fA;

  return pThis;
}

inline void xColorCopy(xColor *pDest, const xColor *pSource)
{
  pDest->R = pSource->R;
  pDest->G = pSource->G;
  pDest->B = pSource->B;
  pDest->A = pSource->A;
}

inline xColor* xColorMultiply(xColor *pDest, const xColor *pLeft, const xColor *pRight)
{
  pDest->R = pLeft->R * pRight->R;
  pDest->G = pLeft->G * pRight->G;
  pDest->B = pLeft->B * pRight->B;
  pDest->A = pLeft->A * pRight->A;

  return pDest;
}

So, that's it. Relatively simple, but extremely effective. I'm sure someone with way more knowledge than me can point out where I'm doing this less efficiently than could be done otherwise, but this has been working well for my needs. I hope this all proves useful to someone =)

All the code shown here, not in compiling format, can be grabbed here.

Post comment Comments
feillyne
feillyne Jan 14 2011, 2:42pm says:

It's a nicely composed tutorial. ;-)

+2 votes     reply to comment
Xienen
Xienen Jan 30 2011, 5:32pm replied:

Thanks, I hope it helps someone out there =)

+1 vote     reply to comment
Post a Comment
click to sign in

You are not logged in, your comment will be anonymous unless you join the community today (totally free - or sign in with your social account on the right) which we encourage all contributors to do.

2000 characters limit; HTML formatting and smileys are not supported - text only

Established
Jan 21, 2009
Privacy
Public
Subscription
Open to all members
Contact
Send Message
Email
Members Only
Membership
Join this group
Group Watch
Track this group
Tutorial
Browse
Tutorials
Report Abuse
Report article
Related Games
Break Blocks
Break Blocks Indie Single Player Rhythm
Related Engines
Custom Built
Custom Built Commercial Released Sep 1, 2007
Related Groups
Greater Good Games
Greater Good Games Developer with 3 members
Indie Devs
Indie Devs Hobbies & Interests group with 1,110 members