Radium Engine  1.5.20
Loading...
Searching...
No Matches
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
19namespace Ra {
20namespace Engine {
21namespace Data {
22
23using 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
27GLenum 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
52ShaderType 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
76ShaderProgram::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
83ShaderProgram::ShaderProgram( const ShaderConfiguration& config ) : ShaderProgram() {
84 load( config );
85}
86
87ShaderProgram::~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
100void ShaderProgram::loadShader( ShaderType type,
101 const std::string& name,
102 const std::set<std::string>& props,
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,
141 if ( b.second == type ) { return std::move( a ) + b.first + std::string( "\n" ); }
142 else { return a; }
143 } );
144
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
168void ShaderProgram::addShaderFromSource( ShaderType type,
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
188void 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
210void 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
258void ShaderProgram::bind() const {
259 m_program->use();
260}
261
262void ShaderProgram::validate() const {
263 m_program->validate();
264 if ( !m_program->isValid() ) { LOG( logDEBUG ) << m_program->infoLog(); }
265}
266
267void ShaderProgram::unbind() const {
268 m_program->release();
269}
270
271void 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
289ShaderConfiguration 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
298template <>
299void ShaderProgram::setUniform( const char* name, const Core::Vector2d& value ) const {
300 m_program->setUniform( name, value.cast<GL_SCALAR_PLAIN>().eval() );
301}
302
303template <>
304void ShaderProgram::setUniform( const char* name, const Core::Vector3d& value ) const {
305 m_program->setUniform( name, value.cast<GL_SCALAR_PLAIN>().eval() );
306}
307
308template <>
309void ShaderProgram::setUniform( const char* name, const Core::Vector4d& value ) const {
310 m_program->setUniform( name, value.cast<GL_SCALAR_PLAIN>().eval() );
311}
312
313template <>
314void ShaderProgram::setUniform( const char* name, const Core::Matrix2d& value ) const {
315 m_program->setUniform( name, value.cast<GL_SCALAR_PLAIN>().eval() );
316}
317
318template <>
319void ShaderProgram::setUniform( const char* name, const Core::Matrix3d& value ) const {
320 m_program->setUniform( name, value.cast<GL_SCALAR_PLAIN>().eval() );
321}
322
323template <>
324void ShaderProgram::setUniform( const char* name, const Core::Matrix4d& value ) const {
325 m_program->setUniform( name, value.cast<GL_SCALAR_PLAIN>().eval() );
326}
327
328template <>
329void ShaderProgram::setUniform( const char* name, const Scalar& value ) const {
330 m_program->setUniform( name, static_cast<GL_SCALAR_PLAIN>( value ) );
331}
332
333template <typename T,
335void 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
346template <typename T,
348void scalarVectorAdapter( globjects::Program* prog,
349 const char* name,
350 const std::vector<T>& value ) {
351 prog->setUniform( name, value );
352}
353
354template <>
355void ShaderProgram::setUniform( const char* name, const std::vector<Scalar>& value ) const {
356 scalarVectorAdapter<Scalar>( m_program.get(), name, value );
357}
358
359void ShaderProgram::setUniform( const char* name, Texture* tex, int texUnit ) const {
360 tex->bind( texUnit );
361
362 m_program->setUniform( name, texUnit );
363}
364
365void 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
373globjects::Program* ShaderProgram::getProgramObject() const {
374 return m_program.get();
375}
376
377/****************************************************
378 * Include workaround due to globject bugs
379 ****************************************************/
380std::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
T accumulate(T... args)
T append(T... args)
T back_inserter(T... args)
T begin(T... args)
Represent a Texture of the engine.
Definition Texture.hpp:120
void bind(int unit=-1)
Bind the texture to GPU texture unit to enable its use in a shader. Need active OpenGL context.
Definition Texture.cpp:129
T end(T... args)
T fill(T... args)
T generate(T... args)
T getline(T... args)
T includes(T... args)
T move(T... args)
hepler function to manage enum as underlying types in VariableSet
Definition Cage.cpp:3
T push_back(T... args)
T regex_search(T... args)
T str(T... args)
T transform(T... args)