Saturday 27 November 2010

GLSL Shader Manager design Part 1

Just been re-writing my lectures for the OpenGL Shading language and decided to re-design my shader manager to be more compliant with the new specification.

When I designed my original system I was using the 1st edition of the Orange Book which I got at a 3D Labs master class in 2004 and is now quite outdated. I've recently go the 3rd edition of the book and read the GLSL API spec and started to design the new system.

The main initial criteria is for it to work as a standalone system without the need of my NGL:: library however it will also be integrated into the library at some stage to replace the existing one and be compatible with the ngl:: datatypes such as Matrix, Vector, Colour etc.

The present system is designed create a single Shader Program by passing in a source file for Vertex, Fragment and optionally a Geometry shader. However the specification says any number of shaders can be created and attached to a Shader Program before compilation. To this end the initial design will separate the Shader and the Program into different classes then have the shader manager contain both the Shaders and the Programs.

Programs will be stored using a std::string name for the user access, and as much as possible the Shader attributes / data values will be accessible via a text string.

GLSL API Process

The following diagram illustrates the basic process of generating shaders and a shader program.
From this process I decided to design the Shader class first as it is a fairly passive class. The outline of the class is as follows.

The main consideration is the class may belong to any number of Shader Programs so a basic reference counting mechanism is being built into the class so the ShaderManager class can see how many references the Shader has, I was initially considering using a boost::shared_ptr class to do this but want to reduce the dependancies on the standalone version. For the eventual ngl integration I may add this as ngl already relies on boost for a number of things.

Shader type is defined as an enum as follows

each shader must be constructed as one of the types which map to the GL data types for shaders. I've added tessellation as an option but don't have a GPU to support it as yet but trying to make things future proof.

The code to create the Shader object is quite simple as follows

Shader::Shader(
                std::string _name,
                SHADERTYPE _type
              )
{
  m_name=_name;
  m_shaderType = _type;
  m_debugState = true;
  m_compiled=false;
  switch (_type)
  {
    case VERTEX : { m_shaderHandle = glCreateShader(GL_VERTEX_SHADER_ARB); break; }
    case FRAGMENT : { m_shaderHandle = glCreateShader(GL_FRAGMENT_SHADER_ARB); break; }
    case GEOMETRY : { m_shaderHandle = glCreateShader(GL_GEOMETRY_SHADER_EXT); break; }
    case TESSELATION : { m_shaderHandle = NULL; std::cerr<<"not yet implemented\n"; }
  }
  m_compiled = false;
  m_refCount=0;
  m_source=0;
}
The handle returned from the glCreateShader function is the one used by OpenGL for the rest of the stages.

To load the source we use a std::string and a nice 1 liner using the istream iterator as follows

void Shader::load(
                   std::string _name
                 )
{
  // see if we already have some source attached
  if(m_source !=0)
  {
    std::cerr<<"deleting existing source code\n";
    delete m_source;
  }
  std::ifstream shaderSource(_name.c_str());
  if (!shaderSource.is_open())
  {
   std::cerr<<"File not found "<<_name.c_str()<<"\n";
   exit(EXIT_FAILURE);
  }
  // now read in the data
  m_source = new std::string((std::istreambuf_iterator<char>(shaderSource)), std::istreambuf_iterator<char>());
  shaderSource.close();
  *m_source+="\0";

  const char* data=m_source->c_str();
  glShaderSource(m_shaderHandle , 1, &data,NULL);
  m_compiled=false;

  if (m_debugState == true)
  {
    std::cerr<<"Shader Loaded and source attached\n";
    printInfoLog(m_shaderHandle);
  }
}

Once this is loaded we can compile the shader using the following commands
void Shader::compile()
{
  if (m_source == 0)
  {
    std::cerr<<"Warning no shader source loaded\n";
    return;
  }
 glCompileShader(m_shaderHandle);
 if(m_debugState==true)
 {
  std::cerr <<"Compiling Shader "<<m_name<<"\n";
  printInfoLog(m_shaderHandle);
  }
  m_compiled=true;
}

This is about it for the Shader class, I decided to keep the shader source as part of the class but it's not needed once attached to the shader so will perhaps write methods to allow the deletion of the source to save space once the shader is compiled.

The next instalment will look at the ShaderProgram class, in the meantime the full source and demo program is available with the lecture notes here in Lecture 8

No comments:

Post a Comment