1 #include "shadermanager.hpp" 2 3 #include <fstream> 4 #include <algorithm> 5 #include <sstream> 6 7 #include <osg/Program> 8 9 #include <boost/filesystem/path.hpp> 10 #include <boost/filesystem/fstream.hpp> 11 12 #include <components/sceneutil/lightmanager.hpp> 13 #include <components/debug/debuglog.hpp> 14 #include <components/misc/stringops.hpp> 15 16 namespace Shader 17 { 18 ShaderManager()19 ShaderManager::ShaderManager() 20 : mLightingMethod(SceneUtil::LightingMethod::FFP) 21 { 22 } 23 setShaderPath(const std::string & path)24 void ShaderManager::setShaderPath(const std::string &path) 25 { 26 mPath = path; 27 } 28 setLightingMethod(SceneUtil::LightingMethod method)29 void ShaderManager::setLightingMethod(SceneUtil::LightingMethod method) 30 { 31 mLightingMethod = method; 32 } 33 addLineDirectivesAfterConditionalBlocks(std::string & source)34 bool addLineDirectivesAfterConditionalBlocks(std::string& source) 35 { 36 for (size_t position = 0; position < source.length(); ) 37 { 38 size_t foundPos = source.find("#endif", position); 39 foundPos = std::min(foundPos, source.find("#elif", position)); 40 foundPos = std::min(foundPos, source.find("#else", position)); 41 42 if (foundPos == std::string::npos) 43 break; 44 45 foundPos = source.find_first_of("\n\r", foundPos); 46 foundPos = source.find_first_not_of("\n\r", foundPos); 47 48 if (foundPos == std::string::npos) 49 break; 50 51 size_t lineDirectivePosition = source.rfind("#line", foundPos); 52 int lineNumber; 53 if (lineDirectivePosition != std::string::npos) 54 { 55 size_t lineNumberStart = lineDirectivePosition + std::string("#line ").length(); 56 size_t lineNumberEnd = source.find_first_not_of("0123456789", lineNumberStart); 57 std::string lineNumberString = source.substr(lineNumberStart, lineNumberEnd - lineNumberStart); 58 lineNumber = std::stoi(lineNumberString) - 1; 59 } 60 else 61 { 62 lineDirectivePosition = 0; 63 lineNumber = 1; 64 } 65 lineNumber += std::count(source.begin() + lineDirectivePosition, source.begin() + foundPos, '\n'); 66 67 source.replace(foundPos, 0, "#line " + std::to_string(lineNumber) + "\n"); 68 69 position = foundPos; 70 } 71 72 return true; 73 } 74 75 // Recursively replaces include statements with the actual source of the included files. 76 // Adjusts #line statements accordingly and detects cyclic includes. 77 // includingFiles is the set of files that include this file directly or indirectly, and is intentionally not a reference to allow automatic cleanup. parseIncludes(boost::filesystem::path shaderPath,std::string & source,const std::string & fileName,int & fileNumber,std::set<boost::filesystem::path> includingFiles)78 static bool parseIncludes(boost::filesystem::path shaderPath, std::string& source, const std::string& fileName, int& fileNumber, std::set<boost::filesystem::path> includingFiles) 79 { 80 // An include is cyclic if it is being included by itself 81 if (includingFiles.insert(shaderPath/fileName).second == false) 82 { 83 Log(Debug::Error) << "Shader " << fileName << " error: Detected cyclic #includes"; 84 return false; 85 } 86 87 Misc::StringUtils::replaceAll(source, "\r\n", "\n"); 88 89 size_t foundPos = 0; 90 while ((foundPos = source.find("#include")) != std::string::npos) 91 { 92 size_t start = source.find('"', foundPos); 93 if (start == std::string::npos || start == source.size() - 1) 94 { 95 Log(Debug::Error) << "Shader " << fileName << " error: Invalid #include"; 96 return false; 97 } 98 size_t end = source.find('"', start + 1); 99 if (end == std::string::npos) 100 { 101 Log(Debug::Error) << "Shader " << fileName << " error: Invalid #include"; 102 return false; 103 } 104 std::string includeFilename = source.substr(start + 1, end - (start + 1)); 105 boost::filesystem::path includePath = shaderPath / includeFilename; 106 107 // Determine the line number that will be used for the #line directive following the included source 108 size_t lineDirectivePosition = source.rfind("#line", foundPos); 109 int lineNumber; 110 if (lineDirectivePosition != std::string::npos) 111 { 112 size_t lineNumberStart = lineDirectivePosition + std::string("#line ").length(); 113 size_t lineNumberEnd = source.find_first_not_of("0123456789", lineNumberStart); 114 std::string lineNumberString = source.substr(lineNumberStart, lineNumberEnd - lineNumberStart); 115 lineNumber = std::stoi(lineNumberString) - 1; 116 } 117 else 118 { 119 lineDirectivePosition = 0; 120 lineNumber = 0; 121 } 122 lineNumber += std::count(source.begin() + lineDirectivePosition, source.begin() + foundPos, '\n'); 123 124 // Include the file recursively 125 boost::filesystem::ifstream includeFstream; 126 includeFstream.open(includePath); 127 if (includeFstream.fail()) 128 { 129 Log(Debug::Error) << "Shader " << fileName << " error: Failed to open include " << includePath.string(); 130 return false; 131 } 132 int includedFileNumber = fileNumber++; 133 134 std::stringstream buffer; 135 buffer << includeFstream.rdbuf(); 136 std::string stringRepresentation = buffer.str(); 137 if (!addLineDirectivesAfterConditionalBlocks(stringRepresentation) 138 || !parseIncludes(shaderPath, stringRepresentation, includeFilename, fileNumber, includingFiles)) 139 { 140 Log(Debug::Error) << "In file included from " << fileName << "." << lineNumber; 141 return false; 142 } 143 144 std::stringstream toInsert; 145 toInsert << "#line 0 " << includedFileNumber << "\n" << stringRepresentation << "\n#line " << lineNumber << " 0\n"; 146 147 source.replace(foundPos, (end - foundPos + 1), toInsert.str()); 148 } 149 return true; 150 } 151 parseFors(std::string & source,const std::string & templateName)152 bool parseFors(std::string& source, const std::string& templateName) 153 { 154 const char escapeCharacter = '$'; 155 size_t foundPos = 0; 156 while ((foundPos = source.find(escapeCharacter)) != std::string::npos) 157 { 158 size_t endPos = source.find_first_of(" \n\r()[].;,", foundPos); 159 if (endPos == std::string::npos) 160 { 161 Log(Debug::Error) << "Shader " << templateName << " error: Unexpected EOF"; 162 return false; 163 } 164 std::string command = source.substr(foundPos + 1, endPos - (foundPos + 1)); 165 if (command != "foreach") 166 { 167 Log(Debug::Error) << "Shader " << templateName << " error: Unknown shader directive: $" << command; 168 return false; 169 } 170 171 size_t iterNameStart = endPos + 1; 172 size_t iterNameEnd = source.find_first_of(" \n\r()[].;,", iterNameStart); 173 if (iterNameEnd == std::string::npos) 174 { 175 Log(Debug::Error) << "Shader " << templateName << " error: Unexpected EOF"; 176 return false; 177 } 178 std::string iteratorName = "$" + source.substr(iterNameStart, iterNameEnd - iterNameStart); 179 180 size_t listStart = iterNameEnd + 1; 181 size_t listEnd = source.find_first_of("\n\r", listStart); 182 if (listEnd == std::string::npos) 183 { 184 Log(Debug::Error) << "Shader " << templateName << " error: Unexpected EOF"; 185 return false; 186 } 187 std::string list = source.substr(listStart, listEnd - listStart); 188 std::vector<std::string> listElements; 189 if (list != "") 190 Misc::StringUtils::split (list, listElements, ","); 191 192 size_t contentStart = source.find_first_not_of("\n\r", listEnd); 193 size_t contentEnd = source.find("$endforeach", contentStart); 194 if (contentEnd == std::string::npos) 195 { 196 Log(Debug::Error) << "Shader " << templateName << " error: Unexpected EOF"; 197 return false; 198 } 199 std::string content = source.substr(contentStart, contentEnd - contentStart); 200 201 size_t overallEnd = contentEnd + std::string("$endforeach").length(); 202 203 size_t lineDirectivePosition = source.rfind("#line", overallEnd); 204 int lineNumber; 205 if (lineDirectivePosition != std::string::npos) 206 { 207 size_t lineNumberStart = lineDirectivePosition + std::string("#line ").length(); 208 size_t lineNumberEnd = source.find_first_not_of("0123456789", lineNumberStart); 209 std::string lineNumberString = source.substr(lineNumberStart, lineNumberEnd - lineNumberStart); 210 lineNumber = std::stoi(lineNumberString); 211 } 212 else 213 { 214 lineDirectivePosition = 0; 215 lineNumber = 2; 216 } 217 lineNumber += std::count(source.begin() + lineDirectivePosition, source.begin() + overallEnd, '\n'); 218 219 std::string replacement = ""; 220 for (std::vector<std::string>::const_iterator element = listElements.cbegin(); element != listElements.cend(); element++) 221 { 222 std::string contentInstance = content; 223 size_t foundIterator; 224 while ((foundIterator = contentInstance.find(iteratorName)) != std::string::npos) 225 contentInstance.replace(foundIterator, iteratorName.length(), *element); 226 replacement += contentInstance; 227 } 228 replacement += "\n#line " + std::to_string(lineNumber); 229 source.replace(foundPos, overallEnd - foundPos, replacement); 230 } 231 232 return true; 233 } 234 parseDefines(std::string & source,const ShaderManager::DefineMap & defines,const ShaderManager::DefineMap & globalDefines,const std::string & templateName)235 bool parseDefines(std::string& source, const ShaderManager::DefineMap& defines, 236 const ShaderManager::DefineMap& globalDefines, const std::string& templateName) 237 { 238 const char escapeCharacter = '@'; 239 size_t foundPos = 0; 240 std::vector<std::string> forIterators; 241 while ((foundPos = source.find(escapeCharacter)) != std::string::npos) 242 { 243 size_t endPos = source.find_first_of(" \n\r()[].;,", foundPos); 244 if (endPos == std::string::npos) 245 { 246 Log(Debug::Error) << "Shader " << templateName << " error: Unexpected EOF"; 247 return false; 248 } 249 std::string define = source.substr(foundPos+1, endPos - (foundPos+1)); 250 ShaderManager::DefineMap::const_iterator defineFound = defines.find(define); 251 ShaderManager::DefineMap::const_iterator globalDefineFound = globalDefines.find(define); 252 if (define == "foreach") 253 { 254 source.replace(foundPos, 1, "$"); 255 size_t iterNameStart = endPos + 1; 256 size_t iterNameEnd = source.find_first_of(" \n\r()[].;,", iterNameStart); 257 if (iterNameEnd == std::string::npos) 258 { 259 Log(Debug::Error) << "Shader " << templateName << " error: Unexpected EOF"; 260 return false; 261 } 262 forIterators.push_back(source.substr(iterNameStart, iterNameEnd - iterNameStart)); 263 } 264 else if (define == "endforeach") 265 { 266 source.replace(foundPos, 1, "$"); 267 if (forIterators.empty()) 268 { 269 Log(Debug::Error) << "Shader " << templateName << " error: endforeach without foreach"; 270 return false; 271 } 272 else 273 forIterators.pop_back(); 274 } 275 else if (std::find(forIterators.begin(), forIterators.end(), define) != forIterators.end()) 276 { 277 source.replace(foundPos, 1, "$"); 278 } 279 else if (defineFound != defines.end()) 280 { 281 source.replace(foundPos, endPos - foundPos, defineFound->second); 282 } 283 else if (globalDefineFound != globalDefines.end()) 284 { 285 source.replace(foundPos, endPos - foundPos, globalDefineFound->second); 286 } 287 else 288 { 289 Log(Debug::Error) << "Shader " << templateName << " error: Undefined " << define; 290 return false; 291 } 292 } 293 return true; 294 } 295 getShader(const std::string & templateName,const ShaderManager::DefineMap & defines,osg::Shader::Type shaderType)296 osg::ref_ptr<osg::Shader> ShaderManager::getShader(const std::string &templateName, const ShaderManager::DefineMap &defines, osg::Shader::Type shaderType) 297 { 298 std::lock_guard<std::mutex> lock(mMutex); 299 300 // read the template if we haven't already 301 TemplateMap::iterator templateIt = mShaderTemplates.find(templateName); 302 if (templateIt == mShaderTemplates.end()) 303 { 304 boost::filesystem::path path = (boost::filesystem::path(mPath) / templateName); 305 boost::filesystem::ifstream stream; 306 stream.open(path); 307 if (stream.fail()) 308 { 309 Log(Debug::Error) << "Failed to open " << path.string(); 310 return nullptr; 311 } 312 std::stringstream buffer; 313 buffer << stream.rdbuf(); 314 315 // parse includes 316 int fileNumber = 1; 317 std::string source = buffer.str(); 318 if (!addLineDirectivesAfterConditionalBlocks(source) 319 || !parseIncludes(boost::filesystem::path(mPath), source, templateName, fileNumber, {})) 320 return nullptr; 321 322 templateIt = mShaderTemplates.insert(std::make_pair(templateName, source)).first; 323 } 324 325 ShaderMap::iterator shaderIt = mShaders.find(std::make_pair(templateName, defines)); 326 if (shaderIt == mShaders.end()) 327 { 328 std::string shaderSource = templateIt->second; 329 if (!parseDefines(shaderSource, defines, mGlobalDefines, templateName) || !parseFors(shaderSource, templateName)) 330 { 331 // Add to the cache anyway to avoid logging the same error over and over. 332 mShaders.insert(std::make_pair(std::make_pair(templateName, defines), nullptr)); 333 return nullptr; 334 } 335 336 osg::ref_ptr<osg::Shader> shader (new osg::Shader(shaderType)); 337 shader->setShaderSource(shaderSource); 338 // Assign a unique name to allow the SharedStateManager to compare shaders efficiently 339 static unsigned int counter = 0; 340 shader->setName(std::to_string(counter++)); 341 342 shaderIt = mShaders.insert(std::make_pair(std::make_pair(templateName, defines), shader)).first; 343 } 344 return shaderIt->second; 345 } 346 getProgram(osg::ref_ptr<osg::Shader> vertexShader,osg::ref_ptr<osg::Shader> fragmentShader)347 osg::ref_ptr<osg::Program> ShaderManager::getProgram(osg::ref_ptr<osg::Shader> vertexShader, osg::ref_ptr<osg::Shader> fragmentShader) 348 { 349 std::lock_guard<std::mutex> lock(mMutex); 350 ProgramMap::iterator found = mPrograms.find(std::make_pair(vertexShader, fragmentShader)); 351 if (found == mPrograms.end()) 352 { 353 osg::ref_ptr<osg::Program> program (new osg::Program); 354 program->addShader(vertexShader); 355 program->addShader(fragmentShader); 356 program->addBindAttribLocation("aOffset", 6); 357 program->addBindAttribLocation("aRotation", 7); 358 if (mLightingMethod == SceneUtil::LightingMethod::SingleUBO) 359 program->addBindUniformBlock("LightBufferBinding", static_cast<int>(UBOBinding::LightBuffer)); 360 found = mPrograms.insert(std::make_pair(std::make_pair(vertexShader, fragmentShader), program)).first; 361 } 362 return found->second; 363 } 364 getGlobalDefines()365 ShaderManager::DefineMap ShaderManager::getGlobalDefines() 366 { 367 return DefineMap(mGlobalDefines); 368 } 369 setGlobalDefines(DefineMap & globalDefines)370 void ShaderManager::setGlobalDefines(DefineMap & globalDefines) 371 { 372 mGlobalDefines = globalDefines; 373 for (auto shaderMapElement: mShaders) 374 { 375 std::string templateId = shaderMapElement.first.first; 376 ShaderManager::DefineMap defines = shaderMapElement.first.second; 377 osg::ref_ptr<osg::Shader> shader = shaderMapElement.second; 378 if (shader == nullptr) 379 // I'm not sure how to handle a shader that was already broken as there's no way to get a potential replacement to the nodes that need it. 380 continue; 381 std::string shaderSource = mShaderTemplates[templateId]; 382 if (!parseDefines(shaderSource, defines, mGlobalDefines, templateId) || !parseFors(shaderSource, templateId)) 383 // We just broke the shader and there's no way to force existing objects back to fixed-function mode as we would when creating the shader. 384 // If we put a nullptr in the shader map, we just lose the ability to put a working one in later. 385 continue; 386 shader->setShaderSource(shaderSource); 387 } 388 } 389 releaseGLObjects(osg::State * state)390 void ShaderManager::releaseGLObjects(osg::State *state) 391 { 392 std::lock_guard<std::mutex> lock(mMutex); 393 for (auto shader : mShaders) 394 { 395 if (shader.second != nullptr) 396 shader.second->releaseGLObjects(state); 397 } 398 for (auto program : mPrograms) 399 program.second->releaseGLObjects(state); 400 } 401 402 } 403