-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathShaderProgram.hpp
420 lines (351 loc) · 13.1 KB
/
ShaderProgram.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
/***
author : r3dux
version : 0.3 - 15/01/2014
description: Gets GLSL source code either provided as strings or can load from filenames,
compiles the shaders, creates a shader program which the shaders are linked
to, then the program is validated and is ready for use via myProgram.use(),
<draw-stuff-here> then calling myProgram.disable();
Attributes and uniforms are stored in <string, int> maps and can be added
via calls to addAttribute(<name-of-attribute>) and then the attribute
index can be obtained via myProgram.attribute(<name-of-attribute>) - Uniforms
work in the exact same way.
***/
#ifndef SHADER_PROGRAM_HPP
#define SHADER_PROGRAM_HPP
#include<GL/glew.h>
#include<GL/glu.h>
#include<GL/gl.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include <map>
class ShaderProgram
{
private:
// static DEBUG flag - if set to false then, errors aside, we'll run completely silent
static const bool DEBUG = true;
// We'll use an enum to differentiate between shaders and shader programs when querying the info log
enum class ObjectType
{
SHADER, PROGRAM
};
// Shader program and individual shader Ids
GLuint programId;
GLuint vertexShaderId;
GLuint fragmentShaderId;
// How many shaders are attached to the shader program
GLuint shaderCount;
// Map of attributes and their binding locations
std::map<std::string, int> attributeMap;
// Map of uniforms and their binding locations
std::map<std::string, int> uniformMap;
// Has this shader program been initialised?
bool initialised;
// ---------- PRIVATE METHODS ----------
// Private method to compile a shader of a given type
GLuint compileShader(std::string shaderSource, GLenum shaderType)
{
std::string shaderTypeString;
switch (shaderType)
{
case GL_VERTEX_SHADER:
shaderTypeString = "GL_VERTEX_SHADER";
break;
case GL_FRAGMENT_SHADER:
shaderTypeString = "GL_FRAGMENT_SHADER";
break;
case GL_GEOMETRY_SHADER:
throw std::runtime_error("Geometry shaders are unsupported at this time.");
break;
default:
throw std::runtime_error("Bad shader type enum in compileShader.");
break;
}
// Generate a shader id
// Note: Shader id will be non-zero if successfully created.
GLuint shaderId = glCreateShader(shaderType);
if (shaderId == 0)
{
// Display the shader log via a runtime_error
throw std::runtime_error("Could not create shader of type " + shaderTypeString + ": " + getInfoLog(ObjectType::SHADER, shaderId));
}
// Get the source string as a pointer to an array of characters
const char *shaderSourceChars = shaderSource.c_str();
// Attach the GLSL source code to the shader
// Params: GLuint shader, GLsizei count, const GLchar **string, const GLint *length
// Note: The pointer to an array of source chars will be null terminated, so we don't need to specify the length and can instead use NULL.
glShaderSource(shaderId, 1, &shaderSourceChars, NULL);
// Compile the shader
glCompileShader(shaderId);
// Check the compilation status and throw a runtime_error if shader compilation failed
GLint shaderStatus;
glGetShaderiv(shaderId, GL_COMPILE_STATUS, &shaderStatus);
if (shaderStatus == GL_FALSE)
{
GLint maxLength = 0;
glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &maxLength);
// The maxLength includes the NULL character
GLchar* errorLog = (GLchar*)malloc(maxLength);
glGetShaderInfoLog(shaderId, maxLength, &maxLength, errorLog);
std::cout << "Shader compile error : " << errorLog << std::endl;
free(errorLog);
glDeleteShader(shaderId); // Don't leak the shader.
throw std::runtime_error(shaderTypeString + " compilation failed: " + getInfoLog(ObjectType::SHADER, shaderId));
}
else
{
if (DEBUG)
{
std::cout << shaderTypeString << " shader compilation successful." << std::endl;
}
}
// If everything went well, return the shader id
return shaderId;
}
// Private method to compile/attach/link/verify the shaders.
// Note: Rather than returning a boolean as a success/fail status we'll just consider
// a failure here to be an unrecoverable error and throw a runtime_error.
void initialise(std::string vertexShaderSource, std::string fragmentShaderSource)
{
// Compile the shaders and return their id values
vertexShaderId = compileShader(vertexShaderSource, GL_VERTEX_SHADER);
fragmentShaderId = compileShader(fragmentShaderSource, GL_FRAGMENT_SHADER);
// Attach the compiled shaders to the shader program
glAttachShader(programId, vertexShaderId);
glAttachShader(programId, fragmentShaderId);
// Link the shader program - details are placed in the program info log
glLinkProgram(programId);
// Once the shader program has the shaders attached and linked, the shaders are no longer required.
// If the linking failed, then we're going to abort anyway so we still detach the shaders.
glDetachShader(programId, vertexShaderId);
glDetachShader(programId, fragmentShaderId);
// Check the program link status and throw a runtime_error if program linkage failed.
GLint programLinkSuccess = GL_FALSE;
glGetProgramiv(programId, GL_LINK_STATUS, &programLinkSuccess);
if (programLinkSuccess == GL_TRUE)
{
if (DEBUG)
{
std::cout << "Shader program link successful." << std::endl;
}
}
else
{
GLint maxLength = 0;
glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &maxLength);
//The maxLength includes the NULL character
GLchar *log = (GLchar *)malloc(maxLength);
glGetProgramInfoLog(programId, maxLength, 0, log);
std::cout << "Linker error : " << log << std::endl;
free(log);
//The program is useless now. So delete it.
glDeleteProgram(programId);
throw std::runtime_error("Shader program link failed: " + getInfoLog(ObjectType::PROGRAM, programId));
}
// Validate the shader program
glValidateProgram(programId);
// Check the validation status and throw a runtime_error if program validation failed
GLint programValidatationStatus;
glGetProgramiv(programId, GL_VALIDATE_STATUS, &programValidatationStatus);
if (programValidatationStatus == GL_TRUE)
{
if (DEBUG)
{
std::cout << "Shader program validation successful." << std::endl;
}
}
else
{
throw std::runtime_error("Shader program validation failed: " + getInfoLog(ObjectType::PROGRAM, programId));
}
// Finally, the shader program is initialised
initialised = true;
}
// Private method to load the shader source code from a file
std::string loadShaderFromFile(const std::string filename)
{
// Create an input filestream and attempt to open the specified file
std::ifstream file(filename.c_str());
// If we couldn't open the file we'll bail out
if (!file.good())
{
throw std::runtime_error("Failed to open file: " + filename);
}
// Otherwise, create a string stream...
std::stringstream stream;
// ...and dump the contents of the file into it.
stream << file.rdbuf();
// Now that we've read the file we can close it
file.close();
// Finally, convert the stringstream into a string and return it
return stream.str();
}
// Private method to return the current shader program info log as a string
std::string getInfoLog(ObjectType type, int id)
{
GLint infoLogLength;
if (type == ObjectType::SHADER)
{
glGetShaderiv(id, GL_INFO_LOG_LENGTH, &infoLogLength);
}
else // type must be ObjectType::PROGRAM
{
glGetProgramiv(id, GL_INFO_LOG_LENGTH, &infoLogLength);
}
GLchar *infoLog = new GLchar[infoLogLength + 1];
if (type == ObjectType::SHADER)
{
glGetShaderInfoLog(id, infoLogLength, NULL, infoLog);
}
else // type must be ObjectType::PROGRAM
{
glGetProgramInfoLog(id, infoLogLength, NULL, infoLog);
}
// Convert the info log to a string
std::string infoLogString(infoLog);
// Delete the char array version of the log
delete[] infoLog;
// Finally, return the string version of the info log
return infoLogString;
}
public:
// Constructor
ShaderProgram()
{
// We start in a non-initialised state - calling initFromFiles() or initFromStrings() will
// initialise us.
initialised = false;
// Generate a unique Id / handle for the shader program
// Note: We MUST have a valid rendering context before generating the programId or we'll segfault!
programId = glCreateProgram();
glUseProgram(programId);
// Initially, we have zero shaders attached to the program
shaderCount = 0;
}
// Destructor
~ShaderProgram()
{
// Delete the shader program from the graphics card memory to
// free all the resources it's been using
glDeleteProgram(programId);
}
// Method to initialise a shader program from shaders provided as files
void initFromFiles(std::string vertexShaderFilename, std::string fragmentShaderFilename)
{
// Get the shader file contents as strings
std::string vertexShaderSource = loadShaderFromFile(vertexShaderFilename);
std::string fragmentShaderSource = loadShaderFromFile(fragmentShaderFilename);
initialise(vertexShaderSource, fragmentShaderSource);
}
// Method to initialise a shader program from shaders provided as strings
void initFromStrings(std::string vertexShaderSource, std::string fragmentShaderSource)
{
initialise(vertexShaderSource, fragmentShaderSource);
}
// Method to enable the shader program - we'll suggest this for inlining
inline void use()
{
// Santity check that we're initialised and ready to go...
if (initialised)
{
glUseProgram(programId);
}
else
{
std::string msg = "Shader program " + programId;
msg += " not initialised - aborting.";
throw std::runtime_error(msg);
}
}
// Method to disable the shader - we'll also suggest this for inlining
inline void disable()
{
glUseProgram(0);
}
// Method to return the bound location of a named attribute, or -1 if the attribute was not found
GLuint attribute(const std::string attributeName)
{
// You could do this method with the single line:
//
// return attributeMap[attribute];
//
// BUT, if you did, and you asked it for a named attribute which didn't exist
// like: attributeMap["FakeAttrib"] then the method would return an invalid
// value which will likely cause the program to segfault. So we're making sure
// the attribute asked for exists, and if it doesn't then we alert the user & bail.
// Create an iterator to look through our attribute map (only create iterator on first run -
// reuse it for all further calls).
static std::map<std::string, int>::const_iterator attributeIter;
// Try to find the named attribute
attributeIter = attributeMap.find(attributeName);
// Not found? Bail.
if (attributeIter == attributeMap.end())
{
throw std::runtime_error("Could not find attribute in shader program: " + attributeName);
}
// Otherwise return the attribute location from the attribute map
return attributeMap[attributeName];
}
// Method to returns the bound location of a named uniform
GLuint uniform(const std::string uniformName)
{
// Note: You could do this method with the single line:
//
// return uniformLocList[uniform];
//
// But we're not doing that. Explanation in the attribute() method above.
// Create an iterator to look through our uniform map (only create iterator on first run -
// reuse it for all further calls).
static std::map<std::string, int>::const_iterator uniformIter;
// Try to find the named uniform
uniformIter = uniformMap.find(uniformName);
// Found it? Great - pass it back! Didn't find it? Alert user and halt.
if (uniformIter == uniformMap.end())
{
throw std::runtime_error("Could not find uniform in shader program: " + uniformName);
}
// Otherwise return the attribute location from the uniform map
return uniformMap[uniformName];
}
// Method to add an attribute to the shader and return the bound location
int addAttribute(const std::string attributeName)
{
// Add the attribute location value for the attributeName key
attributeMap[attributeName] = glGetAttribLocation(programId, attributeName.c_str());
// Check to ensure that the shader contains an attribute with this name
if (attributeMap[attributeName] == -1)
{
throw std::runtime_error("Could not add attribute: " + attributeName + " - location returned -1.");
}
else // Valid attribute location? Inform user if we're in debug mode.
{
if (DEBUG)
{
std::cout << "Attribute " << attributeName << " bound to location: " << attributeMap[attributeName] << std::endl;
}
}
// Return the attribute location
return attributeMap[attributeName];
}
// Method to add a uniform to the shader and return the bound location
int addUniform(const std::string uniformName)
{
// Add the uniform location value for the uniformName key
uniformMap[uniformName] = glGetUniformLocation(programId, uniformName.c_str());
// Check to ensure that the shader contains a uniform with this name
if (uniformMap[uniformName] == -1)
{
throw std::runtime_error("Could not add uniform: " + uniformName + " - location returned -1.");
}
else // Valid uniform location? Inform user if we're in debug mode.
{
if (DEBUG)
{
std::cout << "Uniform " << uniformName << " bound to location: " << uniformMap[uniformName] << std::endl;
}
}
// Return the uniform location
return uniformMap[uniformName];
}
}; // End of class
#endif // SHADER_PROGRAM_HPP