Commit 82115120 authored by Philip Trettner's avatar Philip Trettner
Browse files

shader parser refactoring

parent c606506d
......@@ -35,7 +35,7 @@ public:
ShaderCreator(const std::string& _filename)
: Resource::SingleFileBasedCreator<Shader>(_filename, Base::Settings::the()->getFullShaderPath()),
mType(GL_INVALID_ENUM),
mShaderParserFactory(std::make_shared<ShaderParserFactory>())
mShaderParserFactory(std::make_shared<SimpleShaderParserFactory<IncludingShaderParser> >())
{}
virtual ~ShaderCreator() {}
......@@ -52,7 +52,8 @@ public:
//
/// Sets the shader parser factory
inline ShaderCreator& shaderParserFacoty(SharedShaderParserFactory const& _factory) { mShaderParserFactory = _factory; return *this; }
/// For more detailed documentation, see ShaderParser.hh
inline ShaderCreator& shaderParserFactory(SharedShaderParserFactory const& _factory) { mShaderParserFactory = _factory; return *this; }
// ===================================================================================================== \/
// ============================================================================================ OVERRIDE \/
......
/***********************************************************************
* Copyright 2011-2012 Computer Graphics Group RWTH Aachen University. *
* All rights reserved. *
* Distributed under the terms of the MIT License (see LICENSE.TXT). *
**********************************************************************/
#pragma once
#include <vector>
#include <string>
#include <ACGL/ACGL.hh>
#include <ACGL/Base/Macros.hh>
#include <ACGL/OpenGL/GL.hh>
#include <ACGL/OpenGL/Tools.hh>
namespace ACGL{
namespace OpenGL{
/**
* @brief ShaderParser for processing shader source code
*
* Subclass this and provide alternative ShaderParserFactories to Creators in order to implement custom shader processing
* Override processPragma in order to create custom pragma handling
*/
class ShaderParser
{
ACGL_NOT_COPYABLE(ShaderParser)
public:
ShaderParser( const std::string &_filename );
virtual ~ShaderParser() { }
std::vector< std::string > getSources() const { return mSources; }
std::string getFileNamesPrintable() const;
//! the imported sources don't count the original file!
unsigned int getNumberOfImportedFiles() const { return mSourceFileNames.size()-1; }
//! 0 == original file
std::string getFileName( unsigned int i ) const { return mSourceFileNames[i]; }
/**
* @brief checks if a given Sourcefile name exists
* @param _filename filename to check
* @return true, iff mSourceFileNames contains _filename
*/
bool existsSourcefile(std::string const& _filename);
protected:
/**
* @brief registers a new SourceFile
* @param _name name of the file (may be an actual filename or a name that indicates automatic generation)
* @return an ID that is to be used in #line statements and getFileName-Index.
*/
int registerSourceFile(std::string const& _name);
/**
* @brief adds a chunk of source to the shader
* @param _source source string
*
* Does not have to correspond to files
*/
void addSource(std::string const& _source);
/**
* @brief constructs a line directive
* @param _lineNumber line number (starts at 1)
* @param _fileID file ID (usually from registerSourceFile)
* @return a string with the format "#line lineNr fileID\n"
*/
std::string lineDirective(int _lineNumber, int _fileID) const;
/**
* @brief processes a custom Pragma directive
* @param _tokens vector of tokens after #pragma (e.g. "#pragma myPragma 5" results in _tokens = { "myPragma", "5" })
* @param _filename name of the current source file (changes for different includes, does not reflect custom file names)
*
* Override this function to implement custom pragma handling (no need to call base function)
*
* Recommended behavior:
* - Check if pragma is valid, otherwise call base::processPragma
* - Start with lineNumber 1
* - Register appropriate source file (using registerSourceFile(...))
* - Initialize source content with lineDirective(lineNumber, fileID)
* - Add custom source content
* - Call addSource(...) with your source content
* - Return true
*/
virtual bool processPragma(std::vector<std::string> const& _tokens, std::string const& _filename);
/**
* @brief reads in a source file from file system
* @param _filename filename of the source file
*
* Override this function to implement custom file reading functionality
*/
virtual void readin( const std::string &_filename );
private:
bool lineContainsVersion( const std::string &line, unsigned int &version);
bool lineContainsImport( const std::string &line, std::string &filename);
bool lineContainsPragma( const std::string &line, std::vector<std::string> &pragmaToken);
std::vector< std::string > mSources;
std::vector< std::string > mSourceFileNames; // holds at least one filename
unsigned int mMaxVersion;
};
ACGL_SMARTPOINTER_TYPEDEFS(ShaderParser)
/**
* @brief A ShaderParser that recognizes shader includes
*
* e.g.
* #pragma ACGLimport "matrices.glsl"
*/
class IncludingShaderParser : public ShaderParser
{
public:
/// c'tor
IncludingShaderParser( std::string const& _filename );
/**
* Processes "#pragma ACGLimport filename"
*/
virtual bool processPragma(std::vector<std::string> const& _tokens, std::string const& _filename);
};
ACGL_SMARTPOINTER_TYPEDEFS(IncludingShaderParser)
} // OpenGL
} // ACGL
......@@ -24,6 +24,8 @@ namespace OpenGL{
* @brief Factory for shader parser
*
* This can be used in in the shader program creator to create post-processes for shader
*
* For more detailed documentation, see ShaderParser.hh
*/
class ShaderParserFactory
{
......@@ -41,9 +43,26 @@ public:
* Returns the a default shader parse that is able to include other shader
* Override this function to create your own functionality
*/
virtual SharedShaderParser createParser(std::string const& _filename);
virtual SharedShaderParser createParser(std::string const& _filename) = 0;
};
ACGL_SMARTPOINTER_TYPEDEFS(ShaderParserFactory)
/**
* @brief A simple templated shader parser factory
*
* calls std::make_shared<ShaderParserT>(_filename), so make sure that the (string const&)-c'tor is publicly accessible
*/
template<typename ShaderParserT>
class SimpleShaderParserFactory : public ShaderParserFactory
{
public:
/// see parent
virtual SharedShaderParser createParser(std::string const& _filename)
{
return std::make_shared<ShaderParserT>(_filename);
}
};
} // OpenGL
} // ACGL
......@@ -45,7 +45,7 @@ public:
mAttributeLocations(new LocationMappings),
mFragmentDataLocations(new LocationMappings),
mUniformBufferLocations(new LocationMappings),
mShaderParserFactory(std::make_shared<ShaderParserFactory>())
mShaderParserFactory(std::make_shared<SimpleShaderParserFactory<IncludingShaderParser> >())
{
// the base path is only needed for updating the time stamps, as the shaders itself get loaded via ShaderCreators
// which itself will add the base path! (read: mFileNames will _NOT_ store the base path!)
......@@ -76,7 +76,8 @@ public:
//
/// Sets the shader parser factory
inline ShaderProgramCreator& shaderParserFacoty(SharedShaderParserFactory const& _factory) { mShaderParserFactory = _factory; return *this; }
/// For more detailed documentation, see ShaderParser.hh
inline ShaderProgramCreator& shaderParserFactory(SharedShaderParserFactory const& _factory) { mShaderParserFactory = _factory; return *this; }
//
// Adding files:
......
......@@ -14,6 +14,8 @@
*
* So normally you want to work with ShaderPrograms instead of Shaders (switch Programs,
* set uniforms etc).
*
* Custimizing shader parsing is done via custom ShaderParser classes, see Creator/ShaderParser.hh
*/
#include <vector>
......@@ -24,93 +26,11 @@
#include <ACGL/Base/Macros.hh>
#include <ACGL/OpenGL/GL.hh>
#include <ACGL/OpenGL/Tools.hh>
#include <ACGL/OpenGL/Creator/ShaderParser.hh>
namespace ACGL{
namespace OpenGL{
/**
* @brief ShaderParser for processing shader source code
*
* Subclass this and provide alternative ShaderParserFactories to Creators in order to implement custom shader processing
* Override processPragma in order to create custom pragma handling
*/
class ShaderParser
{
ACGL_NOT_COPYABLE(ShaderParser)
public:
ShaderParser( const std::string &_filename );
virtual ~ShaderParser() { }
std::vector< std::string > getSources() const { return mSources; }
std::string getFileNamesPrintable() const;
//! the imported sources don't count the original file!
unsigned int getNumberOfImportedFiles() const { return mSourceFileNames.size()-1; }
//! 0 == original file
std::string getFileName( unsigned int i ) const { return mSourceFileNames[i]; }
protected:
/**
* @brief registers a new SourceFile
* @param _name name of the file (may be an actual filename or a name that indicates automatic generation)
* @return an ID that is to be used in #line statements and getFileName-Index.
*/
int registerSourceFile(std::string const& _name);
/**
* @brief adds a chunk of source to the shader
* @param _source source string
*
* Does not have to correspond to files
*/
void addSource(std::string const& _source);
/**
* @brief constructs a line directive
* @param _lineNumber line number (starts at 1)
* @param _fileID file ID (usually from registerSourceFile)
* @return a string with the format "#line lineNr fileID\n"
*/
std::string lineDirective(int _lineNumber, int _fileID) const;
/**
* @brief processes a custom Pragma directive
* @param _tokens vector of tokens after #pragma (e.g. "#pragma myPragma 5" results in _tokens = { "myPragma", "5" })
*
* Override this function to implement custom pragma handling (no need to call base function)
*
* Recommended behavior:
* - Check if pragma is valid
* - Start with lineNumber 1
* - Register appropriate source file (using registerSourceFile(...))
* - Initialize source content with lineDirective(lineNumber, fileID)
* - Add custom source content
* - Call addSource(...) with your source content
*/
virtual void processPragma(std::vector<std::string> const& /*_tokens*/) { }
/**
* @brief reads in a source file from file system
* @param _filename filename of the source file
*
* Override this function to implement custom file reading functionality
*/
virtual void readin( const std::string &_filename );
private:
bool lineContainsVersion( const std::string &line, unsigned int &version);
bool lineContainsImport( const std::string &line, std::string &filename);
bool lineContainsPragma( const std::string &line, std::vector<std::string> &pragmaToken);
std::vector< std::string > mSources;
std::vector< std::string > mSourceFileNames; // holds at least one filename
unsigned int mMaxVersion;
};
ACGL_SMARTPOINTER_TYPEDEFS(ShaderParser)
class Shader
{
ACGL_NOT_COPYABLE(Shader)
......
/***********************************************************************
* Copyright 2011-2012 Computer Graphics Group RWTH Aachen University. *
* All rights reserved. *
* Distributed under the terms of the MIT License (see LICENSE.TXT). *
**********************************************************************/
#include <ACGL/OpenGL/Creator/ShaderParser.hh>
#include <ACGL/OpenGL/Tools.hh>
#include <ACGL/Utils/StringHelpers.hh>
#include <iostream>
#include <fstream>
#include <algorithm>
using namespace ACGL::Utils;
using namespace ACGL::OpenGL;
ShaderParser::ShaderParser( const std::string &_filename )
{
mMaxVersion = 110;
mSources.push_back( "#version 330\n" );
std::string path, file;
StringHelpers::splitLastFileOrFolder( _filename, path, file );
//readin( "./"+path+"/"+file );
readin( _filename );
if (mMaxVersion > 110) {
mSources[0] = "#version "+StringHelpers::toString(mMaxVersion)+"\n";
}
}
bool ShaderParser::lineContainsVersion( const std::string &line, unsigned int &version)
{
std::vector< std::string > token = StringHelpers::split( line, ' ' );
if (token.size() >= 2 && token[0] == "#version") {
version = StringHelpers::to<unsigned int>(token[1]);
//debug() << "string " << line << " -> version " << version << std::endl;
return true;
}
return false;
}
bool ShaderParser::lineContainsImport( const std::string &line, std::string &filename)
{
std::vector< std::string > token = StringHelpers::split( line, ' ' );
if (token.size() >= 3 && token[0] == "#pragma" && token[1] == "ACGLimport") {
if (token[2].size() >= 2) {
size_t startName = line.find('"',0);
if (startName == std::string::npos) return false;
size_t endName = line.find('"',startName+1);
if (endName == std::string::npos) return false;
filename = line.substr(startName+1, endName-startName-1); // remove the ""
//debug() << "string " << line << " -> import " << filename << std::endl;
return true;
}
}
return false;
}
bool ShaderParser::lineContainsPragma(const std::string &line, std::vector<std::string> &pragmaToken)
{
std::vector< std::string > token = StringHelpers::split( line, ' ' );
if (token.size() >= 2 && token[0] == "#pragma")
{
pragmaToken.clear();
pragmaToken.insert(pragmaToken.begin(), token.begin() + 1, token.end());
return true;
}
return false;
}
std::string ShaderParser::getFileNamesPrintable() const
{
std::string rString;
for (size_t i = 0; i < mSourceFileNames.size(); ++i) {
rString += " " + StringHelpers::toString<unsigned int>(i+1) + " " + mSourceFileNames[i] + "\n";
}
return rString;
}
bool ShaderParser::existsSourcefile(const std::string &_filename)
{
return std::find(mSourceFileNames.begin(), mSourceFileNames.end(), _filename) != mSourceFileNames.end();
}
int ShaderParser::registerSourceFile(const std::string &_name)
{
mSourceFileNames.push_back(_name);
return mSourceFileNames.size();
}
void ShaderParser::addSource(const std::string &_source)
{
mSources.push_back(_source);
}
std::string ShaderParser::lineDirective(int _lineNumber, int _fileID) const
{
return "#line "+StringHelpers::toString(_lineNumber)+" "+StringHelpers::toString(_fileID)+"\n";
}
bool ShaderParser::processPragma(std::vector<std::string> const&, std::string const&)
{
return false;
}
void ShaderParser::readin( const std::string &_filename )
{
std::string line = "";
std::ifstream fileStream(_filename.c_str(), std::ifstream::in);
unsigned int lineNumber = 1;
unsigned int fileNumber = registerSourceFile(_filename);
// define the file and line number to get correct errors from the shader compiler:
std::string fileContent = lineDirective(lineNumber, fileNumber);
//debug() << "parse file " << _filename << std::endl;
if(fileStream.is_open())
{
while (fileStream.good())
{
std::getline(fileStream,line);
unsigned int version;
std::vector<std::string> pragmaToken;
if ( lineContainsVersion(line, version) )
{
mMaxVersion = std::max( version, mMaxVersion );
fileContent += "\n"; // remove the #version but keep a newline
lineNumber++;
}
else if ( lineContainsPragma(line, pragmaToken) )
{
// push what we have till now:
addSource( fileContent );
// let subclasses process their own pragmas
if ( !processPragma(pragmaToken, _filename) )
addSource(line + "\n"); // pragma not accepted? print it instead
// continue with a fresh chunk of source
lineNumber++;
fileContent = lineDirective(lineNumber, fileNumber);
}
else
{
fileContent += line + "\n";
lineNumber++;
}
}
fileStream.close();
}
else
{
error() << "Failed to open file: " << _filename << std::endl;
return;
}
//debug() << "file: " << fileContent << std::endl;
addSource( fileContent );
}
IncludingShaderParser::IncludingShaderParser(const std::string &_filename) :
ShaderParser(_filename)
{
}
bool IncludingShaderParser::processPragma(const std::vector<std::string> &_tokens, const std::string &_filename)
{
if ( _tokens.size() == 2 && _tokens[0] == "ACGLimport" )
{
std::string fileToImport(_tokens[1]);
// strip '"' or '<' also allows mix of them, but not recommended
// currently no difference in behavior
size_t startName = fileToImport.find('"',0);
if (startName == std::string::npos) startName = fileToImport.find('<',0);
if (startName == std::string::npos) return false;
size_t endName = fileToImport.find('"',startName+1);
if (endName == std::string::npos) endName = fileToImport.find('>',startName+1);
if (endName == std::string::npos) return false;
fileToImport = fileToImport.substr(startName+1, endName-startName-1); // remove the ""
// handle import
if (fileToImport[0] != '/')
{
// it's a relative path:
// remove "./" in case this was given explicitly:
if(fileToImport.size() > 2 && fileToImport[0] == '.' && fileToImport[1] == '/')
fileToImport = fileToImport.substr(2, fileToImport.size()-2);
std::string path, file;
StringHelpers::splitLastFileOrFolder( _filename, path, file );
fileToImport = path+"/"+fileToImport;
}
// "#pragma once"-behavior
if ( !existsSourcefile(fileToImport) )
readin( fileToImport ); // actual include
// success
return true;
}
else return ShaderParser::processPragma(_tokens, _filename);
}
......@@ -13,7 +13,8 @@ using namespace ACGL::Utils;
using namespace ACGL::Base;
using namespace ACGL::OpenGL;
SharedShaderParser ShaderParserFactory::createParser(const std::string &_filename)
{
return std::make_shared<ShaderParser>(_filename);
namespace ACGL{
namespace OpenGL{
}
}
......@@ -83,7 +83,7 @@ SharedShaderProgram ShaderProgramCreator::create()
// attach shader
if ( mShaderType[i] != GL_INVALID_VALUE ) {
ConstSharedShader shader = ShaderFileManager::the()->get(ShaderCreator( mFileNames[i] ).type( mShaderType[i] ).shaderParserFacoty(mShaderParserFactory) );
ConstSharedShader shader = ShaderFileManager::the()->get(ShaderCreator( mFileNames[i] ).type( mShaderType[i] ).shaderParserFactory(mShaderParserFactory) );
if ( shader ) {
shaderProgram->attachShader( shader );
} else {
......
......@@ -169,185 +169,3 @@ void Shader::getCompileLog( std::string &_log, bool &_wasErrorLog ) const
_log = "";
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ShaderParser::ShaderParser( const std::string &_filename )
{
mMaxVersion = 110;
mSources.push_back( "#version 330\n" );
std::string path, file;
StringHelpers::splitLastFileOrFolder( _filename, path, file );
//readin( "./"+path+"/"+file );
readin( _filename );
if (mMaxVersion > 110) {
mSources[0] = "#version "+StringHelpers::toString(mMaxVersion)+"\n";
}
}
bool ShaderParser::lineContainsVersion( const std::string &line, unsigned int &version)
{
std::vector< std::string > token = StringHelpers::split( line, ' ' );
if (token.size() >= 2 && token[0] == "#version") {
version = StringHelpers::to<unsigned int>(token[1]);
//debug() << "string " << line << " -> version " << version << std::endl;
return true;
}
return false;
}
bool ShaderParser::lineContainsImport( const std::string &line, std::string &filename)