Newer
Older
Import / applications / HighwayDash / ports / Framework / GLProgram.cpp
#include "GLProgram.h"
#include "Log.h"

#ifdef USE_OPENGLES2
#  include <OpenGLES/ES2/gl.h>
#else
#  define GL_GLEXT_PROTOTYPES
#  include <GL/gl.h>
#  include <GL/glext.h>
#endif

#include <string>


struct GLProgram::Pimpl
{
  // private implementation details here
  typedef std::function<void(GLuint a_program)> ProgramCallback;

  bool loadShaders(const GLchar *a_vertexShader, const GLchar *a_fragmentShader,
      Precision a_defaultPrecision, const std::map<std::string,bool>& a_options, 
      ProgramCallback a_bindAttribs, ProgramCallback a_setupUniforms);
  void useProgram();
  void deleteProgram();
  bool compileShader(GLuint *shader, GLenum type, const GLchar *source,
      Precision a_defaultPrecision, const std::map<std::string,bool>& a_options);
  bool linkProgram();
  bool validateProgram();
  void getProgramLog();

  GLuint m_program = 0;
};


GLProgram::GLProgram()
  : m_pimpl(std::unique_ptr<Pimpl>(new Pimpl()))
{
}


GLProgram::~GLProgram()
{
  deleteProgram();
}


bool GLProgram::loadShaders(const char *a_vertexShader, const char *a_fragmentShader,
                 Precision a_defaultPrecision, const std::map<std::string,bool>& a_options, 
                 ProgramCallback a_bindAttribs, ProgramCallback a_setupUniforms)
{
  return m_pimpl->loadShaders(a_vertexShader, a_fragmentShader, a_defaultPrecision, a_options, a_bindAttribs, a_setupUniforms);
}


void GLProgram::useProgram()
{
  m_pimpl->useProgram();
}


void GLProgram::deleteProgram()
{
  m_pimpl->deleteProgram();
}


#ifndef USE_OPENGLES2
// Helper function which strips out 'lowp' and 'highp' from the string
// Needed so that an OpenGLES shader program can be compiled with OpenGL on desktop
static std::string RemovePrecisionDecorators(std::string text)
{
  std::string out;
  int state = 0;
  for (size_t i = 0; i < text.size(); i++)
  {
    char ch = text[i];
    switch (state)
    {
      case 0: if (ch == ' ' || ch == '\n' || ch == '\t') state++; break;
      case 1: if (ch == 'l') state++; else if (ch == 'h') state=7; else state=0; break;
      case 2: if (ch == 'o') state++; else state=0; break;
      case 3: if (ch == 'w') state++; else state=0; break;
      case 4: if (ch == 'p') state++; else state=0; break;
      case 5: if (ch == ' ' || ch == '\n') state++; else state=0; break;
      case 6: /* lowp */ out = out.substr(0, out.size()-5); state=0; break;
      case 7: if (ch == 'i') state++; else state=0; break;
      case 8: if (ch == 'g') state++; else state=0; break;
      case 9: if (ch == 'h') state++; else state=0; break;
      case 10: if (ch == 'p') state++; else state=0; break;
      case 11: if (ch == ' ' || ch == '\n') state++; else state=0; break;
      case 12: /* lowp */ out = out.substr(0, out.size()-6); state=0; break;
    }
    if (state == 0 && (ch == ' ' || ch == '\n' || ch == '\t'))
      state++;
    out.push_back(ch);
  }
  return out;
}
#endif


bool GLProgram::Pimpl::loadShaders(const GLchar *a_vertexShader, const GLchar *a_fragmentShader,
                            Precision a_defaultPrecision, const std::map<std::string,bool>& a_options, 
                            ProgramCallback a_bindAttribs, ProgramCallback a_setupUniforms)
{
  GLuint vertShader, fragShader;

  // Create shader program.
  m_program = glCreateProgram();

  const GLchar *vertexShader   = a_vertexShader;
  const GLchar *fragmentShader = a_fragmentShader;

#ifndef USE_OPENGLES2
  // While precision specifiers could be removed from the shaders, they might be useful to tweak performance on lower end targets
  // On desktop targets, the precision specifiers aren't supported (shaders can fail to compile), so remove them if they are there.
  std::string vShad = RemovePrecisionDecorators(a_vertexShader);
  std::string fShad = RemovePrecisionDecorators(a_fragmentShader);
  vertexShader   = vShad.c_str();
  fragmentShader = fShad.c_str();
#endif

  // Create and compile vertex shader.
  if (!compileShader(&vertShader, GL_VERTEX_SHADER, vertexShader, a_defaultPrecision, a_options)) {
    Log(LL_Error, "context", "Failed to compile vertex shader");
    return false;
  }

  // Create and compile fragment shader.
  if (!compileShader(&fragShader, GL_FRAGMENT_SHADER, fragmentShader, a_defaultPrecision, a_options)) {
    Log(LL_Error, "context", "Failed to compile fragment shader");
    return false;
  }

  // Attach vertex shader to program.
  glAttachShader(m_program, vertShader);

  // Attach fragment shader to program.
  glAttachShader(m_program, fragShader);

  // Bind attribute locations.
  // This needs to be done prior to linking.
  a_bindAttribs(m_program);

  // TODO: bind using the vertex specification (a visitor implementation)
  // vertex.visit([m_program](const char* name, GLuint index, ...){ glBindAttribLocation(m_program, index, name); });

  // Link program.
  if (!linkProgram()) {
    Log(LL_Error, "context", "Failed to link program: %d", m_program);
    if (vertShader) {
      glDeleteShader(vertShader);
      vertShader = 0;
    }
    if (fragShader) {
      glDeleteShader(fragShader);
      fragShader = 0;
    }
    deleteProgram();
    return false;
  }

  // Get uniform locations.
  a_setupUniforms(m_program);

  // Release vertex and fragment shaders.
  if (vertShader) {
    glDetachShader(m_program, vertShader);
    glDeleteShader(vertShader);
  }
  if (fragShader) {
    glDetachShader(m_program, fragShader);
    glDeleteShader(fragShader);
  }

  return true;
}


void GLProgram::Pimpl::useProgram()
{
  glUseProgram(m_program);
}


bool GLProgram::Pimpl::compileShader(GLuint *shader, GLenum type, const GLchar *source,
    Precision a_defaultPrecision, const std::map<std::string,bool>& a_options)
{
  GLint status;
  Log(LL_Info, "context", "compileShader");

  *shader = glCreateShader(type);

#ifdef USE_OPENGLES2
  bool openGLES2 = true;
#else
  bool openGLES2 = false;
#endif

  std::string options;
  for (auto i : a_options)
    options += "const bool " + i.first + " = " + (i.second ? "true;\n" : "false;\n");

  // TODO: perhaps depending on device specs, can modify the precision, eg, iPhone4 use lowp, iPhone5 use highp etc.
  const GLchar *precisions[4] = { "precision lowp float;\n", "precision mediump float;\n", "precision highp float;\n", "\n" };
  const GLchar *sources[3] = { precisions[openGLES2 ? a_defaultPrecision : 3], options.c_str(), source };
  glShaderSource(*shader, 3, sources, NULL);
  glCompileShader(*shader);

#if defined(DEBUG)
  GLint logLength;
  glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &logLength);
  if (logLength > 1) {
    GLchar *log = new GLchar[logLength];
    glGetShaderInfoLog(*shader, logLength, &logLength, log);
    Log(LL_Error, "context", "Shader compile log:\n%s", log);
    Log(LL_Error, "context", "Shader source:\n%s", source);
    delete[] log;
  }
#endif

  glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);
  if (status == 0) {
    glDeleteShader(*shader);
    return false;
  }

  return true;
}


void GLProgram::Pimpl::getProgramLog()
{
#if defined(DEBUG)
  GLint logLength;
  glGetProgramiv(m_program, GL_INFO_LOG_LENGTH, &logLength);
  if (logLength > 1) {
    GLchar *log = new GLchar[logLength];
    glGetProgramInfoLog(m_program, logLength, &logLength, log);
    Log(LL_Error, "context", "Program log:\n%s", log);
    delete[] log;
  }
#endif
}


bool GLProgram::Pimpl::linkProgram()
{
  GLint status;
  glLinkProgram(m_program);
  getProgramLog();
  glGetProgramiv(m_program, GL_LINK_STATUS, &status);
  return (status != 0);
}


bool GLProgram::Pimpl::validateProgram()
{
  GLint status;
  glValidateProgram(m_program);
  getProgramLog();
  glGetProgramiv(m_program, GL_VALIDATE_STATUS, &status);
  return (status != 0);
}


void GLProgram::Pimpl::deleteProgram()
{
  if (m_program) {
    glDeleteProgram(m_program);
    m_program = 0;
  }
}


// Helpers to use type overloading to automatically dispatch to the correct OpenGL function based on the type
void glSetUniform(GLint a_location, mat3 a_matrix3) { glUniformMatrix3fv(a_location, 1, 0, a_matrix3.m); }
void glSetUniform(GLint a_location, mat4 a_matrix4) { glUniformMatrix4fv(a_location, 1, 0, a_matrix4.m); }
//void glSetUniform(GLint a_location, float a_flt)    { glUniform1f(a_location, a_flt); }
void glSetUniform(GLint a_location, float a_flt)    { glUniform1fv(a_location, 1, &a_flt); }
void glSetUniform(GLint a_location, int a_int)      { glUniform1i(a_location, a_int); }
void glSetUniform(GLint a_location, bool a_bool)    { glUniform1i(a_location, a_bool); }
void glSetUniform(GLint a_location, vec4attrib a_vec4) { glUniform4fv(a_location, 1, a_vec4.attrib); }


//GLAPI void APIENTRY glUniform1f (GLint location, GLfloat v0);
//GLAPI void APIENTRY glUniform1fv (GLint location, GLsizei count, const GLfloat *value);


void AttribPointerVisitor(const char* /*name*/, GLuint index, GLsizei size, GLenum type, GLboolean normalized, GLsizei stride, const void* pointer)
{
  // &(((vertexAttribs*)(NULL))->posAttribute)
  glEnableVertexAttribArray(index);
  glVertexAttribPointer(index, size, type, normalized, stride, pointer);
  //glVertexAttribDivisorEXT(index, 1); // test for instancing
}


/*
void AttribLinkerVisitor(const char* name, GLuint index, GLsizei size, GLenum type, GLboolean normalized, GLsizei stride, const void* pointer)
{
  glBindAttribLocation(m_program, index, name);
}
*/