GLESGAE:Fixed Function Rendering Contexts
Contents |
GLESGAE - Fixed Function Rendering Contexts
Introduction
Fixed Function Rendering is actually pretty easy.
You've a specific set of things you can and cannot do, and also your Meshes must conform to a reasonable standard for the most part.
This makes getting something up and running really quick and easy.
Following on from GLESGAE:Making a Mesh, we now have a Mesh object available to draw... so let's go ahead and do so.
Fast Track
We're on SVN revision 4 now, which includes all the mesh code from previous guide. svn co -r 4 http://svn3.xp-dev.com/svn/glesgae/trunk/ glesgae
The Fixed Function Context
Our Fixed Function Context is probably still quite empty, so let's start filling it up with some functions:
FixedFunctionContext.h
#ifndef _FIXED_FUNCTION_CONTEXT_H_
#define _FIXED_FUNCTION_CONTEXT_H_
#if defined(GLX)
#include "../GLee.h"
#elif defined(PANDORA)
#include <GLES/gl.h>
#endif
namespace GLESGAE
{
class Material;
class Mesh;
class FixedFunctionContext
{
/**
The quickest way for a Fixed Function pipeline to work, is to have all data match up to a
common format. As such, we use global state to deal with Vertex Attributes.
This means that you really need to order your data correctly so you're doing as few state
switches as possible when rendering - the context just renders, it doesn't organise for you.
Additionally, dealing with multiple texture co-ordinates is a pain as we can only really deal
with them as and when we get them, and store how many we've enabled/disabled since last time.
Also, with a Fixed Function pipeline, each Texture must have Texture Co-ordinates, so be
mindful of that with your data!
**/
public:
FixedFunctionContext();
virtual ~FixedFunctionContext();
/// Enable Vertex Positions
void enableFixedFunctionVertexPositions();
/// Disable Vertex Positions
void disableFixedFunctionVertexPositions();
/// Enable Vertex Colours
void enableFixedFunctionVertexColours();
/// Disable Vertex Colours
void disableFixedFunctionVertexColours();
/// Enable Vertex Normals
void enableFixedFunctionVertexNormals();
/// Disable Vertex Normals
void disableFixedFunctionVertexNormals();
protected:
/// Draw a Mesh using the Fixed Function Pipeline
void drawMeshFixedFunction(Mesh* const mesh);
/// Setup texturing - check if the requested texture unit is on and bind a texture from the material.
void setupFixedFunctionTexturing(unsigned int* textureUnit, const Material* const material);
/// Disable Texture Units
void disableFixedFunctionTexturing(const unsigned int currentTextureUnit);
private:
bool mFixedFunctionTexUnits[8]; // 8 Texture Units sounds like they'd be enough to me!
unsigned int mFixedFunctionLastTexUnit; // Last Texture Unit we were working on, in case it's the same.
};
}
#endif
FixedFunctionContext.cpp
#include "FixedFunctionContext.h"
#include "../IndexBuffer.h"
#include "../Material.h"
#include "../Mesh.h"
#include "../Texture.h"
#include "../VertexBuffer.h"
using namespace GLESGAE;
FixedFunctionContext::FixedFunctionContext()
: mFixedFunctionTexUnits()
, mFixedFunctionLastTexUnit(0U)
{
// Mark all texture co-ordinate arrays as offline.
for (unsigned int index(0U); index < 8U; ++index)
mFixedFunctionTexUnits[index] = false;
}
FixedFunctionContext::~FixedFunctionContext()
{
}
void FixedFunctionContext::drawMeshFixedFunction(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);
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:
glVertexPointer(2, GL_FLOAT, 0, vertexBuffer->getData() + itr->getOffset());
break;
case VertexBuffer::FORMAT_POSITION_3F:
glVertexPointer(3, GL_FLOAT, 0, vertexBuffer->getData() + itr->getOffset());
break;
case VertexBuffer::FORMAT_POSITION_4F:
glVertexPointer(4, GL_FLOAT, 0, vertexBuffer->getData() + itr->getOffset());
break;
case VertexBuffer::FORMAT_POSITION_2S:
glVertexPointer(2, GL_SHORT, 0, vertexBuffer->getData() + itr->getOffset());
break;
case VertexBuffer::FORMAT_POSITION_3S:
glVertexPointer(3, GL_SHORT, 0, vertexBuffer->getData() + itr->getOffset());
break;
case VertexBuffer::FORMAT_POSITION_4S:
glVertexPointer(4, GL_SHORT, 0, vertexBuffer->getData() + itr->getOffset());
break;
case VertexBuffer::FORMAT_POSITION_2B:
glVertexPointer(2, GL_BYTE, 0, vertexBuffer->getData() + itr->getOffset());
break;
case VertexBuffer::FORMAT_POSITION_3B:
glVertexPointer(3, GL_BYTE, 0, vertexBuffer->getData() + itr->getOffset());
break;
case VertexBuffer::FORMAT_POSITION_4B:
glVertexPointer(4, GL_BYTE, 0, vertexBuffer->getData() + itr->getOffset());
break;
// Normal
case VertexBuffer::FORMAT_NORMAL_3F:
glNormalPointer(GL_FLOAT, 0, vertexBuffer->getData() + itr->getOffset());
break;
case VertexBuffer::FORMAT_NORMAL_3S:
glNormalPointer(GL_SHORT, 0, vertexBuffer->getData() + itr->getOffset());
break;
case VertexBuffer::FORMAT_NORMAL_3B:
glNormalPointer(GL_BYTE, 0, vertexBuffer->getData() + itr->getOffset());
break;
// Colour
case VertexBuffer::FORMAT_COLOUR_4F:
glColorPointer(4, GL_FLOAT, 0, vertexBuffer->getData() + itr->getOffset());
break;
case VertexBuffer::FORMAT_COLOUR_3F:
glColorPointer(3, GL_FLOAT, 0, vertexBuffer->getData() + itr->getOffset());
break;
case VertexBuffer::FORMAT_COLOUR_4S:
glColorPointer(4, GL_SHORT, 0, vertexBuffer->getData() + itr->getOffset());
break;
case VertexBuffer::FORMAT_COLOUR_3S:
glColorPointer(3, GL_SHORT, 0, vertexBuffer->getData() + itr->getOffset());
break;
case VertexBuffer::FORMAT_COLOUR_4UB:
glColorPointer(4, GL_UNSIGNED_BYTE, 0, vertexBuffer->getData() + itr->getOffset());
break;
case VertexBuffer::FORMAT_COLOUR_3UB:
glColorPointer(3, GL_UNSIGNED_BYTE, 0, vertexBuffer->getData() + itr->getOffset());
break;
// Textures and Co-ordinates
case VertexBuffer::FORMAT_TEXTURE_2F:
setupFixedFunctionTexturing(¤tTextureUnit, material);
glTexCoordPointer(2, GL_FLOAT, 0, vertexBuffer->getData() + itr->getOffset());
break;
case VertexBuffer::FORMAT_TEXTURE_3F:
setupFixedFunctionTexturing(¤tTextureUnit, material);
glTexCoordPointer(3, GL_FLOAT, 0, vertexBuffer->getData() + itr->getOffset());
break;
case VertexBuffer::FORMAT_TEXTURE_4F:
setupFixedFunctionTexturing(¤tTextureUnit, material);
glTexCoordPointer(4, GL_FLOAT, 0, vertexBuffer->getData() + itr->getOffset());
break;
case VertexBuffer::FORMAT_TEXTURE_2S:
setupFixedFunctionTexturing(¤tTextureUnit, material);
glTexCoordPointer(2, GL_SHORT, 0, vertexBuffer->getData() + itr->getOffset());
break;
case VertexBuffer::FORMAT_TEXTURE_3S:
setupFixedFunctionTexturing(¤tTextureUnit, material);
glTexCoordPointer(3, GL_SHORT, 0, vertexBuffer->getData() + itr->getOffset());
break;
case VertexBuffer::FORMAT_TEXTURE_4S:
setupFixedFunctionTexturing(¤tTextureUnit, material);
glTexCoordPointer(4, GL_SHORT, 0, vertexBuffer->getData() + itr->getOffset());
break;
case VertexBuffer::FORMAT_TEXTURE_2B:
setupFixedFunctionTexturing(¤tTextureUnit, material);
glTexCoordPointer(2, GL_BYTE, 0, vertexBuffer->getData() + itr->getOffset());
break;
case VertexBuffer::FORMAT_TEXTURE_3B:
setupFixedFunctionTexturing(¤tTextureUnit, material);
glTexCoordPointer(3, GL_BYTE, 0, vertexBuffer->getData() + itr->getOffset());
break;
case VertexBuffer::FORMAT_TEXTURE_4B:
setupFixedFunctionTexturing(¤tTextureUnit, material);
glTexCoordPointer(4, GL_BYTE, 0, vertexBuffer->getData() + itr->getOffset());
break;
default:
break;
};
}
disableFixedFunctionTexturing(currentTextureUnit); // Disable any excess texture units
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;
};
mFixedFunctionLastTexUnit = currentTextureUnit;
}
void FixedFunctionContext::enableFixedFunctionVertexPositions()
{
glEnableClientState(GL_VERTEX_ARRAY);
}
void FixedFunctionContext::disableFixedFunctionVertexPositions()
{
glDisableClientState(GL_VERTEX_ARRAY);
}
void FixedFunctionContext::enableFixedFunctionVertexColours()
{
glEnableClientState(GL_COLOR_ARRAY);
}
void FixedFunctionContext::disableFixedFunctionVertexColours()
{
glDisableClientState(GL_COLOR_ARRAY);
}
void FixedFunctionContext::enableFixedFunctionVertexNormals()
{
glEnableClientState(GL_NORMAL_ARRAY);
}
void FixedFunctionContext::disableFixedFunctionVertexNormals()
{
glDisableClientState(GL_NORMAL_ARRAY);
}
void FixedFunctionContext::setupFixedFunctionTexturing(unsigned int* textureUnit, const Material* const material)
{
if (false == mFixedFunctionTexUnits[*textureUnit]) { // This texture unit isn't currently enabled
mFixedFunctionTexUnits[*textureUnit] = true;
glClientActiveTexture(GL_TEXTURE0 + *textureUnit);
glEnable(GL_TEXTURE_2D);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
} else {
if (*textureUnit != mFixedFunctionLastTexUnit) { // This texture unit is enabled but it's not the current one
glActiveTexture(GL_TEXTURE0 + *textureUnit);
}
}
Texture* const texture(material->getTexture(*textureUnit));
glBindTexture(GL_TEXTURE_2D, texture->getId());
*textureUnit++;
}
void FixedFunctionContext::disableFixedFunctionTexturing(const unsigned int currentTextureUnit)
{
unsigned int delta(mFixedFunctionLastTexUnit);
while (delta > currentTextureUnit) { // We're using less texture units than we need
glClientActiveTexture(GL_TEXTURE0 + delta);
glDisable(GL_TEXTURE_2D);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
--delta;
}
}
Most of this should be fairly straight-forward... but let's go into some of it, anyway.
Enabling/Disabling Vertex Attributes
A Vertex Attribute is generally one of the following:
- Vertex Position
- Vertex Normal
- Vertex Colour
- Vertex Texture Co-ordinate
We need to enable/disable these based on what our Vertex Format is.
It's also rather costly to enable/disable these at will, so generally we'll want all our meshes to correspond to a set standard. Therefore, the enable/disableFixedFunctionVertex* functions affect your global rendering state, and are public to be accessed when needed.
Additionally, enabling and disabling Texture Units is just as costly, so we have to store our own state here too.
Rendering via Vertex Arrays
The real meat of the class is the draw function.
It's not really that scary when you sit down and pick it apart, though! Really!
Most of it is my pedanticness of ensuring I can support as wide a range of things as possible.
So let's take one strip out - in particular, the stuff we need to draw our Mesh described in the previous Chapter:
void FixedFunctionContext::drawMeshFixedFunction(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);
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_3F:
glVertexPointer(3, GL_FLOAT, 0, vertexBuffer->getData() + itr->getOffset());
break;
case VertexBuffer::FORMAT_COLOUR_3F:
glColorPointer(3, GL_FLOAT, 0, vertexBuffer->getData() + itr->getOffset());
break;
default:
break;
};
}
So, bit more concise and easier to read, now!
We don't have any normal data, so we don't draw a normal. We do have Positional data, represented by 3 Floats, so we mark a glVertexPointer to the current iterator position offset from the vertex Buffer's data pointer - which is our big fat interleaved array of goodies.
The iterator itself is actually running through the Vertex Buffer's format array which we described and setup in the last guide, in the newSprite function, so it'll run through all the Format descriptors to find one in the switch and act appropriately, such as setting the glVertexPointer, or the glColorPointer.
disableFixedFunctionTexturing(currentTextureUnit); // Disable any excess texture units
switch (indexBuffer->getFormat()) {
case IndexBuffer::FORMAT_UNSIGNED_BYTE:
glDrawElements(GL_TRIANGLES, indexBuffer->getSize(), GL_UNSIGNED_BYTE, indexBuffer->getData());
break;
default:
break;
};
mFixedFunctionLastTexUnit = currentTextureUnit;
}
Although we don't deal with texturing yet, I've put the code in to handle most of it already. In this case, we're seeing if we need to enable/disable more texture units than we had the previous frame. Pretty straight forward stuff.. leaving texture units enabled when you don't need them can cause oddness, for example.. and obviously not enabling enough isn't really going to help much either.
Anyway, we get on to running through our Index Buffer to see what type it is, and then call glDrawElements specifying the Mesh as a set of triangles, how many indices to draw, the type, and where the data resides. This then draws all the Attributes we've defined based upon the indices specified in our Index buffer. Pretty nifty, huh? We don't need to know or care what comes in any more as long as it's been wrapped properly in a Mesh object.
Multi-Coloured Quad Goodness
Of course, we're still missing stuff - the transform matrices, for example - but we'll get there in due course. We can now display our quad on the screen with the following example program.
#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 "../../Maths/Matrix4.h"
using namespace GLESGAE;
void updateRender(Mesh* const mesh);
Mesh* makeSprite();
int main(void)
{
EventSystem* eventSystem(new EventSystem);
InputSystem* inputSystem(new InputSystem(eventSystem));
GraphicsSystem* graphicsSystem(new GraphicsSystem(GraphicsSystem::FIXED_FUNCTION_RENDERING));
if (false == graphicsSystem->initialise("GLESGAE Fixed Function Test", 800, 480, 16, false)) {
//TODO: OH NOES! WE'VE DIEDED!
return -1;
}
Mesh* mesh(makeSprite());
FixedFunctionContext* const fixedContext(graphicsSystem->getFixedContext());
if (0 != fixedContext) {
fixedContext->enableFixedFunctionVertexPositions();
fixedContext->enableFixedFunctionVertexColours();
}
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()
{
float vertexData[24] = {// Position - 12 floats
-1.0F, 1.0F, 0.0F,
1.0F, 1.0F, 0.0F,
1.0F, -1.0F, 0.0F,
-1.0F, -1.0F, 0.0F,
// Colour - 12 floats
0.0F, 1.0F, 0.0F,
1.0F, 0.0F, 0.0F,
0.0F, 0.0F, 1.0F,
1.0F, 1.0F, 1.0F};
unsigned int vertexSize = 24 * 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_3F, 4U);
newVertexBuffer->addFormatIdentifier(VertexBuffer::FORMAT_COLOUR_3F, 4U);
IndexBuffer* newIndexBuffer = new IndexBuffer(reinterpret_cast<unsigned char*>(&indexData), indexSize, IndexBuffer::FORMAT_UNSIGNED_BYTE);
Material* newMaterial = new Material;
Matrix4* newTransform = new Matrix4;
return new Mesh(newVertexBuffer, newIndexBuffer, newMaterial, newTransform);
}
Building the Example
In the SVN there are Makefiles already setup for you.. just trigger make -f MakefileES1.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
Let's render the same thing again, but through a Shader based system.