// // shader.h // J // // Created by Joshua Moerman on 7/01/11. // Copyright 2011 Vadovas. All rights reserved. // /* This is a very basic shader class. The compilation and linking is done in the constructor, and deletion is don in the destructor. As a result, you can't copy a shader, use smart pointers instead. Because OpenGL ES2 uses attribute-indices at link-time, you have to specify your attributes in the constructor (this is a bit cumbersome). There are a lot of uniform setters, for both values and array's, they are called set_uniform() for all types. For matrices a additional parameter (trans) can be given, but in ES2 this value has to be false. It's still a parameter to avoid ambiguity, because the overload works with c-style arrays (vector4 is not distinguishable from matrix2x2). A fix would be to introduce types like vector4 and matrix2x2 and such, but Astrant already has those, so I refrain to do that. Using the setters should only be done between the begin() and end() functions. */ #ifndef SHADER_H #define SHADER_H #include #include #include #include #include #include #include "basic.h" #include "array.h" // TODO: add error checking at set_uniforms? // TODO: use an uniform map? (benchmark first!) // TODO: allow arrays in uniforms (now all counts are 1). To make this possible, first make vector and matrix structs. (But Astrant already has those) // TODO: matrix attributes are not handled well (they take up 4 indices in the attributes instead of one) // TODO: make the attribute-list nicer (it is really cumbersome now, with the vector) // NOTE: there is only 1 set_uniform for std::array... namespace J { class shader { // NOTE: it could be hardcoded to use vertex_shader and fragment_shader, for ShaderMap. There is no geometry_shader in ES2. typedef std::map ShaderMap; // shader_type -> shader_name typedef std::map AttributeMap; // attribute_name -> attribute_index GLuint program; ShaderMap shaders; AttributeMap attributes; public: // *********** // constructor shader(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename, const std::vector& attributes_to_use = std::vector()) : program(0), shaders(), attributes() { program = glCreateProgram(); if(program == 0) { throw std::runtime_error("Program couldn't be created"); } { // vertex shader // NOTE: C++03 mandates .c_str(), ugly, nothing to do about it... std::ifstream vertex_shader(vertex_shader_filename.c_str()); if(!vertex_shader) throw std::runtime_error(vertex_shader_filename + " couldn't be opened"); compile_shader(vertex_shader, GL_VERTEX_SHADER); } { // fragment shader std::ifstream fragment_shader(fragment_shader_filename.c_str()); if(!fragment_shader) throw std::runtime_error(fragment_shader_filename + " couldn't be opened"); compile_shader(fragment_shader, GL_FRAGMENT_SHADER); } for (unsigned int i = 0; i < attributes_to_use.size(); ++i) { glBindAttribLocation(program, i, attributes_to_use[i].c_str()); attributes[attributes_to_use[i]] = i; } link_program(); check_error(); } ~shader(){ begin(); for(ShaderMap::const_iterator i = shaders.begin(); i != shaders.end(); ++i){ ShaderMap::value_type const & it = *i; GLuint shader = it.second; glDetachShader(program, shader); glDeleteShader(shader); // NOTE: If a shader object to be deleted is attached to a program object, it will be flagged for deletion, but it will not be deleted until it is no longer attached to any program object. Also deleting a shader with name '0' is safe. } end(); glDeleteProgram(program); } // ***** // usage void begin() { glUseProgram(program); } void end() { glUseProgram(0); } // ***************** // attribute setters // NOTE: the normalized parameter is for integer types, if true they will be mapped to [0, 1], if false it stays in [0, 256] or so. void set_attribute(const std::string& name, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr) const { auto it = attributes.find(name); if(it == attributes.end()) throw std::runtime_error(name + " attribute could not be found"); glEnableVertexAttribArray(it->second); glVertexAttribPointer(it->second, size, type, normalized, stride, ptr); } // *************** // uniform setters // float vectors (multiple values) void set_uniform(char const* name, float x) const { glUniform1f(get_uniform_location(name), x); } void set_uniform(char const* name, float x, float y) const { glUniform2f(get_uniform_location(name), x, y); } void set_uniform(char const* name, float x, float y, float z) const { glUniform3f(get_uniform_location(name), x, y, z); } void set_uniform(char const* name, float x, float y, float z, float w) const { glUniform4f(get_uniform_location(name), x, y, z, w); } // integer vectors (multiple values) void set_uniform(char const* name, int x) const { glUniform1i(get_uniform_location(name), x); } void set_uniform(char const* name, int x, int y) const { glUniform2i(get_uniform_location(name), x, y); } void set_uniform(char const* name, int x, int y, int z) const { glUniform3i(get_uniform_location(name), x, y, z); } void set_uniform(char const* name, int x, int y, int z, int w) const { glUniform4i(get_uniform_location(name), x, y, z, w); } // Float vectors (array) void set_uniform(char const* name, const GLfloat (&value)[4]) const { glUniform4fv(get_uniform_location(name), 1, value); } void set_uniform(char const* name, const GLfloat (&value)[3]) const { glUniform3fv(get_uniform_location(name), 1, value); } void set_uniform(char const* name, const GLfloat (&value)[2]) const { glUniform2fv(get_uniform_location(name), 1, value); } void set_uniform(char const* name, const GLfloat (&value)[1]) const { glUniform1fv(get_uniform_location(name), 1, value); } // Integer vectors (array) void set_uniform(char const* name, const GLint (&value)[4]) const { glUniform4iv(get_uniform_location(name), 1, value); } void set_uniform(char const* name, const GLint (&value)[3]) const { glUniform3iv(get_uniform_location(name), 1, value); } void set_uniform(char const* name, const GLint (&value)[2]) const { glUniform2iv(get_uniform_location(name), 1, value); } void set_uniform(char const* name, const GLint (&value)[1]) const { glUniform1iv(get_uniform_location(name), 1, value); } // matrices // NOTE: in ES2 there are only square matrices void set_uniform(char const* name, const GLfloat (&value)[16], GLboolean trans) const { glUniformMatrix4fv(get_uniform_location(name), 1, trans, value); } void set_uniform(char const* name, const std::array & value, GLboolean trans) const { glUniformMatrix4fv(get_uniform_location(name), 1, trans, &value[0]); } void set_uniform(char const* name, const GLfloat (&value)[9], GLboolean trans) const { glUniformMatrix3fv(get_uniform_location(name), 1, trans, value); } void set_uniform(char const* name, const GLfloat (&value)[4], GLboolean trans) const { glUniformMatrix2fv(get_uniform_location(name), 1, trans, value); } // textures // NOTE: with a texture class this can be made prettier (also se the TODO in fbo.h) void set_texture(const char* name, GLenum target, GLuint tex, int textureLocation, GLenum filter = GL_LINEAR) const { glActiveTexture(GL_TEXTURE0 + textureLocation); glBindTexture(target, tex); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); set_uniform(name, textureLocation); } // ********* // validator void validate() { #if defined(DEBUG) GLint status; glValidateProgram(program); glGetProgramiv(program, GL_VALIDATE_STATUS, &status); if(status == GL_FALSE){ std::cerr << "validator reports:" << std::endl; show_program_log(); throw std::runtime_error("Program not valid"); } #endif } private: mutable std::map uniforms; GLint get_uniform_location(char const * name) const { auto it = uniforms.find(name); if(it != uniforms.end()) return it->second; return uniforms[name] = glGetUniformLocation(program, name); } // ******************* // compiling / linking void compile_shader(std::istream& file, GLenum type) { // get the c-string, the shortest way (not fastest), from http://tinodidriksen.com/ std::string buffer((std::istreambuf_iterator(file)), std::istreambuf_iterator()); const char* sptr = buffer.c_str(); int ssize = buffer.size(); // create and compile shader GLuint shader = glCreateShader(type); glShaderSource(shader, 1, &sptr, &ssize); glCompileShader(shader); check_compile_status(shader, buffer); // put it in tha map shaders[type] = shader; } void link_program() { // attach all shaders for(ShaderMap::const_iterator i = shaders.begin(); i != shaders.end(); ++i){ ShaderMap::value_type const & it = *i; GLuint shader = it.second; glAttachShader(program, shader); } // link glLinkProgram(program); check_link_status(); } // *************** // status checking void check_compile_status(GLuint shader, std::string source = "") { #if defined(DEBUG) GLint status = GL_FALSE; glGetShaderiv(shader, GL_COMPILE_STATUS, &status); if(status == GL_FALSE || shader == 0) { std::cerr << "source:\n" << source << std::endl; std::cerr << "shader reports:" << std::endl; show_shader_log(shader); throw std::runtime_error("Shader failed to compile"); } #endif } void check_link_status() { #if defined(DEBUG) GLint status; glGetProgramiv(program, GL_LINK_STATUS, &status); if(status == GL_FALSE) { std::cerr << "linker reports:" << std::endl; show_program_log(); throw std::runtime_error("Shader failed to link"); } #endif } void show_program_log(std::ostream& out = std::cerr) { GLint infoLength = 0; glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLength); char* infoBuffer = new GLchar[infoLength+10]; glGetProgramInfoLog(program, infoLength+10, &infoLength, infoBuffer); out << infoBuffer << std::endl; delete [] infoBuffer; } void show_shader_log(GLuint shader, std::ostream& out = std::cerr) { GLint infoLength = 0; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLength); char* infoBuffer = new GLchar[infoLength+10]; glGetShaderInfoLog(shader, infoLength+10, &infoLength, infoBuffer); out << infoBuffer << std::endl; delete [] infoBuffer; } }; } // namespace J #endif // SHADER_H