├── LICENSE ├── README.md ├── shaderset.cpp └── shaderset.h /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Nicolas Guillemot 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ShaderSet 2 | GLSL shader live-reloading 3 | 4 | Explanation here: https://nlguillemot.wordpress.com/2016/07/28/glsl-shader-live-reloading/ 5 | -------------------------------------------------------------------------------- /shaderset.cpp: -------------------------------------------------------------------------------- 1 | #include "shaderset.h" 2 | 3 | #ifdef _WIN32 4 | #include 5 | #else 6 | // Not Windows? Assume unix-like. 7 | #include 8 | #include 9 | #include 10 | #endif 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | static uint64_t GetShaderFileTimestamp(const char* filename) 18 | { 19 | uint64_t timestamp = 0; 20 | 21 | #ifdef _WIN32 22 | struct __stat64 stFileInfo; 23 | if (_stat64(filename, &stFileInfo) == 0) 24 | { 25 | timestamp = stFileInfo.st_mtime; 26 | } 27 | #else 28 | struct stat fileStat; 29 | 30 | if (stat(filename, &fileStat) == -1) 31 | { 32 | perror(filename); 33 | return 0; 34 | } 35 | 36 | #ifdef __APPLE__ 37 | timestamp = fileStat.st_mtimespec.tv_sec; 38 | #else 39 | timestamp = fileStat.st_mtime; 40 | #endif 41 | #endif 42 | 43 | return timestamp; 44 | } 45 | 46 | static std::string ShaderStringFromFile(const char* filename) 47 | { 48 | std::ifstream fs(filename); 49 | if (!fs) 50 | { 51 | return ""; 52 | } 53 | 54 | std::string s( 55 | std::istreambuf_iterator{fs}, 56 | std::istreambuf_iterator{}); 57 | 58 | return s; 59 | } 60 | 61 | ShaderSet::~ShaderSet() 62 | { 63 | for (std::pair& shader : mShaders) 64 | { 65 | glDeleteShader(shader.second.Handle); 66 | } 67 | 68 | for (std::pair, Program>& program : mPrograms) 69 | { 70 | glDeleteProgram(program.second.InternalHandle); 71 | } 72 | } 73 | 74 | void ShaderSet::SetVersion(const std::string& version) 75 | { 76 | mVersion = version; 77 | } 78 | 79 | void ShaderSet::SetPreamble(const std::string& preamble) 80 | { 81 | mPreamble = preamble; 82 | } 83 | 84 | GLuint* ShaderSet::AddProgram(const std::vector>& typedShaders) 85 | { 86 | std::vector shaderNameTypes; 87 | 88 | // find references to existing shaders, and create ones that didn't exist previously. 89 | for (const std::pair& shaderNameType : typedShaders) 90 | { 91 | ShaderNameTypePair tmpShaderNameType; 92 | std::tie(tmpShaderNameType.Name, tmpShaderNameType.Type) = shaderNameType; 93 | 94 | // test that the file can be opened (to catch typos or missing file bugs) 95 | { 96 | std::ifstream ifs(tmpShaderNameType.Name); 97 | if (!ifs) 98 | { 99 | fprintf(stderr, "Failed to open shader %s\n", tmpShaderNameType.Name.c_str()); 100 | } 101 | } 102 | 103 | auto foundShader = mShaders.emplace(std::move(tmpShaderNameType), Shader{}).first; 104 | if (!foundShader->second.Handle) 105 | { 106 | foundShader->second.Handle = glCreateShader(shaderNameType.second); 107 | // Mask the hash to 16 bits because some implementations are limited to that number of bits. 108 | // The sign bit is masked out, since some shader compilers treat the #line as signed, and others treat it unsigned. 109 | foundShader->second.HashName = (int32_t)std::hash()(shaderNameType.first) & 0x7FFF; 110 | } 111 | shaderNameTypes.push_back(&foundShader->first); 112 | } 113 | 114 | // ensure the programs have a canonical order 115 | std::sort(begin(shaderNameTypes), end(shaderNameTypes)); 116 | shaderNameTypes.erase(std::unique(begin(shaderNameTypes), end(shaderNameTypes)), end(shaderNameTypes)); 117 | 118 | // find the program associated to these shaders (or create it if missing) 119 | auto foundProgram = mPrograms.emplace(shaderNameTypes, Program{}).first; 120 | if (!foundProgram->second.InternalHandle) 121 | { 122 | // public handle is 0 until the program has linked without error 123 | foundProgram->second.PublicHandle = 0; 124 | 125 | foundProgram->second.InternalHandle = glCreateProgram(); 126 | for (const ShaderNameTypePair* shader : shaderNameTypes) 127 | { 128 | glAttachShader(foundProgram->second.InternalHandle, mShaders[*shader].Handle); 129 | } 130 | } 131 | 132 | return &foundProgram->second.PublicHandle; 133 | } 134 | 135 | void ShaderSet::UpdatePrograms() 136 | { 137 | // find all shaders with updated timestamps 138 | std::set*> updatedShaders; 139 | for (std::pair& shader : mShaders) 140 | { 141 | uint64_t timestamp = GetShaderFileTimestamp(shader.first.Name.c_str()); 142 | if (timestamp > shader.second.Timestamp) 143 | { 144 | shader.second.Timestamp = timestamp; 145 | updatedShaders.insert(&shader); 146 | } 147 | } 148 | 149 | // recompile all updated shaders 150 | for (std::pair* shader : updatedShaders) 151 | { 152 | // the #line prefix ensures error messages have the right line number for their file 153 | // the #line directive also allows specifying a "file name" number, which makes it possible to identify which file the error came from. 154 | std::string version = "#version " + mVersion + "\n"; 155 | 156 | std::string defines; 157 | switch (shader->first.Type) { 158 | case GL_VERTEX_SHADER: defines += "#define VERTEX_SHADER\n"; break; 159 | case GL_FRAGMENT_SHADER: defines += "#define FRAGMENT_SHADER\n"; break; 160 | case GL_GEOMETRY_SHADER: defines += "#define GEOMETRY_SHADER\n"; break; 161 | case GL_TESS_CONTROL_SHADER: defines += "#define TESS_CONTROL_SHADER\n"; break; 162 | case GL_TESS_EVALUATION_SHADER: defines += "#define TESS_EVALUATION_SHADER\n"; break; 163 | case GL_COMPUTE_SHADER: defines += "#define COMPUTE_SHADER\n"; break; 164 | } 165 | 166 | std::string preamble_hash = std::to_string((int32_t)std::hash()("preamble") & 0x7FFF); 167 | std::string preamble = "#line 1 " + preamble_hash + "\n" + 168 | mPreamble + "\n"; 169 | 170 | std::string source_hash = std::to_string(shader->second.HashName); 171 | std::string source = "#line 1 " + source_hash + "\n" + 172 | ShaderStringFromFile(shader->first.Name.c_str()) + "\n"; 173 | 174 | const char* strings[] = { 175 | version.c_str(), 176 | defines.c_str(), 177 | preamble.c_str(), 178 | source.c_str() 179 | }; 180 | GLint lengths[] = { 181 | (GLint)version.length(), 182 | (GLint)defines.length(), 183 | (GLint)preamble.length(), 184 | (GLint)source.length() 185 | }; 186 | 187 | glShaderSource(shader->second.Handle, sizeof(strings) / sizeof(*strings), strings, lengths); 188 | glCompileShader(shader->second.Handle); 189 | 190 | GLint status; 191 | glGetShaderiv(shader->second.Handle, GL_COMPILE_STATUS, &status); 192 | if (!status) 193 | { 194 | GLint logLength; 195 | glGetShaderiv(shader->second.Handle, GL_INFO_LOG_LENGTH, &logLength); 196 | std::vector log(logLength + 1); 197 | glGetShaderInfoLog(shader->second.Handle, logLength, NULL, log.data()); 198 | 199 | std::string log_s = log.data(); 200 | 201 | // replace all filename hashes in the error messages with actual filenames 202 | for (size_t found_preamble; (found_preamble = log_s.find(preamble_hash)) != std::string::npos;) { 203 | log_s.replace(found_preamble, preamble_hash.size(), "preamble"); 204 | } 205 | for (size_t found_source; (found_source = log_s.find(source_hash)) != std::string::npos;) { 206 | log_s.replace(found_source, source_hash.size(), shader->first.Name); 207 | } 208 | 209 | fprintf(stderr, "Error compiling %s:\n%s\n", shader->first.Name.c_str(), log_s.c_str()); 210 | } 211 | } 212 | 213 | // relink all programs that had their shaders updated and have all their shaders compiling successfully 214 | for (std::pair, Program>& program : mPrograms) 215 | { 216 | bool programNeedsRelink = false; 217 | for (const ShaderNameTypePair* programShader : program.first) 218 | { 219 | for (std::pair* shader : updatedShaders) 220 | { 221 | if (&shader->first == programShader) 222 | { 223 | programNeedsRelink = true; 224 | break; 225 | } 226 | } 227 | 228 | if (programNeedsRelink) 229 | break; 230 | } 231 | 232 | // Don't attempt to link shaders that didn't compile successfully 233 | bool canRelink = true; 234 | if (programNeedsRelink) 235 | { 236 | for (const ShaderNameTypePair* programShader : program.first) 237 | { 238 | GLint status; 239 | glGetShaderiv(mShaders[*programShader].Handle, GL_COMPILE_STATUS, &status); 240 | if (!status) 241 | { 242 | canRelink = false; 243 | break; 244 | } 245 | } 246 | } 247 | 248 | if (programNeedsRelink && canRelink) 249 | { 250 | glLinkProgram(program.second.InternalHandle); 251 | 252 | GLint logLength; 253 | glGetProgramiv(program.second.InternalHandle, GL_INFO_LOG_LENGTH, &logLength); 254 | std::vector log(logLength + 1); 255 | glGetProgramInfoLog(program.second.InternalHandle, logLength, NULL, log.data()); 256 | 257 | std::string log_s = log.data(); 258 | 259 | // replace all filename hashes in the error messages with actual filenames 260 | std::string preamble_hash = std::to_string((int32_t)std::hash()("preamble")); 261 | for (size_t found_preamble; (found_preamble = log_s.find(preamble_hash)) != std::string::npos;) { 262 | log_s.replace(found_preamble, preamble_hash.size(), "preamble"); 263 | } 264 | for (const ShaderNameTypePair* shaderInProgram : program.first) 265 | { 266 | std::string source_hash = std::to_string(mShaders[*shaderInProgram].HashName); 267 | for (size_t found_source; (found_source = log_s.find(source_hash)) != std::string::npos;) { 268 | log_s.replace(found_source, source_hash.size(), shaderInProgram->Name); 269 | } 270 | } 271 | 272 | GLint status; 273 | glGetProgramiv(program.second.InternalHandle, GL_LINK_STATUS, &status); 274 | 275 | if (!status) 276 | { 277 | fprintf(stderr, "Error linking"); 278 | } 279 | else 280 | { 281 | fprintf(stderr, "Successfully linked"); 282 | } 283 | 284 | fprintf(stderr, " program ("); 285 | for (const ShaderNameTypePair* shader : program.first) 286 | { 287 | if (shader != program.first.front()) 288 | { 289 | fprintf(stderr, ", "); 290 | } 291 | 292 | fprintf(stderr, "%s", shader->Name.c_str()); 293 | } 294 | fprintf(stderr, ")"); 295 | if (log[0] != '\0') 296 | { 297 | fprintf(stderr, ":\n%s\n", log_s.c_str()); 298 | } 299 | else 300 | { 301 | fprintf(stderr, "\n"); 302 | } 303 | 304 | if (!status) 305 | { 306 | program.second.PublicHandle = 0; 307 | } 308 | else 309 | { 310 | program.second.PublicHandle = program.second.InternalHandle; 311 | } 312 | } 313 | } 314 | } 315 | 316 | void ShaderSet::SetPreambleFile(const std::string& preambleFilename) 317 | { 318 | SetPreamble(ShaderStringFromFile(preambleFilename.c_str())); 319 | } 320 | 321 | GLuint* ShaderSet::AddProgramFromExts(const std::vector& shaders) 322 | { 323 | std::vector> typedShaders; 324 | for (const std::string& shader : shaders) 325 | { 326 | size_t extLoc = shader.find_last_of('.'); 327 | if (extLoc == std::string::npos) 328 | { 329 | return nullptr; 330 | } 331 | 332 | GLenum shaderType; 333 | 334 | std::string ext = shader.substr(extLoc + 1); 335 | if (ext == "vert") 336 | shaderType = GL_VERTEX_SHADER; 337 | else if (ext == "frag") 338 | shaderType = GL_FRAGMENT_SHADER; 339 | else if (ext == "geom") 340 | shaderType = GL_GEOMETRY_SHADER; 341 | else if (ext == "tesc") 342 | shaderType = GL_TESS_CONTROL_SHADER; 343 | else if (ext == "tese") 344 | shaderType = GL_TESS_EVALUATION_SHADER; 345 | else if (ext == "comp") 346 | shaderType = GL_COMPUTE_SHADER; 347 | else 348 | return nullptr; 349 | 350 | typedShaders.emplace_back(shader, shaderType); 351 | } 352 | 353 | return AddProgram(typedShaders); 354 | } 355 | 356 | GLuint* ShaderSet::AddProgramFromCombinedFile(const std::string &filename, const std::vector &shaderTypes) 357 | { 358 | std::vector> typedShaders; 359 | 360 | for (auto type: shaderTypes) 361 | typedShaders.emplace_back(filename, type); 362 | 363 | return AddProgram(typedShaders); 364 | } 365 | 366 | -------------------------------------------------------------------------------- /shaderset.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Replace with your own GL header include 4 | #include "opengl.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | class ShaderSet 13 | { 14 | // typedefs for readability 15 | using ShaderHandle = GLuint; 16 | using ProgramHandle = GLuint; 17 | 18 | // filename and shader type 19 | struct ShaderNameTypePair 20 | { 21 | std::string Name; 22 | GLenum Type; 23 | bool operator<(const ShaderNameTypePair& rhs) const { return std::tie(Name, Type) < std::tie(rhs.Name, rhs.Type); } 24 | }; 25 | 26 | // Shader in the ShaderSet system 27 | struct Shader 28 | { 29 | ShaderHandle Handle; 30 | // Timestamp of the last update of the shader 31 | uint64_t Timestamp; 32 | // Hash of the name of the shader. This is used to recover the shader name from the GLSL compiler error messages. 33 | // It's not a perfect solution, but it's a miracle when it doesn't work. 34 | int32_t HashName; 35 | }; 36 | 37 | // Program in the ShaderSet system. 38 | struct Program 39 | { 40 | // The handle exposed externally ("public") and the most recent (succeeding/failed) linked program ("internal") 41 | // the public handle becomes 0 when a linking failure happens, until the linking error gets fixed. 42 | ProgramHandle PublicHandle; 43 | ProgramHandle InternalHandle; 44 | }; 45 | 46 | // the version in the version string that gets prepended to each shader 47 | std::string mVersion; 48 | // the preamble which gets prepended to each shader (for eg. shared binding conventions) 49 | std::string mPreamble; 50 | // maps shader name/types to handles, in order to reuse shared shaders. 51 | std::map mShaders; 52 | // allows looking up the program that represents a linked set of shaders 53 | std::map, Program> mPrograms; 54 | 55 | public: 56 | ShaderSet() = default; 57 | 58 | // Destructor releases all owned shaders 59 | ~ShaderSet(); 60 | 61 | // The version string to prepend to all shaders 62 | // Separated from the preamble because #version doesn't compile in C++ 63 | void SetVersion(const std::string& version); 64 | 65 | // A string that gets prepended to every shader that gets compiled 66 | // Useful for compile-time constant #defines (like attrib locations) 67 | void SetPreamble(const std::string& preamble); 68 | 69 | // Convenience for reading the preamble from a file 70 | // The preamble is NOT auto-reloaded. 71 | void SetPreambleFile(const std::string& preambleFilename); 72 | 73 | // list of (file name, shader type) pairs 74 | // eg: AddProgram({ {"foo.vert", GL_VERTEX_SHADER}, {"bar.frag", GL_FRAGMENT_SHADER} }); 75 | // To be const-correct, this should maybe return "const GLuint*". I'm trusting you not to write to that pointer. 76 | GLuint* AddProgram(const std::vector>& typedShaders); 77 | 78 | // Polls the timestamps of all the shaders and recompiles/relinks them if they changed 79 | void UpdatePrograms(); 80 | 81 | // Convenience to add shaders based on extension file naming conventions 82 | // vertex shader: .vert 83 | // fragment shader: .frag 84 | // geometry shader: .geom 85 | // tessellation control shader: .tesc 86 | // tessellation evaluation shader: .tese 87 | // compute shader: .comp 88 | // eg: AddProgramFromExts({"foo.vert", "bar.frag"}); 89 | // To be const-correct, this should maybe return "const GLuint*". I'm trusting you not to write to that pointer. 90 | GLuint* AddProgramFromExts(const std::vector& shaders); 91 | 92 | // Convenience to add a single file that contains many shader stages. 93 | // Similar to what is explained here: https://software.intel.com/en-us/blogs/2012/03/26/using-ifdef-in-opengl-es-20-shaders 94 | // eg: AddProgramFromCombinedFile("shader.glsl", { GL_VERTEX_SHADER, GL_FRAGMENT_SHADER }); 95 | // 96 | // The shader will be compiled many times with a different #define based on which shader stage it's being used for, similarly to the Intel article above. 97 | // The defines are as follows: 98 | // vertex shader: VERTEX_SHADER 99 | // fragment shader: FRAGMENT_SHADER 100 | // geometry shader: GEOMETRY_SHADER 101 | // tessellation control shader: TESS_CONTROL_SHADER 102 | // tessellation evaluation shader: TESS_EVALUATION_SHADER 103 | // compute shader: COMPUTE_SHADER 104 | // 105 | // Note: These defines are not unique to the AddProgramFromCombinedFile API. The defines are also set with any other AddProgram*() API. 106 | // Note: You may use the defines from inside the preamble. (ie. the preamble is inserted after those defines.) 107 | // 108 | // Example combined file shader: 109 | // #ifdef VERTEX_SHADER 110 | // void main() { /* your vertex shader main */ } 111 | // #endif 112 | // 113 | // #ifdef FRAGMENT_SHADER 114 | // void main() { /* your fragment shader main */ } 115 | // #endif 116 | GLuint* AddProgramFromCombinedFile(const std::string &filename, const std::vector &shaderTypes); 117 | }; 118 | --------------------------------------------------------------------------------