A group dedicated to indie and standalone game development.

Post tutorial Report RSS 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 on - 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:

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:

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):

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:

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:

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:

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:

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:

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:

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):

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:

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 Staff
feillyne

It's a nicely composed tutorial. ;-)

Reply Good karma+2 votes
Xienen Author
Xienen

Thanks, I hope it helps someone out there =)

Reply Good karma+1 vote
Post a comment

Your comment will be anonymous unless you join the community. Or sign in with your social account: