Developer Documentation
Loading...
Searching...
No Matches
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.
static ShaderCache * getInstance()
Return instance of the ShaderCache singleton.
void clearCache()
Delete all cached shaders.
GLSL::Program * getComputeProgram(const char *_computeShaderFile, QStringList *_macros=0, bool _verbose=true)
Query a static compute shader program from cache.
int compareShaderGenDescs(const CacheEntry *_a, const CacheEntry *_b)
Returns true, if the shaders are not equal.
bool compareTimeStamp(const CacheEntry *_a, const CacheEntry *_b)
Returns true, if the shaders have the timestamp.
GLSL::Program * getProgram(const ShaderGenDesc *_desc, const std::vector< unsigned int > &_mods)
Query a dynamically generated program from cache.
void setDebugOutputDir(const char *_outputDir)
Enable debug output of generated shaders to specified directory.
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.
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.
GLSL geometry shader.
GLSL program class.
bool isLinked()
Returns if the program object has been succesfully linked.
void link()
Links the shader objects to the program.
void attach(PtrConstShader _shader)
Attaches a shader object to the program object.
A generic shader base class.
Definition GLSLShader.hh:71
bool compile(bool verbose=true)
Compile the shader object.
void setSource(const StringList &source)
Upload the source of the shader.
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)
GLSL::StringList loadShader(const char *filename, const GLSL::StringList *macros, bool appendNewLineChar, GLSL::StringList *outIncludes)
Loads the shader source.
GLSL::PtrProgram loadComputeProgram(const char *computeShaderFile, const GLSL::StringList *macros, bool verbose)