Tulip  5.2.0
Large graphs analysis and drawing
GlShaderProgram.h
1 /*
2  *
3  * This file is part of Tulip (http://tulip.labri.fr)
4  *
5  * Authors: David Auber and the Tulip development Team
6  * from LaBRI, University of Bordeaux
7  *
8  * Tulip is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU Lesser General Public License
10  * as published by the Free Software Foundation, either version 3
11  * of the License, or (at your option) any later version.
12  *
13  * Tulip is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16  * See the GNU General Public License for more details.
17  *
18  */
19 ///@cond DOXYGEN_HIDDEN
20 
21 #ifndef GL_SHADER_PROGRAM
22 #define GL_SHADER_PROGRAM
23 
24 #include <tulip/OpenGlIncludes.h>
25 
26 #include <string>
27 #include <vector>
28 
29 #include <tulip/tulipconf.h>
30 #include <tulip/Matrix.h>
31 #include <tulip/Color.h>
32 
33 namespace tlp {
34 
35 enum ShaderType { Vertex, Fragment, Geometry };
36 
37 /**
38  * \brief A class to manage shader objects, components of a shader program
39  *
40  * This class allow to create and compile OpenGL shader object. Shaders are used to program the
41  * graphics processing unit (GPU) rendering pipeline.
42  * The three existing types of shaders are managed :
43  *
44  * -> Vertex shader : run once for each vertex given to the graphics processor. The purpose is to
45  * transform each vertex's 3D position in virtual space
46  * to the 2D coordinate at which it appears on the screen (as well as a depth value for the
47  * Z-buffer).
48  * Vertex shaders can manipulate properties such as position, color, and texture coordinate, but
49  * cannot create new vertices.
50  * The output of the vertex shader goes to the next stage in the pipeline, which is either a
51  * geometry shader if present or the rasterizer otherwise.
52  *
53  * -> Geometry shader : can add and remove vertices from a mesh. Geometry shaders can be used to
54  * generate geometry procedurally
55  * or to add volumetric detail to existing meshes that would be too costly to process on the
56  * CPU. If geometry shaders are being used,
57  * the output is then sent to the rasterizer.
58  *
59  * -> Fragment shader (Pixel shader) : calculate the color of individual pixels. The input to this
60  * stage comes from the rasterizer,
61  * which fills in the polygons being sent through the graphics pipeline.
62  *
63  * Shaders source codes have to be written with the "OpenGL Shading Language (GLSL)"
64  */
65 class TLP_GL_SCOPE GlShader {
66 
67  friend class GlShaderProgram;
68 
69 public:
70  /**
71  * Vertex and Fragment shader constructor
72  *
73  * Use this constructor to create either a vertex shader or a fragment shader
74  *
75  * \param shaderType Type of the shader to create, Vertex or Fragment
76  */
77  GlShader(ShaderType shaderType);
78 
79  /**
80  * Geometry shader constructor
81  *
82  * Use this constructor to create a geometry shader
83  *
84  * \param inputPrimitiveType the type of graphic primitive the geometry shader takes as input.
85  * (must be one from the following list : GL_POINTS, GL_LINES, GL_LINES_ADJACENCY_EXT,
86  * GL_TRIANGLES, GL_TRIANGLES_ADJACENCY_EXT)
87  *
88  * \param outputPrimitiveType the type of graphics primitives the geometry shader will output
89  * (must be one of the following list : GL_POINTS, GL_LINE_STRIP, GL_TRIANGLE_STRIP)
90  */
91  GlShader(GLenum inputPrimitiveType, GLenum outputPrimitiveType);
92 
93  /**
94  * GlShader destructor
95  */
96  ~GlShader();
97 
98  /**
99  * Return the GL identifier of this shader object
100  */
101  GLuint getShaderId() const {
102  return shaderObjectId;
103  }
104 
105  /**
106  * Return the type of the shader (Vertex, Geometry or Fragment)
107  */
108  ShaderType getShaderType() const {
109  return shaderType;
110  }
111 
112  /**
113  * Method only relevant for geometry shaders. Return the graphic primitive type this geometry
114  * shader takes as input.
115  */
116  GLenum getInputPrimitiveType() const {
117  return inputPrimitiveType;
118  }
119 
120  /**
121  * Method only relevant for geometry shaders. Return the graphics primitives type this geometry
122  * shader will output.
123  */
124  GLenum getOutputPrimitiveType() const {
125  return outputPrimitiveType;
126  }
127 
128  /**
129  * Method only relevant for geometry shaders. Set the graphic primitive type this geometry shader
130  * takes as input.
131  * Note that when modifying the input primitive type, the associated shader program (whose object
132  * is from type GlShaderProgram)
133  * has to be relinked for changes to take effect.
134  *
135  * \param inputPrimitiveType the type of graphic primitive the geometry shader takes as input.
136  * (must be one from the following list : GL_POINTS, GL_LINES, GL_LINES_ADJACENCY_EXT,
137  * GL_TRIANGLES, GL_TRIANGLES_ADJACENCY_EXT)
138  */
139  void setInputPrimitiveType(const GLenum inputPrimitiveType) {
140  this->inputPrimitiveType = inputPrimitiveType;
141  }
142 
143  /**
144  * Method only relevant for geometry shaders. Set the graphics primitives type this geometry
145  * shader will output.
146  * Note that when modifying the output primitive type, the associated shader program (whose object
147  * is from type GlShaderProgram)
148  * has to be relinked for changes to take effect.
149  *
150  * \param outputPrimitiveType the type of graphics primitives the geometry shader will output
151  * (must be one of the following list : GL_POINTS, GL_LINE_STRIP, GL_TRIANGLE_STRIP)
152  */
153  void setOutputPrimitiveType(const GLenum outputPrimitiveType) {
154  this->outputPrimitiveType = outputPrimitiveType;
155  }
156 
157  /**
158  * Set the shader source code from a C string and compile it.
159  *
160  * \param shaderSrc a C string containing the shader source code
161  */
162  void compileFromSourceCode(const char *shaderSrc);
163 
164  /**
165  * Set the shader source code from a C++ string and compile it.
166  *
167  * \param shaderSrc a C++ string containing the shader source code
168  */
169  void compileFromSourceCode(const std::string &shaderSrc);
170 
171  /**
172  * Set the shader source code from a file and compile it.
173  *
174  * \param shaderSrcFilename the absolute path of the file containing the shader source code
175  */
176  void compileFromSourceFile(const std::string &shaderSrcFilename);
177 
178  /**
179  * Return true if the shader compilation was successfull, false otherwise
180  */
181  bool isCompiled() const {
182  return shaderCompiled;
183  }
184 
185  /**
186  * Return the log output by the shader compiler
187  */
188  std::string getCompilationLog() const {
189  return compilationLog;
190  }
191 
192 private:
193  void setAnonymousCreation(const bool anonymousCreation) {
194  this->anonymousCreation = anonymousCreation;
195  }
196  bool anonymouslyCreated() const {
197  return anonymousCreation;
198  }
199 
200  void compileShaderObject(const char *shaderSrc);
201 
202  ShaderType shaderType;
203  GLuint shaderObjectId;
204  GLenum inputPrimitiveType, outputPrimitiveType;
205  bool shaderCompiled;
206  std::string compilationLog;
207  bool anonymousCreation;
208 };
209 
210 /**
211  * \brief A class to manage OpenGL shader program.
212  *
213  * This class allows to create and use shader programs by linking several shader objects. At least
214  * one shader object must be
215  * provided in order to use the shader program. Multiple shader objects of the same type can be
216  * added but exactly one
217  * of these shader objects must have a main() function. As in C, in order to use a function defined
218  * in a separate shader object
219  * from another shader object, this function has to be declared with the same prototype in the
220  * source code of the last one.
221  *
222  * This class also allows to specify uniform and attribute variables values of the shader program.
223  */
224 class TLP_GL_SCOPE GlShaderProgram {
225 
226 public:
227  /**
228  * GlShaderProgram constructor
229  *
230  * \param name An optionnal name can be provided to identify the shader program
231  */
232  GlShaderProgram(const std::string &name = "");
233 
234  /**
235  * GlShaderProgram destructor
236  */
237  ~GlShaderProgram();
238 
239  /**
240  * A static function which returns true if vertex and fragment shaders are supported by the host
241  * graphic card
242  */
243  static bool shaderProgramsSupported();
244 
245  /**
246  * A static function which returns true if geometry shaders are supported by the host graphic card
247  */
248  static bool geometryShaderSupported();
249 
250  /**
251  * A static function which returns the current active shader if any
252  */
253  static GlShaderProgram *getCurrentActiveShader();
254 
255  /**
256  * Return the string identifier of this shader program
257  */
258  std::string getName() const {
259  return programName;
260  }
261 
262  /**
263  * Return the OpenGL identifier of this shader program
264  */
265  GLuint getShaderProgramId() const {
266  return programObjectId;
267  }
268 
269  /**
270  * Add a shader object to this shader program
271  *
272  * \param shader the shader object to add to this shader program
273  */
274  void addShader(GlShader *shader);
275 
276  /**
277  * Remove a shader object from this shader program
278  * Note that the shader object will not be destroyed
279  *
280  * \param shader the shader object to remove from this shader program
281  */
282  void removeShader(GlShader *shader);
283 
284  /**
285  * remove all shaders from this shader program
286  */
287  void removeAllShaders();
288 
289  /**
290  * Convenient method to add a shader object (from type Vertex or Fragment) from a source code
291  * stored in a C string
292  * The created shader object will be automatically destroyed when removing all attached shader
293  * objects
294  * or destroying the shader program
295  *
296  * \param shaderType the type of the shader object to add (must be Vertex or Fragment)
297  * \param shaderSrc the C string containing the shader object source code
298  */
299  void addShaderFromSourceCode(const ShaderType shaderType, const char *shaderSrc);
300 
301  /**
302  * Convenient method to add a shader object (from type Vertex or Fragment) from a source code
303  * stored in a C++ string
304  * The created shader object will be automatically destroyed when removing all attached shader
305  * objects
306  * or destroying the shader program
307  *
308  * \param shaderType the type of the shader object to add (must be Vertex or Fragment)
309  * \param shaderSrc the C++ string containing the shader object source code
310  */
311  void addShaderFromSourceCode(const ShaderType shaderType, const std::string &shaderSrc);
312 
313  /**
314  * Convenient method to add a shader object (from type Vertex or Fragment) from a source code
315  * stored in a file
316  * The created shader object will be automatically destroyed when removing all attached shader
317  * objects
318  * or destroying the shader program
319  *
320  * \param shaderType the type of the shader object to add (must be Vertex or Fragment)
321  * \param shaderSrcFilename the aboslute path to the file containing the shader object source code
322  */
323  void addShaderFromSourceFile(const ShaderType shaderType, const std::string &shaderSrcFilename);
324 
325  /**
326  * Convenient method to add a geometry shader object from a source code stored in a C string
327  * The created shader object will be automatically destroyed when removing all attached shader
328  * objects
329  * or destroying the shader program
330  *
331  * \param geometryShaderSrc the C string containing the geometry shader object source code
332  * \param inputPrimitiveType the type of graphic primitive the geometry shader takes as input.
333  * (must be one from the following list : GL_POINTS, GL_LINES, GL_LINES_ADJACENCY_EXT,
334  * GL_TRIANGLES, GL_TRIANGLES_ADJACENCY_EXT)
335  *
336  * \param outputPrimitiveType the type of graphics primitives the geometry shader will output
337  * (must be one of the following list : GL_POINTS, GL_LINE_STRIP, GL_TRIANGLE_STRIP)
338  */
339  void addGeometryShaderFromSourceCode(const char *geometryShaderSrc, GLenum inputPrimitiveType,
340  GLenum outputPrimitiveType);
341 
342  /**
343  * Convenient method to add a geometry shader object from a source code stored in a C++ string
344  * The created shader object will be automatically destroyed when removing all attached shader
345  * objects
346  * or destroying the shader program
347  *
348  * \param geometryShaderSrc the C++ string containing the geometry shader object source code
349  * \param inputPrimitiveType the type of graphic primitive the geometry shader takes as input.
350  * (must be one from the following list : GL_POINTS, GL_LINES, GL_LINES_ADJACENCY_EXT,
351  * GL_TRIANGLES, GL_TRIANGLES_ADJACENCY_EXT)
352  *
353  * \param outputPrimitiveType the type of graphics primitives the geometry shader will output
354  * (must be one of the following list : GL_POINTS, GL_LINE_STRIP, GL_TRIANGLE_STRIP)
355  */
356  void addGeometryShaderFromSourceCode(const std::string &geometryShaderSrc,
357  GLenum inputPrimitiveType, GLenum outputPrimitiveType);
358 
359  /**
360  * Convenient method to add a geometry shader object from a source code stored in a file
361  * The created shader object will be automatically destroyed when removing all attached shader
362  * objects
363  * or destroying the shader program
364  *
365  * \param geometryShaderSrcFilename the absolute path to the file containing the geometry shader
366  * object source code
367  * \param inputPrimitiveType the type of graphic primitive the geometry shader takes as input.
368  * (must be one from the following list : GL_POINTS, GL_LINES, GL_LINES_ADJACENCY_EXT,
369  * GL_TRIANGLES, GL_TRIANGLES_ADJACENCY_EXT)
370  *
371  * \param outputPrimitiveType the type of graphics primitives the geometry shader will output
372  * (must be one of the following list : GL_POINTS, GL_LINE_STRIP, GL_TRIANGLE_STRIP)
373  */
374  void addGeometryShaderFromSourceFile(const std::string &geometryShaderSrcFilename,
375  GLenum inputPrimitiveType, GLenum outputPrimitiveType);
376 
377  /**
378  * Link the shader program.
379  */
380  void link();
381 
382  /**
383  * return true if the shader program has been successfully linked, false otherwise
384  */
385  bool isLinked() const {
386  return programLinked;
387  }
388 
389  /**
390  * Print the info log containing errors and warnings related to shader objects compilation and
391  * shader program linkage
392  */
393  void printInfoLog();
394 
395  /**
396  * Activate the shader program. If the shader program has not been linked, the link method will be
397  * called.
398  */
399  void activate();
400 
401  /**
402  * Deactivate the shader program.
403  */
404  void desactivate();
405 
406  void setUniformFloat(const std::string &variateName, const float f);
407  void setUniformVec2Float(const std::string &variableName, const Vector<float, 2> &vec2f);
408  void setUniformVec2Float(const std::string &variableName, const float f1, const float f2);
409  void setUniformVec3Float(const std::string &variableName, const Vector<float, 3> &vec3f);
410  void setUniformVec3Float(const std::string &variableName, const float f1, const float f2,
411  const float f3);
412  void setUniformVec4Float(const std::string &variableName, const Vector<float, 4> &vec4f);
413  void setUniformVec4Float(const std::string &variableName, const float f1, const float f2,
414  const float f3, const float f4);
415  void setUniformMat2Float(const std::string &variableName, const Matrix<float, 2> &mat2f,
416  const bool transpose = false);
417  void setUniformMat2Float(const std::string &variableName, const float *f,
418  const bool transpose = false);
419  void setUniformMat3Float(const std::string &variableName, const Matrix<float, 3> &mat3f,
420  const bool transpose = false);
421  void setUniformMat3Float(const std::string &variableName, const float *f,
422  const bool transpose = false);
423  void setUniformMat4Float(const std::string &variableName, const Matrix<float, 4> &mat4f,
424  const bool transpose = false);
425  void setUniformMat4Float(const std::string &variableName, const float *f,
426  const bool transpose = false);
427 
428  void setUniformInt(const std::string &variableName, const int f);
429  void setUniformVec2Int(const std::string &variableName, const Vector<int, 2> &vec2i);
430  void setUniformVec2Int(const std::string &variableName, const int i1, const int i2);
431  void setUniformVec3Int(const std::string &variableName, const Vector<int, 3> &vec3i);
432  void setUniformVec3Int(const std::string &variableName, const int i1, const int i2, const int i3);
433  void setUniformVec4Int(const std::string &variableName, const Vector<int, 4> &vec4i);
434  void setUniformVec4Int(const std::string &variableName, const int i1, const int i2, const int i3,
435  const int i4);
436 
437  void setUniformBool(const std::string &variableName, const bool b);
438  void setUniformVec2Bool(const std::string &variableName, const Array<bool, 2> &vec2b);
439  void setUniformVec2Bool(const std::string &variableName, const bool b1, const bool b2);
440  void setUniformVec3Bool(const std::string &variableName, const Array<bool, 3> &vec3b);
441  void setUniformVec3Bool(const std::string &variableName, const bool b1, const bool b2,
442  const bool b3);
443  void setUniformVec4Bool(const std::string &variableName, const Array<bool, 4> &vec4b);
444  void setUniformVec4Bool(const std::string &variableName, const bool i1, const bool i2,
445  const bool i3, const bool i4);
446 
447  void setAttributeFloat(const std::string &variableName, const float f);
448  void setAttributeVec2Float(const std::string &variableName, const Vector<float, 2> &vec2f);
449  void setAttributeVec2Float(const std::string &variableName, const float f1, const float f2);
450  void setAttributeVec3Float(const std::string &variableName, const Vector<float, 3> &vec3f);
451  void setAttributeVec3Float(const std::string &variableName, const float f1, const float f2,
452  const float f3);
453  void setAttributeVec4Float(const std::string &variableName, const Vector<float, 4> &vec4f);
454  void setAttributeVec4Float(const std::string &variableName, const float f1, const float f2,
455  const float f3, const float f4);
456 
457  void setAttributeInt(const std::string &variableName, const int f);
458  void setAttributeVec2Int(const std::string &variableName, const Vector<int, 2> &vec2i);
459  void setAttributeVec2Int(const std::string &variableName, const int i1, const int i2);
460  void setAttributeVec3Int(const std::string &variableName, const Vector<int, 3> &vec3i);
461  void setAttributeVec3Int(const std::string &variableName, const int i1, const int i2,
462  const int i3);
463  void setAttributeVec4Int(const std::string &variableName, const Vector<int, 4> &vec4i);
464  void setAttributeVec4Int(const std::string &variableName, const int i1, const int i2,
465  const int i3, const int i4);
466 
467  void setAttributeBool(const std::string &variableName, const bool b);
468  void setAttributeVec2Bool(const std::string &variableName, const Array<bool, 2> &vec2b);
469  void setAttributeVec2Bool(const std::string &variableName, const bool b1, const bool b2);
470  void setAttributeVec3Bool(const std::string &variableName, const Array<bool, 3> &vec3b);
471  void setAttributeVec3Bool(const std::string &variableName, const bool b1, const bool b2,
472  const bool b3);
473  void setAttributeVec4Bool(const std::string &variableName, const Array<bool, 4> &vec4b);
474  void setAttributeVec4Bool(const std::string &variableName, const bool b1, const bool b2,
475  const bool b3, const bool b4);
476 
477  void setUniformTextureSampler(const std::string &samplerVariateName, const int samplerId);
478  void setUniformColor(const std::string &variableName, const Color &color);
479  void setAttributeColor(const std::string &variableName, const Color &color);
480 
481  template <unsigned int SIZE>
482  void setUniformFloatArray(const std::string &variableName, const Vector<float, SIZE> &vecf);
483  void setUniformFloatArray(const std::string &variableName, const unsigned int fCount,
484  const float *f);
485 
486  template <unsigned int SIZE>
487  void setUniformVec2FloatArray(const std::string &variableName,
488  const Array<Vector<float, 2>, SIZE> &vecvec2f);
489  void setUniformVec2FloatArray(const std::string &variableName, const unsigned int vec2fCount,
490  const float *f);
491 
492  template <unsigned int SIZE>
493  void setUniformVec3FloatArray(const std::string &variableName,
494  const Array<Vector<float, 3>, SIZE> &vecvec3f);
495  void setUniformVec3FloatArray(const std::string &variableName, const unsigned int vec3fCount,
496  const float *f);
497 
498  template <unsigned int SIZE>
499  void setUniformVec4FloatArray(const std::string &variableName,
500  const Array<Vector<float, 4>, SIZE> &vecvec4f);
501  void setUniformVec4FloatArray(const std::string &variableName, const unsigned int vec4fCount,
502  const float *f);
503 
504  template <unsigned int SIZE>
505  void setUniformMat2FloatArray(const std::string &variableName,
506  const Vector<Matrix<float, 2>, SIZE> &vecmat2f,
507  const bool transpose = false);
508  void setUniformMat2FloatArray(const std::string &variableName, const unsigned int mat2fCount,
509  const float *f, const bool transpose = false);
510 
511  template <unsigned int SIZE>
512  void setUniformMat3FloatArray(const std::string &variableName,
513  const Vector<Matrix<float, 3>, SIZE> &vecmat3f,
514  const bool transpose = false);
515  void setUniformMat3FloatArray(const std::string &variableName, const unsigned int mat3fCount,
516  const float *f, const bool transpose = false);
517 
518  template <unsigned int SIZE>
519  void setUniformMat4FloatArray(const std::string &variableName,
520  const Vector<Matrix<float, 4>, SIZE> &vecmat4f,
521  const bool transpose = false);
522  void setUniformMat4FloatArray(const std::string &variableName, const unsigned int mat4fCount,
523  const float *f, const bool transpose = false);
524 
525  template <unsigned int SIZE>
526  void setUniformIntArray(const std::string &variableName, const Vector<int, SIZE> &veci);
527  void setUniformIntArray(const std::string &variableName, const unsigned int iCount, const int *i);
528 
529  template <unsigned int SIZE>
530  void setUniformVec2IntArray(const std::string &variableName,
531  const Array<Vector<int, 2>, SIZE> &vecvec2i);
532  void setUniformVec2IntArray(const std::string &variableName, const unsigned int vec2iCount,
533  const int *i);
534 
535  template <unsigned int SIZE>
536  void setUniformVec3IntArray(const std::string &variableName,
537  const Array<Vector<int, 3>, SIZE> &vecvec3i);
538  void setUniformVec3IntArray(const std::string &variableName, const unsigned int vec3iCount,
539  const int *i);
540 
541  template <unsigned int SIZE>
542  void setUniformVec4IntArray(const std::string &variableName,
543  const Array<Vector<int, 4>, SIZE> &vecvec4i);
544  void setUniformVec4IntArray(const std::string &variableName, const unsigned int vec4iCount,
545  const int *i);
546 
547  template <unsigned int SIZE>
548  void setUniformBoolArray(const std::string &variableName, const Array<bool, SIZE> &vecb);
549  void setUniformBoolArray(const std::string &variableName, const unsigned int bCount,
550  const bool *b);
551 
552  template <unsigned int SIZE>
553  void setUniformVec2BoolArray(const std::string &variableName,
554  const Array<Array<bool, 2>, SIZE> &vecvec2b);
555  void setUniformVec2BoolArray(const std::string &variableName, const unsigned int vec2bCount,
556  const bool *b);
557 
558  template <unsigned int SIZE>
559  void setUniformVec3BoolArray(const std::string &variableName,
560  const Array<Array<bool, 3>, SIZE> &vecvec3b);
561  void setUniformVec3BoolArray(const std::string &variableName, const unsigned int vec3bCount,
562  const bool *b);
563 
564  template <unsigned int SIZE>
565  void setUniformVec4BoolArray(const std::string &variableName,
566  const Array<Array<bool, 4>, SIZE> &vecvec4b);
567  void setUniformVec4BoolArray(const std::string &variableName, const unsigned int vec4bCount,
568  const bool *b);
569 
570  void getUniformFloatVariableValue(const std::string &variableName, float *value);
571  void getUniformIntVariableValue(const std::string &variableName, int *value);
572  void getUniformBoolVariableValue(const std::string &variableName, bool *value);
573  void getUniformVec2BoolVariableValue(const std::string &variableName, bool *value);
574  void getUniformVec3BoolVariableValue(const std::string &variableName, bool *value);
575  void getUniformVec4BoolVariableValue(const std::string &variableName, bool *value);
576 
577  // This method must be called before calling the link method to
578  // set the max number of vertices a geometry shader can output
579  // If not called, the maximum value is set when linking the shader program (not recommended for
580  // performance).
581  void setMaxGeometryShaderOutputVertices(const int maxOutputVertices);
582 
583 private:
584  GLint getUniformVariableLocation(const std::string &variableName);
585  GLint getAttributeVariableLocation(const std::string &variableName);
586 
587  std::string programName;
588  GLuint programObjectId;
589 
590  std::string programLinkLog;
591  bool programLinked;
592 
593  std::vector<GlShader *> attachedShaders;
594  int maxGeometryShaderOutputVertices;
595 
596  static GlShaderProgram *currentActiveShaderProgram;
597 };
598 }
599 
600 #endif // GL_SHADER_PROGRAM
601 ///@endcond