Commit 6bbee087 authored by Sebastian Eichelbaum's avatar Sebastian Eichelbaum

[ADD] - added a more generic scheme for preprocessing shader codes. It is now...

[ADD] - added a more generic scheme for preprocessing shader codes. It is now more comfortable to handle define-statements or various compile-time options in shaders.
parent 1e018595
......@@ -25,6 +25,7 @@
#ifndef WCONDITION_H
#define WCONDITION_H
#include <boost/shared_ptr.hpp>
#include <boost/function.hpp>
#include <boost/signals2/signal.hpp>
#include <boost/thread.hpp>
......@@ -39,6 +40,15 @@ class OWCOMMON_EXPORT WCondition // NOLINT
{
friend class WCondition_test;
public:
/**
* Shared pointer type for WCondition.
*/
typedef boost::shared_ptr< WCondition > SPtr;
/**
* Const shared pointer type for WCondition.
*/
typedef boost::shared_ptr< const WCondition > ConstSPtr;
/**
* Default constructor.
......
......@@ -65,7 +65,8 @@ osg::ref_ptr< WGEOffscreenTexturePass > WGEOffscreenRenderNode::addTextureProces
return pass;
}
osg::ref_ptr< WGEOffscreenRenderPass > WGEOffscreenRenderNode::addGeometryRenderPass( osg::ref_ptr< osg::Node > node, osg::ref_ptr< WGEShader > shader,
osg::ref_ptr< WGEOffscreenRenderPass > WGEOffscreenRenderNode::addGeometryRenderPass( osg::ref_ptr< osg::Node > node,
osg::ref_ptr< WGEShader > shader,
std::string name )
{
// create a plain render pass and add some geometry
......
......@@ -25,6 +25,7 @@
#include <map>
#include <string>
#include <sstream>
#include <ostream>
#include <boost/algorithm/string.hpp>
#include <boost/filesystem.hpp>
......@@ -38,8 +39,11 @@
#include <osg/Node>
#include "WGraphicsEngine.h"
#include "WGEShaderPreprocessor.h"
#include "WGEShaderVersionPreprocessor.h"
#include "../common/WLogger.h"
#include "../common/WPathHelper.h"
#include "../common/WPredicateHelper.h"
#include "WGEShader.h"
......@@ -61,6 +65,9 @@ WGEShader::WGEShader( std::string name, boost::filesystem::path search ):
addShader( m_fragmentShader );
addShader( m_geometryShader );
// this preprocessor is always needed. It removes the #version statement from the code and puts it to the beginning.
m_versionPreprocessor = WGEShaderPreprocessor::SPtr( new WGEShaderVersionPreprocessor() );
m_reloadSignalConnection = WGraphicsEngine::getGraphicsEngine()->subscribeSignal( GE_RELOADSHADERS, boost::bind( &WGEShader::reload, this ) );
}
......@@ -192,20 +199,15 @@ void WGEShader::SafeUpdaterCallback::operator()( osg::Node* node, osg::NodeVisit
traverse( node, nv );
}
std::string WGEShader::processShader( const std::string filename, bool optional, int level )
std::string WGEShader::processShaderRecursive( const std::string filename, bool optional, int level )
{
std::stringstream output; // processed output
if ( level == 0 )
{
// for the shader (not the included one, for which level != 0)
// apply defines
for ( std::map< std::string, std::string >::const_iterator mi = m_defines.begin(); mi != m_defines.end(); ++mi )
{
output << "#define " << mi->first << " " << mi->second << std::endl;
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Before the preprocessors get applied, the following code build the complete shader code from many parts (includes) and handles the version
// statement automatically. This is important since the GLSL compiler (especially ATI's) relies on it. After completely loading the whole
// code, the preprocessors get applied.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// we encountered an endless loop
if ( level > 32 )
......@@ -221,9 +223,7 @@ std::string WGEShader::processShader( const std::string filename, bool optional,
}
// this is the proper regular expression for includes. This also excludes commented includes
static const boost::regex re( "^[ ]*#[ ]*include[ ]+[\"<](.*)[\">].*" );
// this is an expression for the #version statement
static const boost::regex ver( "^[ ]*#[ ]*version[ ]+[123456789][0123456789]+.*$" );
static const boost::regex includeRegexp( "^[ ]*#[ ]*include[ ]+[\"<](.*)[\">].*" );
// the input stream, first check existence of shader
// search these places in this order:
......@@ -291,65 +291,100 @@ std::string WGEShader::processShader( const std::string filename, bool optional,
// go through each line and process includes
std::string line; // the current line
boost::smatch matches; // the list of matches
std::string versionLine; // the version
bool foundVersion = false;
while ( std::getline( input, line ) )
{
if ( boost::regex_search( line, matches, re ) )
{
output << processShader( matches[1], false, level + 1 );
}
else if( boost::regex_match( line, ver ) ) // look for the #version statement
if ( boost::regex_search( line, matches, includeRegexp ) )
{
// there already was a version statement in this file
// this does not track multiple version statements through included files
if( foundVersion )
{
WLogger::getLogger()->addLogMessage( "Multiple version statements in shader file \"" + fn + "\".",
"WGEShader (" + filename + ")", LL_ERROR
);
return "";
}
versionLine = line;
foundVersion = true;
output << processShaderRecursive( matches[1], false, level + 1 );
}
else
{
output << line;
}
// NOTE: we do not apply the m_processors here since the recursive processShaders may have produced many lines. We would need to loop
// through each one of them. This is done later on for the whole code.
output << std::endl;
}
input.close();
// no version statement found
if( !foundVersion )
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Done. Return code.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// this string contains the processed shader code
return output.str();
}
std::string WGEShader::processShader( const std::string filename, bool optional )
{
// load all the code
std::string code = processShaderRecursive( filename, optional );
if ( code.empty() )
{
return "";
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// The whole code is loaded now. Apply preprocessors.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// apply all preprocessors
PreprocessorsList::ReadTicket r = m_preprocessors.getReadTicket();
for ( PreprocessorsList::ConstIterator pp = r->get().begin(); pp != r->get().end(); ++pp )
{
return output.str();
code = ( *pp ).first->process( filename, code );
}
r.reset();
// the ATI compiler needs the version statement to be the first statement in the shader
std::stringstream vs;
vs << versionLine.c_str() << std::endl << output.str();
return vs.str();
// finally ensure ONE #version at the beginning.
return m_versionPreprocessor->process( filename, code );
}
void WGEShader::eraseDefine( std::string key )
void WGEShader::addPreprocessor( WGEShaderPreprocessor::SPtr preproc )
{
m_defines.erase( key );
m_reload = true;
PreprocessorsList::WriteTicket w = m_preprocessors.getWriteTicket();
if ( !w->get().count( preproc ) ) // if already exists, no connection needed
{
// subscribe the preprocessors update condition
boost::signals2::connection con = preproc->getChangeCondition()->subscribeSignal( boost::bind( &WGEShader::reload, this ) );
w->get().insert( std::make_pair( preproc, con ) );
}
w.reset();
reload();
}
void WGEShader::eraseAllDefines()
void WGEShader::removePreprocessor( WGEShaderPreprocessor::SPtr preproc )
{
m_defines.clear();
m_reload = true;
PreprocessorsList::WriteTicket w = m_preprocessors.getWriteTicket();
if ( w->get().count( preproc ) ) // is it in out list?
{
w->get().operator[]( preproc ).disconnect();
w->get().erase( preproc );
}
w.reset();
reload();
}
void WGEShader::clearPreprocessors()
{
PreprocessorsList::WriteTicket w = m_preprocessors.getWriteTicket();
// we need to disconnect each signal subscription
for ( PreprocessorsList::Iterator pp = w->get().begin(); pp != w->get().end(); ++pp )
{
( *pp ).second.disconnect();
}
w->get().clear();
w.reset();
reload();
}
void WGEShader::setDefine( std::string key )
WGEShaderDefineSwitch::SPtr WGEShader::setDefine( std::string key )
{
this->setDefine( key, "Defined" );
return this->setDefine< bool >( key, true );
}
......@@ -26,6 +26,7 @@
#define WGESHADER_H
#include <map>
#include <list>
#include <string>
#include <boost/filesystem.hpp>
......@@ -35,6 +36,9 @@
#include <osg/Program>
#include "../common/WPathHelper.h"
#include "../common/WSharedAssociativeContainer.h"
#include "WGEShaderPreprocessor.h"
#include "WGEShaderDefine.h"
#include "WExportWGE.h"
/**
......@@ -94,7 +98,7 @@ public:
* \param value The value of the define. If this is not specified, the define can be used as simple ifdef switch.
*/
template < typename T >
void setDefine( std::string key, T value );
typename WGEShaderDefine< T >::SPtr setDefine( std::string key, T value );
/**
* Sets a define which is include into the shader source code. This allows the preprocessor to turn on/off several parts of your code. In GLSL
......@@ -102,25 +106,32 @@ public:
*
* \param key The name of the define
*/
void setDefine( std::string key );
WGEShaderDefineSwitch::SPtr setDefine( std::string key );
/**
* Deletes a define from the internal list
* Adds the specified preprocessor to this shader. The preprocessor is able to force shader reloads.
*
* \param key The name of the define
* \param preproc the preprocessor to add.
*/
void eraseDefine( std::string key );
void addPreprocessor( WGEShaderPreprocessor::SPtr preproc );
/**
* Removes all existing defines.
* Removes the specified preprocessor. Changes inside the preprocessor won't cause any updates anymore.
*
* \param preproc the preprocessor to remove. If not exists: nothing is done.
*/
void eraseAllDefines();
void removePreprocessor( WGEShaderPreprocessor::SPtr preproc );
/**
* Removes all preprocessors. Be careful with this one since it removes the WGESHaderVersionPreprocessor too, which is mandatory.
*/
void clearPreprocessors();
protected:
/**
* This method searches and processes all includes in the shader source. The filenames in the include statement are assumed to
* be relative to this shader's path.
* be relative to this shader's path. It simply unrolls the code.
*
* \param filename the filename of the shader to process.
* \param optional denotes whether a "file not found" is critical or not
......@@ -128,7 +139,20 @@ protected:
*
* \return the processed source.
*/
std::string processShader( const std::string filename, bool optional = false, int level = 0 );
std::string processShaderRecursive( const std::string filename, bool optional = false, int level = 0 );
/**
* This method searches and processes all includes in the shader source. The filenames in the include statement are assumed to
* be relative to this shader's path. It additionally applies preprocessors.
*
* \see processShaderRecursive
*
* \param filename the filename of the shader to process.
* \param optional denotes whether a "file not found" is critical or not
*
* \return the processed source.
*/
std::string processShader( const std::string filename, bool optional = false );
/**
* This completely reloads the shader file and processes it. It also resets m_reload to false.
......@@ -171,9 +195,19 @@ protected:
boost::signals2::connection m_reloadSignalConnection;
/**
* a map of all set defines
* The list of preprocessors - Type
*/
typedef WSharedAssociativeContainer< std::map< WGEShaderPreprocessor::SPtr, boost::signals2::connection > > PreprocessorsList;
/**
* List of all pre-processing that need to be applied to this shader instance
*/
PreprocessorsList m_preprocessors;
/**
* This preprocessor needs to be run LAST. It handles version-statements in GLSL.
*/
std::map< std::string, std::string > m_defines;
WGEShaderPreprocessor::SPtr m_versionPreprocessor;
/**
* the vertex shader object
......@@ -226,13 +260,31 @@ private:
};
template < typename T >
void WGEShader::setDefine( std::string key, T value )
typename WGEShaderDefine< T >::SPtr WGEShader::setDefine( std::string key, T value )
{
if ( key.length() > 0 )
typename WGEShaderDefine< T >::SPtr def;
// try to find the define. If it exists, set it. If not, add it.
PreprocessorsList::ReadTicket r = m_preprocessors.getReadTicket();
for ( PreprocessorsList::ConstIterator pp = r->get().begin(); pp != r->get().end(); ++pp )
{
typename WGEShaderDefine< T >::SPtr define = boost::shared_dynamic_cast< WGEShaderDefine< T > >( ( *pp ).first );
if ( define && ( define->getName() == key ) )
{
define->setValue( value );
def = define;
break;
}
}
r.reset();
// did not find it. Add.
if ( !def )
{
m_defines[key] = boost::lexical_cast< std::string >( value );
m_reload = true;
def = typename WGEShaderDefine< T >::SPtr( new WGEShaderDefine< T >( key, value ) );
addPreprocessor( def );
}
return def;
}
#endif // WGESHADER_H
......
//---------------------------------------------------------------------------
//
// Project: OpenWalnut ( http://www.openwalnut.org )
//
// Copyright 2009 OpenWalnut Community, BSV@Uni-Leipzig and CNCF@MPI-CBS
// For more information see http://www.openwalnut.org/copying
//
// This file is part of OpenWalnut.
//
// OpenWalnut is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// OpenWalnut is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with OpenWalnut. If not, see <http://www.gnu.org/licenses/>.
//
//---------------------------------------------------------------------------
#include "WGEShaderDefine.h"
//---------------------------------------------------------------------------
//
// Project: OpenWalnut ( http://www.openwalnut.org )
//
// Copyright 2009 OpenWalnut Community, BSV@Uni-Leipzig and CNCF@MPI-CBS
// For more information see http://www.openwalnut.org/copying
//
// This file is part of OpenWalnut.
//
// OpenWalnut is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// OpenWalnut is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with OpenWalnut. If not, see <http://www.gnu.org/licenses/>.
//
//---------------------------------------------------------------------------
#ifndef WGESHADERDEFINE_H
#define WGESHADERDEFINE_H
#include <string>
#include <boost/shared_ptr.hpp>
#include <boost/lexical_cast.hpp>
#include "../common/WPropertyBase.h"
#include "WGEShaderPreprocessor.h"
#include "WExportWGE.h"
/**
* This class is able to provide arbitrary values as define statements in GLSL code.
*/
template< typename ValueType = bool >
class WGE_EXPORT WGEShaderDefine: public WGEShaderPreprocessor
{
public:
/**
* Shared pointer for this class.
*/
typedef boost::shared_ptr< WGEShaderDefine< ValueType > > SPtr;
/**
* A const shared pointer for this class.
*/
typedef boost::shared_ptr< const WGEShaderDefine< ValueType > > ConstSPtr;
/**
* Constructs a define with a given name and initial value.
*
* \param name name of the define
* \param value the initial value.
*/
WGEShaderDefine( std::string name, ValueType value = ValueType( 0 ) );
/**
* Destructor.
*/
virtual ~WGEShaderDefine();
/**
* Process the whole code. It is not allowed to modify some internal state in this function because it might be called by several shaders.
*
* \param code the code to process
* \param file the filename of the shader currently processed. Should be used for debugging output.
*
* \return the resulting new code
*/
virtual std::string process( const std::string& file, const std::string& code ) const;
/**
* Returns the name of the define.
*
* \return the name
*/
std::string getName() const;
/**
* Returns the current value.
*
* \return the current value
*/
const ValueType& getValue() const;
/**
* Sets the new value for this define. Causes an reload of all associated shaders.
*
* \param value the new value.
*/
void setValue( const ValueType& value );
protected:
private:
/**
* The name of the define.
*/
std::string m_name;
/**
* The value of the define as a string.
*/
ValueType m_value;
};
template< typename ValueType >
WGEShaderDefine< ValueType >::WGEShaderDefine( std::string name, ValueType value ):
WGEShaderPreprocessor(),
m_name( name ),
m_value( value )
{
// initialize
}
template< typename ValueType >
WGEShaderDefine< ValueType >::~WGEShaderDefine()
{
// cleanup
}
template< typename ValueType >
std::string WGEShaderDefine< ValueType >::process( const std::string& file, const std::string& code ) const
{
if ( !getActive() )
{
return code;
}
return "#define " + getName() + " " + boost::lexical_cast< std::string >( getValue() ) + "\n" + code;
}
template< typename ValueType >
std::string WGEShaderDefine< ValueType >::getName() const
{
return m_name;
}
template< typename ValueType >
const ValueType& WGEShaderDefine< ValueType >::getValue() const
{
return m_value;
}
template< typename ValueType >
void WGEShaderDefine< ValueType >::setValue( const ValueType& value )
{
m_value = value;
updated();
}
///////////////////////////////////////////////////////////////////////////////
// Some typedefs
///////////////////////////////////////////////////////////////////////////////
typedef WGEShaderDefine< bool > WGEShaderDefineSwitch;
#endif // WGESHADERDEFINE_H
//---------------------------------------------------------------------------
//
// Project: OpenWalnut ( http://www.openwalnut.org )
//
// Copyright 2009 OpenWalnut Community, BSV@Uni-Leipzig and CNCF@MPI-CBS
// For more information see http://www.openwalnut.org/copying
//
// This file is part of OpenWalnut.
//
// OpenWalnut is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// OpenWalnut is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with OpenWalnut. If not, see <http://www.gnu.org/licenses/>.
//
//---------------------------------------------------------------------------
#include <stdarg.h>
#include <algorithm>
#include "../common/exceptions/WPreconditionNotMet.h"
#include "WGEShaderDefineOptions.h"
WGEShaderDefineOptions::WGEShaderDefineOptions( std::string first ):
WGEShaderPreprocessor(),
m_options( 1, first ),
m_idx( 0 )
{
// init
}
WGEShaderDefineOptions::~WGEShaderDefineOptions()
{
// cleanup
}
std::string WGEShaderDefineOptions::process( const std::string& /*file*/, const std::string& code ) const
{
if ( !getActive() )
{
return code;
}
return "#define " + getActiveOptionName() + "\n" + code;
}
size_t WGEShaderDefineOptions::getActiveOption() const
{
return m_idx;
}
std::string WGEShaderDefineOptions::getActiveOptionName() const
{
return m_options[ m_idx ];
}
void WGEShaderDefineOptions::activateOption( size_t idx )
{