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