1 /*
2 * Stellarium Scenery3d Plug-in
3 *
4 * Copyright (C) 2014 Simon Parzer, Peter Neubauer, Georg Zotti, Andrei Borza, Florian Schaukowitsch
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
19 */
20
21 #include "StelOpenGL.hpp"
22 #include "ShaderManager.hpp"
23 #include "StelFileMgr.hpp"
24
25 #include <QDir>
26 #include <QOpenGLShaderProgram>
27 #include <QCryptographicHash>
28 #include <QDebug>
29
30 Q_LOGGING_CATEGORY(shaderMgr, "stel.plugin.scenery3d.shadermgr")
31
32 ShaderMgr::t_UniformStrings ShaderMgr::uniformStrings;
33 ShaderMgr::t_FeatureFlagStrings ShaderMgr::featureFlagsStrings;
34
ShaderMgr()35 ShaderMgr::ShaderMgr()
36 {
37 if(uniformStrings.size()==0)
38 {
39 //initialize the strings
40 uniformStrings["u_mModelView"] = UNIFORM_MAT_MODELVIEW;
41 uniformStrings["u_mProjection"] = UNIFORM_MAT_PROJECTION;
42 uniformStrings["u_mMVP"] = UNIFORM_MAT_MVP;
43 uniformStrings["u_mNormal"] = UNIFORM_MAT_NORMAL;
44 uniformStrings["u_mShadow0"] = UNIFORM_MAT_SHADOW0;
45 uniformStrings["u_mShadow1"] = UNIFORM_MAT_SHADOW1;
46 uniformStrings["u_mShadow2"] = UNIFORM_MAT_SHADOW2;
47 uniformStrings["u_mShadow3"] = UNIFORM_MAT_SHADOW3;
48 uniformStrings["u_mCubeMVP"] = UNIFORM_MAT_CUBEMVP;
49 uniformStrings["u_mCubeMVP[]"] = UNIFORM_MAT_CUBEMVP;
50 uniformStrings["u_mCubeMVP[0]"] = UNIFORM_MAT_CUBEMVP;
51
52 //textures
53 uniformStrings["u_texDiffuse"] = UNIFORM_TEX_DIFFUSE;
54 uniformStrings["u_texEmissive"] = UNIFORM_TEX_EMISSIVE;
55 uniformStrings["u_texBump"] = UNIFORM_TEX_BUMP;
56 uniformStrings["u_texHeight"] = UNIFORM_TEX_HEIGHT;
57 uniformStrings["u_texShadow0"] = UNIFORM_TEX_SHADOW0;
58 uniformStrings["u_texShadow1"] = UNIFORM_TEX_SHADOW1;
59 uniformStrings["u_texShadow2"] = UNIFORM_TEX_SHADOW2;
60 uniformStrings["u_texShadow3"] = UNIFORM_TEX_SHADOW3;
61
62 //materials
63 uniformStrings["u_vMatShininess"] = UNIFORM_MTL_SHININESS;
64 uniformStrings["u_vMatAlpha"] = UNIFORM_MTL_ALPHA;
65
66 //pre-modulated lighting (light * material)
67 uniformStrings["u_vMixAmbient"] = UNIFORM_MIX_AMBIENT;
68 uniformStrings["u_vMixDiffuse"] = UNIFORM_MIX_DIFFUSE;
69 uniformStrings["u_vMixSpecular"] = UNIFORM_MIX_SPECULAR;
70 uniformStrings["u_vMixTorchDiffuse"] = UNIFORM_MIX_TORCHDIFFUSE;
71 uniformStrings["u_vMixEmissive"] = UNIFORM_MIX_EMISSIVE;
72
73 //light
74 uniformStrings["u_vLightDirectionView"] = UNIFORM_LIGHT_DIRECTION_VIEW;
75 uniformStrings["u_fTorchAttenuation"] = UNIFORM_TORCH_ATTENUATION;
76
77 //others
78 uniformStrings["u_vColor"] = UNIFORM_VEC_COLOR;
79 uniformStrings["u_vSplits"] = UNIFORM_VEC_SPLITDATA;
80 uniformStrings["u_vLightOrthoScale"] = UNIFORM_VEC_LIGHTORTHOSCALE;
81 uniformStrings["u_vLightOrthoScale[]"] = UNIFORM_VEC_LIGHTORTHOSCALE;
82 uniformStrings["u_vLightOrthoScale[0]"] = UNIFORM_VEC_LIGHTORTHOSCALE;
83 uniformStrings["u_fAlphaThresh"] = UNIFORM_FLOAT_ALPHA_THRESH;
84 }
85 if(featureFlagsStrings.size()==0)
86 {
87 featureFlagsStrings["TRANSFORM"] = TRANSFORM;
88 featureFlagsStrings["SHADING"] = SHADING;
89 featureFlagsStrings["PIXEL_LIGHTING"] = PIXEL_LIGHTING;
90 featureFlagsStrings["SHADOWS"] = SHADOWS;
91 featureFlagsStrings["BUMP"] = BUMP;
92 featureFlagsStrings["HEIGHT"] = HEIGHT;
93 featureFlagsStrings["ALPHATEST"] = ALPHATEST;
94 featureFlagsStrings["SHADOW_FILTER"] = SHADOW_FILTER;
95 featureFlagsStrings["SHADOW_FILTER_HQ"] = SHADOW_FILTER_HQ;
96 featureFlagsStrings["MAT_SPECULAR"] = MAT_SPECULAR;
97 featureFlagsStrings["MAT_DIFFUSETEX"] = MAT_DIFFUSETEX;
98 featureFlagsStrings["MAT_EMISSIVETEX"] = MAT_EMISSIVETEX;
99 featureFlagsStrings["GEOMETRY_SHADER"] = GEOMETRY_SHADER;
100 featureFlagsStrings["CUBEMAP"] = CUBEMAP;
101 featureFlagsStrings["BLENDING"] = BLENDING;
102 featureFlagsStrings["TORCH"] = TORCH;
103 featureFlagsStrings["DEBUG"] = DEBUG;
104 featureFlagsStrings["PCSS"] = PCSS;
105 featureFlagsStrings["SINGLE_SHADOW_FRUSTUM"] = SINGLE_SHADOW_FRUSTUM;
106 featureFlagsStrings["OGL_ES2"] = OGL_ES2;
107 featureFlagsStrings["HW_SHADOW_SAMPLERS"] = HW_SHADOW_SAMPLERS;
108 }
109 }
110
~ShaderMgr()111 ShaderMgr::~ShaderMgr()
112 {
113 clearCache();
114 }
115
clearCache()116 void ShaderMgr::clearCache()
117 {
118 qCDebug(shaderMgr)<<"Clearing"<<m_shaderContentCache.size()<<"shaders";
119
120 //iterate over the shaderContentCache - this contains the same amount of shaders as actually exist!
121 //the shaderCache could contain duplicate entries
122 for (auto* shader : m_shaderContentCache)
123 {
124 if (shader)
125 delete shader;
126 }
127
128 m_shaderCache.clear();
129 m_uniformCache.clear();
130 m_shaderContentCache.clear();
131 }
132
findOrLoadShader(uint flags)133 QOpenGLShaderProgram* ShaderMgr::findOrLoadShader(uint flags)
134 {
135 auto it = m_shaderCache.find(flags);
136
137 // This may also return Q_NULLPTR if the load failed.
138 //We wait until user explictly forces shader reload until we try again to avoid spamming errors.
139 if(it!=m_shaderCache.end())
140 return *it;
141
142 //get shader file names
143 QString vShaderFile = getVShaderName(flags);
144 QString gShaderFile = getGShaderName(flags);
145 QString fShaderFile = getFShaderName(flags);
146 qCDebug(shaderMgr)<<"Loading Scenery3d shader: flags:"<<flags<<", vs:"<<vShaderFile<<", gs:"<<gShaderFile<<", fs:"<<fShaderFile<<"";
147
148 //load shader files & preprocess
149 QByteArray vShader,gShader,fShader;
150
151 QOpenGLShaderProgram *prog = Q_NULLPTR;
152
153 if(preprocessShader(vShaderFile,flags,vShader) &&
154 preprocessShader(gShaderFile,flags,gShader) &&
155 preprocessShader(fShaderFile,flags,fShader)
156 )
157 {
158 //check if this content-hash was already created for optimization
159 //(so that shaders with different flags, but identical implementation use the same program)
160 QCryptographicHash hash(QCryptographicHash::Sha256);
161 hash.addData(vShader);
162 hash.addData(gShader);
163 hash.addData(fShader);
164
165 QByteArray contentHash = hash.result();
166 if(m_shaderContentCache.contains(contentHash))
167 {
168 #ifndef NDEBUG
169 //qCDebug(shaderMgr)<<"Using existing shader with content-hash"<<contentHash.toHex();
170 #endif
171 prog = m_shaderContentCache[contentHash];
172 }
173 else
174 {
175 //we have to compile the shader
176 prog = new QOpenGLShaderProgram();
177
178 if(!loadShader(*prog,vShader,gShader,fShader))
179 {
180 delete prog;
181 prog = Q_NULLPTR;
182 qCCritical(shaderMgr)<<"ERROR: Shader '"<<flags<<"' could not be compiled. Fix errors and reload shaders or restart program.";
183 }
184 #ifndef NDEBUG
185 else
186 {
187 //qCDebug(shaderMgr)<<"Shader '"<<flags<<"' created, content-hash"<<contentHash.toHex();
188 }
189 #endif
190 m_shaderContentCache[contentHash] = prog;
191 }
192 }
193 else
194 {
195 qCCritical(shaderMgr)<<"ERROR: Shader '"<<flags<<"' could not be loaded/preprocessed.";
196 }
197
198
199 //may put null in cache on fail!
200 m_shaderCache[flags] = prog;
201
202 return prog;
203 }
204
getVShaderName(uint flags)205 QString ShaderMgr::getVShaderName(uint flags)
206 {
207 if(flags & SHADING)
208 {
209 if (! (flags & PIXEL_LIGHTING ))
210 return "s3d_vertexlit.vert";
211 else
212 return "s3d_pixellit.vert";
213 }
214 else if (flags & CUBEMAP)
215 {
216 return "s3d_cube.vert";
217 }
218 else if (flags & TRANSFORM)
219 {
220 return "s3d_transform.vert";
221 }
222 else if (flags & MAT_DIFFUSETEX)
223 {
224 return "s3d_texture.vert";
225 }
226 else if (flags & DEBUG)
227 {
228 return "s3d_debug.vert";
229 }
230 return QString();
231 }
232
getGShaderName(uint flags)233 QString ShaderMgr::getGShaderName(uint flags)
234 {
235 if(flags & GEOMETRY_SHADER)
236 {
237 if(flags & PIXEL_LIGHTING)
238 return "s3d_pixellit.geom";
239 else
240 return "s3d_vertexlit.geom";
241 }
242 return QString();
243 }
244
getFShaderName(uint flags)245 QString ShaderMgr::getFShaderName(uint flags)
246 {
247 if(flags & SHADING)
248 {
249 if (! (flags & PIXEL_LIGHTING ))
250 return "s3d_vertexlit.frag";
251 else
252 {
253 if(flags & OGL_ES2)
254 return "s3d_pixellit_es.frag"; //for various reasons, ES version is separate
255 else
256 return "s3d_pixellit.frag";
257 }
258 }
259 else if (flags & CUBEMAP)
260 {
261 return "s3d_cube.frag";
262 }
263 else if (flags & TRANSFORM)
264 {
265 //OGL ES2 always wants a fragment shader (at least on ANGLE)
266 if((flags & ALPHATEST) || (flags & OGL_ES2))
267 return "s3d_transform.frag";
268 }
269 else if (flags == MAT_DIFFUSETEX)
270 {
271 return "s3d_texture.frag";
272 }
273 else if (flags & DEBUG)
274 {
275 return "s3d_debug.frag";
276 }
277 return QString();
278 }
279
preprocessShader(const QString & fileName,const uint flags,QByteArray & processedSource)280 bool ShaderMgr::preprocessShader(const QString &fileName, const uint flags, QByteArray &processedSource)
281 {
282 if(fileName.isEmpty())
283 {
284 //no shader of this type
285 return true;
286 }
287
288 QDir dir("data/shaders/");
289 QString filePath = StelFileMgr::findFile(dir.filePath(fileName),StelFileMgr::File);
290
291 //open and load file
292 QFile file(filePath);
293 #ifndef NDEBUG
294 //qCDebug(shaderMgr)<<"File path:"<<filePath;
295 #endif
296 if(!file.open(QFile::ReadOnly))
297 {
298 qCCritical(shaderMgr)<<"Could not open file"<<filePath;
299 return false;
300 }
301
302 processedSource.clear();
303 processedSource.reserve(static_cast<int>(file.size()));
304
305 //use a text stream for "parsing"
306 QTextStream in(&file),lineStream;
307
308 QString line,word;
309 while(!in.atEnd())
310 {
311 line = in.readLine();
312 lineStream.setString(&line,QIODevice::ReadOnly);
313
314 QString write = line;
315
316 //read first word
317 lineStream>>word;
318 if(word == "#define")
319 {
320 //read second word
321 lineStream>>word;
322
323 //try to find it in our flags list
324 auto it = featureFlagsStrings.find(word);
325 if(it!=featureFlagsStrings.end())
326 {
327 bool val = it.value() & flags;
328 write = "#define " + word + (val?" 1":" 0");
329 #ifdef NDEBUG
330 }
331 #else
332 //output matches for debugging
333 //qCDebug(shaderMgr)<<"preprocess match: "<<line <<" --> "<<write;
334 }
335 else
336 {
337 //qCDebug(shaderMgr)<<"unknown define, ignoring: "<<line;
338 }
339 #endif
340 }
341
342 //write output
343 processedSource.append(write);
344 processedSource.append('\n');
345 }
346 return true;
347 }
348
loadShader(QOpenGLShaderProgram & program,const QByteArray & vShader,const QByteArray & gShader,const QByteArray & fShader)349 bool ShaderMgr::loadShader(QOpenGLShaderProgram& program, const QByteArray& vShader, const QByteArray& gShader, const QByteArray& fShader)
350 {
351 //clear old shader data, if exists
352 program.removeAllShaders();
353
354 if(!vShader.isEmpty())
355 {
356 if(!program.addShaderFromSourceCode(QOpenGLShader::Vertex,vShader))
357 {
358 qCCritical(shaderMgr) << "Unable to compile vertex shader";
359 qCCritical(shaderMgr) << program.log();
360 return false;
361 }
362 else
363 {
364 //TODO Qt wrapper does not seem to provide warnings (regardless of what its doc says)!
365 //Raise a bug with them or handle shader loading ourselves?
366 QString log = program.log().trimmed();
367 if(!log.isEmpty())
368 {
369 qCWarning(shaderMgr)<<"Vertex shader warnings:";
370 qCWarning(shaderMgr)<<log;
371 }
372 }
373 }
374
375 if(!gShader.isEmpty())
376 {
377 if(!program.addShaderFromSourceCode(QOpenGLShader::Geometry,gShader))
378 {
379 qCCritical(shaderMgr) << "Unable to compile geometry shader";
380 qCCritical(shaderMgr) << program.log();
381 return false;
382 }
383 else
384 {
385 //TODO Qt wrapper does not seem to provide warnings (regardless of what its doc says)!
386 //Raise a bug with them or handle shader loading ourselves?
387 QString log = program.log().trimmed();
388 if(!log.isEmpty())
389 {
390 qCWarning(shaderMgr)<<"Geometry shader warnings:";
391 qCWarning(shaderMgr)<<log;
392 }
393 }
394 }
395
396 if(!fShader.isEmpty())
397 {
398 if(!program.addShaderFromSourceCode(QOpenGLShader::Fragment,fShader))
399 {
400 qCCritical(shaderMgr) << "Unable to compile fragment shader";
401 qCCritical(shaderMgr) << program.log();
402 return false;
403 }
404 else
405 {
406 //TODO Qt wrapper does not seem to provide warnings (regardless of what its doc says)!
407 //Raise a bug with them or handle shader loading ourselves?
408 QString log = program.log().trimmed();
409 if(!log.isEmpty())
410 {
411 qCWarning(shaderMgr)<<"Fragment shader warnings:";
412 qCWarning(shaderMgr)<<log;
413 }
414 }
415 }
416
417 //Set attribute locations to hardcoded locations.
418 //This enables us to use a single VAO configuration for all shaders!
419 program.bindAttributeLocation("a_vertex",StelOpenGLArray::ATTLOC_VERTEX);
420 program.bindAttributeLocation("a_normal", StelOpenGLArray::ATTLOC_NORMAL);
421 program.bindAttributeLocation("a_texcoord",StelOpenGLArray::ATTLOC_TEXCOORD);
422 program.bindAttributeLocation("a_tangent",StelOpenGLArray::ATTLOC_TANGENT);
423 program.bindAttributeLocation("a_bitangent",StelOpenGLArray::ATTLOC_BITANGENT);
424
425
426 //link program
427 if(!program.link())
428 {
429 qCCritical(shaderMgr)<<"[ShaderMgr] unable to link shader";
430 qCCritical(shaderMgr)<<program.log();
431 return false;
432 }
433
434 buildUniformCache(program);
435 return true;
436 }
437
buildUniformCache(QOpenGLShaderProgram & program)438 void ShaderMgr::buildUniformCache(QOpenGLShaderProgram &program)
439 {
440 //this enumerates all available uniforms of this shader, and stores their locations in a map
441 GLuint prog = program.programId();
442 GLint numUniforms=0,bufSize;
443
444 QOpenGLFunctions* gl = QOpenGLContext::currentContext()->functions();
445 GL(gl->glGetProgramiv(prog, GL_ACTIVE_UNIFORMS, &numUniforms));
446 GL(gl->glGetProgramiv(prog, GL_ACTIVE_UNIFORM_MAX_LENGTH, &bufSize));
447
448 QByteArray buf(bufSize,'\0');
449 GLsizei length;
450 GLint size;
451 GLenum type;
452
453 #ifndef NDEBUG
454 //qCDebug(shaderMgr)<<"Shader has"<<numUniforms<<"uniforms";
455 #endif
456 for(uint i =0;i<static_cast<GLuint>(numUniforms);++i)
457 {
458 GL(gl->glGetActiveUniform(prog,i,bufSize,&length,&size,&type,buf.data()));
459 QString str(buf);
460 str = str.trimmed(); // no idea if this is required
461
462 GLint loc = program.uniformLocation(str);
463
464 auto it = uniformStrings.find(str);
465
466 // This may also return Q_NULLPTR if the load failed.
467 //We wait until user explictly forces shader reload until we try again to avoid spamming errors.
468 if(it!=uniformStrings.end())
469 {
470 //this is uniform we recognize
471 //need to get the uniforms location (!= index)
472 m_uniformCache[&program][*it] = static_cast<GLuint>(loc);
473 //output mapping for debugging
474 //qCDebug(shaderMgr)<<i<<loc<<str<<size<<type<<" mapped to "<<*it;
475 }
476 else
477 {
478 qCWarning(shaderMgr)<<i<<loc<<str<<size<<type<<" --- unknown uniform! ---";
479 }
480 }
481 }
482