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