OpenWalnut  1.5.0dev
WGEShader.cpp
1 //---------------------------------------------------------------------------
2 //
3 // Project: OpenWalnut ( http://www.openwalnut.org )
4 //
5 // Copyright 2009 OpenWalnut Community, BSV@Uni-Leipzig and CNCF@MPI-CBS
6 // For more information see http://www.openwalnut.org/copying
7 //
8 // This file is part of OpenWalnut.
9 //
10 // OpenWalnut is free software: you can redistribute it and/or modify
11 // it under the terms of the GNU Lesser General Public License as published by
12 // the Free Software Foundation, either version 3 of the License, or
13 // (at your option) any later version.
14 //
15 // OpenWalnut is distributed in the hope that it will be useful,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU Lesser General Public License for more details.
19 //
20 // You should have received a copy of the GNU Lesser General Public License
21 // along with OpenWalnut. If not, see <http://www.gnu.org/licenses/>.
22 //
23 //---------------------------------------------------------------------------
24 
25 #include <map>
26 #include <ostream>
27 #include <sstream>
28 #include <string>
29 #include <fstream>
30 
31 #include <boost/algorithm/string.hpp>
32 #include <boost/filesystem.hpp>
33 #include <boost/function.hpp>
34 #include <boost/regex.hpp>
35 #include <boost/signals2/signal.hpp>
36 #include <boost/tokenizer.hpp>
37 
38 #include <osg/Node>
39 #include <osg/StateSet>
40 
41 #include "../../common/WLogger.h"
42 #include "../../common/WPathHelper.h"
43 #include "../../common/WPredicateHelper.h"
44 #include "../WGraphicsEngine.h"
45 #include "WGEShader.h"
46 #include "WGEShaderPreprocessor.h"
47 #include "WGEShaderVersionPreprocessor.h"
48 
49 WGEShader::WGEShader( std::string name, boost::filesystem::path search ):
50  osg::Program(),
51  m_shaderPath( search ),
52  m_name( name ),
53  m_reload( true ),
54  m_shaderLoaded( false ),
55  m_deactivated( false )
56 {
57  // create shader
58  m_vertexShader = osg::ref_ptr< osg::Shader >( new osg::Shader( osg::Shader::VERTEX ) );
59  m_fragmentShader = osg::ref_ptr< osg::Shader >( new osg::Shader( osg::Shader::FRAGMENT ) );
60  m_geometryShader = osg::ref_ptr< osg::Shader >( new osg::Shader( osg::Shader::GEOMETRY ) );
61 
62  // add them
63  addShader( m_vertexShader );
64  addShader( m_fragmentShader );
65  addShader( m_geometryShader );
66 
67  // this preprocessor is always needed. It removes the #version statement from the code and puts it to the beginning.
69 
70  m_reloadSignalConnection = WGraphicsEngine::getGraphicsEngine()->subscribeSignal( GE_RELOADSHADERS, boost::bind( &WGEShader::reload, this ) );
71 }
72 
74 {
75  // cleanup
76  m_reloadSignalConnection.disconnect();
77 }
78 
79 void WGEShader::apply( osg::ref_ptr< osg::Node > node )
80 {
81  // set the shader attribute
82  // NOTE: the attribute is protected to avoid father nodes overwriting it
83  osg::StateSet* rootState = node->getOrCreateStateSet();
84  rootState->setAttributeAndModes( this, osg::StateAttribute::ON | osg::StateAttribute::PROTECTED );
85  m_deactivated = false;
87 
88  // add a custom callback which actually sets and updated the shader.
89  node->addUpdateCallback( osg::ref_ptr< SafeUpdaterCallback >( new SafeUpdaterCallback( this ) ) );
90 }
91 
92 void WGEShader::applyDirect( osg::State& state ) // NOLINT <- ensure this matches the official OSG API by using a non-const ref
93 {
95  osg::Program::apply( state );
96 }
97 
98 void WGEShader::deactivate( osg::ref_ptr< osg::Node > node )
99 {
100  if( !node )
101  {
102  return;
103  }
104 
105  // set the shader attribute
106  // NOTE: the attribute is protected to avoid father nodes overwriting it
107  osg::StateSet* rootState = node->getOrCreateStateSet();
108  rootState->setAttributeAndModes( this, osg::StateAttribute::ON | osg::StateAttribute::PROTECTED );
109 
110  m_deactivated = true;
111  m_shaderLoaded = false;
112 
113  // add a custom callback which actually sets and updated the shader.
114  node->addUpdateCallback( osg::ref_ptr< SafeUpdaterCallback >( new SafeUpdaterCallback( this ) ) );
115 }
116 
118 {
119  m_reload = true;
120 }
121 
123 {
124  try
125  {
126  // remove the shaders
127  removeShader( m_vertexShader );
128  removeShader( m_fragmentShader );
129  removeShader( m_geometryShader );
130 
131  // reload the sources and set the shader
132  // vertex shader
133  WLogger::getLogger()->addLogMessage( "Reloading vertex shader \"" + m_name + "-vertex.glsl\"", "WGEShader", LL_DEBUG );
134  std::string source = processShader( m_name + "-vertex.glsl" );
135  if( source != "" )
136  {
137  m_vertexShader->setShaderSource( source );
138  addShader( m_vertexShader );
139  }
140 
141  // fragment shader
142  WLogger::getLogger()->addLogMessage( "Reloading fragment shader \"" + m_name + "-fragment.glsl\"", "WGEShader", LL_DEBUG );
143  source = processShader( m_name + "-fragment.glsl" );
144  if( source != "" )
145  {
146  m_fragmentShader->setShaderSource( source );
147  addShader( m_fragmentShader );
148  }
149 
150  // Geometry Shader
151  WLogger::getLogger()->addLogMessage( "Reloading geometry shader \"" + m_name + "-geometry.glsl\"", "WGEShader", LL_DEBUG );
152  source = processShader( m_name + "-geometry.glsl", true );
153  if( source != "" )
154  {
155  m_geometryShader->setShaderSource( source );
156  addShader( m_geometryShader );
157  }
158 
159  m_shaderLoaded = true;
160  }
161  catch( const std::exception& e )
162  {
163  m_shaderLoaded = false;
164 
165  WLogger::getLogger()->addLogMessage( "Problem loading shader.", "WGEShader", LL_ERROR );
166 
167  // clean up the mess
168  removeShader( m_vertexShader );
169  removeShader( m_fragmentShader );
170  removeShader( m_geometryShader );
171  }
172 
173  // everything done now.
174  m_reload = false;
175 }
176 
178 {
179  // is it needed to do something here?
180  if( m_deactivated )
181  {
182  // remove the shaders
183  removeShader( m_vertexShader );
184  removeShader( m_fragmentShader );
185  removeShader( m_geometryShader );
186  }
187  else if( m_reload )
188  {
189  reloadShader();
190  }
191 }
192 
194  m_shader( shader )
195 {
196 }
197 
198 void WGEShader::SafeUpdaterCallback::operator()( osg::Node* node, osg::NodeVisitor* nv )
199 {
200  m_shader->updatePrograms();
201 
202  // forward the call
203  traverse( node, nv );
204 }
205 
206 std::string WGEShader::processShaderRecursive( const std::string filename, bool optional, int level )
207 {
208  std::stringstream output; // processed output
209 
210  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
211  // Before the preprocessors get applied, the following code build the complete shader code from many parts (includes) and handles the version
212  // statement automatically. This is important since the GLSL compiler (especially ATI's) relies on it. After completely loading the whole
213  // code, the preprocessors get applied.
214  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
215 
216  // we encountered an endless loop
217  if( level > 32 )
218  {
219  // reached a certain level. This normally denotes a inclusion cycle.
220  // We do not throw an exception here to avoid OSG to crash.
221  WLogger::getLogger()->addLogMessage( "Inclusion depth is too large. Maybe there is a inclusion cycle in the shader code.",
222  "WGEShader (" + filename + ")", LL_ERROR
223  );
224 
225  // just return unprocessed source
226  return "";
227  }
228 
229  // this is the proper regular expression for includes. This also excludes commented includes
230  static const boost::regex includeRegexp( "^[ ]*#[ ]*include[ ]+[\"<](.*)[\">].*" );
231 
232  // the input stream, first check existence of shader
233  // search these places in this order:
234  // 1. m_shaderPath
235  // 2. m_shaderPath / shaders
236  // 3. WPathHelper::getShaderPath()
237 
238  // use one of the following paths
239  std::string fn = filename;
240  std::string fnLocal = ( m_shaderPath / filename ).string();
241  std::string fnLocalShaders = ( m_shaderPath / "shaders" / filename ).string();
242  std::string fnGlobal = ( WPathHelper::getShaderPath() / filename ).string();
243 
244  if( boost::filesystem::exists( m_shaderPath / filename ) )
245  {
246  fn = fnLocal;
247  }
248  else if( boost::filesystem::exists( m_shaderPath / "shaders" / filename ) )
249  {
250  fn = fnLocalShaders;
251  }
252  else if( boost::filesystem::exists( WPathHelper::getShaderPath() / filename ) )
253  {
254  fn = fnGlobal;
255  }
256  else if( !optional )
257  {
258  WLogger::getLogger()->addLogMessage( "The requested shader \"" + filename + "\" does not exist in \"" +
259  m_shaderPath.string() + "\", \"" + ( m_shaderPath / "shaders" ).string() + "\" or \"" +
260  WPathHelper::getShaderPath().string() + "\".", "WGEShader (" + filename + ")", LL_ERROR
261  );
262 
263  return "";
264  }
265  else
266  {
267  return "";
268  }
269 
270  std::ifstream input( fn.c_str() );
271  if( !input.is_open() )
272  {
273  if( optional )
274  {
275  return "";
276  }
277 
278  // file does not exist. Do not throw an exception to avoid OSG crash
279  if( level == 0 )
280  {
281  WLogger::getLogger()->addLogMessage( "Can't open shader file \"" + filename + "\".",
282  "WGEShader (" + filename + ")", LL_ERROR
283  );
284  }
285  else
286  {
287  WLogger::getLogger()->addLogMessage( "Can't open shader file for inclusion \"" + filename + "\".",
288  "WGEShader (" + filename + ")", LL_ERROR
289  );
290  }
291 
292  return "";
293  }
294 
295  // go through each line and process includes
296  std::string line; // the current line
297  boost::smatch matches; // the list of matches
298  std::size_t lineNo = 0; // keep track of line numbers
299 
300  output << "#line 1 ";
301 
302  while( std::getline( input, line ) )
303  {
304  lineNo++;
305  if( boost::regex_search( line, matches, includeRegexp ) )
306  {
307  // a new file begins. Use GLSL line directive to ensure proper numbering
308  output << "#line 1" << std::endl;
309  output << processShaderRecursive( matches[1], false, level + 1 ) << std::endl;
310  // reset numbering in source file of the previous include
311  output << "#line " << lineNo << std::endl;
312  }
313  else
314  {
315  output << line;
316  }
317 
318  // NOTE: we do not apply the m_processors here since the recursive processShaders may have produced many lines. We would need to loop
319  // through each one of them. This is done later on for the whole code.
320 
321  output << std::endl;
322  }
323 
324  input.close();
325 
326  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
327  // Done. Return code.
328  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
329 
330  // this string contains the processed shader code
331  return output.str();
332 }
333 
334 std::string WGEShader::processShader( const std::string filename, bool optional )
335 {
336  // load all the code
337  std::string code = processShaderRecursive( filename, optional );
338  if( code.empty() )
339  {
340  return "";
341  }
342 
343  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
344  // The whole code is loaded now. Apply preprocessors.
345  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
346 
347  // apply all preprocessors
349  for( PreprocessorsList::ConstIterator pp = r->get().begin(); pp != r->get().end(); ++pp )
350  {
351  code = ( *pp ).first->process( filename, code );
352  }
353  r.reset();
354 
355  // finally ensure ONE #version at the beginning.
356  return m_versionPreprocessor->process( filename, code );
357 }
358 
360 {
362  if( !w->get().count( preproc ) ) // if already exists, no connection needed
363  {
364  // subscribe the preprocessors update condition
365  boost::signals2::connection con = preproc->getChangeCondition()->subscribeSignal( boost::bind( &WGEShader::reload, this ) );
366  w->get().insert( std::make_pair( preproc, con ) );
367  }
368  w.reset();
369  reload();
370 }
371 
373 {
375  if( w->get().count( preproc ) ) // is it in our list?
376  {
377  w->get().operator[]( preproc ).disconnect();
378  w->get().erase( preproc );
379  }
380  w.reset();
381  reload();
382 }
383 
385 {
387 
388  // we need to disconnect each signal subscription
389  for( PreprocessorsList::Iterator pp = w->get().begin(); pp != w->get().end(); ++pp )
390  {
391  ( *pp ).second.disconnect();
392  }
393  w->get().clear();
394  w.reset();
395  reload();
396 }
397 
399 {
400  return this->setDefine< bool >( key, true );
401 }
402 
std::shared_ptr< WGEShaderDefine< ValueType > > SPtr
Shared pointer for this class.
std::shared_ptr< WGEShaderPreprocessor > SPtr
Shared pointer for this class.
This preprocessor removes the version-statement from the code and puts it to the beginning of the cod...
Update callback which handles the shader reloading.
Definition: WGEShader.h:247
SafeUpdaterCallback(WGEShader *shader)
Constructor.
Definition: WGEShader.cpp:193
virtual void operator()(osg::Node *node, osg::NodeVisitor *nv)
Callback method called by the NodeVisitor when visiting a node.
Definition: WGEShader.cpp:198
Class encapsulating the OSG Program class for a more convenient way of adding and modifying shader.
Definition: WGEShader.h:48
bool m_shaderLoaded
True if the shaders have been loaded successfully previously.
Definition: WGEShader.h:200
osg::ref_ptr< osg::Shader > m_vertexShader
the vertex shader object
Definition: WGEShader.h:230
virtual void deactivate(osg::ref_ptr< osg::Node > node)
Removes the shader from the specified node.
Definition: WGEShader.cpp:98
std::string processShaderRecursive(const std::string filename, bool optional=false, int level=0)
This method searches and processes all includes in the shader source.
Definition: WGEShader.cpp:206
WGEShaderDefine< T >::SPtr setDefine(std::string key, T value)
Sets a define which is include into the shader source code.
Definition: WGEShader.h:277
virtual void reload()
Initiate a reload of the shader during the next update cycle.
Definition: WGEShader.cpp:117
void updatePrograms()
Handles all state changes in m_reload and m_deactivated.
Definition: WGEShader.cpp:177
osg::ref_ptr< osg::Shader > m_fragmentShader
the fragment shader object
Definition: WGEShader.h:235
virtual void applyDirect(osg::State &state)
If enabled, activate our program in the GL pipeline, performing any rebuild operations that might be ...
Definition: WGEShader.cpp:92
WGEShaderPreprocessor::SPtr m_versionPreprocessor
This preprocessor needs to be run LAST.
Definition: WGEShader.h:225
void removePreprocessor(WGEShaderPreprocessor::SPtr preproc)
Removes the specified preprocessor.
Definition: WGEShader.cpp:372
void reloadShader()
This completely reloads the shader file and processes it.
Definition: WGEShader.cpp:122
boost::signals2::connection m_reloadSignalConnection
Connection object to the reload signal from WGraphbicsEngine.
Definition: WGEShader.h:210
virtual void apply(osg::ref_ptr< osg::Node > node)
Apply this shader to the specified node.
Definition: WGEShader.cpp:79
WGEShader(std::string name, boost::filesystem::path search=WPathHelper::getShaderPath())
Default constructor.
Definition: WGEShader.cpp:49
bool m_reload
Flag denoting whether a shader should be reloaded.
Definition: WGEShader.h:195
void clearPreprocessors()
Removes all preprocessors.
Definition: WGEShader.cpp:384
bool m_deactivated
Flag denoting whether a shader should be deactivated.
Definition: WGEShader.h:205
void addPreprocessor(WGEShaderPreprocessor::SPtr preproc)
Adds the specified preprocessor to this shader.
Definition: WGEShader.cpp:359
osg::ref_ptr< osg::Shader > m_geometryShader
the geometry shader object
Definition: WGEShader.h:240
boost::filesystem::path m_shaderPath
String that stores the location of all shader files.
Definition: WGEShader.h:185
virtual ~WGEShader()
Destructor.
Definition: WGEShader.cpp:73
std::string processShader(const std::string filename, bool optional=false)
This method searches and processes all includes in the shader source.
Definition: WGEShader.cpp:334
std::string m_name
The name of the shader.
Definition: WGEShader.h:190
PreprocessorsList m_preprocessors
List of all pre-processing that need to be applied to this shader instance.
Definition: WGEShader.h:220
static std::shared_ptr< WGraphicsEngine > getGraphicsEngine()
Returns instance of the graphics engine.
void addLogMessage(std::string message, std::string source="", LogLevel level=LL_DEBUG)
Appends a log message to the logging queue.
Definition: WLogger.cpp:84
static WLogger * getLogger()
Returns pointer to the currently running logger instance.
Definition: WLogger.cpp:64
static boost::filesystem::path getShaderPath()
The path to the global shaders.
T::iterator Iterator
A typedef for the correct iterator to traverse this sequence container.
T::const_iterator ConstIterator
A typedef for the correct const iterator useful to traverse this sequence container.
std::shared_ptr< WSharedObjectTicketRead< T > > ReadTicket
Type for read tickets.
Definition: WSharedObject.h:65
ReadTicket getReadTicket() const
Returns a ticket to get read access to the contained data.
std::shared_ptr< WSharedObjectTicketWrite< T > > WriteTicket
Type for write tickets.
Definition: WSharedObject.h:70
WriteTicket getWriteTicket(bool suppressNotify=false) const
Returns a ticket to get write access to the contained data.