/* This file is part of the Pangolin Project. * http://github.com/stevenlovegrove/Pangolin * * Copyright (c) 2011 Steven Lovegrove * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_GLES #define GLhandleARB GLuint #endif #if defined(HAVE_EIGEN) && !defined(__CUDACC__) //prevent including Eigen in cuda files #define USE_EIGEN #endif #ifdef USE_EIGEN #include #endif // USE_EIGEN namespace pangolin { //////////////////////////////////////////////// // Standard attribute locations //////////////////////////////////////////////// const GLuint DEFAULT_LOCATION_POSITION = 0; const GLuint DEFAULT_LOCATION_COLOUR = 1; const GLuint DEFAULT_LOCATION_NORMAL = 2; const GLuint DEFAULT_LOCATION_TEXCOORD = 3; const char DEFAULT_NAME_POSITION[] = "a_position"; const char DEFAULT_NAME_COLOUR[] = "a_color"; const char DEFAULT_NAME_NORMAL[] = "a_normal"; const char DEFAULT_NAME_TEXCOORD[] = "a_texcoord"; //////////////////////////////////////////////// // Interface //////////////////////////////////////////////// enum GlSlShaderType { GlSlAnnotatedShader = 0, GlSlFragmentShader = GL_FRAGMENT_SHADER, GlSlVertexShader = GL_VERTEX_SHADER, GlSlGeometryShader = 0x8DD9 /*GL_GEOMETRY_SHADER*/, GlSlComputeShader = 0x91B9 /*GL_COMPUTE_SHADER*/ }; class GlSlProgram { public: GlSlProgram(); //! Move Constructor GlSlProgram(GlSlProgram&& tex); ~GlSlProgram(); bool AddShader( GlSlShaderType shader_type, const std::string& source_code, const std::map& program_defines = std::map(), const std::vector& search_path = std::vector() ); bool AddShaderFromFile( GlSlShaderType shader_type, const std::string& filename, const std::map& program_defines = std::map(), const std::vector& search_path = std::vector() ); bool Link(); // Remove all shaders from this program, and reload from files. bool ReloadShaderFiles(); GLint GetAttributeHandle(const std::string& name); GLint GetUniformHandle(const std::string& name); // Before setting uniforms, be sure to Bind() the GlSl program first. void SetUniform(const std::string& name, int x); void SetUniform(const std::string& name, int x1, int x2); void SetUniform(const std::string& name, int x1, int x2, int x3); void SetUniform(const std::string& name, int x1, int x2, int x3, int x4); void SetUniform(const std::string& name, float f); void SetUniform(const std::string& name, float f1, float f2); void SetUniform(const std::string& name, float f1, float f2, float f3); void SetUniform(const std::string& name, float f1, float f2, float f3, float f4); void SetUniform(const std::string& name, Colour c); void SetUniform(const std::string& name, const OpenGlMatrix& m); #ifdef HAVE_EIGEN void SetUniform(const std::string& name, const Eigen::Matrix3f& m); void SetUniform(const std::string& name, const Eigen::Matrix4f& m); void SetUniform(const std::string& name, const Eigen::Matrix3d& m); void SetUniform(const std::string& name, const Eigen::Matrix4d& m); #endif #if GL_VERSION_4_3 GLint GetProgramResourceIndex(const std::string& name); void SetShaderStorageBlock(const std::string& name, const int& bindingIndex); #endif void Bind(); void SaveBind(); void Unbind(); void BindPangolinDefaultAttribLocationsAndLink(); // Unlink all shaders from program void ClearShaders(); GLint ProgramId() const { return prog; } bool Valid() const { return ProgramId() != 0; } protected: struct ShaderFileOrCode { GlSlShaderType shader_type; std::string filename; std::string code; std::map program_defines; std::vector search_path; }; // Convenience method to load shader description bool AddShaderFile(const ShaderFileOrCode &shader_file); std::string ParseIncludeFilename( const std::string& location ); std::string SearchIncludePath( const std::string& filename, const std::vector& search_path, const std::string& current_path ); bool AddPreprocessedShader( GlSlShaderType shader_type, const std::string& source_code, const std::string& name_for_errors ); void PreprocessGLSL( std::istream& input, std::ostream& output, const std::map& program_defines, const std::vector& search_path, const std::string& current_path ); // Split 'code' into several code blocks per shader type // shader blocks in 'code' must be annotated with: // @start vertex, @start fragment, @start geometry or @start compute static std::map SplitAnnotatedShaders(const std::string& code); bool linked; std::vector shaders; GLenum prog; GLint prev_prog; std::vector shader_files; }; class GlSlUtilities { public: inline static GlSlProgram& OffsetAndScale(float offset, float scale) { GlSlProgram& prog = Instance().prog_offsetscale; prog.Bind(); prog.SetUniform("offset", offset); prog.SetUniform("scale", scale); return prog; } inline static GlSlProgram& Scale(float scale, float bias = 0.0f) { GlSlProgram& prog = Instance().prog_scale; prog.Bind(); prog.SetUniform("scale", scale); prog.SetUniform("bias", bias); return prog; } inline static void UseNone() { glUseProgram(0); } protected: static GlSlUtilities& Instance() { // TODO: BUG: The GlSLUtilities instance needs to be tied // to the OpenGL context, not the thread, or globally. #ifndef PANGO_NO_THREADLOCAL thread_local #else static #endif GlSlUtilities instance; return instance; } // protected constructor GlSlUtilities() { const char* source_scale = "uniform float scale;" "uniform float bias;" "uniform sampler2D tex;" "void main() {" " vec2 uv = gl_TexCoord[0].st;" " if(0.0 <= uv.x && uv.x <= 1.0 && 0.0 <= uv.y && uv.y <= 1.0) {" " gl_FragColor = texture2D(tex,uv);" " gl_FragColor.xyz *= scale;" " gl_FragColor.xyz += vec3(bias,bias,bias);" " }else{" " float v = 0.1;" " gl_FragColor.xyz = vec3(v,v,v);" " }" "}"; prog_scale.AddShader(GlSlFragmentShader, source_scale); prog_scale.Link(); const char* source_offsetscale = "uniform float offset;" "uniform float scale;" "uniform sampler2D tex;" "void main() {" " vec2 uv = gl_TexCoord[0].st;" " if(0.0 <= uv.x && uv.x <= 1.0 && 0.0 <= uv.y && uv.y <= 1.0) {" " gl_FragColor = texture2D(tex,gl_TexCoord[0].st);" " gl_FragColor.xyz += vec3(offset,offset,offset);" " gl_FragColor.xyz *= scale;" " }else{" " float v = 0.1;" " gl_FragColor.xyz = vec3(v,v,v);" " }" "}"; prog_offsetscale.AddShader(GlSlFragmentShader, source_offsetscale); prog_offsetscale.Link(); } GlSlProgram prog_scale; GlSlProgram prog_offsetscale; }; //////////////////////////////////////////////// // Implementation //////////////////////////////////////////////// inline bool IsLinkSuccessPrintLog(GLhandleARB prog) { GLint status; glGetProgramiv(prog, GL_LINK_STATUS, &status); if(status != GL_TRUE) { pango_print_error("GLSL Program link failed: "); const int PROGRAM_LOG_MAX_LEN = 10240; char infolog[PROGRAM_LOG_MAX_LEN]; GLsizei len; glGetProgramInfoLog(prog, PROGRAM_LOG_MAX_LEN, &len, infolog); if(len) { pango_print_error("%s\n",infolog); }else{ pango_print_error("No details provided.\n"); } return false; } return true; } inline bool IsCompileSuccessPrintLog(GLhandleARB shader, const std::string& name_for_errors) { GLint status; glGetShaderiv(shader, GL_COMPILE_STATUS, &status); if(status != GL_TRUE) { pango_print_error("GLSL Shader compilation failed: "); const int SHADER_LOG_MAX_LEN = 10240; char infolog[SHADER_LOG_MAX_LEN]; GLsizei len; glGetShaderInfoLog(shader, SHADER_LOG_MAX_LEN, &len, infolog); if(len) { pango_print_error("%s:\n%s\n",name_for_errors.c_str(), infolog); }else{ pango_print_error("%s:\nNo details provided.\n",name_for_errors.c_str()); } return false; } return true; } inline GlSlProgram::GlSlProgram() : linked(false), prog(0), prev_prog(0) { } //! Move Constructor inline GlSlProgram::GlSlProgram(GlSlProgram&& o) : linked(o.linked), shaders(o.shaders), prog(o.prog), prev_prog(o.prev_prog) { o.prog = 0; } inline GlSlProgram::~GlSlProgram() { if(prog) { ClearShaders(); glDeleteProgram(prog); } } inline void PrintSourceCode(const std::string& src) { std::stringstream ss(src); std::string line; for(int linenum=1; std::getline(ss,line,'\n'); ++linenum) { std::cout << linenum << ":\t" << line << std::endl; } } inline bool GlSlProgram::AddPreprocessedShader( GlSlShaderType shader_type, const std::string& source_code, const std::string& name_for_errors ) { if(!prog) { prog = glCreateProgram(); } // PrintSourceCode(source_code); GLhandleARB shader = glCreateShader(shader_type); const char* source = source_code.c_str(); glShaderSource(shader, 1, &source, NULL); glCompileShader(shader); bool success = IsCompileSuccessPrintLog(shader, name_for_errors); if(success) { glAttachShader(prog, shader); shaders.push_back(shader); linked = false; } return success; } inline std::string GlSlProgram::ParseIncludeFilename(const std::string& location) { size_t start = location.find_first_of("\"<"); if(start != std::string::npos) { size_t end = location.find_first_of("\">", start+1); if(end != std::string::npos) { return location.substr(start+1, end - start - 1); } } throw std::runtime_error("GLSL Parser: Unable to parse include location " + location ); } inline std::string GlSlProgram::SearchIncludePath( const std::string& filename, const std::vector& search_path, const std::string& current_path ) { if(FileExists(current_path + "/" + filename)) { return current_path + "/" + filename; }else{ for(size_t i=0; i < search_path.size(); ++i) { const std::string hypoth = search_path[i] + "/" + filename; if( FileExists(hypoth) ) { return hypoth; } } } return ""; } inline void GlSlProgram::PreprocessGLSL( std::istream& input, std::ostream& output, const std::map& program_defines, const std::vector &search_path, const std::string ¤t_path ) { const size_t MAXLINESIZE = 10240; char line[MAXLINESIZE] = ""; while(!input.eof()) { // Take like from source input.getline(line,MAXLINESIZE); // Transform if( !strncmp(line, "#include", 8 ) ) { // C++ / G3D style include directive const std::string import_file = ParseIncludeFilename(line+8); const std::string resolved_file = SearchIncludePath(import_file, search_path, current_path); std::ifstream ifs(resolved_file.c_str()); if(ifs.good()) { const std::string file_path = pangolin::PathParent(resolved_file); PreprocessGLSL(ifs, output, program_defines, search_path, file_path); }else{ throw std::runtime_error("GLSL Parser: Unable to open " + import_file ); } }else if( !strncmp(line, "#expect", 7) ) { // G3D style 'expect' directive, annotating expected preprocessor // definition with document string // Consume whitespace before token size_t token_start = 7; while( std::isspace(line[token_start]) ) ++token_start; // Iterate over contigous charecters until \0 or whitespace size_t token_end = token_start; while( line[token_end] && !std::isspace(line[token_end]) ) ++token_end; std::string token(line+token_start, line+token_end); std::map::const_iterator it = program_defines.find(token); if( it == program_defines.end() ) { pango_print_warn("Expected define missing (defaulting to 0): '%s'\n%s\n", token.c_str(), line + token_end ); output << "#define " << token << " 0" << std::endl; }else{ output << "#define " << token << " " << it->second << std::endl; } }else{ // Output directly output << line << std::endl; } } } inline void GlSlProgram::ClearShaders() { // Remove and delete each shader for(size_t i=0; i"; if(shader_file.shader_type == GlSlAnnotatedShader) { const std::map split_progs = SplitAnnotatedShaders(code); for(const auto& type_code : split_progs) { if(!AddPreprocessedShader(type_code.first, type_code.second, input_name )) { return false; } } return true; }else{ return AddPreprocessedShader(shader_file.shader_type, code, input_name); } } inline bool GlSlProgram::AddShaderFromFile( GlSlShaderType shader_type, const std::string& filename, const std::map& program_defines, const std::vector& search_path ) { ShaderFileOrCode shader_file = { shader_type, pangolin::PathExpand(filename), std::string(), program_defines, search_path }; shader_files.push_back(shader_file); return AddShaderFile(shader_file); } inline bool GlSlProgram::AddShader( GlSlShaderType shader_type, const std::string& source_code, const std::map& program_defines, const std::vector& search_path ) { ShaderFileOrCode shader_file = { shader_type, std::string(), source_code, program_defines, search_path }; shader_files.push_back(shader_file); return AddShaderFile(shader_file); } inline bool GlSlProgram::ReloadShaderFiles() { ClearShaders(); for(const auto& sf : shader_files) { if(!AddShaderFile(sf)) { return false; } } Link(); return true; } inline std::map GlSlProgram::SplitAnnotatedShaders(const std::string& code) { std::map ret; std::stringstream input(code); std::stringstream output; const size_t MAXLINESIZE = 10240; char line[MAXLINESIZE]; GlSlShaderType current_type = GlSlAnnotatedShader; auto finish_block = [&](GlSlShaderType type){ if(current_type != GlSlAnnotatedShader) { ret[current_type] = output.str(); } output.str(std::string()); current_type = type; }; while(!input.eof()) { // Take like from source input.getline(line,MAXLINESIZE); // Transform if( !strncmp(line, "@start", 6 ) ) { const std::string str_shader_type = pangolin::Trim(std::string(line).substr(6)); if(str_shader_type == "vertex") { finish_block(GlSlVertexShader); }else if(str_shader_type == "fragment") { finish_block(GlSlFragmentShader); }else if(str_shader_type == "geometry") { finish_block(GlSlGeometryShader); }else if(str_shader_type == "compute") { finish_block(GlSlComputeShader); } }else{ output << line << std::endl; } } finish_block(GlSlAnnotatedShader); return ret; } inline bool GlSlProgram::Link() { glLinkProgram(prog); return IsLinkSuccessPrintLog(prog); } inline void GlSlProgram::Bind() { prev_prog = 0; glUseProgram(prog); } inline void GlSlProgram::SaveBind() { glGetIntegerv(GL_CURRENT_PROGRAM, &prev_prog); glUseProgram(prog); } inline void GlSlProgram::Unbind() { glUseProgram(prev_prog); } inline GLint GlSlProgram::GetAttributeHandle(const std::string& name) { return glGetAttribLocation(prog, name.c_str()); } inline GLint GlSlProgram::GetUniformHandle(const std::string& name) { return glGetUniformLocation(prog, name.c_str()); } inline void GlSlProgram::SetUniform(const std::string& name, int x) { glUniform1i( GetUniformHandle(name), x); } inline void GlSlProgram::SetUniform(const std::string& name, int x1, int x2) { glUniform2i( GetUniformHandle(name), x1, x2); } inline void GlSlProgram::SetUniform(const std::string& name, int x1, int x2, int x3) { glUniform3i( GetUniformHandle(name), x1, x2, x3); } inline void GlSlProgram::SetUniform(const std::string& name, int x1, int x2, int x3, int x4) { glUniform4i( GetUniformHandle(name), x1, x2, x3, x4); } inline void GlSlProgram::SetUniform(const std::string& name, float f) { glUniform1f( GetUniformHandle(name), f); } inline void GlSlProgram::SetUniform(const std::string& name, float f1, float f2) { glUniform2f( GetUniformHandle(name), f1,f2); } inline void GlSlProgram::SetUniform(const std::string& name, float f1, float f2, float f3) { glUniform3f( GetUniformHandle(name), f1,f2,f3); } inline void GlSlProgram::SetUniform(const std::string& name, float f1, float f2, float f3, float f4) { glUniform4f( GetUniformHandle(name), f1,f2,f3,f4); } inline void GlSlProgram::SetUniform(const std::string& name, Colour c) { glUniform4f( GetUniformHandle(name), c.r, c.g, c.b, c.a); } inline void GlSlProgram::SetUniform(const std::string& name, const OpenGlMatrix& mat) { // glUniformMatrix4dv seems to be crashing... float m[16]; for (int i = 0; i < 16; ++i) { m[i] = (float)mat.m[i]; } glUniformMatrix4fv( GetUniformHandle(name), 1, GL_FALSE, m); } #ifdef HAVE_EIGEN inline void GlSlProgram::SetUniform(const std::string& name, const Eigen::Matrix3f& m) { glUniformMatrix3fv( GetUniformHandle(name), 1, GL_FALSE, m.data()); } inline void GlSlProgram::SetUniform(const std::string& name, const Eigen::Matrix4f& m) { glUniformMatrix4fv( GetUniformHandle(name), 1, GL_FALSE, m.data()); } inline void GlSlProgram::SetUniform(const std::string& name, const Eigen::Matrix3d& m) { glUniformMatrix3dv( GetUniformHandle(name), 1, GL_FALSE, m.data()); } inline void GlSlProgram::SetUniform(const std::string& name, const Eigen::Matrix4d& m) { glUniformMatrix4dv( GetUniformHandle(name), 1, GL_FALSE, m.data()); } #endif inline void GlSlProgram::BindPangolinDefaultAttribLocationsAndLink() { glBindAttribLocation(prog, DEFAULT_LOCATION_POSITION, DEFAULT_NAME_POSITION); glBindAttribLocation(prog, DEFAULT_LOCATION_COLOUR, DEFAULT_NAME_COLOUR); glBindAttribLocation(prog, DEFAULT_LOCATION_NORMAL, DEFAULT_NAME_NORMAL); glBindAttribLocation(prog, DEFAULT_LOCATION_TEXCOORD, DEFAULT_NAME_TEXCOORD); Link(); } #if GL_VERSION_4_3 inline GLint GlSlProgram::GetProgramResourceIndex(const std::string& name) { return glGetProgramResourceIndex(prog, GL_SHADER_STORAGE_BLOCK, name.c_str()); } inline void GlSlProgram::SetShaderStorageBlock(const std::string& name, const int& bindingIndex) { glShaderStorageBlockBinding(prog, GetProgramResourceIndex(name), bindingIndex); } #endif }