GLESGAE:Shader Based Contexts

From Pandora Wiki
Jump to: navigation, search

Contents

GLESGAE - The Shader Based Context

Fast Track

We're on SVN revision 4 now, which includes all the rendering stuff, and the mesh code from previous guides. svn co -r 4 http://svn3.xp-dev.com/svn/glesgae/trunk/ glesgae

Introduction

This was much bigger than it is now.. as I originally jumped from Events to Shader Based Rendering due to there already being some Fixed Function stuff on the Wiki. However, I also needed to create a Mesh object and some foundation, so I split things up and did everything else first.

Therefore, it is highly advisable to have at least read GLESGAE:Making a Mesh before continuing. Reading GLESGAE:Fixed Function Rendering Contexts wouldn't kill you either.

Platform Specific Madness

As an aside, this is where platform specifics can bite your bum.
On my netbook with Intel GMA 950, my default gl.h does not include shader support, so I had to pull in GLee instead, which rips it out from the driver itself, and gives you handy checking abilities to ensure you're not trying to use something which isn't there.
Technically, GLee and GLEW do the same thing, so it's a matter of taste as to which one you use really, but they're immensely handy for when you can't be bothered doing all the actual extension-o-rama with OpenGL yourself.

You can get GLee from: http://elf-stone.com/glee.php

The Shader

In a shader-based rendering system, the shader is perhaps one of - if not the most - important parts of the renderer.
What do I mean by this? Well, your shader will be what defines what gets sent to the GPU - vertex positions, colours, texture co-ordinates, etc...
If the shader isn't expecting it, it can cause Open GL to crash viciously.
Therefore, it's of high importance to get the whole concept of a shader completely defined from the start - what it does, and how you use them.

Attributes and Uniforms

A shader comprises of two main types of modifiable data that we can send to it from the engine - attributes and uniforms.
Attributes are used only in the Vertex Shader, and are then converted to Varyings which are passed to the Fragment Shader.

Attributes are your vertex descriptors as previously mentioned; position, colour, co-ordinates, etc... whereas uniforms are any random bit of data that you want your shader to process; time for example.

Now, obviously we should know what our shader is expecting as we wrote it, but it's much more convenient to pull this information back out of the shader as we load it up.
This allows us to just push any old shader in, and our rendering context will configure itself accordingly.
Of course, we still have to send the data over, and if we don't feed our shader what it needs, it can cause rather bizarre effects - or worse, crashes.

Before we get that far though, we need to load the shader up.

Writing our First Shaders

In the GL ES variant of GLSL you're allowed vertex and fragment shaders. When combined, this creates a shader program.
Vertex Shaders deal with manipulating geometry; pushing vertices around on the screen.
Fragment Shaders deal with manipulating fragments - or pixels; such as texturing or colouring.
Vertex Shaders deal with attributes, modifying these as varyings to pass to the Fragment shader; both of which can also deal with uniforms.
A Shader Program can comprise of many of these Vertex and Fragment shaders, but must have only one main() function for each Vertex/Fragment part.
To make matters trickier than they need be, GLSL is not file-based like HLSL or CG, therefore you need to manage common functionality yourself rather than define a set of common functions in one shader file, and including it where it needs be.
Believe me, this is an arse to deal with properly and you really need to come up with some form of effects descriptor to handle it correctly... something beyond our scope for the moment.

Anyway, once we load up a Vertex and Fragment shader, we link them together to form a Shader Program. We can then pull out all the attributes and uniforms which have not been optimised out.
Read that last line again, as it's rather important. The GLSL shader compiler will automatically optimise out attributes and uniforms which are not used, so although you may define them, and expect them to be there; if they've not been used, they won't be!
This is why I much prefer to check the Shader Program to see what it has, rather than assuming what I'm going to get.

It's easier to understand what's going on with an example:

A Simple Vertex Shader

attribute vec4 a_position;
attribute vec4 a_colour;

varying vec4 v_colour;

void main() {
	gl_Position = a_position;
	v_colour = a_colour;
}

This shader expects two Attributes - a_position and a_colour. This means, it expect a Vertex Position and a Vertex Colour for every vertex it receives.
It also expects them as four vectors, or floats in other words. Simple enough?
We don't do anything to the position, and just pass it directly to OpenGL via the special gl_Position variable.
We also don't do anything to the colour, but we do want to pass it along to the Fragment Shader, so we create a varying, and make that equal to our attribute.

A Simple Fragment Shader

varying vec4 		v_colour;
uniform sampler2D 	s_texture;
uniform float 		u_random;

void main()
{
	gl_FragColor = v_colour;
}

Our Fragment shader is even simpler, we just equal our special gl_FragColor variable to our varying which has come from the Vertex Shader and we're done!
I've also included two uniforms in here, a standard float - u_random, and a texture sampler - s_texture. These don't get used, so will be optimised out by the compiler when it links these two shaders together.

A quick gotcha is that of precision... some GL ES implementations (Pandora's for instance) require a default precision to be set, others have one pre-defined. Desktop GLSL doesn't support default precision either, so you're left with the fun task of marking every sodding thing with lowp, mediump and highp if you run into it... or adding it into your shader compile chain ( which we will do later, I've done a trick in the example instead. )

That wasn't so hard was it?

The Shader Loader

We have our two shaders, now we need to compile them and make them do something.

For this, we shall create a new class so we can store all our fun Shader uniforms and attributes. We shall call this Shader, due to lack of imagination ( you can call it Bob, if you prefer. )

Shader.h

#ifndef _SHADER_H_
#define _SHADER_H_

#include <string>
#include <vector>

#if defined(GLX)
	#include "GLee.h"
#elif defined(PANDORA)
	#include <GLES2/gl2.h>
#endif

namespace GLESGAE
{
	class Shader
	{
		public:
			Shader();
			~Shader();

			/// Create a shader from source
			void createFromSource(const std::string& vertex, const std::string& fragment);

			/// Create a shader from file
			void createFromFile(const std::string& vertex, const std::string& fragment);

			/// Get Attribute Location
			const GLint getAttribute(const std::string& attribute) const;

			/// Get Uniform Location
			const GLint getUniform(const std::string& uniform) const;

			/// Get Program Id
			const GLuint getProgramId() const { return mShaderProgram; }

		protected:
			/// Actually load and compile the shader source
			const GLuint loadShader(const std::string& shader, const GLenum type);

			/// Clear out any shader stuff we may currently have - useful for forcing a recompile of the shader
			void resetShader();

		private:
			std::vector<std::pair<std::string, GLint> > mUniforms;
			std::vector<std::pair<std::string, GLint> > mAttributes;
			GLuint mVertexShader;
			GLuint mFragmentShader;
			GLuint mShaderProgram;
	};
}

#endif

Pretty straight forward really.
While Desktops can have Geometry shaders as well, we're aiming for compatibility with GL ES for the most part; which does not have Geometry shaders.. so we'll only deal with Vertex and Fragments.
We also store two arrays; one of uniforms and another of attributes, so we know which location they are stored when we come to bind and use this shader.

Shader.cpp

#include "Shader.h"

using namespace GLESGAE;

Shader::Shader()
: mUniforms()
, mAttributes()
, mVertexShader(GL_INVALID_VALUE)
, mFragmentShader(GL_INVALID_VALUE)
, mShaderProgram(GL_INVALID_VALUE)
{

}

Shader::~Shader()
{
	resetShader();
}

void Shader::createFromFile(const std::string& vertex, const std::string& fragment)
{
	// TODO: implement
}

void Shader::createFromSource(const std::string& vertex, const std::string& fragment)
{
	mVertexShader = loadShader(vertex, GL_VERTEX_SHADER);
	mFragmentShader = loadShader(fragment, GL_FRAGMENT_SHADER);

	if ((GL_INVALID_VALUE == mVertexShader) || (GL_INVALID_VALUE == mFragmentShader)) {
		// TODO: check that either shader has come back as GL_INVALID_VALUE and scream!
		return;
	}

	mShaderProgram = glCreateProgram();
	glAttachShader(mShaderProgram, mVertexShader);
	glAttachShader(mShaderProgram, mFragmentShader);
	glLinkProgram(mShaderProgram);

	GLint isLinked;
	glGetProgramiv(mShaderProgram, GL_LINK_STATUS, &isLinked);
	if (false == isLinked) {
		GLint infoLen(0U);

		glGetProgramiv(mShaderProgram, GL_INFO_LOG_LENGTH, &infoLen);
		if (infoLen > 1) {
			char* infoLog(new char[infoLen]);
			glGetShaderInfoLog(mShaderProgram, infoLen, NULL, infoLog);
			// TODO: something bad happened.. print the infolog and die.
			delete [] infoLog;
		}

		// TODO: Die.. something bad has happened
		resetShader();
		return;
	}

	{ // Find all Uniforms.
		GLint numUniforms;
		GLint maxUniformLen;
		glGetProgramiv(mShaderProgram, GL_ACTIVE_UNIFORMS, &numUniforms);
		glGetProgramiv(mShaderProgram, GL_ACTIVE_UNIFORM_MAX_LENGTH, &maxUniformLen);
		char* uniformName(new char[maxUniformLen]);

		for (GLint index(0); index < numUniforms; ++index) {
			GLint size;
			GLenum type;
			GLint location;

			glGetActiveUniform(mShaderProgram, index, maxUniformLen, NULL, &size, &type, uniformName);
			location = glGetUniformLocation(mShaderProgram, uniformName);

			std::pair<std::string, GLint> parameter;
			parameter.first = std::string(uniformName);
			parameter.second = location;
			mUniforms.push_back(parameter);
		}

		delete [] uniformName;
	}

	{ // Find all Attributes
		GLint numAttributes;
		GLint maxAttributeLen;
		glGetProgramiv(mShaderProgram, GL_ACTIVE_ATTRIBUTES, &numAttributes);
		glGetProgramiv(mShaderProgram, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &maxAttributeLen);
		char* attributeName(new char[maxAttributeLen]);

		for (GLint index(0); index < numAttributes; ++index) {
			GLint size;
			GLenum type;
			GLint location;

			glGetActiveAttrib(mShaderProgram, index, maxAttributeLen, NULL, &size, &type, attributeName);
			location = glGetAttribLocation(mShaderProgram, attributeName);

			std::pair<std::string, GLint> parameter;
			parameter.first = std::string(attributeName);
			parameter.second = location;
			mAttributes.push_back(parameter);
		}

		delete [] attributeName;
	}
}

const GLuint Shader::loadShader(const std::string& shader, const GLenum type)
{
	GLuint newShader(glCreateShader(type));

	const char* shaderSource(shader.c_str());
	glShaderSource(newShader, 1, &shaderSource, NULL);
	glCompileShader(newShader);

	GLint isCompiled;
	glGetShaderiv(newShader, GL_COMPILE_STATUS, &isCompiled);
	if (!isCompiled) {
		GLint infoLen(0);
		glGetShaderiv(newShader, GL_INFO_LOG_LENGTH, &infoLen);
		if (infoLen > 1) {
			char* infoLog(new char[infoLen]);
			glGetShaderInfoLog(newShader, infoLen, NULL, infoLog);
			delete [] infoLog;
		}

		glDeleteShader(newShader);
		// TODO: catch this... this is bad
		return GL_INVALID_VALUE;
	}

	return newShader;
}

const GLint Shader::getAttribute(const std::string& attribute) const
{
	for (std::vector<std::pair<std::string, GLint> >::const_iterator itr(mAttributes.begin()); itr < mAttributes.end(); ++itr) {
		if (attribute == itr->first)
			return itr->second;
	}

	// TODO: catch this! obviously this is bad!
	return GL_INVALID_VALUE;
}

const GLint Shader::getUniform(const std::string& uniform) const
{
	for (std::vector<std::pair<std::string, GLint> >::const_iterator itr(mUniforms.begin()); itr < mUniforms.end(); ++itr) {
		if (uniform == itr->first)
			return itr->second;
	}

	// TODO: catch this! obviously this is bad!
	return GL_INVALID_VALUE;
}

void Shader::resetShader()
{
	if (GL_INVALID_VALUE != mVertexShader) {
		glDetachShader(mShaderProgram, mVertexShader);
		glDeleteShader(mVertexShader);
	}
	if (GL_INVALID_VALUE != mFragmentShader) {
		glDetachShader(mShaderProgram, mFragmentShader);
		glDeleteShader(mFragmentShader);
	}
	if (GL_INVALID_VALUE != mShaderProgram)
		glDeleteProgram(mShaderProgram);

	mUniforms.clear();
	mAttributes.clear();

	mVertexShader = GL_INVALID_VALUE;
	mFragmentShader = GL_INVALID_VALUE;
	mShaderProgram = GL_INVALID_VALUE;
}

Lots of code, but again, it's pretty straight forward.. I'd highly recommend the OpenGL ES 2.0 Programming Guide to go through if you want more detail. It should be by your side while doing any ES work though, seriously!

Shader Context Additions

Now we have a Shader object, we need to put the infrastructure in to actually use it. This means ShaderBasedContext is getting updated a bit.

ShaderBasedContext.h

#ifndef _SHADER_BASED_CONTEXT_H_
#define _SHADER_BASED_CONTEXT_H_

#if defined(GLX)
	#include "../GLee.h"
#elif defined(PANDORA)
	#include <GLES2/gl2.h>
#endif

namespace GLESGAE
{
	class Mesh;
	class Shader;
	class ShaderBasedContext
	{
		public:
			ShaderBasedContext();
			virtual ~ShaderBasedContext();

		protected:
			/// Reset all attribute location links
			void resetAttributes();

			/// Draw a Mesh using the Shader Based Pipeline
			void drawMeshShaderBased(Mesh* const mesh);

			/// Bind a shader
			void bindShader(const Shader* const shader);

		private:
			const Shader* mCurrentShader;
			GLuint a_position;
			GLuint a_colour;
			GLuint a_normal;
			GLuint a_texCoord0;
			GLuint a_texCoord1;
			GLuint a_custom0;
			GLuint a_custom1;
			GLuint a_custom2;
	};

}

#endif

Not much to this, really...
One oddity is that I'm caching the default set of attributes - a_position, a_colour, a_normal, a_texCoord0, a_texCoord1, a_custom0, a_custom1 and a_custom2. This is so we don't need to regrab them every object if they share the same shader.

To make it more generic, you could just store an array of GLuints instead, and run through them in order. I shall leave that as a reader exercise for the moment.

ShaderBasedContext.cpp

#include "ShaderBasedContext.h"

#include "../IndexBuffer.h"
#include "../Material.h"
#include "../Mesh.h"
#include "../Texture.h"
#include "../VertexBuffer.h"
#include "../Shader.h"

#include <cstdio>

using namespace GLESGAE;

ShaderBasedContext::ShaderBasedContext()
: mCurrentShader(0)
, a_position(GL_INVALID_VALUE)
, a_colour(GL_INVALID_VALUE)
, a_normal(GL_INVALID_VALUE)
, a_texCoord0(GL_INVALID_VALUE)
, a_texCoord1(GL_INVALID_VALUE)
, a_custom0(GL_INVALID_VALUE)
, a_custom1(GL_INVALID_VALUE)
, a_custom2(GL_INVALID_VALUE)
{

}

ShaderBasedContext::~ShaderBasedContext()
{
	mCurrentShader = 0;
}

void ShaderBasedContext::bindShader(const Shader* const shader)
{
	if (mCurrentShader != shader) {
		mCurrentShader = shader;
		glUseProgram(shader->getProgramId());
		resetAttributes();
	}
}

void ShaderBasedContext::resetAttributes()
{
	a_position = GL_INVALID_VALUE;
	a_colour = GL_INVALID_VALUE;
	a_normal = GL_INVALID_VALUE;
	a_texCoord0 = GL_INVALID_VALUE;
	a_texCoord1 = GL_INVALID_VALUE;
	a_custom0 = GL_INVALID_VALUE;
	a_custom1 = GL_INVALID_VALUE;
	a_custom2 = GL_INVALID_VALUE;

	if (0 != mCurrentShader) {
		a_position = mCurrentShader->getAttribute("a_position");
		a_colour = mCurrentShader->getAttribute("a_colour");
		a_normal = mCurrentShader->getAttribute("a_normal");
		a_texCoord0 = mCurrentShader->getAttribute("a_texCoord0");
		a_texCoord1 = mCurrentShader->getAttribute("a_texCoord1");
		a_custom0 = mCurrentShader->getAttribute("a_custom0");
		a_custom1 = mCurrentShader->getAttribute("a_custom1");
		a_custom2 = mCurrentShader->getAttribute("a_custom2");
	}

	if (GL_INVALID_VALUE != a_position)
		glEnableVertexAttribArray(a_position);
	if (GL_INVALID_VALUE != a_colour)
		glEnableVertexAttribArray(a_colour);
	if (GL_INVALID_VALUE != a_normal)
		glEnableVertexAttribArray(a_normal);
	if (GL_INVALID_VALUE != a_texCoord0)
		glEnableVertexAttribArray(a_texCoord0);
	if (GL_INVALID_VALUE != a_texCoord1)
		glEnableVertexAttribArray(a_texCoord1);
	if (GL_INVALID_VALUE != a_custom0)
		glEnableVertexAttribArray(a_custom0);
	if (GL_INVALID_VALUE != a_custom1)
		glEnableVertexAttribArray(a_custom1);
	if (GL_INVALID_VALUE != a_custom2)
		glEnableVertexAttribArray(a_custom2);
}

void ShaderBasedContext::drawMeshShaderBased(Mesh* const mesh)
{
	const IndexBuffer* const indexBuffer(mesh->getIndexBuffer());
	const VertexBuffer* const vertexBuffer(mesh->getVertexBuffer());
	const Material* const material(mesh->getMaterial());
	unsigned int currentTextureUnit(0U);

	bindShader(material->getShader());

	const std::vector<VertexBuffer::Format>& meshFormat(vertexBuffer->getFormat());
	for (std::vector<VertexBuffer::Format>::const_iterator itr(meshFormat.begin()); itr < meshFormat.end(); ++itr) {
		switch (itr->getType()) {
			// Position
			case VertexBuffer::FORMAT_POSITION_2F:
				glVertexAttribPointer(a_position, 2, GL_FLOAT, GL_FALSE, 0, vertexBuffer->getData() + itr->getOffset());
				break;
			case VertexBuffer::FORMAT_POSITION_3F:
				glVertexAttribPointer(a_position, 3, GL_FLOAT, GL_FALSE, 0, vertexBuffer->getData() + itr->getOffset());
				break;
			case VertexBuffer::FORMAT_POSITION_4F:
				glVertexAttribPointer(a_position, 4, GL_FLOAT, GL_FALSE, 0, vertexBuffer->getData() + itr->getOffset());
				break;
			case VertexBuffer::FORMAT_POSITION_2S:
				glVertexAttribPointer(a_position, 2, GL_SHORT, GL_FALSE, 0, vertexBuffer->getData() + itr->getOffset());
				break;
			case VertexBuffer::FORMAT_POSITION_3S:
				glVertexAttribPointer(a_position, 3, GL_SHORT, GL_FALSE, 0, vertexBuffer->getData() + itr->getOffset());
				break;
			case VertexBuffer::FORMAT_POSITION_4S:
				glVertexAttribPointer(a_position, 4, GL_SHORT, GL_FALSE, 0, vertexBuffer->getData() + itr->getOffset());
				break;
			case VertexBuffer::FORMAT_POSITION_2B:
				glVertexAttribPointer(a_position, 2, GL_BYTE, GL_FALSE, 0, vertexBuffer->getData() + itr->getOffset());
				break;
			case VertexBuffer::FORMAT_POSITION_3B:
				glVertexAttribPointer(a_position, 3, GL_BYTE, GL_FALSE, 0, vertexBuffer->getData() + itr->getOffset());
				break;
			case VertexBuffer::FORMAT_POSITION_4B:
				glVertexAttribPointer(a_position, 4, GL_BYTE, GL_FALSE, 0, vertexBuffer->getData() + itr->getOffset());
				break;
			// Normal
			case VertexBuffer::FORMAT_NORMAL_3F:
				glVertexAttribPointer(a_normal, 3, GL_FLOAT, GL_FALSE, 0, vertexBuffer->getData() + itr->getOffset());
				break;
			case VertexBuffer::FORMAT_NORMAL_3S:
				glVertexAttribPointer(a_normal, 3, GL_SHORT, GL_FALSE, 0, vertexBuffer->getData() + itr->getOffset());
				break;
			case VertexBuffer::FORMAT_NORMAL_3B:
				glVertexAttribPointer(a_normal, 3, GL_BYTE, GL_FALSE, 0, vertexBuffer->getData() + itr->getOffset());
				break;
			// Colour
			case VertexBuffer::FORMAT_COLOUR_4F:
				glVertexAttribPointer(a_colour, 4, GL_FLOAT, GL_FALSE, 0, vertexBuffer->getData() + itr->getOffset());
				break;
			case VertexBuffer::FORMAT_COLOUR_3F:
				glVertexAttribPointer(a_colour, 3, GL_FLOAT, GL_FALSE, 0, vertexBuffer->getData() + itr->getOffset());
				break;
			case VertexBuffer::FORMAT_COLOUR_4S:
				glVertexAttribPointer(a_colour, 4, GL_SHORT, GL_FALSE, 0, vertexBuffer->getData() + itr->getOffset());
				break;
			case VertexBuffer::FORMAT_COLOUR_3S:
				glVertexAttribPointer(a_colour, 3, GL_SHORT, GL_FALSE, 0, vertexBuffer->getData() + itr->getOffset());
				break;
			case VertexBuffer::FORMAT_COLOUR_4UB:
				glVertexAttribPointer(a_colour, 4, GL_UNSIGNED_BYTE, GL_FALSE, 0, vertexBuffer->getData() + itr->getOffset());
				break;
			case VertexBuffer::FORMAT_COLOUR_3UB:
				glVertexAttribPointer(a_colour, 3, GL_UNSIGNED_BYTE, GL_FALSE, 0, vertexBuffer->getData() + itr->getOffset());
				break;
			default:
				break;
		};
	}

	switch (indexBuffer->getFormat()) {
		case IndexBuffer::FORMAT_FLOAT:
			glDrawElements(GL_TRIANGLES, indexBuffer->getSize(), GL_FLOAT, indexBuffer->getData());
			break;
		case IndexBuffer::FORMAT_UNSIGNED_BYTE:
			glDrawElements(GL_TRIANGLES, indexBuffer->getSize(), GL_UNSIGNED_BYTE, indexBuffer->getData());
			break;
		case IndexBuffer::FORMAT_UNSIGNED_SHORT:
			glDrawElements(GL_TRIANGLES, indexBuffer->getSize(), GL_UNSIGNED_SHORT, indexBuffer->getData());
			break;
		default:
			break;
	};
}

If you've come from the Fixed Function guide, the above will look rather familiar. The only real difference is we specify a Shader handle to bind the Vertex Pointer to, but other than that, what was said there still applies here. If you haven't read it, or have forgotten, you might want to - GLESGAE:Fixed Function Rendering Contexts

One thing we don't deal with yet is texturing.
Texturing is a bit weird on Shader systems, so we're holding off on that just now.. but we'll sort it out soon.

Feeding The Shader

We're not going to be setting up a uniform system just yet.. so our shaders are going to be rather basic for now.
We do have the ability to pull uniforms out the shader and bind stuff to them as it stands, however it's far better to have some sort of system that can take care of the grunt work for you.
We'll be looking at a uniform system soon.

A Simple Test

Our test app is getting a bit cluttered now...

#include <cstdio>
#include <cstdlib>

#include "../../Graphics/GraphicsSystem.h"
#include "../../Graphics/Context/FixedFunctionContext.h"
#include "../../Events/EventSystem.h"
#include "../../Input/InputSystem.h"
#include "../../Input/Keyboard.h"
#include "../../Input/Pad.h"

#include "../../Graphics/Mesh.h"
#include "../../Graphics/VertexBuffer.h"
#include "../../Graphics/IndexBuffer.h"
#include "../../Graphics/Material.h"
#include "../../Graphics/Shader.h"
#include "../../Maths/Matrix4.h"

using namespace GLESGAE;

void updateRender(Mesh* const mesh);
Mesh* makeSprite(Shader* const shader);
Shader* makeSpriteShader();

int main(void)
{
	EventSystem* eventSystem(new EventSystem);
	InputSystem* inputSystem(new InputSystem(eventSystem));
	GraphicsSystem* graphicsSystem(new GraphicsSystem(GraphicsSystem::SHADER_BASED_RENDERING));

	if (false == graphicsSystem->initialise("GLESGAE Shader Test", 800, 480, 16, false)) {
		//TODO: OH NOES! WE'VE DIEDED!
		return -1;
	}

	Mesh* mesh(makeSprite(makeSpriteShader()));
 
	eventSystem->bindToWindow(graphicsSystem->getWindow());

	Controller::KeyboardController* myKeyboard(inputSystem->newKeyboard());

	while(false == myKeyboard->getKey(Controller::KEY_ESCAPE)) {
		eventSystem->update();
		inputSystem->update();
		graphicsSystem->beginFrame();
		graphicsSystem->drawMesh(mesh);
		graphicsSystem->endFrame();
	}

	delete graphicsSystem;
	delete inputSystem;
	delete eventSystem;
	delete mesh;

	return 0;
}

Mesh* makeSprite(Shader* const shader)
{
	float vertexData[32] = {// Position - 16 floats
					-1.0F, 1.0F, 0.0F, 1.0F,
					1.0F, 1.0F, 0.0F, 1.0F,
					1.0F, -1.0F, 0.0F, 1.0F,
					-1.0F, -1.0F, 0.0F, 1.0F,
					// Colour - 16 floats
					0.0F, 1.0F, 0.0F, 1.0F,
					1.0F, 0.0F, 0.0F, 1.0F,
					0.0F, 0.0F, 1.0F, 1.0F,
					1.0F, 1.0F, 1.0F, 1.0F};

	unsigned int vertexSize = 32 * sizeof(float);

	unsigned char indexData[6] = { 0, 1, 2, 2, 3, 0 };
	unsigned int indexSize = 6 * sizeof(unsigned char);

	VertexBuffer* newVertexBuffer = new VertexBuffer(reinterpret_cast<unsigned char*>(&vertexData), vertexSize);
	newVertexBuffer->addFormatIdentifier(VertexBuffer::FORMAT_POSITION_4F, 4U);
	newVertexBuffer->addFormatIdentifier(VertexBuffer::FORMAT_COLOUR_4F, 4U);
	IndexBuffer* newIndexBuffer = new IndexBuffer(reinterpret_cast<unsigned char*>(&indexData), indexSize, IndexBuffer::FORMAT_UNSIGNED_BYTE);
	Material* newMaterial = new Material;
	newMaterial->setShader(shader);
	Matrix4* newTransform = new Matrix4;

	return new Mesh(newVertexBuffer, newIndexBuffer, newMaterial, newTransform);
}

#if defined(GLX)
	#include "../../Graphics/GLee.h"
#endif

Shader* makeSpriteShader()
{
	std::string vShader =
		"attribute vec4 a_position;		\n"
		"attribute vec4 a_colour;		\n"
		"varying vec4 v_colour;			\n"
		"void main()				\n"
		"{					\n"
		"	gl_Position = a_position;	\n"
		"	v_colour = a_colour;		\n"
		"}					\n";

	std::string fShader =
		#ifdef GLES2
		"precision mediump float;		\n" // setting the default precision for Pandora
		#endif
		"varying vec4 v_colour;			\n"
		"void main()				\n"
		"{					\n"
		"	gl_FragColor.grb = v_colour.rgb;\n"
		"}					\n";

	#ifndef GLES1
		Shader* newShader(new Shader());
		newShader->createFromSource(vShader, fShader);

		return newShader;
	#else
		return 0;
	#endif
}

Notice that the ES1 stuff has been ripped out?
ES1 does not support shaders, so there's no point in having that cluttering up our file. I've also removed the Pandora buttons from the Input example and just checking on Q being pressed.
Additionally, this is near enough exactly the same as the Fixed Function example, just with a shader being created and bound to the Material as well.

Building the Example

In the SVN there are Makefiles already setup for you.. just trigger make -f MakefileES2.pandora or whatever your chosen configuration is, and it'll happily build for you and spit out a GLESGAE.pandora binary for you to run.

Alternatively, if you use CodeLite, there's a Workspace/Project set for you preconfigured.

Next Time

Textures, followed by drawing via Vertex Buffer Object (VBO), then we Meet the Matrices.

Personal tools
community