1 // Copyright © 2008-2021 Pioneer Developers. See AUTHORS.txt for details
2 // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
3 
4 #include "Program.h"
5 #include "FileSystem.h"
6 #include "StringF.h"
7 #include "StringRange.h"
8 #include "graphics/Graphics.h"
9 #include "utils.h"
10 
11 #include <set>
12 
13 namespace Graphics {
14 
15 	namespace OGL {
16 
17 		// #version 140 for OpenGL3.1
18 		// #version 150 for OpenGL3.2
19 		// #version 330 for OpenGL3.3
20 		static const char *s_glslVersion = "#version 140\n";
21 		GLuint Program::s_curProgram = 0;
22 
23 		// Check and warn about compile & link errors
check_glsl_errors(const char * filename,GLuint obj)24 		static bool check_glsl_errors(const char *filename, GLuint obj)
25 		{
26 			//check if shader or program
27 			bool isShader = (glIsShader(obj) == GL_TRUE);
28 
29 			int infologLength = 0;
30 			char infoLog[1024];
31 
32 			if (isShader)
33 				glGetShaderInfoLog(obj, 1024, &infologLength, infoLog);
34 			else
35 				glGetProgramInfoLog(obj, 1024, &infologLength, infoLog);
36 
37 			GLint status;
38 			if (isShader)
39 				glGetShaderiv(obj, GL_COMPILE_STATUS, &status);
40 			else
41 				glGetProgramiv(obj, GL_LINK_STATUS, &status);
42 
43 			if (status == GL_FALSE) {
44 				Error("Error compiling shader: %s:\n%sOpenGL vendor: %s\nOpenGL renderer string: %s",
45 					filename, infoLog, glGetString(GL_VENDOR), glGetString(GL_RENDERER));
46 				return false;
47 			}
48 
49 #if 0
50 	if (!isShader) {
51 		// perform general validation that the program is usable
52 		glValidateProgram(obj);
53 
54 		glGetProgramiv(obj, GL_VALIDATE_STATUS, &status);
55 
56 		if (status == GL_FALSE) {
57 			Error("Error vaildating shader: %s:\n%sOpenGL vendor: %s\nOpenGL renderer string: %s",
58 				filename, infoLog, glGetString(GL_VENDOR), glGetString(GL_RENDERER));
59 			return false;
60 		}
61 	}
62 #endif
63 
64 			// TODO: this code is obsolete
65 			// Log warnings even if successfully compiled
66 			// Sometimes the log is full of junk "success" messages so
67 			// this is not a good use for OS::Warning
68 #ifndef NDEBUG
69 			if (infologLength > 0) {
70 				if (pi_strcasestr("infoLog", "warning"))
71 					Output("%s: %s", filename, infoLog);
72 			}
73 #endif
74 
75 			return true;
76 		}
77 
78 		struct Shader {
ShaderGraphics::OGL::Shader79 			Shader(GLenum type, const std::string &filename, const std::string &defines)
80 			{
81 				RefCountedPtr<FileSystem::FileData> filecode = FileSystem::gameDataFiles.ReadFile(filename);
82 
83 				if (!filecode.Valid())
84 					Error("Could not load %s", filename.c_str());
85 
86 				std::string strCode(filecode->AsStringRange().ToString());
87 				size_t found = strCode.find("#include");
88 				while (found != std::string::npos) {
89 					// find the name of the file to include
90 					const size_t begFilename = strCode.find_first_of("\"", found + 8) + 1;
91 					const size_t endFilename = strCode.find_first_of("\"", begFilename + 1);
92 
93 					const std::string incFilename = strCode.substr(begFilename, endFilename - begFilename);
94 
95 					// check we haven't it already included it (avoids circular dependencies)
96 					const std::set<std::string>::const_iterator foundIt = previousIncludes.find(incFilename);
97 					if (foundIt != previousIncludes.end()) {
98 						Error("Circular, or multiple, include of %s\n", incFilename.c_str());
99 					} else {
100 						previousIncludes.insert(incFilename);
101 					}
102 
103 					// build path for include
104 					const std::string incPathBuffer = stringf("shaders/opengl/%0", incFilename);
105 
106 					// read included file
107 					RefCountedPtr<FileSystem::FileData> incCode = FileSystem::gameDataFiles.ReadFile(incPathBuffer);
108 					assert(incCode.Valid());
109 
110 					if (incCode.Valid()) {
111 						// replace the #include and filename with the included files text
112 						strCode.replace(found, (endFilename + 1) - found, incCode->GetData(), incCode->GetSize());
113 						found = strCode.find("#include");
114 					} else {
115 						Error("Could not load shader #include %s for shader %s\n", incPathBuffer.c_str(), filename.c_str());
116 					}
117 				}
118 				// Store the modified text with the included files (if any)
119 				const StringRange code(strCode.c_str(), strCode.size());
120 
121 				// Build the final shader text to be compiled
122 				AppendSource(s_glslVersion);
123 				AppendSource(defines.c_str());
124 				if (type == GL_VERTEX_SHADER) {
125 					AppendSource("#define VERTEX_SHADER\n");
126 				} else {
127 					AppendSource("#define FRAGMENT_SHADER\n");
128 				}
129 				AppendSource(code.StripUTF8BOM());
130 #if 0
131 		static bool s_bDumpShaderSource = true;
132 		if (s_bDumpShaderSource) {
133 			const char SHADER_OUT_DIR_NAME[] = "shaders";
134 			const char SHADER_OGL_OUT_DIR_NAME[] = "shaders/opengl";
135 			FileSystem::userFiles.MakeDirectory(SHADER_OUT_DIR_NAME);
136 			FileSystem::userFiles.MakeDirectory(SHADER_OGL_OUT_DIR_NAME);
137 			const std::string outFilename(FileSystem::GetUserDir() + "/" + filename);
138 			FILE *tmp = fopen(outFilename.c_str(), "wb");
139 			if(tmp) {
140 				Output("%s", filename);
141 				for( Uint32 i=0; i<blocks.size(); i++ ) {
142 					const char *block = blocks[i];
143 					const GLint sizes = block_sizes[i];
144 					if(block && sizes>0) {
145 						fprintf(tmp, "%.*s", sizes, block);
146 					}
147 				}
148 				fclose(tmp);
149 			} else {
150 				Output("Could not open file %s", outFilename.c_str());
151 			}
152 		}
153 #endif
154 				shader = glCreateShader(type);
155 				if (glIsShader(shader) != GL_TRUE)
156 					throw ShaderException();
157 
158 				Compile(shader);
159 
160 				if (!check_glsl_errors(filename.c_str(), shader))
161 					throw ShaderException();
162 			};
163 
~ShaderGraphics::OGL::Shader164 			~Shader()
165 			{
166 				glDeleteShader(shader);
167 			}
168 
169 			GLuint shader;
170 
171 		private:
AppendSourceGraphics::OGL::Shader172 			void AppendSource(const char *str)
173 			{
174 				blocks.push_back(str);
175 				block_sizes.push_back(std::strlen(str));
176 			}
177 
AppendSourceGraphics::OGL::Shader178 			void AppendSource(StringRange str)
179 			{
180 				blocks.push_back(str.begin);
181 				block_sizes.push_back(str.Size());
182 			}
183 
CompileGraphics::OGL::Shader184 			void Compile(GLuint shader_id)
185 			{
186 				assert(blocks.size() == block_sizes.size());
187 				glShaderSource(shader_id, blocks.size(), &blocks[0], &block_sizes[0]);
188 				glCompileShader(shader_id);
189 			}
190 
191 			std::vector<const char *> blocks;
192 			std::vector<GLint> block_sizes;
193 			std::set<std::string> previousIncludes;
194 		};
195 
Program()196 		Program::Program() :
197 			m_name(""),
198 			m_defines(""),
199 			m_program(0),
200 			success(false)
201 		{
202 		}
203 
Program(const std::string & name,const std::string & defines)204 		Program::Program(const std::string &name, const std::string &defines) :
205 			m_name(name),
206 			m_defines(defines),
207 			m_program(0),
208 			success(false)
209 		{
210 			LoadShaders(name, defines);
211 			InitUniforms();
212 		}
213 
~Program()214 		Program::~Program()
215 		{
216 			glDeleteProgram(m_program);
217 		}
218 
Reload()219 		void Program::Reload()
220 		{
221 			Unuse();
222 			glDeleteProgram(m_program);
223 			LoadShaders(m_name, m_defines);
224 			InitUniforms();
225 		}
226 
Use()227 		void Program::Use()
228 		{
229 			if (s_curProgram != m_program)
230 				glUseProgram(m_program);
231 			s_curProgram = m_program;
232 		}
233 
Unuse()234 		void Program::Unuse()
235 		{
236 			glUseProgram(0);
237 			s_curProgram = 0;
238 		}
239 
240 		//load, compile and link
LoadShaders(const std::string & name,const std::string & defines)241 		void Program::LoadShaders(const std::string &name, const std::string &defines)
242 		{
243 			PROFILE_SCOPED()
244 			const std::string filename = std::string("shaders/opengl/") + name;
245 
246 			//load, create and compile shaders
247 			Shader vs(GL_VERTEX_SHADER, filename + ".vert", defines);
248 			Shader fs(GL_FRAGMENT_SHADER, filename + ".frag", defines);
249 
250 			//create program, attach shaders and link
251 			m_program = glCreateProgram();
252 			if (glIsProgram(m_program) != GL_TRUE)
253 				throw ProgramException();
254 
255 			glAttachShader(m_program, vs.shader);
256 
257 			glAttachShader(m_program, fs.shader);
258 
259 			//extra attribs, if they exist
260 			glBindAttribLocation(m_program, 0, "a_vertex");
261 			glBindAttribLocation(m_program, 1, "a_normal");
262 			glBindAttribLocation(m_program, 2, "a_color");
263 			glBindAttribLocation(m_program, 3, "a_uv0");
264 			glBindAttribLocation(m_program, 4, "a_uv1");
265 			glBindAttribLocation(m_program, 5, "a_tangent");
266 			glBindAttribLocation(m_program, 6, "a_transform");
267 			// a_transform @ 6 shadows (uses) 7, 8, and 9
268 			// next available is layout (location = 10)
269 
270 			glBindFragDataLocation(m_program, 0, "frag_color");
271 
272 			glLinkProgram(m_program);
273 
274 			success = check_glsl_errors(name.c_str(), m_program);
275 
276 			//shaders may now be deleted by Shader destructor
277 		}
278 
InitUniforms()279 		void Program::InitUniforms()
280 		{
281 			PROFILE_SCOPED()
282 			//Init generic uniforms, like matrices
283 			uProjectionMatrix.Init("uProjectionMatrix", m_program);
284 			uViewMatrix.Init("uViewMatrix", m_program);
285 			uViewMatrixInverse.Init("uViewMatrixInverse", m_program);
286 			uViewProjectionMatrix.Init("uViewProjectionMatrix", m_program);
287 			uNormalMatrix.Init("uNormalMatrix", m_program);
288 
289 			//Light uniform parameters
290 			char cLight[64];
291 			for (int i = 0; i < 4; i++) {
292 				snprintf(cLight, 64, "uLight[%d]", i);
293 				const std::string strLight(cLight);
294 				lights[i].diffuse.Init((strLight + ".diffuse").c_str(), m_program);
295 				lights[i].specular.Init((strLight + ".specular").c_str(), m_program);
296 				lights[i].position.Init((strLight + ".position").c_str(), m_program);
297 			}
298 
299 			invLogZfarPlus1.Init("invLogZfarPlus1", m_program);
300 			diffuse.Init("material.diffuse", m_program);
301 			emission.Init("material.emission", m_program);
302 			specular.Init("material.specular", m_program);
303 			shininess.Init("material.shininess", m_program);
304 			texture0.Init("texture0", m_program);
305 			texture1.Init("texture1", m_program);
306 			texture2.Init("texture2", m_program);
307 			texture3.Init("texture3", m_program);
308 			texture4.Init("texture4", m_program);
309 			texture5.Init("texture5", m_program);
310 			texture6.Init("texture6", m_program);
311 			heatGradient.Init("heatGradient", m_program);
312 			heatingMatrix.Init("heatingMatrix", m_program);
313 			heatingNormal.Init("heatingNormal", m_program);
314 			heatingAmount.Init("heatingAmount", m_program);
315 			sceneAmbient.Init("scene.ambient", m_program);
316 		}
317 
318 	} // namespace OGL
319 
320 } // namespace Graphics
321