OpenWalnut  1.2.5
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Friends Groups Pages
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 
30 #include <boost/algorithm/string.hpp>
31 // Use filesystem version 2 for compatibility with newer boost versions.
32 #ifndef BOOST_FILESYSTEM_VERSION
33  #define BOOST_FILESYSTEM_VERSION 2
34 #endif
35 #include <boost/filesystem.hpp>
36 #include <boost/function.hpp>
37 #include <boost/lexical_cast.hpp>
38 #include <boost/regex.hpp>
39 #include <boost/signals2/signal.hpp>
40 #include <boost/tokenizer.hpp>
41 
42 #include <osg/Node>
43 #include <osg/StateSet>
44 
45 #include "../../common/WLogger.h"
46 #include "../../common/WPathHelper.h"
47 #include "../../common/WPredicateHelper.h"
48 #include "../WGraphicsEngine.h"
49 #include "WGEShader.h"
50 #include "WGEShaderPreprocessor.h"
51 #include "WGEShaderVersionPreprocessor.h"
52 
53 WGEShader::WGEShader( std::string name, boost::filesystem::path search ):
54  osg::Program(),
55  m_shaderPath( search ),
56  m_name( name ),
57  m_reload( true ),
58  m_shaderLoaded( false ),
59  m_deactivated( false )
60 {
61  // create shader
62  m_vertexShader = osg::ref_ptr< osg::Shader >( new osg::Shader( osg::Shader::VERTEX ) );
63  m_fragmentShader = osg::ref_ptr< osg::Shader >( new osg::Shader( osg::Shader::FRAGMENT ) );
64  m_geometryShader = osg::ref_ptr< osg::Shader >( new osg::Shader( osg::Shader::GEOMETRY ) );
65 
66  // add them
67  addShader( m_vertexShader );
68  addShader( m_fragmentShader );
69  addShader( m_geometryShader );
70 
71  // this preprocessor is always needed. It removes the #version statement from the code and puts it to the beginning.
73 
74  m_reloadSignalConnection = WGraphicsEngine::getGraphicsEngine()->subscribeSignal( GE_RELOADSHADERS, boost::bind( &WGEShader::reload, this ) );
75 }
76 
78 {
79  // cleanup
80  m_reloadSignalConnection.disconnect();
81 }
82 
83 void WGEShader::apply( osg::ref_ptr< osg::Node > node )
84 {
85  // set the shader attribute
86  // NOTE: the attribute is protected to avoid father nodes overwriting it
87  osg::StateSet* rootState = node->getOrCreateStateSet();
88  rootState->setAttributeAndModes( this, osg::StateAttribute::ON | osg::StateAttribute::PROTECTED );
89  m_deactivated = false;
91 
92  // add a custom callback which actually sets and updated the shader.
93  node->addUpdateCallback( osg::ref_ptr< SafeUpdaterCallback >( new SafeUpdaterCallback( this ) ) );
94 }
95 
96 void WGEShader::applyDirect( osg::State& state ) // NOLINT <- ensure this matches the official OSG API by using a non-const ref
97 {
99  osg::Program::apply( state );
100 }
101 
102 void WGEShader::deactivate( osg::ref_ptr< osg::Node > node )
103 {
104  if( !node )
105  {
106  return;
107  }
108 
109  // set the shader attribute
110  // NOTE: the attribute is protected to avoid father nodes overwriting it
111  osg::StateSet* rootState = node->getOrCreateStateSet();
112  rootState->setAttributeAndModes( this, osg::StateAttribute::ON | osg::StateAttribute::PROTECTED );
113 
114  m_deactivated = true;
115  m_shaderLoaded = false;
116 
117  // add a custom callback which actually sets and updated the shader.
118  node->addUpdateCallback( osg::ref_ptr< SafeUpdaterCallback >( new SafeUpdaterCallback( this ) ) );
119 }
120 
122 {
123  m_reload = true;
124 }
125 
127 {
128  try
129  {
130  // remove the shaders
131  removeShader( m_vertexShader );
132  removeShader( m_fragmentShader );
133  removeShader( m_geometryShader );
134 
135  // reload the sources and set the shader
136  // vertex shader
137  WLogger::getLogger()->addLogMessage( "Reloading vertex shader \"" + m_name + "-vertex.glsl\"", "WGEShader", LL_DEBUG );
138  std::string source = processShader( m_name + "-vertex.glsl" );
139  if( source != "" )
140  {
141  m_vertexShader->setShaderSource( source );
142  addShader( m_vertexShader );
143  }
144 
145  // fragment shader
146  WLogger::getLogger()->addLogMessage( "Reloading fragment shader \"" + m_name + "-fragment.glsl\"", "WGEShader", LL_DEBUG );
147  source = processShader( m_name + "-fragment.glsl" );
148  if( source != "" )
149  {
150  m_fragmentShader->setShaderSource( source );
151  addShader( m_fragmentShader );
152  }
153 
154  // Geometry Shader
155  WLogger::getLogger()->addLogMessage( "Reloading geometry shader \"" + m_name + "-geometry.glsl\"", "WGEShader", LL_DEBUG );
156  source = processShader( m_name + "-geometry.glsl", true );
157  if( source != "" )
158  {
159  m_geometryShader->setShaderSource( source );
160  addShader( m_geometryShader );
161  }
162 
163  m_shaderLoaded = true;
164  }
165  catch( const std::exception& e )
166  {
167  m_shaderLoaded = false;
168 
169  WLogger::getLogger()->addLogMessage( "Problem loading shader.", "WGEShader", LL_ERROR );
170 
171  // clean up the mess
172  removeShader( m_vertexShader );
173  removeShader( m_fragmentShader );
174  removeShader( m_geometryShader );
175  }
176 
177  // everything done now.
178  m_reload = false;
179 }
180 
182 {
183  // is it needed to do something here?
184  if( m_deactivated )
185  {
186  // remove the shaders
187  removeShader( m_vertexShader );
188  removeShader( m_fragmentShader );
189  removeShader( m_geometryShader );
190  }
191  else if( m_reload )
192  {
193  reloadShader();
194  }
195 }
196 
198  m_shader( shader )
199 {
200 }
201 
202 void WGEShader::SafeUpdaterCallback::operator()( osg::Node* node, osg::NodeVisitor* nv )
203 {
204  m_shader->updatePrograms();
205 
206  // forward the call
207  traverse( node, nv );
208 }
209 
210 std::string WGEShader::processShaderRecursive( const std::string filename, bool optional, int level )
211 {
212  std::stringstream output; // processed output
213 
214  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
215  // Before the preprocessors get applied, the following code build the complete shader code from many parts (includes) and handles the version
216  // statement automatically. This is important since the GLSL compiler (especially ATI's) relies on it. After completely loading the whole
217  // code, the preprocessors get applied.
218  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
219 
220  // we encountered an endless loop
221  if( level > 32 )
222  {
223  // reached a certain level. This normally denotes a inclusion cycle.
224  // We do not throw an exception here to avoid OSG to crash.
225  WLogger::getLogger()->addLogMessage( "Inclusion depth is too large. Maybe there is a inclusion cycle in the shader code.",
226  "WGEShader (" + filename + ")", LL_ERROR
227  );
228 
229  // just return unprocessed source
230  return "";
231  }
232 
233  // this is the proper regular expression for includes. This also excludes commented includes
234  static const boost::regex includeRegexp( "^[ ]*#[ ]*include[ ]+[\"<](.*)[\">].*" );
235 
236  // the input stream, first check existence of shader
237  // search these places in this order:
238  // 1. m_shaderPath
239  // 2. m_shaderPath / shaders
240  // 3. WPathHelper::getShaderPath()
241 
242  // use one of the following paths
243  std::string fn = filename;
244  std::string fnLocal = ( m_shaderPath / filename ).file_string();
245  std::string fnLocalShaders = ( m_shaderPath / "shaders" / filename ).file_string();
246  std::string fnGlobal = ( WPathHelper::getShaderPath() / filename ).file_string();
247 
248  if( boost::filesystem::exists( m_shaderPath / filename ) )
249  {
250  fn = fnLocal;
251  }
252  else if( boost::filesystem::exists( m_shaderPath / "shaders" / filename ) )
253  {
254  fn = fnLocalShaders;
255  }
256  else if( boost::filesystem::exists( WPathHelper::getShaderPath() / filename ) )
257  {
258  fn = fnGlobal;
259  }
260  else if( !optional )
261  {
262  WLogger::getLogger()->addLogMessage( "The requested shader \"" + filename + "\" does not exist in \"" +
263  m_shaderPath.file_string() + "\", \"" + ( m_shaderPath / "shaders" ).file_string() + "\" or \"" +
264  WPathHelper::getShaderPath().file_string() + "\".", "WGEShader (" + filename + ")", LL_ERROR
265  );
266 
267  return "";
268  }
269  else
270  {
271  return "";
272  }
273 
274  std::ifstream input( fn.c_str() );
275  if( !input.is_open() )
276  {
277  if( optional )
278  {
279  return "";
280  }
281 
282  // file does not exist. Do not throw an exception to avoid OSG crash
283  if( level == 0 )
284  {
285  WLogger::getLogger()->addLogMessage( "Can't open shader file \"" + filename + "\".",
286  "WGEShader (" + filename + ")", LL_ERROR
287  );
288  }
289  else
290  {
291  WLogger::getLogger()->addLogMessage( "Can't open shader file for inclusion \"" + filename + "\".",
292  "WGEShader (" + filename + ")", LL_ERROR
293  );
294  }
295 
296  return "";
297  }
298 
299  // go through each line and process includes
300  std::string line; // the current line
301  boost::smatch matches; // the list of matches
302 
303  while( std::getline( input, line ) )
304  {
305  if( boost::regex_search( line, matches, includeRegexp ) )
306  {
307  output << processShaderRecursive( matches[1], false, level + 1 );
308  }
309  else
310  {
311  output << line;
312  }
313 
314  // NOTE: we do not apply the m_processors here since the recursive processShaders may have produced many lines. We would need to loop
315  // through each one of them. This is done later on for the whole code.
316 
317  output << std::endl;
318  }
319 
320  input.close();
321 
322  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
323  // Done. Return code.
324  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
325 
326  // this string contains the processed shader code
327  return output.str();
328 }
329 
330 std::string WGEShader::processShader( const std::string filename, bool optional )
331 {
332  // load all the code
333  std::string code = processShaderRecursive( filename, optional );
334  if( code.empty() )
335  {
336  return "";
337  }
338 
339  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
340  // The whole code is loaded now. Apply preprocessors.
341  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
342 
343  // apply all preprocessors
345  for( PreprocessorsList::ConstIterator pp = r->get().begin(); pp != r->get().end(); ++pp )
346  {
347  code = ( *pp ).first->process( filename, code );
348  }
349  r.reset();
350 
351  // finally ensure ONE #version at the beginning.
352  return m_versionPreprocessor->process( filename, code );
353 }
354 
356 {
358  if( !w->get().count( preproc ) ) // if already exists, no connection needed
359  {
360  // subscribe the preprocessors update condition
361  boost::signals2::connection con = preproc->getChangeCondition()->subscribeSignal( boost::bind( &WGEShader::reload, this ) );
362  w->get().insert( std::make_pair( preproc, con ) );
363  }
364  w.reset();
365  reload();
366 }
367 
369 {
371  if( w->get().count( preproc ) ) // is it in our list?
372  {
373  w->get().operator[]( preproc ).disconnect();
374  w->get().erase( preproc );
375  }
376  w.reset();
377  reload();
378 }
379 
381 {
383 
384  // we need to disconnect each signal subscription
385  for( PreprocessorsList::Iterator pp = w->get().begin(); pp != w->get().end(); ++pp )
386  {
387  ( *pp ).second.disconnect();
388  }
389  w->get().clear();
390  w.reset();
391  reload();
392 }
393 
395 {
396  return this->setDefine< bool >( key, true );
397 }
398