I wanted to try using CRTP (also new for me) to try making the loading of shared resources implicit. (Specifically I'm using it for loading OpenGl shader programs)
This will also help separate some of the boiler plate setup code (combining shaders into a program) from the more specific setup code (assigning locations for shader uniforms), and making sure that no one program is loaded more than once.
If it applies (pretty certain it does), what concepts/ fundamentals am I missing/ not seeing?
By using statics I understand that this isn't thread safe, however I don't think I'll be using multiple threads- but even with that said, any ideas/ tips on that matter would still be much appreciated!
The code lightly uses the Mesh
class from the online tutorial series Learning Modern 3D Graphics Programming by Jason L. McKesson, and the source code for that can be found here under framework. The shaders being used are found under Tut 10/ data (but I will post them as they are very short). It also uses glm, freeglut and other libraries found in the glsdk folder of the source code.
CRTP base header, ProgramBase.h
const std::string LOCALFILEDIR = "data\\";
const int PROJECTION_BLOCK_INDEX = 2;
template<class CustomProgram> class ProgramBase
{
public:
static GLuint program_location();
protected:
ProgramBase();
~ProgramBase(){}
private:
static GLuint LoadShader(GLenum shader_type_e, const std::string &base_shader_filename);
static GLuint CreateProgram(const std::vector<GLuint> &shader_locations);
static GLuint program_location_;
};
The idea is that every CustomProgram
will have to define at least three members in order for the below code to work:
vertex_shader_names_
A container of vertex shaders names to be found and linkedfragment_shader_names_
A list of fragment shaders names to be found and linkedInit()
A function specific to shaders named above that will find uniforms
template<class CustomProgram> ProgramBase<CustomProgram>::ProgramBase()
{
if(program_location_ == -1)
{
std::vector<GLuint> shader_locations;
for(const std::string& name : CustomProgram::vertex_shader_names_)
{
shader_locations.push_back(LoadShader(GL_VERTEX_SHADER, name));
}
for(const std::string& name : CustomProgram::fragment_shader_names_)
{
shader_locations.push_back(LoadShader(GL_FRAGMENT_SHADER, name));
}
program_location_ = CreateProgram(shader_locations);
CustomProgram::Init();
}
}
template<class CustomProgram> GLuint ProgramBase<CustomProgram>::LoadShader(
GLenum shader_type_e,
const std::string &base_shader_filename)
{
std::ifstream shader_file((LOCALFILEDIR + base_shader_filename).c_str());
if(!shader_file.is_open())
{
throw std::runtime_error("Could not find the file " + base_shader_filename);
}
std::stringstream shader_data;
shader_data << shader_file.rdbuf();
shader_file.close();
try
{
return glutil::CompileShader(shader_type_e, shader_data.str());
}
catch(std::exception &e)
{
fprintf(stderr, "%s\n", e.what());
throw;
}
}
template<class CustomProgram> GLuint ProgramBase<CustomProgram>::CreateProgram(
const std::vector<GLuint> &shader_locations)
{
try
{
GLuint prog = glutil::LinkProgram(shader_locations);
std::for_each(shader_locations.begin(), shader_locations.end(), glDeleteShader);
return prog;
}
catch(std::exception &e)
{
std::for_each(shader_locations.begin(), shader_locations.end(), glDeleteShader);
fprintf(stderr, "%s\n", e.what());
throw;
}
}
template<class CustomProgram> GLuint ProgramBase<CustomProgram>::program_location()
{
return program_location_;
}
template<class CustomProgram> GLuint ProgramBase<CustomProgram>::program_location_ = -1;
One program I incorporate is a program which simply positions the model relative to the camera, and then to clip space before drawing a single color. (this is pretty much directly from the tutorial)
PosTransform.vert
#version 330
layout(location = 0) in vec3 position;
uniform mat4 modelToCameraMatrix;
uniform Projection
{
mat4 cameraToClipMatrix;
};
void main()
{
gl_Position = cameraToClipMatrix * (modelToCameraMatrix * vec4(position, 1.0));
}
UniformColor.frag
#version 330
uniform vec4 objectColor;
out vec4 outputColor;
void main()
{
outputColor = objectColor;
}
This is the class I've defined for it. BasicProgram.h
class BasicProgram : ProgramBase<BasicProgram>
{
friend class ProgramBase<BasicProgram>;
public:
BasicProgram()
: model_matrix_(glm::mat4(1.0f)), color_vector_(glm::vec4(1.0f))
{ }
BasicProgram(glm::mat4& model_matrix, glm::vec4& color_vector)
: model_matrix_(model_matrix), color_vector_(color_vector)
{ }
void Render(Framework::Mesh* mesh);
glm::mat4 model_matrix();
void set_model_matrix(glm::mat4 model_matrix);
glm::vec4 color_vector();
void set_color_vector(glm::vec4 color_vector);
private:
static void Init();
glm::mat4 model_matrix_;
glm::vec4 color_vector_;
static const std::array<std::string,1> vertex_shader_names_;
static const std::array<std::string,1> fragment_shader_names_;
static GLuint model_matrix_unif_;
static GLuint color_vector_unif_;
};
And the guts
void BasicProgram::Render(Framework::Mesh* mesh)
{
glUseProgram(program_location());
glUniformMatrix4fv(model_matrix_unif_, 1, GL_FALSE, glm::value_ptr(model_matrix_));
glUniform4fv(color_vector_unif_, 1, glm::value_ptr(color_vector_));
mesh->Render();
glUseProgram(0);
}
void BasicProgram::Init()
{
model_matrix_unif_ = glGetUniformLocation(program_location(), "modelToCameraMatrix");
color_vector_unif_ = glGetUniformLocation(program_location(), "objectColor");
GLuint projectionBlock = glGetUniformBlockIndex(program_location(), "Projection");
glUniformBlockBinding(program_location(), projectionBlock, PROJECTION_BLOCK_INDEX);
}
const std::array<std::string,1> BasicProgram::vertex_shader_names_ =
{
"PosTransform.vert",
};
const std::array<std::string,1> BasicProgram::fragment_shader_names_ =
{
"UniformColor.frag",
};
glm::mat4 BasicProgram::model_matrix()
{
return glm::mat4(model_matrix_);
}
void BasicProgram::set_model_matrix(glm::mat4 model_matrix)
{
model_matrix_ = glm::mat4(model_matrix);
}
glm::vec4 BasicProgram::color_vector()
{
return glm::vec4(color_vector_);
}
void BasicProgram::set_color_vector(glm::vec4 color_vector)
{
color_vector_ = glm::vec4(color_vector);
}