// BlockyFroggy
// Copyright © 2017 John Ryland.
// All rights reserved.
#include "GLProgram.h"
#define GL_GLEXT_PROTOTYPES
#include <OpenGL/gl.h>
#include <OpenGL/glext.h>
#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 OptionsType& 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 OptionsType& 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 OptionsType& 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();
}
bool GLProgram::Pimpl::loadShaders(const GLchar *a_vertexShader, const GLchar *a_fragmentShader,
Precision a_defaultPrecision, const OptionsType& 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;
// Create and compile vertex shader.
if (!compileShader(&vertShader, GL_VERTEX_SHADER, vertexShader, a_defaultPrecision, a_options)) {
Log(LL_Error, "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, "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, "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 OptionsType& a_options)
{
GLint status;
*shader = glCreateShader(type);
#ifdef USE_OPENGLES2
bool openGLES2 = true;
const char* defines = "";
#else
bool openGLES2 = false;
const char* defines = "#define highp\n#define lowp\n#define mediump\n";
#endif
std::string options;
for (auto i : a_options)
options += "const bool " + std::string(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[4] = { defines, precisions[openGLES2 ? a_defaultPrecision : 3], options.c_str(), source };
glShaderSource(*shader, 4, sources, NULL);
glCompileShader(*shader);
#if 1 // 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, "Shader compile log:\n%s", log);
Log(LL_Error, "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, "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[0].m); }
void glSetUniform(GLint a_location, mat4 a_matrix4) { glUniformMatrix4fv(a_location, 1, 0, a_matrix4.m[0].m); }
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, float a_flt) { glUniform1fv(a_location, 1, &a_flt); }
void glSetUniform(GLint a_location, vec2 a_vec2) { glUniform2fv(a_location, 1, a_vec2.m); }
void glSetUniform(GLint a_location, vec3 a_vec3) { glUniform3fv(a_location, 1, a_vec3.m); }
void glSetUniform(GLint a_location, vec4 a_vec4) { glUniform4fv(a_location, 1, a_vec4.m); }
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);
}
*/