#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);
}
*/