//////////////////////////////////////////////////////////////////////////////////////// // Kara Jensen - mail@karajensen.com - fragmentlinker.h //////////////////////////////////////////////////////////////////////////////////////// #pragma once #include <vector> #include <unordered_map> #include <boost/noncopyable.hpp> class Shader; /** * Generates a shader from a file replacing any special syntax or defined values */ class FragmentLinker : boost::noncopyable { public: FragmentLinker() = default; ~FragmentLinker() = default; /** * Initialises the fragment linker * @param maxWaves The amount of overlapping waves for water * @param maxLights The amount of lights the shader will consider * @param blurWeights The values for blurring surrounding pixels * @return Whether initialisation was successful */ bool Initialise(unsigned int maxWaves, unsigned int maxLights, const std::vector<float>& blurWeights); /** * Generates a new shader * @param shader The shader information to generate from * @return Whether generation was successful */ bool GenerateShader(const Shader& shader); private: /** * Generates a new shader * @param baseFilePath path of the file to use as a base for generation * @param generatedFilePath path of the file to save to once generated * @param generateFromFragments Whether to create a new shader based off conditionals * @return Whether generation was successful */ bool GenerateShader(const std::string& baseFilePath, const std::string& generatedFilePath, bool generateFromFragments); /** * Reads the base shader until the end of the file * @param baseFile The filestream for the base shader * @param generatedFile The filestream of the shader being created * @param previousLine The line last added to the generated shader * @return the last line read after the target string is found */ std::string ReadBaseShader(std::ifstream& baseFile, std::ofstream& generatedFile, std::string& previousLine); /** * Reads the base shader until the end of the file * Any components the generated shader requires will be written to it * @note Used recursively when determining component conditionals * @param baseFile The filestream for the base shader * @param generatedFile The filestream of the shader being created * @param targets The target strings to read the base file until * @param previousLine The line last added to the generated shader * @param skiplines Whether to write to the generated file or not * @param level The recursive level this function is on * @return the last line read after the target string is found */ std::string ReadBaseShader(std::ifstream& baseFile, std::ofstream& generatedFile, const std::vector<std::string>& targets, std::string& previousLine, bool skiplines, int level); /** * Determines if given line is a conditional and solves it if so * @param level The recursive level ReadBaseShader() is on * @param line The current line read from the base shader * @param previousLine The line last added to the generated shader * @param baseFile The filestream for the base shader * @param generatedFile The filestream of the shader being created * @param skiplines Whether to write to the generated file or not * @return whether the line was a conditional statement or not */ bool SolveConditionalLine(int level, std::string line, std::string& previousLine, std::ifstream& baseFile, std::ofstream& generatedFile, bool skiplines); /** * Gets the next line from the file, filtering and removing syntax as needed * @param file The shader file to read from * @return the next line from the file */ std::string GetNextLine(std::ifstream& file) const; /** * Determines whether the conditional if-else block should be included * @param conditional The conditional keyword of the block * @param line The line with the conditional keyword * @return whether the block should be included in the generated shader or not */ bool ShouldSkipConditionalBlock( const std::string& conditional, std::string line) const; std::unordered_map<std::string, std::string> m_defines; ///< map of #defined items to replace unsigned int m_shaderComponents; ///< components of shader undergoing linking }; //////////////////////////////////////////////////////////////////////////////////////// // Kara Jensen - mail@karajensen.com - fragmentlinker.cpp //////////////////////////////////////////////////////////////////////////////////////// #include "fragmentlinker.h" #include "shader.h" #include "renderdata.h" #include "logger.h" #include <fstream> #include <boost/assign.hpp> #include <boost/algorithm/string.hpp> #include <boost/regex.hpp> namespace { /** * Syntax for base shader components */ const std::string END_OF_FILE("End-of-file"); const std::string FAILURE("Failure"); const std::string IF("ifdef: "); const std::string ELSE("else:"); const std::string ELSEIF("elseif: "); const std::string ENDIF("endif"); } bool FragmentLinker::GenerateShader(const Shader& shader) { m_shaderComponents = shader.GetComponents(); const bool useFragments = shader.GenerateFromFragments(); return GenerateShader(shader.GLSLVertexBase(), shader.GLSLVertexFile(), useFragments) && GenerateShader(shader.GLSLFragmentBase(), shader.GLSLFragmentFile(), useFragments) && GenerateShader(shader.HLSLShaderBase(), shader.HLSLShaderFile(), useFragments); } bool FragmentLinker::GenerateShader(const std::string& baseFilePath, const std::string& generatedFilePath, bool generateFromFragments) { std::ofstream generatedFile(generatedFilePath.c_str(), std::ios_base::out|std::ios_base::trunc); if(!generatedFile.is_open()) { Logger::LogError("Could not open " + generatedFilePath); return false; } std::ifstream baseFile(baseFilePath.c_str(), std::ios_base::in|std::ios_base::_Nocreate); if(!baseFile.is_open()) { Logger::LogError("Could not open " + baseFilePath); return false; } std::string previousLine = FAILURE; if (generateFromFragments) { std::vector<std::string> emptyTarget; previousLine = ReadBaseShader(baseFile, generatedFile, emptyTarget, previousLine, false, 0); } else { previousLine = ReadBaseShader( baseFile, generatedFile, previousLine); } generatedFile.close(); baseFile.close(); return previousLine != FAILURE; } std::string FragmentLinker::ReadBaseShader(std::ifstream& baseFile, std::ofstream& generatedFile, std::string& previousLine) { while(!baseFile.eof()) { const std::string line = GetNextLine(baseFile); const std::string trimmedLine = boost::trim_copy(line); if(!trimmedLine.empty() || !previousLine.empty()) { generatedFile << line << std::endl; } previousLine = trimmedLine; if(baseFile.fail() || baseFile.bad()) { Logger::LogError("Base shader is corrupted"); return FAILURE; } } return END_OF_FILE; } std::string FragmentLinker::ReadBaseShader(std::ifstream& baseFile, std::ofstream& generatedFile, const std::vector<std::string>& targets, std::string& previousLine, bool skiplines, int level) { while(!baseFile.eof()) { std::string line = GetNextLine(baseFile); // Check for conditional keywords for(const auto& key : targets) { if(boost::algorithm::icontains(line, key)) { // Block has finished once reaching target keyword return line; } } // Check if the line contains conditional syntax if(!SolveConditionalLine(level, line, previousLine, baseFile, generatedFile, skiplines)) { // If the line only contains whitespace after a previous whitespace line don't add std::string trimmedline = boost::trim_left_copy(line); if (!skiplines && !(previousLine.empty() && trimmedline.empty())) { // Make sure text is aligned once conditionals are removed const int spacesInTabs = 4; const int spaceOffset = targets.empty() ? 0 : spacesInTabs * level; const int spaceAmount = line.size() - trimmedline.size() - spaceOffset; const std::string spaces(std::max(0, spaceAmount), ' '); const std::string extraSpaces(spaceOffset, ' '); boost::ireplace_all(trimmedline, ":", extraSpaces + ":"); // Ensure semantics align generatedFile << spaces << trimmedline << std::endl; previousLine = trimmedline; } } if(boost::algorithm::icontains(line, END_OF_FILE)) { return END_OF_FILE; } else if(baseFile.fail() || baseFile.bad()) { Logger::LogError("Base shader is corrupted"); return FAILURE; } } return END_OF_FILE; } bool FragmentLinker::ShouldSkipConditionalBlock(const std::string& conditional, std::string line) const { if(conditional != IF && conditional != ELSEIF) { return false; } std::vector<std::string> components; boost::algorithm::trim(line); boost::erase_head(line, conditional.size()); boost::split(components, line, boost::is_any_of("|")); auto isComponentSuccessful = [this](std::string component) { const bool required = !boost::icontains(component, "!"); boost::ireplace_first(component, "!", ""); const auto value = Shader::StringAsComponent(component); const bool found = (m_shaderComponents & value) == value; return (found && required) || (!found && !required); }; return std::find_if_not(components.begin(), components.end(), isComponentSuccessful) != components.end(); } bool FragmentLinker::SolveConditionalLine(int level, std::string line, std::string& previousLine, std::ifstream& baseFile, std::ofstream& generatedFile, bool skiplines) { if(!boost::algorithm::icontains(line, IF)) { return false; } if(skiplines) { ReadBaseShader(baseFile, generatedFile, boost::assign::list_of(ENDIF), previousLine, skiplines, level+1); return true; } // Recursively solve ifdefined/ifndefined blocks bool skipConditionalBlock = ShouldSkipConditionalBlock(IF, line); bool solvedConditional = !skipConditionalBlock; line = ReadBaseShader(baseFile, generatedFile, boost::assign::list_of(ELSE)(ENDIF)(ELSEIF), previousLine, skipConditionalBlock, level+1); // Solve elseif blocks while(boost::algorithm::icontains(line, ELSEIF)) { skipConditionalBlock = solvedConditional ? true : ShouldSkipConditionalBlock(ELSEIF, line); solvedConditional = !skipConditionalBlock ? true : solvedConditional; line = ReadBaseShader(baseFile, generatedFile, boost::assign::list_of(ELSE)(ENDIF)(ELSEIF), previousLine, skipConditionalBlock, level+1); } // Solve else blocks if(boost::algorithm::icontains(line, ELSE)) { ReadBaseShader(baseFile, generatedFile, boost::assign::list_of(ENDIF), previousLine, solvedConditional, level+1); } return true; } std::string FragmentLinker::GetNextLine(std::ifstream& file) const { std::string line; std::getline(file, line); // Remove all comments if(line.find("//") != -1) { line = boost::regex_replace(line, boost::regex("//.*?$"), ""); } // Replace any defined values for(const auto& define : m_defines) { boost::ireplace_all(line, define.first, define.second); } return line; } bool FragmentLinker::Initialise(unsigned int maxWaves, unsigned int maxLights, const std::vector<float>& blurWeights) { m_defines["MAX_LIGHTS"] = std::to_string(maxLights); m_defines["MAX_WAVES"] = std::to_string(maxWaves); m_defines["SAMPLES"] = std::to_string(MULTISAMPLING_COUNT); m_defines["WINDOW_WIDTH"] = std::to_string(WINDOW_WIDTH); m_defines["WINDOW_HEIGHT"] = std::to_string(WINDOW_HEIGHT); m_defines["SCENE_TEXTURES"] = std::to_string(SCENE_TEXTURES); m_defines["ID_COLOUR"] = std::to_string(SCENE_ID); m_defines["ID_DEPTH"] = std::to_string(DEPTH_ID); for (auto i = 0u; i < blurWeights.size(); ++i) { const std::string key("WEIGHT" + std::to_string(i)); m_defines[key] = std::to_string(blurWeights[i]); } return true; }