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
61namespace ACG
62{
63
64ShaderCache::ShaderCache():
65 cache_(),
66 cacheStatic_(),
67 timeCheck_(false)
68{
69}
70
71ShaderCache::~ShaderCache()
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
102GLSL::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();
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
281GLSL::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);
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
435GLSL::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
440GLSL::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);
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
602void ACG::ShaderCache::setDebugOutputDir(const char* _outputDir)
603{
604 dbgOutputDir_ = _outputDir;
605}
606
607//=============================================================================
608} // namespace ACG
609//=============================================================================
Cache for shaders.
Definition: ShaderCache.hh:74
static ShaderCache * getInstance()
Return instance of the ShaderCache singleton.
Definition: ShaderCache.cc:84
void clearCache()
Delete all cached shaders.
Definition: ShaderCache.cc:595
GLSL::Program * getComputeProgram(const char *_computeShaderFile, QStringList *_macros=0, bool _verbose=true)
Query a static compute shader program from cache.
Definition: ShaderCache.cc:440
int compareShaderGenDescs(const CacheEntry *_a, const CacheEntry *_b)
Returns true, if the shaders are not equal.
Definition: ShaderCache.cc:564
bool compareTimeStamp(const CacheEntry *_a, const CacheEntry *_b)
Returns true, if the shaders have the timestamp.
Definition: ShaderCache.cc:545
GLSL::Program * getProgram(const ShaderGenDesc *_desc, const std::vector< unsigned int > &_mods)
Query a dynamically generated program from cache.
Definition: ShaderCache.cc:102
void setDebugOutputDir(const char *_outputDir)
Enable debug output of generated shaders to specified directory.
Definition: ShaderCache.cc:602
QString toString() const
convert ShaderGenDesc to string format for debugging
const QStringList & getTessEvaluationShaderCode()
Returns generated tessellation control shader code.
const QStringList & getTessControlShaderCode()
Returns generated vertex shader code.
bool hasTessEvaluationShader() const
check whether there is a tess-evaluation shader present
const QStringList & getGeometryShaderCode()
Returns generated tessellation evaluation shader code.
static QString getShaderDir()
bool hasGeometryShader() const
check whether there is a geometry shader present
bool hasTessControlShader() const
check whether there is a tess-control shader present
const QStringList & getFragmentShaderCode()
Returns generated fragment shader code.
const QStringList & getVertexShaderCode()
Returns generated vertex shader code.
GLSL fragment shader.
Definition: GLSLShader.hh:110
GLSL geometry shader.
Definition: GLSLShader.hh:124
GLSL program class.
Definition: GLSLShader.hh:211
bool isLinked()
Returns if the program object has been succesfully linked.
Definition: GLSLShader.cc:370
void link()
Links the shader objects to the program.
Definition: GLSLShader.cc:326
void attach(PtrConstShader _shader)
Attaches a shader object to the program object.
Definition: GLSLShader.cc:292
A generic shader base class.
Definition: GLSLShader.hh:71
bool compile(bool verbose=true)
Compile the shader object.
Definition: GLSLShader.cc:146
void setSource(const StringList &source)
Upload the source of the shader.
Definition: GLSLShader.cc:103
GLSL vertex shader.
Definition: GLSLShader.hh:95
Namespace providing different geometric functions concerning angles.
void glCheckErrors()
Definition: GLError.hh:96
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::StringList loadShader(const char *filename, const GLSL::StringList *macros, bool appendNewLineChar, GLSL::StringList *outIncludes)
Loads the shader source.
Definition: GLSLShader.cc:921
GLSL::PtrProgram loadComputeProgram(const char *computeShaderFile, const GLSL::StringList *macros, bool verbose)
Definition: GLSLShader.cc:1154