Developer Documentation
ShaderCache.cc
1 /*===========================================================================*\
2  * *
3  * OpenFlipper *
4  * Copyright (c) 2001-2015, RWTH-Aachen University *
5  * Department of Computer Graphics and Multimedia *
6  * All rights reserved. *
7  * www.openflipper.org *
8  * *
9  *---------------------------------------------------------------------------*
10  * This file is part of OpenFlipper. *
11  *---------------------------------------------------------------------------*
12  * *
13  * Redistribution and use in source and binary forms, with or without *
14  * modification, are permitted provided that the following conditions *
15  * are met: *
16  * *
17  * 1. Redistributions of source code must retain the above copyright notice, *
18  * this list of conditions and the following disclaimer. *
19  * *
20  * 2. Redistributions in binary form must reproduce the above copyright *
21  * notice, this list of conditions and the following disclaimer in the *
22  * documentation and/or other materials provided with the distribution. *
23  * *
24  * 3. Neither the name of the copyright holder nor the names of its *
25  * contributors may be used to endorse or promote products derived from *
26  * this software without specific prior written permission. *
27  * *
28  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS *
29  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED *
30  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
31  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER *
32  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, *
33  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, *
34  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR *
35  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF *
36  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING *
37  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS *
38  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
39  * *
40 \*===========================================================================*/
41 
42 
43 
44 
45 #include "ShaderCache.hh"
46 
47 #include <cstdio>
48 #include <iostream>
49 #include <fstream>
50 
51 
52 #include <QFile>
53 #include <QFileInfo>
54 #include <QDir>
55 #include <QTextStream>
56 
57 #include <ACG/GL/GLError.hh>
58 #include <ACG/ShaderUtils/GLSLShader.hh>
59 
60 
61 namespace ACG
62 {
63 
64 ShaderCache::ShaderCache():
65  cache_(),
66  cacheStatic_(),
67  timeCheck_(false)
68 {
69 }
70 
72 {
73  // free all glsl programs in cache
74  for (CacheList::iterator it = cache_.begin(); it != cache_.end(); ++it)
75  delete it->second;
76 
77  for (CacheList::iterator it = cacheStatic_.begin(); it != cacheStatic_.end(); ++it)
78  delete it->second;
79 
80  for (CacheList::iterator it = cacheComputeShaders_.begin(); it != cacheComputeShaders_.end(); ++it)
81  delete it->second;
82 }
83 
85 {
86  static ShaderCache singleton;
87  return &singleton;
88 }
89 
90 
92 {
93  std::vector<unsigned int> dummy;
94  return getProgram(_desc, dummy);
95 }
96 
97 //***********************************************************************
98 // TODO implement binary search eventually (if cache access is getting too slow)
99 // - modify compareShaderGenDescs s.t. it defines an order
100 // or generate a hash key from ShaderGenDesc
101 
102 GLSL::Program* ACG::ShaderCache::getProgram( const ShaderGenDesc* _desc, const std::vector<unsigned int>& _mods )
103 {
104  CacheEntry newEntry;
105  newEntry.desc = *_desc;
106  newEntry.mods = _mods;
107 
108  if (!_desc->fragmentTemplateFile.isEmpty())
109  {
110  newEntry.strFragmentTemplate = _desc->fragmentTemplateFile;
111  newEntry.fragmentFileLastMod = QFileInfo(newEntry.strFragmentTemplate).lastModified();
112  }
113 
114  if (!_desc->tessControlTemplateFile.isEmpty())
115  {
116  newEntry.strTessControlTemplate = _desc->tessControlTemplateFile;
117  newEntry.tessControlFileLastMod = QFileInfo(newEntry.strTessControlTemplate).lastModified();
118  }
119 
120  if (!_desc->tessEvaluationTemplateFile.isEmpty())
121  {
122  newEntry.strTessEvaluationTemplate = _desc->tessEvaluationTemplateFile;
123  newEntry.tessEvaluationFileLastMod = QFileInfo(newEntry.strTessEvaluationTemplate).lastModified();
124  }
125 
126  if (!_desc->geometryTemplateFile.isEmpty())
127  {
128  newEntry.strGeometryTemplate = _desc->geometryTemplateFile;
129  newEntry.geometryFileLastMod = QFileInfo(newEntry.strGeometryTemplate).lastModified();
130  }
131 
132  if (!_desc->vertexTemplateFile.isEmpty())
133  {
134  newEntry.strVertexTemplate = _desc->vertexTemplateFile;
135  newEntry.vertexFileLastMod = QFileInfo(newEntry.strVertexTemplate).lastModified();
136  }
137 
138  CacheList::iterator oldCache = cache_.end();
139 
140  for (CacheList::iterator it = cache_.begin(); it != cache_.end(); ++it)
141  {
142  // If the shaders are equal, we return the cached entry
143  if (!compareShaderGenDescs(&it->first, &newEntry))
144  {
145  if ( timeCheck_ && !compareTimeStamp(&it->first, &newEntry))
146  oldCache = it;
147  else
148  return it->second;
149  }
150  }
151 
152  // glsl program not in cache, generate shaders
153  ShaderProgGenerator progGen(_desc, _mods);
154 
155  if (!dbgOutputDir_.isEmpty())
156  {
157  static int counter = 0;
158 
159  QString fileName = QString("shader_%1.glsl").arg(counter++,2,10,QLatin1Char('0')) ;
160  fileName = dbgOutputDir_ + QDir::separator() + fileName;
161 
162  QFile fileOut(fileName);
163  if (fileOut.open(QFile::WriteOnly | QFile::Truncate))
164  {
165  QTextStream outStrm(&fileOut);
166 
167  outStrm << _desc->toString();
168  outStrm << "\nmods: ";
169 
170  for (size_t i = 0; i < _mods.size(); ++i)
171  outStrm << _mods[i] << (i+1 < _mods.size() ? ", " : "");
172  outStrm << "\n";
173 
174 
175  outStrm << "\n---------------------vertex-shader--------------------\n\n";
176 
177  for (int i = 0; i < progGen.getVertexShaderCode().size(); ++i)
178  outStrm << progGen.getVertexShaderCode()[i] << "\n";
179 
180  if (progGen.hasTessControlShader())
181  {
182  outStrm << "\n---------------------tesscontrol-shader--------------------\n\n";
183 
184  for (int i = 0; i < progGen.getTessControlShaderCode().size(); ++i)
185  outStrm << progGen.getTessControlShaderCode()[i] << "\n";
186  }
187 
188  if (progGen.hasTessEvaluationShader())
189  {
190  outStrm << "\n---------------------tesseval-shader--------------------\n\n";
191 
192  for (int i = 0; i < progGen.getTessEvaluationShaderCode().size(); ++i)
193  outStrm << progGen.getTessEvaluationShaderCode()[i] << "\n";
194  }
195 
196  if (progGen.hasGeometryShader())
197  {
198  outStrm << "\n---------------------geometry-shader--------------------\n\n";
199 
200  for (int i = 0; i < progGen.getGeometryShaderCode().size(); ++i)
201  outStrm << progGen.getGeometryShaderCode()[i] << "\n";
202  }
203 
204  outStrm << "\n---------------------fragment-shader--------------------\n\n";
205 
206  for (int i = 0; i < progGen.getFragmentShaderCode().size(); ++i)
207  outStrm << progGen.getFragmentShaderCode()[i] << "\n";
208 
209 
210  fileOut.close();
211  }
212  }
213 
214  GLSL::FragmentShader* fragShader = new GLSL::FragmentShader();
215  GLSL::VertexShader* vertShader = new GLSL::VertexShader();
216 
217  vertShader->setSource(progGen.getVertexShaderCode());
218  fragShader->setSource(progGen.getFragmentShaderCode());
219 
220  vertShader->compile();
221  fragShader->compile();
222 
223  GLSL::Program* prog = new GLSL::Program();
224  prog->attach(vertShader);
225  prog->attach(fragShader);
226 
227  // Check if we have a geometry shader and if we have support for it, enable it here
228  if ( progGen.hasGeometryShader() ) {
229  GLSL::GeometryShader* geomShader = new GLSL::GeometryShader();
230  geomShader->setSource(progGen.getGeometryShaderCode());
231  geomShader->compile();
232  prog->attach(geomShader);
233  }
234 
235  // Check if we have tessellation shaders and if we have support for it, enable it here
236  if ( progGen.hasTessControlShader() || progGen.hasTessEvaluationShader() ) {
237  GLSL::Shader* tessControlShader = 0, *tessEvalShader = 0;
238 
239 #ifdef GL_ARB_tessellation_shader
240  tessControlShader = new GLSL::TessControlShader();
241  tessEvalShader = new GLSL::TessEvaluationShader();
242 #endif // GL_ARB_tessellation_shader
243 
244  if (tessControlShader && progGen.hasTessControlShader())
245  {
246  tessControlShader->setSource(progGen.getTessControlShaderCode());
247  tessControlShader->compile();
248  prog->attach(tessControlShader);
249  }
250 
251  if (tessEvalShader && progGen.hasTessEvaluationShader())
252  {
253  tessEvalShader->setSource(progGen.getTessEvaluationShaderCode());
254  tessEvalShader->compile();
255  prog->attach(tessEvalShader);
256  }
257 
258  }
259 
260  prog->link();
261  glCheckErrors();
262 
263  if (oldCache != cache_.end())
264  {
265  if (!prog->isLinked())
266  {
267  delete prog;
268  return oldCache->second;
269  }
270  else
271  {
272  cache_.erase(oldCache);
273  }
274  }
275 
276  cache_.push_back(std::pair<CacheEntry, GLSL::Program*>(newEntry, prog));
277 
278  return prog;
279 }
280 
281 GLSL::Program* ACG::ShaderCache::getProgram( const char* _vertexShaderFile,
282  const char* _tessControlShaderFile,
283  const char* _tessEvalShaderFile,
284  const char* _geometryShaderFile,
285  const char* _fragmentShaderFile,
286  QStringList* _macros, bool _verbose )
287 {
288  CacheEntry newEntry;
289 
290 
291  // store filenames and timestamps in new entry
292 
293  // fragment shader
294  QFileInfo fileInfo(_fragmentShaderFile);
295  if (fileInfo.isRelative())
296  {
297  QString absFilename = ACG::ShaderProgGenerator::getShaderDir() + QDir::separator() + QString(_fragmentShaderFile);
298  fileInfo = QFileInfo(absFilename);
299 
300  newEntry.strFragmentTemplate = absFilename;
301  newEntry.fragmentFileLastMod = fileInfo.lastModified();
302  }
303  else
304  {
305  newEntry.strFragmentTemplate = _fragmentShaderFile;
306  newEntry.fragmentFileLastMod = fileInfo.lastModified();
307  }
308 
309  // vertex shader
310  fileInfo = QFileInfo(_vertexShaderFile);
311  if (fileInfo.isRelative())
312  {
313  QString absFilename = ACG::ShaderProgGenerator::getShaderDir() + QDir::separator() + QString(_vertexShaderFile);
314  fileInfo = QFileInfo(absFilename);
315 
316  newEntry.strVertexTemplate = absFilename;
317  newEntry.vertexFileLastMod = fileInfo.lastModified();
318  }
319  else
320  {
321  newEntry.strVertexTemplate = _vertexShaderFile;
322  newEntry.vertexFileLastMod = fileInfo.lastModified();
323  }
324 
325 
326  // geometry shader
327  if (_geometryShaderFile)
328  {
329  fileInfo = QFileInfo(_geometryShaderFile);
330  if (fileInfo.isRelative())
331  {
332  QString absFilename = ACG::ShaderProgGenerator::getShaderDir() + QDir::separator() + QString(_geometryShaderFile);
333  fileInfo = QFileInfo(absFilename);
334 
335  newEntry.strGeometryTemplate = absFilename;
336  newEntry.geometryFileLastMod = fileInfo.lastModified();
337  }
338  else
339  {
340  newEntry.strGeometryTemplate = _geometryShaderFile;
341  newEntry.geometryFileLastMod = fileInfo.lastModified();
342  }
343  }
344 
345  // tess-ctrl shader
346  if (_tessControlShaderFile)
347  {
348  fileInfo = QFileInfo(_tessControlShaderFile);
349  if (fileInfo.isRelative())
350  {
351  QString absFilename = ACG::ShaderProgGenerator::getShaderDir() + QDir::separator() + QString(_tessControlShaderFile);
352  fileInfo = QFileInfo(absFilename);
353 
354  newEntry.strTessControlTemplate = absFilename;
355  newEntry.tessControlFileLastMod = fileInfo.lastModified();
356  }
357  else
358  {
359  newEntry.strTessControlTemplate = _tessControlShaderFile;
360  newEntry.tessControlFileLastMod = fileInfo.lastModified();
361  }
362  }
363 
364  // tess-eval shader
365  if (_tessEvalShaderFile)
366  {
367  fileInfo = QFileInfo(_tessEvalShaderFile);
368  if (fileInfo.isRelative())
369  {
370  QString absFilename = ACG::ShaderProgGenerator::getShaderDir() + QDir::separator() + QString(_tessEvalShaderFile);
371  fileInfo = QFileInfo(absFilename);
372 
373  newEntry.strTessEvaluationTemplate = absFilename;
374  newEntry.tessEvaluationFileLastMod = fileInfo.lastModified();
375  }
376  else
377  {
378  newEntry.strTessEvaluationTemplate = _tessEvalShaderFile;
379  newEntry.tessEvaluationFileLastMod = fileInfo.lastModified();
380  }
381  }
382 
383 
384 
385  if (_macros)
386  newEntry.macros = *_macros;
387 
388  CacheList::iterator oldCache = cacheStatic_.end();
389 
390  for (CacheList::iterator it = cacheStatic_.begin(); it != cacheStatic_.end(); ++it)
391  {
392  // If the shaders are equal, we return the cached entry
393  if (!compareShaderGenDescs(&it->first, &newEntry))
394  {
395  if ( timeCheck_ && !compareTimeStamp(&it->first, &newEntry))
396  oldCache = it;
397  else
398  return it->second;
399  }
400  }
401 
402 
403  // convert QStringList to GLSL::StringList
404 
405  GLSL::StringList glslMacros;
406 
407  if (_macros)
408  {
409  for (QStringList::const_iterator it = _macros->constBegin(); it != _macros->constEnd(); ++it)
410  glslMacros.push_back(it->toStdString());
411  }
412 
413 
414  GLSL::Program* prog = GLSL::loadProgram(_vertexShaderFile, _tessControlShaderFile, _tessEvalShaderFile, _geometryShaderFile, _fragmentShaderFile, &glslMacros, _verbose);
415  glCheckErrors();
416 
417  if (oldCache != cacheStatic_.end())
418  {
419  if (!prog || !prog->isLinked())
420  {
421  delete prog;
422  return oldCache->second;
423  }
424  else
425  {
426  cacheStatic_.erase(oldCache);
427  }
428  }
429 
430  cacheStatic_.push_back(std::pair<CacheEntry, GLSL::Program*>(newEntry, prog));
431 
432  return prog;
433 }
434 
435 GLSL::Program* ACG::ShaderCache::getProgram( const char* _vertexShaderFile, const char* _fragmentShaderFile, QStringList* _macros, bool _verbose )
436 {
437  return getProgram(_vertexShaderFile, 0, 0, 0, _fragmentShaderFile, _macros, _verbose);
438 }
439 
440 GLSL::Program* ACG::ShaderCache::getComputeProgram(const char* _computeShaderFile, QStringList* _macros /* = 0 */, bool _verbose /* = true */)
441 {
442  CacheEntry newEntry;
443 
444  // store filenames and timestamps in new entry
445  // use vertex shader filename as compute shader
446  QFileInfo fileInfo(_computeShaderFile);
447  if (fileInfo.isRelative())
448  {
449  QString absFilename = ACG::ShaderProgGenerator::getShaderDir() + QDir::separator() + QString(_computeShaderFile);
450  fileInfo = QFileInfo(absFilename);
451 
452  newEntry.strVertexTemplate = absFilename;
453  newEntry.vertexFileLastMod = fileInfo.lastModified();
454  }
455  else
456  {
457  newEntry.strVertexTemplate = _computeShaderFile;
458  newEntry.vertexFileLastMod = fileInfo.lastModified();
459  }
460 
461  if (_macros)
462  newEntry.macros = *_macros;
463 
464  CacheList::iterator oldCache = cacheComputeShaders_.end();
465 
466  for (CacheList::iterator it = cacheComputeShaders_.begin(); it != cacheComputeShaders_.end(); ++it)
467  {
468  // If the shaders are equal, we return the cached entry
469  if (!compareShaderGenDescs(&it->first, &newEntry))
470  {
471  if ( ( timeCheck_ && !compareTimeStamp(&it->first, &newEntry)) || !it->second || !it->second->isLinked())
472  oldCache = it;
473  else
474  return it->second;
475  }
476  }
477 
478 
479  // convert QStringList to GLSL::StringList
480 
481  GLSL::StringList glslMacros;
482 
483  if (_macros)
484  {
485  for (QStringList::const_iterator it = _macros->constBegin(); it != _macros->constEnd(); ++it)
486  glslMacros.push_back(it->toStdString());
487  }
488 
489 
490  GLSL::Program* prog = GLSL::loadComputeProgram(newEntry.strVertexTemplate.toUtf8(), &glslMacros, _verbose);
491  glCheckErrors();
492 
493  if (oldCache != cacheComputeShaders_.end())
494  {
495  if (!prog || !prog->isLinked())
496  {
497  delete prog;
498 
499 
500  // dump shader source including macros to debug output
501  if (!glslMacros.empty() && !dbgOutputDir_.isEmpty())
502  {
503  GLSL::StringList shaderSrc = GLSL::loadShader(newEntry.strVertexTemplate.toUtf8(), &glslMacros);
504 
505  // compute FNV hash of macros
506 
507  unsigned int fnv_prime = 16777619;
508  unsigned int fnv_hash = 2166136261;
509 
510  for (GLSL::StringList::iterator it = shaderSrc.begin(); it != shaderSrc.end(); ++it)
511  {
512  for (size_t i = 0; i < it->length(); ++i)
513  {
514  fnv_hash *= fnv_prime;
515  fnv_hash ^= static_cast<unsigned char>((*it)[i]);
516  }
517  }
518 
519  QString fnv_string = QString::number(fnv_hash,16).toUpper();
520 
521  std::string dumpFilename = QString(dbgOutputDir_ + QDir::separator() + fileInfo.fileName() + fnv_string).toStdString();
522 
523  std::ofstream dumpStream(dumpFilename.c_str());
524  if (dumpStream.is_open())
525  {
526  for (GLSL::StringList::iterator it = shaderSrc.begin(); it != shaderSrc.end(); ++it)
527  dumpStream << it->c_str();
528  dumpStream.close();
529  }
530  }
531 
532  return oldCache->second;
533  }
534  else
535  {
536  cacheComputeShaders_.erase(oldCache);
537  }
538  }
539 
540  cacheComputeShaders_.push_back(std::pair<CacheEntry, GLSL::Program*>(newEntry, prog));
541 
542  return prog;
543 }
544 
546 {
547  if (_a->vertexFileLastMod != _b->vertexFileLastMod)
548  return false;
549 
550  if (_a->geometryFileLastMod != _b->geometryFileLastMod)
551  return false;
552 
553  if (_a->fragmentFileLastMod != _b->fragmentFileLastMod)
554  return false;
555 
556  if (_a->tessControlFileLastMod != _b->tessControlFileLastMod)
557  return false;
558 
559  if (_a->tessEvaluationFileLastMod != _b->tessEvaluationFileLastMod)
560  return false;
561  return true;
562 }
563 
565 {
566  if (_a->mods != _b->mods)
567  return -1;
568 
569  const ShaderGenDesc* a = &_a->desc;
570  const ShaderGenDesc* b = &_b->desc;
571 
572  if (_a->strFragmentTemplate != _b->strFragmentTemplate)
573  return -1;
574 
575  if (_a->strGeometryTemplate != _b->strGeometryTemplate)
576  return -1;
577 
578  if (_a->strVertexTemplate != _b->strVertexTemplate)
579  return -1;
580 
581  if (_a->strTessControlTemplate != _b->strTessControlTemplate)
582  return -1;
583 
584  if (_a->strTessEvaluationTemplate != _b->strTessEvaluationTemplate)
585  return -1;
586 
587  if (_a->macros != _b->macros)
588  return -1;
589 
590 
591  return *a == *b ? 0 : -1;
592 }
593 
594 
596 {
597  cache_.clear();
598  cacheStatic_.clear();
599  cacheComputeShaders_.clear();
600 }
601 
602 void ACG::ShaderCache::setDebugOutputDir(const char* _outputDir)
603 {
604  dbgOutputDir_ = _outputDir;
605 }
606 
607 //=============================================================================
608 } // namespace ACG
609 //=============================================================================
static QString getShaderDir()
GLSL::Program * getComputeProgram(const char *_computeShaderFile, QStringList *_macros=0, bool _verbose=true)
Query a static compute shader program from cache.
Definition: ShaderCache.cc:440
void setSource(const StringList &source)
Upload the source of the shader.
Definition: GLSLShader.cc:103
const QStringList & getTessEvaluationShaderCode()
Returns generated tessellation control shader code.
bool hasTessControlShader() const
check whether there is a tess-control shader present
void setDebugOutputDir(const char *_outputDir)
Enable debug output of generated shaders to specified directory.
Definition: ShaderCache.cc:602
Namespace providing different geometric functions concerning angles.
GLSL fragment shader.
Definition: GLSLShader.hh:110
bool compile(bool verbose=true)
Compile the shader object.
Definition: GLSLShader.cc:146
const QStringList & getTessControlShaderCode()
Returns generated vertex shader code.
GLSL::PtrProgram loadComputeProgram(const char *computeShaderFile, const GLSL::StringList *macros, bool verbose)
Definition: GLSLShader.cc:1154
const QStringList & getFragmentShaderCode()
Returns generated fragment shader code.
bool hasTessEvaluationShader() const
check whether there is a tess-evaluation shader present
GLSL::StringList loadShader(const char *filename, const GLSL::StringList *macros, bool appendNewLineChar, GLSL::StringList *outIncludes)
Loads the shader source.
Definition: GLSLShader.cc:921
void glCheckErrors()
Definition: GLError.hh:96
GLSL vertex shader.
Definition: GLSLShader.hh:95
bool isLinked()
Returns if the program object has been succesfully linked.
Definition: GLSLShader.cc:370
void attach(PtrConstShader _shader)
Attaches a shader object to the program object.
Definition: GLSLShader.cc:292
GLSL::PtrProgram loadProgram(const char *vertexShaderFile, const char *tessControlShaderFile, const char *tessEvaluationShaderFile, const char *geometryShaderFile, const char *fragmentShaderFile, const GLSL::StringList *macros, bool verbose)
Definition: GLSLShader.cc:1076
GLSL program class.
Definition: GLSLShader.hh:211
static ShaderCache * getInstance()
Return instance of the ShaderCache singleton.
Definition: ShaderCache.cc:84
virtual ~ShaderCache()
Destructor.
Definition: ShaderCache.cc:71
Cache for shaders.
Definition: ShaderCache.hh:73
GLSL geometry shader.
Definition: GLSLShader.hh:124
GLSL::Program * getProgram(const ShaderGenDesc *_desc, const std::vector< unsigned int > &_mods)
Query a dynamically generated program from cache.
Definition: ShaderCache.cc:102
void clearCache()
Delete all cached shaders.
Definition: ShaderCache.cc:595
const QStringList & getGeometryShaderCode()
Returns generated tessellation evaluation shader code.
int compareShaderGenDescs(const CacheEntry *_a, const CacheEntry *_b)
Returns true, if the shaders are not equal.
Definition: ShaderCache.cc:564
QString toString() const
convert ShaderGenDesc to string format for debugging
A generic shader base class.
Definition: GLSLShader.hh:71
const QStringList & getVertexShaderCode()
Returns generated vertex shader code.
bool hasGeometryShader() const
check whether there is a geometry shader present
bool compareTimeStamp(const CacheEntry *_a, const CacheEntry *_b)
Returns true, if the shaders have the timestamp.
Definition: ShaderCache.cc:545
void link()
Links the shader objects to the program.
Definition: GLSLShader.cc:326