Radium Engine  1.5.0
ShaderProgram.cpp
1 #include <Engine/Data/ShaderProgram.hpp>
2 #include <Engine/OpenGL.hpp>
3 
4 #include <glbinding/gl/enum.h>
5 #include <globjects/NamedString.h>
6 #include <globjects/Program.h>
7 #include <globjects/Shader.h>
8 #include <globjects/Texture.h>
9 #include <globjects/base/File.h>
10 #include <globjects/base/StaticStringSource.h>
11 
12 #include <Core/Utils/Log.hpp>
13 #include <Engine/Data/Texture.hpp>
14 
15 #include <algorithm>
16 #include <numeric> // for std::accumulate
17 #include <regex>
18 
19 namespace Ra {
20 namespace Engine {
21 namespace Data {
22 
23 using namespace Core::Utils; // log
24 
25 // The two following methods are independent of any ShaderProgram object.
26 // Fixed : made them local function to remove dependency on openGL.h for the class header
27 GLenum getTypeAsGLEnum( ShaderType type ) {
28  switch ( type ) {
29  case ShaderType_VERTEX:
30  return GL_VERTEX_SHADER;
31  case ShaderType_FRAGMENT:
32  return GL_FRAGMENT_SHADER;
33  case ShaderType_GEOMETRY:
34  return GL_GEOMETRY_SHADER;
35  case ShaderType_TESS_EVALUATION:
36  return GL_TESS_EVALUATION_SHADER;
37  case ShaderType_TESS_CONTROL:
38  return GL_TESS_CONTROL_SHADER;
39 #ifndef OS_MACOS
40  // GL_COMPUTE_SHADER requires OpenGL >= 4.2, Apple provides OpenGL 4.1
41  case ShaderType_COMPUTE:
42  return GL_COMPUTE_SHADER;
43 #endif
44  default:
45  CORE_ERROR( "Wrong ShaderType" );
46  }
47 
48  // Should never get there
49  return GL_ZERO;
50 }
51 
52 ShaderType getGLenumAsType( GLenum type ) {
53  switch ( type ) {
54  case GL_VERTEX_SHADER:
55  return ShaderType_VERTEX;
56  case GL_FRAGMENT_SHADER:
57  return ShaderType_FRAGMENT;
58  case GL_GEOMETRY_SHADER:
59  return ShaderType_GEOMETRY;
60  case GL_TESS_EVALUATION_SHADER:
61  return ShaderType_TESS_EVALUATION;
62  case GL_TESS_CONTROL_SHADER:
63  return ShaderType_TESS_CONTROL;
64 #ifndef OS_MACOS
65  case GL_COMPUTE_SHADER:
66  return ShaderType_COMPUTE;
67 #endif
68  default:
69  CORE_ERROR( "Wrong GLenum" );
70  }
71 
72  // Should never get there
73  return ShaderType_COUNT;
74 }
75 
76 ShaderProgram::ShaderProgram() : m_program { nullptr } {
77  std::generate( m_shaderObjects.begin(), m_shaderObjects.end(), []() {
78  return std::pair<bool, std::unique_ptr<globjects::Shader>> { false, nullptr };
79  } );
80  std::fill( m_shaderSources.begin(), m_shaderSources.end(), nullptr );
81 }
82 
83 ShaderProgram::ShaderProgram( const ShaderConfiguration& config ) : ShaderProgram() {
84  load( config );
85 }
86 
87 ShaderProgram::~ShaderProgram() {
88  // first delete shader objects (before program and source) since it refer to
89  // them during delete
90  // See ~Shader (setSource(nullptr)
91  for ( auto& s : m_shaderObjects ) {
92  s.second.reset( nullptr );
93  }
94  for ( auto& s : m_shaderSources ) {
95  s.reset( nullptr );
96  }
97  m_program.reset( nullptr );
98 }
99 
100 void ShaderProgram::loadShader( ShaderType type,
101  const std::string& name,
102  const std::set<std::string>& props,
103  const std::vector<std::pair<std::string, ShaderType>>& includes,
104  bool fromFile,
105  const std::string& version ) {
106 #ifdef OS_MACOS
107  if ( type == ShaderType_COMPUTE ) {
108  LOG( logERROR ) << "No compute shader on OsX !";
109  return;
110  }
111 #endif
112  // Radium V2 --> for the moment : standard includepaths. Might be controlled per shader ...
113  // Paths in which globjects will be looking for shaders includes.
114  // "/" refer to the root of the directory structure conaining the shader (i.e. the Shaders/
115  // directory).
116 
117  // header string that contains #version and pre-declarations ...
118  std::string shaderHeader;
119  if ( type == ShaderType_VERTEX ) {
120  shaderHeader = std::string( version + "\n\n"
121  "out gl_PerVertex {\n"
122  " vec4 gl_Position;\n"
123  " float gl_PointSize;\n"
124  " float gl_ClipDistance[];\n"
125  "};\n\n" );
126  }
127  else { shaderHeader = std::string( version + "\n\n" ); }
128 
129  // Add properties at the beginning of the file.
130  shaderHeader = std::accumulate(
131  props.begin(), props.end(), shaderHeader, []( std::string a, const std::string& b ) {
132  return std::move( a ) + b + std::string( "\n" );
133  } );
134 
135  // Add includes, depending on the shader type.
136  shaderHeader = std::accumulate(
137  includes.begin(),
138  includes.end(),
139  shaderHeader,
140  [type]( std::string a, const std::pair<std::string, ShaderType>& b ) -> std::string {
141  if ( b.second == type ) { return std::move( a ) + b.first + std::string( "\n" ); }
142  else { return a; }
143  } );
144 
145  std::unique_ptr<globjects::StaticStringSource> fullsource { nullptr };
146  if ( fromFile ) {
147  auto loadedSource = globjects::Shader::sourceFromFile( name );
148  fullsource = globjects::Shader::sourceFromString( shaderHeader + loadedSource->string() );
149  }
150  else { fullsource = globjects::Shader::sourceFromString( shaderHeader + name ); }
151 
152  // Radium V2 : allow to define global replacement per renderer, shader, rendertechnique ...
153  auto shaderSource = globjects::Shader::applyGlobalReplacements( fullsource.get() );
154 
155 #if 1
156  // Workaround for #include directive
157  // Radium VB2 : rely on GL_ARB_shading_language_include to manage includes
158  std::string preprocessedSource = preprocessIncludes( name, shaderSource->string(), 0 );
159 
160  auto ptrSource = globjects::Shader::sourceFromString( preprocessedSource );
161 #else
162  // this code do not work, include are not processed :( maybe a globjects bug.
163  auto ptrSource = globjects::Shader::sourceFromString( shaderSource->string() );
164 #endif
165  addShaderFromSource( type, std::move( ptrSource ), name, fromFile );
166 }
167 
168 void ShaderProgram::addShaderFromSource( ShaderType type,
169  std::unique_ptr<globjects::StaticStringSource>&& ptrSource,
170  const std::string& name,
171  bool fromFile ) {
172 
173  auto shader = globjects::Shader::create( getTypeAsGLEnum( type ) );
174 
175  shader->setName( name );
176  shader->setSource( ptrSource.get() );
177  shader->compile();
178 
179  GL_CHECK_ERROR;
180  m_shaderObjects[type].first = fromFile;
181  m_shaderObjects[type].second.swap( shader );
182 
183  m_shaderSources[type].swap( ptrSource );
184  // ^^^ raw ptrSource are stored in shader object, need to keep them valid during
185  // shader life
186 }
187 
188 void ShaderProgram::load( const ShaderConfiguration& shaderConfig ) {
189  m_configuration = shaderConfig;
190 
191  CORE_ERROR_IF( m_configuration.isComplete(),
192  ( "Shader program " + shaderConfig.m_name +
193  " is incomplete (e.g. misses vertex or fragment shader)." )
194  .c_str() );
195 
196  for ( size_t i = 0; i < ShaderType_COUNT; ++i ) {
197  if ( !m_configuration.m_shaders[i].first.empty() ) {
198  loadShader( ShaderType( i ),
199  m_configuration.m_shaders[i].first,
200  m_configuration.getProperties(),
201  m_configuration.getIncludes(),
202  m_configuration.m_shaders[i].second,
203  m_configuration.m_version );
204  }
205  }
206 
207  link();
208 }
209 
210 void ShaderProgram::link() {
211  m_program = globjects::Program::create();
212 
213  for ( unsigned int i = 0; i < ShaderType_COUNT; ++i ) {
214  if ( m_shaderObjects[i].second ) { m_program->attach( m_shaderObjects[i].second.get() ); }
215  }
216 
217  m_program->setParameter( GL_PROGRAM_SEPARABLE, GL_TRUE );
218 
219  m_program->link();
220  GL_CHECK_ERROR;
221  int texUnit = 0;
222  auto total = GLuint( m_program->get( GL_ACTIVE_UNIFORMS ) );
223  textureUnits.clear();
224 
225  for ( GLuint i = 0; i < total; ++i ) {
226  auto name = m_program->getActiveUniformName( i );
227  auto type = m_program->getActiveUniform( i, GL_UNIFORM_TYPE );
228 
230  if ( type == GL_SAMPLER_1D || type == GL_SAMPLER_2D || type == GL_SAMPLER_3D ||
231  type == GL_SAMPLER_CUBE || type == GL_SAMPLER_1D_SHADOW ||
232  type == GL_SAMPLER_2D_SHADOW || type == GL_SAMPLER_CUBE_SHADOW ||
233  type == GL_SAMPLER_2D_RECT || type == GL_SAMPLER_2D_RECT_SHADOW ||
234  type == GL_SAMPLER_1D_ARRAY || type == GL_SAMPLER_2D_ARRAY ||
235  type == GL_SAMPLER_BUFFER || type == GL_SAMPLER_1D_ARRAY_SHADOW ||
236  type == GL_SAMPLER_2D_ARRAY_SHADOW || type == GL_INT_SAMPLER_1D ||
237  type == GL_INT_SAMPLER_2D || type == GL_INT_SAMPLER_3D ||
238  type == GL_INT_SAMPLER_CUBE || type == GL_INT_SAMPLER_2D_RECT ||
239  type == GL_INT_SAMPLER_1D_ARRAY || type == GL_INT_SAMPLER_2D_ARRAY ||
240  type == GL_INT_SAMPLER_BUFFER || type == GL_UNSIGNED_INT_SAMPLER_1D ||
241  type == GL_UNSIGNED_INT_SAMPLER_2D || type == GL_UNSIGNED_INT_SAMPLER_3D ||
242  type == GL_UNSIGNED_INT_SAMPLER_CUBE || type == GL_UNSIGNED_INT_SAMPLER_2D_RECT ||
243  type == GL_UNSIGNED_INT_SAMPLER_1D_ARRAY || type == GL_UNSIGNED_INT_SAMPLER_2D_ARRAY ||
244  type == GL_UNSIGNED_INT_SAMPLER_BUFFER || type == GL_SAMPLER_CUBE_MAP_ARRAY ||
245  type == GL_SAMPLER_CUBE_MAP_ARRAY_SHADOW || type == GL_INT_SAMPLER_CUBE_MAP_ARRAY ||
246  type == GL_UNSIGNED_INT_SAMPLER_CUBE_MAP_ARRAY || type == GL_SAMPLER_2D_MULTISAMPLE ||
247  type == GL_INT_SAMPLER_2D_MULTISAMPLE ||
248  type == GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE ||
249  type == GL_SAMPLER_2D_MULTISAMPLE_ARRAY ||
250  type == GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY ||
251  type == GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY ) {
252  auto location = m_program->getUniformLocation( name );
253  textureUnits[name] = TextureBinding( texUnit++, location );
254  }
255  }
256 }
257 
258 void ShaderProgram::bind() const {
259  m_program->use();
260 }
261 
262 void ShaderProgram::validate() const {
263  m_program->validate();
264  if ( !m_program->isValid() ) { LOG( logDEBUG ) << m_program->infoLog(); }
265 }
266 
267 void ShaderProgram::unbind() const {
268  m_program->release();
269 }
270 
271 void ShaderProgram::reload() {
272  for ( auto& s : m_shaderObjects ) {
273  if ( s.second != nullptr ) {
274  if ( s.first ) { LOG( logDEBUG ) << "Reloading shader " << s.second->name(); }
275 
276  m_program->detach( s.second.get() );
277  loadShader( getGLenumAsType( s.second->type() ),
278  s.second->name(),
279  m_configuration.getProperties(),
280  m_configuration.getIncludes(),
281  s.first,
282  m_configuration.m_version );
283  }
284  }
285 
286  link();
287 }
288 
289 ShaderConfiguration ShaderProgram::getBasicConfiguration() const {
290  ShaderConfiguration basicConfig;
291 
292  basicConfig.m_shaders = m_configuration.m_shaders;
293  basicConfig.m_name = m_configuration.m_name;
294 
295  return basicConfig;
296 }
297 
298 template <>
299 void ShaderProgram::setUniform( const char* name, const Core::Vector2d& value ) const {
300  m_program->setUniform( name, value.cast<GL_SCALAR_PLAIN>().eval() );
301 }
302 
303 template <>
304 void ShaderProgram::setUniform( const char* name, const Core::Vector3d& value ) const {
305  m_program->setUniform( name, value.cast<GL_SCALAR_PLAIN>().eval() );
306 }
307 
308 template <>
309 void ShaderProgram::setUniform( const char* name, const Core::Vector4d& value ) const {
310  m_program->setUniform( name, value.cast<GL_SCALAR_PLAIN>().eval() );
311 }
312 
313 template <>
314 void ShaderProgram::setUniform( const char* name, const Core::Matrix2d& value ) const {
315  m_program->setUniform( name, value.cast<GL_SCALAR_PLAIN>().eval() );
316 }
317 
318 template <>
319 void ShaderProgram::setUniform( const char* name, const Core::Matrix3d& value ) const {
320  m_program->setUniform( name, value.cast<GL_SCALAR_PLAIN>().eval() );
321 }
322 
323 template <>
324 void ShaderProgram::setUniform( const char* name, const Core::Matrix4d& value ) const {
325  m_program->setUniform( name, value.cast<GL_SCALAR_PLAIN>().eval() );
326 }
327 
328 template <>
329 void ShaderProgram::setUniform( const char* name, const Scalar& value ) const {
330  m_program->setUniform( name, static_cast<GL_SCALAR_PLAIN>( value ) );
331 }
332 
333 template <typename T,
334  typename std::enable_if<!std::is_same<T, GL_SCALAR_PLAIN>::value>::type* = nullptr>
335 void scalarVectorAdapter( globjects::Program* prog,
336  const char* name,
337  const std::vector<T>& value ) {
338  std::vector<GL_SCALAR_PLAIN> convertedValue;
339  std::transform( value.begin(),
340  value.end(),
341  std::back_inserter( convertedValue ),
342  []( Scalar c ) -> GL_SCALAR_PLAIN { return c; } );
343  prog->setUniform( name, convertedValue );
344 }
345 
346 template <typename T,
347  typename std::enable_if<std::is_same<T, GL_SCALAR_PLAIN>::value>::type* = nullptr>
348 void scalarVectorAdapter( globjects::Program* prog,
349  const char* name,
350  const std::vector<T>& value ) {
351  prog->setUniform( name, value );
352 }
353 
354 template <>
355 void ShaderProgram::setUniform( const char* name, const std::vector<Scalar>& value ) const {
356  scalarVectorAdapter<Scalar>( m_program.get(), name, value );
357 }
358 
359 void ShaderProgram::setUniform( const char* name, Texture* tex, int texUnit ) const {
360  tex->bind( texUnit );
361 
362  m_program->setUniform( name, texUnit );
363 }
364 
365 void ShaderProgram::setUniformTexture( const char* name, Texture* tex ) const {
366  auto itr = textureUnits.find( std::string( name ) );
367  if ( itr != textureUnits.end() ) {
368  tex->bind( itr->second.m_texUnit );
369  m_program->setUniform( itr->second.m_location, itr->second.m_texUnit );
370  }
371 }
372 
373 globjects::Program* ShaderProgram::getProgramObject() const {
374  return m_program.get();
375 }
376 
377 /****************************************************
378  * Include workaround due to globject bugs
379  ****************************************************/
380 std::string ShaderProgram::preprocessIncludes( const std::string& name,
381  const std::string& shader,
382  int level,
383  int line ) {
384  CORE_UNUSED( line ); // left for radium v2 ??
385  CORE_ERROR_IF( level < 32, "Shader inclusion depth limit reached." );
386 
387  std::string result {};
388  std::vector<std::string> finalStrings;
389 
390  uint nline = 0;
391 
392  static const std::regex reg( "^[ ]*#[ ]*include[ ]+[\"<](.*)[\">].*" );
393 
394  // source: https://www.fluentcpp.com/2017/04/21/how-to-split-a-string-in-c/
395  std::istringstream iss( shader );
396  std::string codeline;
397  while ( std::getline( iss, codeline, '\n' ) ) {
398  std::smatch match;
399  if ( std::regex_search( codeline, match, reg ) ) {
400  // Radium V2 : for composable shaders, use the includePaths set elsewhere.
401  auto includeNameString =
402  globjects::NamedString::getFromRegistry( std::string( "/" ) + match[1].str() );
403  if ( includeNameString != nullptr ) {
404 
405  codeline =
406  preprocessIncludes( match[1].str(), includeNameString->string(), level + 1, 0 );
407  }
408  else {
409  LOG( logWARNING ) << "Cannot open included file " << match[1].str() << " at line"
410  << nline << " of file " << name << ". Ignored.";
411  continue;
412  }
413  // Radium V2, adapt this if globjct includes bug is still present
414  /*
415  std::string inc;
416  std::string file = m_filepath + match[1].str();
417  if (parseFile(file, inc))
418  {
419  sublerr.start = nline;
420  sublerr.name = file;
421  lerr.subfiles.push_back(sublerr);
422 
423  line = preprocessIncludes(inc, level + 1, lerr.subfiles.back());
424  nline = lerr.subfiles.back().end;
425  }
426  else
427  {
428  LOG(logWARNING) << "Cannot open included file " << file << " from " << m_filename << ".
429  Ignored."; continue;
430  }
431  */
432  }
433 
434  finalStrings.push_back( codeline );
435  ++nline;
436  }
437 
438  // Build final shader string
439  for ( const auto& l : finalStrings ) {
440  result.append( l );
441  result.append( "\n" );
442  }
443 
444  result.append( "\0" );
445 
446  return result;
447 }
448 
449 } // namespace Data
450 } // namespace Engine
451 } // namespace Ra
void bind(int unit=-1)
Bind the texture to enable its use in a shader.
Definition: Texture.cpp:88
Definition: Cage.cpp:3