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;
160  fileName.sprintf("shader_%02d.glsl", counter++);
161  fileName = dbgOutputDir_ + QDir::separator() + fileName;
162 
163  QFile fileOut(fileName);
164  if (fileOut.open(QFile::WriteOnly | QFile::Truncate))
165  {
166  QTextStream outStrm(&fileOut);
167 
168  outStrm << _desc->toString();
169  outStrm << "\nmods: ";
170 
171  for (size_t i = 0; i < _mods.size(); ++i)
172  outStrm << _mods[i] << (i+1 < _mods.size() ? ", " : "");
173  outStrm << "\n";
174 
175 
176  outStrm << "\n---------------------vertex-shader--------------------\n\n";
177 
178  for (int i = 0; i < progGen.getVertexShaderCode().size(); ++i)
179  outStrm << progGen.getVertexShaderCode()[i] << "\n";
180 
181  if (progGen.hasTessControlShader())
182  {
183  outStrm << "\n---------------------tesscontrol-shader--------------------\n\n";
184 
185  for (int i = 0; i < progGen.getTessControlShaderCode().size(); ++i)
186  outStrm << progGen.getTessControlShaderCode()[i] << "\n";
187  }
188 
189  if (progGen.hasTessEvaluationShader())
190  {
191  outStrm << "\n---------------------tesseval-shader--------------------\n\n";
192 
193  for (int i = 0; i < progGen.getTessEvaluationShaderCode().size(); ++i)
194  outStrm << progGen.getTessEvaluationShaderCode()[i] << "\n";
195  }
196 
197  if (progGen.hasGeometryShader())
198  {
199  outStrm << "\n---------------------geometry-shader--------------------\n\n";
200 
201  for (int i = 0; i < progGen.getGeometryShaderCode().size(); ++i)
202  outStrm << progGen.getGeometryShaderCode()[i] << "\n";
203  }
204 
205  outStrm << "\n---------------------fragment-shader--------------------\n\n";
206 
207  for (int i = 0; i < progGen.getFragmentShaderCode().size(); ++i)
208  outStrm << progGen.getFragmentShaderCode()[i] << "\n";
209 
210 
211  fileOut.close();
212  }
213  }
214 
215  GLSL::FragmentShader* fragShader = new GLSL::FragmentShader();
216  GLSL::VertexShader* vertShader = new GLSL::VertexShader();
217 
218  vertShader->setSource(progGen.getVertexShaderCode());
219  fragShader->setSource(progGen.getFragmentShaderCode());
220 
221  vertShader->compile();
222  fragShader->compile();
223 
224  GLSL::Program* prog = new GLSL::Program();
225  prog->attach(vertShader);
226  prog->attach(fragShader);
227 
228  // Check if we have a geometry shader and if we have support for it, enable it here
229  if ( progGen.hasGeometryShader() ) {
230  GLSL::GeometryShader* geomShader = new GLSL::GeometryShader();
231  geomShader->setSource(progGen.getGeometryShaderCode());
232  geomShader->compile();
233  prog->attach(geomShader);
234  }
235 
236  // Check if we have tessellation shaders and if we have support for it, enable it here
237  if ( progGen.hasTessControlShader() || progGen.hasTessEvaluationShader() ) {
238  GLSL::Shader* tessControlShader = 0, *tessEvalShader = 0;
239 
240 #ifdef GL_ARB_tessellation_shader
241  tessControlShader = new GLSL::TessControlShader();
242  tessEvalShader = new GLSL::TessEvaluationShader();
243 #endif // GL_ARB_tessellation_shader
244 
245  if (tessControlShader && progGen.hasTessControlShader())
246  {
247  tessControlShader->setSource(progGen.getTessControlShaderCode());
248  tessControlShader->compile();
249  prog->attach(tessControlShader);
250  }
251 
252  if (tessEvalShader && progGen.hasTessEvaluationShader())
253  {
254  tessEvalShader->setSource(progGen.getTessEvaluationShaderCode());
255  tessEvalShader->compile();
256  prog->attach(tessEvalShader);
257  }
258 
259  }
260 
261  prog->link();
262  glCheckErrors();
263 
264  if (oldCache != cache_.end())
265  {
266  if (!prog->isLinked())
267  {
268  delete prog;
269  return oldCache->second;
270  }
271  else
272  {
273  cache_.erase(oldCache);
274  }
275  }
276 
277  cache_.push_back(std::pair<CacheEntry, GLSL::Program*>(newEntry, prog));
278 
279  return prog;
280 }
281 
282 GLSL::Program* ACG::ShaderCache::getProgram( const char* _vertexShaderFile,
283  const char* _tessControlShaderFile,
284  const char* _tessEvalShaderFile,
285  const char* _geometryShaderFile,
286  const char* _fragmentShaderFile,
287  QStringList* _macros, bool _verbose )
288 {
289  CacheEntry newEntry;
290 
291 
292  // store filenames and timestamps in new entry
293 
294  // fragment shader
295  QFileInfo fileInfo(_fragmentShaderFile);
296  if (fileInfo.isRelative())
297  {
298  QString absFilename = ACG::ShaderProgGenerator::getShaderDir() + QDir::separator() + QString(_fragmentShaderFile);
299  fileInfo = QFileInfo(absFilename);
300 
301  newEntry.strFragmentTemplate = absFilename;
302  newEntry.fragmentFileLastMod = fileInfo.lastModified();
303  }
304  else
305  {
306  newEntry.strFragmentTemplate = _fragmentShaderFile;
307  newEntry.fragmentFileLastMod = fileInfo.lastModified();
308  }
309 
310  // vertex shader
311  fileInfo = QFileInfo(_vertexShaderFile);
312  if (fileInfo.isRelative())
313  {
314  QString absFilename = ACG::ShaderProgGenerator::getShaderDir() + QDir::separator() + QString(_vertexShaderFile);
315  fileInfo = QFileInfo(absFilename);
316 
317  newEntry.strVertexTemplate = absFilename;
318  newEntry.vertexFileLastMod = fileInfo.lastModified();
319  }
320  else
321  {
322  newEntry.strVertexTemplate = _vertexShaderFile;
323  newEntry.vertexFileLastMod = fileInfo.lastModified();
324  }
325 
326 
327  // geometry shader
328  if (_geometryShaderFile)
329  {
330  fileInfo = QFileInfo(_geometryShaderFile);
331  if (fileInfo.isRelative())
332  {
333  QString absFilename = ACG::ShaderProgGenerator::getShaderDir() + QDir::separator() + QString(_geometryShaderFile);
334  fileInfo = QFileInfo(absFilename);
335 
336  newEntry.strGeometryTemplate = absFilename;
337  newEntry.geometryFileLastMod = fileInfo.lastModified();
338  }
339  else
340  {
341  newEntry.strGeometryTemplate = _geometryShaderFile;
342  newEntry.geometryFileLastMod = fileInfo.lastModified();
343  }
344  }
345 
346  // tess-ctrl shader
347  if (_tessControlShaderFile)
348  {
349  fileInfo = QFileInfo(_tessControlShaderFile);
350  if (fileInfo.isRelative())
351  {
352  QString absFilename = ACG::ShaderProgGenerator::getShaderDir() + QDir::separator() + QString(_tessControlShaderFile);
353  fileInfo = QFileInfo(absFilename);
354 
355  newEntry.strTessControlTemplate = absFilename;
356  newEntry.tessControlFileLastMod = fileInfo.lastModified();
357  }
358  else
359  {
360  newEntry.strTessControlTemplate = _tessControlShaderFile;
361  newEntry.tessControlFileLastMod = fileInfo.lastModified();
362  }
363  }
364 
365  // tess-eval shader
366  if (_tessEvalShaderFile)
367  {
368  fileInfo = QFileInfo(_tessEvalShaderFile);
369  if (fileInfo.isRelative())
370  {
371  QString absFilename = ACG::ShaderProgGenerator::getShaderDir() + QDir::separator() + QString(_tessEvalShaderFile);
372  fileInfo = QFileInfo(absFilename);
373 
374  newEntry.strTessEvaluationTemplate = absFilename;
375  newEntry.tessEvaluationFileLastMod = fileInfo.lastModified();
376  }
377  else
378  {
379  newEntry.strTessEvaluationTemplate = _tessEvalShaderFile;
380  newEntry.tessEvaluationFileLastMod = fileInfo.lastModified();
381  }
382  }
383 
384 
385 
386  if (_macros)
387  newEntry.macros = *_macros;
388 
389  CacheList::iterator oldCache = cacheStatic_.end();
390 
391  for (CacheList::iterator it = cacheStatic_.begin(); it != cacheStatic_.end(); ++it)
392  {
393  // If the shaders are equal, we return the cached entry
394  if (!compareShaderGenDescs(&it->first, &newEntry))
395  {
396  if ( timeCheck_ && !compareTimeStamp(&it->first, &newEntry))
397  oldCache = it;
398  else
399  return it->second;
400  }
401  }
402 
403 
404  // convert QStringList to GLSL::StringList
405 
406  GLSL::StringList glslMacros;
407 
408  if (_macros)
409  {
410  for (QStringList::const_iterator it = _macros->constBegin(); it != _macros->constEnd(); ++it)
411  glslMacros.push_back(it->toStdString());
412  }
413 
414 
415  GLSL::Program* prog = GLSL::loadProgram(_vertexShaderFile, _tessControlShaderFile, _tessEvalShaderFile, _geometryShaderFile, _fragmentShaderFile, &glslMacros, _verbose);
416  glCheckErrors();
417 
418  if (oldCache != cacheStatic_.end())
419  {
420  if (!prog || !prog->isLinked())
421  {
422  delete prog;
423  return oldCache->second;
424  }
425  else
426  {
427  cacheStatic_.erase(oldCache);
428  }
429  }
430 
431  cacheStatic_.push_back(std::pair<CacheEntry, GLSL::Program*>(newEntry, prog));
432 
433  return prog;
434 }
435 
436 GLSL::Program* ACG::ShaderCache::getProgram( const char* _vertexShaderFile, const char* _fragmentShaderFile, QStringList* _macros, bool _verbose )
437 {
438  return getProgram(_vertexShaderFile, 0, 0, 0, _fragmentShaderFile, _macros, _verbose);
439 }
440 
441 GLSL::Program* ACG::ShaderCache::getComputeProgram(const char* _computeShaderFile, QStringList* _macros /* = 0 */, bool _verbose /* = true */)
442 {
443  CacheEntry newEntry;
444 
445  // store filenames and timestamps in new entry
446  // use vertex shader filename as compute shader
447  QFileInfo fileInfo(_computeShaderFile);
448  if (fileInfo.isRelative())
449  {
450  QString absFilename = ACG::ShaderProgGenerator::getShaderDir() + QDir::separator() + QString(_computeShaderFile);
451  fileInfo = QFileInfo(absFilename);
452 
453  newEntry.strVertexTemplate = absFilename;
454  newEntry.vertexFileLastMod = fileInfo.lastModified();
455  }
456  else
457  {
458  newEntry.strVertexTemplate = _computeShaderFile;
459  newEntry.vertexFileLastMod = fileInfo.lastModified();
460  }
461 
462  if (_macros)
463  newEntry.macros = *_macros;
464 
465  CacheList::iterator oldCache = cacheComputeShaders_.end();
466 
467  for (CacheList::iterator it = cacheComputeShaders_.begin(); it != cacheComputeShaders_.end(); ++it)
468  {
469  // If the shaders are equal, we return the cached entry
470  if (!compareShaderGenDescs(&it->first, &newEntry))
471  {
472  if ( ( timeCheck_ && !compareTimeStamp(&it->first, &newEntry)) || !it->second || !it->second->isLinked())
473  oldCache = it;
474  else
475  return it->second;
476  }
477  }
478 
479 
480  // convert QStringList to GLSL::StringList
481 
482  GLSL::StringList glslMacros;
483 
484  if (_macros)
485  {
486  for (QStringList::const_iterator it = _macros->constBegin(); it != _macros->constEnd(); ++it)
487  glslMacros.push_back(it->toStdString());
488  }
489 
490 
491  GLSL::Program* prog = GLSL::loadComputeProgram(newEntry.strVertexTemplate.toUtf8(), &glslMacros, _verbose);
492  glCheckErrors();
493 
494  if (oldCache != cacheComputeShaders_.end())
495  {
496  if (!prog || !prog->isLinked())
497  {
498  delete prog;
499 
500 
501  // dump shader source including macros to debug output
502  if (!glslMacros.empty() && !dbgOutputDir_.isEmpty())
503  {
504  GLSL::StringList shaderSrc = GLSL::loadShader(newEntry.strVertexTemplate.toUtf8(), &glslMacros);
505 
506  // compute FNV hash of macros
507 
508  unsigned int fnv_prime = 16777619;
509  unsigned int fnv_hash = 2166136261;
510 
511  for (GLSL::StringList::iterator it = shaderSrc.begin(); it != shaderSrc.end(); ++it)
512  {
513  for (size_t i = 0; i < it->length(); ++i)
514  {
515  fnv_hash *= fnv_prime;
516  fnv_hash ^= static_cast<unsigned char>((*it)[i]);
517  }
518  }
519 
520  QString fnv_string;
521  fnv_string.sprintf("%X", fnv_hash);
522 
523  std::string dumpFilename = QString(dbgOutputDir_ + QDir::separator() + fileInfo.fileName() + fnv_string).toStdString();
524 
525  std::ofstream dumpStream(dumpFilename.c_str());
526  if (dumpStream.is_open())
527  {
528  for (GLSL::StringList::iterator it = shaderSrc.begin(); it != shaderSrc.end(); ++it)
529  dumpStream << it->c_str();
530  dumpStream.close();
531  }
532  }
533 
534  return oldCache->second;
535  }
536  else
537  {
538  cacheComputeShaders_.erase(oldCache);
539  }
540  }
541 
542  cacheComputeShaders_.push_back(std::pair<CacheEntry, GLSL::Program*>(newEntry, prog));
543 
544  return prog;
545 }
546 
548 {
549  if (_a->vertexFileLastMod != _b->vertexFileLastMod)
550  return false;
551 
552  if (_a->geometryFileLastMod != _b->geometryFileLastMod)
553  return false;
554 
555  if (_a->fragmentFileLastMod != _b->fragmentFileLastMod)
556  return false;
557 
558  if (_a->tessControlFileLastMod != _b->tessControlFileLastMod)
559  return false;
560 
561  if (_a->tessEvaluationFileLastMod != _b->tessEvaluationFileLastMod)
562  return false;
563  return true;
564 }
565 
567 {
568  if (_a->mods != _b->mods)
569  return -1;
570 
571  const ShaderGenDesc* a = &_a->desc;
572  const ShaderGenDesc* b = &_b->desc;
573 
574  if (_a->strFragmentTemplate != _b->strFragmentTemplate)
575  return -1;
576 
577  if (_a->strGeometryTemplate != _b->strGeometryTemplate)
578  return -1;
579 
580  if (_a->strVertexTemplate != _b->strVertexTemplate)
581  return -1;
582 
583  if (_a->strTessControlTemplate != _b->strTessControlTemplate)
584  return -1;
585 
586  if (_a->strTessEvaluationTemplate != _b->strTessEvaluationTemplate)
587  return -1;
588 
589  if (_a->macros != _b->macros)
590  return -1;
591 
592 
593  return *a == *b ? 0 : -1;
594 }
595 
596 
598 {
599  cache_.clear();
600  cacheStatic_.clear();
601  cacheComputeShaders_.clear();
602 }
603 
604 void ACG::ShaderCache::setDebugOutputDir(const char* _outputDir)
605 {
606  dbgOutputDir_ = _outputDir;
607 }
608 
609 //=============================================================================
610 } // namespace ACG
611 //=============================================================================
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:441
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:604
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:597
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:566
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:547
void link()
Links the shader objects to the program.
Definition: GLSLShader.cc:326