1 module gfm.opengl.program;
2 
3 import std.conv,
4        std.string,
5        std.regex,
6        std.algorithm;
7 
8 import derelict.opengl3.gl3;
9 
10 import gfm.math.vector,
11        gfm.math.matrix,
12        gfm.opengl.opengl,
13        gfm.opengl.shader,
14        gfm.opengl.uniform,
15        gfm.opengl.uniformblock;
16 
17 /// OpenGL Program wrapper.
18 final class GLProgram
19 {
20     public
21     {
22         /// Creates an empty program.
23         /// Throws: $(D OpenGLException) on error.
24         this(OpenGL gl)
25         {
26             _gl = gl;
27             _program = glCreateProgram();
28             if (_program == 0)
29                 throw new OpenGLException("glCreateProgram failed");
30             _initialized = true;
31         }
32 
33         /// Creates a program from a set of compiled shaders.
34         /// Throws: $(D OpenGLException) on error.
35         this(OpenGL gl, GLShader[] shaders...)
36         {
37             this(gl);
38             attach(shaders);
39             link();
40         }
41 
42         /**
43          * Compiles N times the same GLSL source and link to a program.
44          *
45          * <p>
46          * The same input is compiled 1 to 6 times, each time prepended
47          * with a $(D #define) specific to a shader type.
48          * </p>
49          * $(UL
50          *    $(LI $(D VERTEX_SHADER))
51          *    $(LI $(D FRAGMENT_SHADER))
52          *    $(LI $(D GEOMETRY_SHADER))
53          *    $(LI $(D TESS_CONTROL_SHADER))
54          *    $(LI $(D TESS_EVALUATION_SHADER))
55          *    $(LI $(D COMPUTE_SHADER))
56          * )
57          * <p>
58          * Each of these macros are alternatively set to 1 while the others are
59          * set to 0. If such a macro isn't used in any preprocessor directive
60          * of your source, this shader stage is considered unused.</p>
61          *
62          * <p>For conformance reasons, any #version directive on the first line will stay at the top.</p>
63          *
64          * Warning: <b>THIS FUNCTION REWRITES YOUR SHADER A BIT.</b>
65          * Expect slightly wrong lines in GLSL compiler's error messages.
66          *
67          * Example of a combined shader source:
68          * ---
69          *      #version 110
70          *      uniform vec4 color;
71          *
72          *      #if VERTEX_SHADER
73          *
74          *      void main()
75          *      {
76          *          gl_Vertex = ftransform();
77          *      }
78          *
79          *      #elif FRAGMENT_SHADER
80          *
81          *      void main()
82          *      {
83          *          gl_FragColor = color;
84          *      }
85          *
86          *      #endif
87          * ---
88          *
89          * Limitations:
90          * $(UL
91          *   $(LI All of #preprocessor directives should not have whitespaces before the #.)
92          *   $(LI sourceLines elements should be individual lines!)
93          * )
94          *
95          * Throws: $(D OpenGLException) on error.
96          */
97         this(OpenGL gl, string[] sourceLines)
98         {
99             _gl = gl;
100             bool[6] present;
101             enum string[6] defines =
102             [
103               "VERTEX_SHADER",
104               "FRAGMENT_SHADER",
105               "GEOMETRY_SHADER",
106               "TESS_CONTROL_SHADER",
107               "TESS_EVALUATION_SHADER",
108               "COMPUTE_SHADER"
109             ];
110 
111             enum GLenum[6] shaderTypes =
112             [
113                 GL_VERTEX_SHADER,
114                 GL_FRAGMENT_SHADER,
115                 GL_GEOMETRY_SHADER,
116                 GL_TESS_CONTROL_SHADER,
117                 GL_TESS_EVALUATION_SHADER,
118                 GL_COMPUTE_SHADER
119             ];
120 
121             // from GLSL spec: "Each number sign (#) can be preceded in its line only by
122             //                  spaces or horizontal tabs."
123             enum directiveRegexp = ctRegex!(r"^[ \t]*#");
124             enum versionRegexp = ctRegex!(r"^[ \t]*#[ \t]*version");
125 
126             present[] = false;
127             int versionLine = -1;
128 
129             // scan source for #version and usage of shader macros in preprocessor lines
130             foreach(int lineIndex, string line; sourceLines)
131             {
132                 // if the line is a preprocessor directive
133                 if (match(line, directiveRegexp))
134                 {
135                     foreach (int i, string define; defines)
136                         if (!present[i] && countUntil(line, define) != -1)
137                             present[i] = true;
138 
139                     if (match(line, versionRegexp))
140                     {
141                         if (versionLine != -1)
142                         {
143                             string message = "Your shader program has several #version directives, you are looking for problems.";
144                             debug
145                                 throw new OpenGLException(message);
146                             else
147                                 gl._logger.warning(message);
148                         }
149                         else
150                         {
151                             if (lineIndex != 0)
152                                 gl._logger.warning("For maximum compatibility, #version directive should be the first line of your shader.");
153 
154                             versionLine = lineIndex;
155                         }
156                     }
157                 }
158             }
159 
160             GLShader[] shaders;
161 
162             foreach (int i, string define; defines)
163             {
164                 if (present[i])
165                 {
166                     string[] newSource;
167 
168                     // add #version line
169                     if (versionLine != -1)
170                         newSource ~= sourceLines[versionLine];
171 
172                     // add each #define with the right value
173                     foreach (int j, string define2; defines)
174                         if (present[j])
175                             newSource ~= format("#define %s %d\n", define2, i == j ? 1 : 0);
176 
177                     // add all lines except the #version one
178                     foreach (int l, string line; sourceLines)
179                         if (l != versionLine)
180                             newSource ~= line;
181 
182                     shaders ~= new GLShader(_gl, shaderTypes[i], newSource);
183                 }
184             }
185             this(gl, shaders);
186         }
187 
188         /// Ditto, except with lines in a single string.
189         this(OpenGL gl, string wholeSource)
190         {
191             // split on end-of-lines
192             this(gl, splitLines(wholeSource));
193         }
194 
195         ~this()
196         {
197             close();
198         }
199 
200         /// Releases the OpenGL program resource.
201         void close()
202         {
203             if (_initialized)
204             {
205                 glDeleteProgram(_program);
206                 _initialized = false;
207             }
208         }
209 
210         /// Attaches OpenGL shaders to this program.
211         /// Throws: $(D OpenGLException) on error.
212         void attach(GLShader[] compiledShaders...)
213         {
214             foreach(shader; compiledShaders)
215             {
216                 glAttachShader(_program, shader._shader);
217                 _gl.runtimeCheck();
218             }
219         }
220 
221         /// Links this OpenGL program.
222         /// Throws: $(D OpenGLException) on error.
223         void link()
224         {
225             glLinkProgram(_program);
226             _gl.runtimeCheck();
227             GLint res;
228             glGetProgramiv(_program, GL_LINK_STATUS, &res);
229             if (GL_TRUE != res)
230             {
231                 const(char)[] linkLog = getLinkLog();
232                 if (linkLog != null)
233                     _gl._logger.errorf("%s", linkLog);
234                 throw new OpenGLException("Cannot link program");
235             }
236 
237             // When getting uniform and attribute names, add some length because of stories like this:
238             // http://stackoverflow.com/questions/12555165/incorrect-value-from-glgetprogramivprogram-gl-active-uniform-max-length-outpa
239             enum SAFETY_SPACE = 128;
240 
241             // get active uniforms
242             {
243                 GLint uniformNameMaxLength;
244                 glGetProgramiv(_program, GL_ACTIVE_UNIFORM_MAX_LENGTH, &uniformNameMaxLength);
245 
246                 GLchar[] buffer = new GLchar[uniformNameMaxLength + SAFETY_SPACE];
247 
248                 GLint numActiveUniforms;
249                 glGetProgramiv(_program, GL_ACTIVE_UNIFORMS, &numActiveUniforms);
250 
251                 // get uniform block indices (if > 0, it's a block uniform)
252                 GLuint[] uniformIndex;
253                 GLint[] blockIndex;
254                 uniformIndex.length = numActiveUniforms;
255                 blockIndex.length = numActiveUniforms;
256 
257                 for (GLint i = 0; i < numActiveUniforms; ++i)
258                     uniformIndex[i] = cast(GLuint)i;
259 
260                 glGetActiveUniformsiv( _program,
261                                        cast(GLint)uniformIndex.length,
262                                        uniformIndex.ptr,
263                                        GL_UNIFORM_BLOCK_INDEX,
264                                        blockIndex.ptr);
265                 _gl.runtimeCheck();
266 
267                 // get active uniform blocks
268                 getUniformBlocks(_gl, this);
269 
270                 for (GLint i = 0; i < numActiveUniforms; ++i)
271                 {
272                     if(blockIndex[i] >= 0)
273                         continue;
274 
275                     GLint size;
276                     GLenum type;
277                     GLsizei length;
278                     glGetActiveUniform(_program,
279                                        cast(GLuint)i,
280                                        cast(GLint)(buffer.length),
281                                        &length,
282                                        &size,
283                                        &type,
284                                        buffer.ptr);
285                     _gl.runtimeCheck();
286                     string name = fromStringz(buffer.ptr).idup;
287                    _activeUniforms[name] = new GLUniform(_gl, _program, type, name, size);
288                 }
289             }
290 
291             // get active attributes
292             {
293                 GLint attribNameMaxLength;
294                 glGetProgramiv(_program, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &attribNameMaxLength);
295 
296                 GLchar[] buffer = new GLchar[attribNameMaxLength + SAFETY_SPACE];
297 
298                 GLint numActiveAttribs;
299                 glGetProgramiv(_program, GL_ACTIVE_ATTRIBUTES, &numActiveAttribs);
300 
301                 for (GLint i = 0; i < numActiveAttribs; ++i)
302                 {
303                     GLint size;
304                     GLenum type;
305                     GLsizei length;
306                     glGetActiveAttrib(_program, cast(GLuint)i, cast(GLint)(buffer.length), &length, &size, &type, buffer.ptr);
307                     _gl.runtimeCheck();
308                     string name = fromStringz(buffer.ptr).idup;
309                     GLint location = glGetAttribLocation(_program, buffer.ptr);
310                     _gl.runtimeCheck();
311 
312                     _activeAttributes[name] = new GLAttribute(_gl, name, location, type, size);
313                 }
314             }
315 
316         }
317 
318         /// Uses this program for following draw calls.
319         /// Throws: $(D OpenGLException) on error.
320         void use()
321         {
322             glUseProgram(_program);
323             _gl.runtimeCheck();
324 
325             // upload uniform values then
326             // this allow setting uniform at anytime without binding the program
327             foreach(uniform; _activeUniforms)
328                 uniform.use();
329         }
330 
331         /// Unuses this program.
332         /// Throws: $(D OpenGLException) on error.
333         void unuse()
334         {
335             foreach(uniform; _activeUniforms)
336                 uniform.unuse();
337             glUseProgram(0);
338             _gl.runtimeCheck();
339         }
340 
341         /// Gets the linking report.
342         /// Returns: Log output of the GLSL linker. Can return null!
343         /// Throws: $(D OpenGLException) on error.
344         const(char)[] getLinkLog()
345         {
346             GLint logLength;
347             glGetProgramiv(_program, GL_INFO_LOG_LENGTH, &logLength);
348             if (logLength <= 0) // " If program has no information log, a value of 0 is returned."
349                 return null;
350 
351             char[] log = new char[logLength + 1];
352             GLint dummy;
353             glGetProgramInfoLog(_program, logLength, &dummy, log.ptr);
354             _gl.runtimeCheck();
355             return fromStringz(log.ptr);
356         }
357 
358         /// Gets an uniform by name.
359         /// Returns: A GLUniform with this name. This GLUniform might be created on demand if
360         ///          the name hasn't been found. So it might be a "fake" uniform.
361         /// See_also: GLUniform.
362         GLUniform uniform(string name)
363         {
364             GLUniform* u = name in _activeUniforms;
365 
366             if (u is null)
367             {
368                 // no such variable found, either it's really missing or the OpenGL driver discarded an unused uniform
369                 // create a fake disabled GLUniform to allow the show to proceed
370                 _activeUniforms[name] = new GLUniform(_gl, name);
371                 return _activeUniforms[name];
372             }
373             return *u;
374         }
375 
376         /// Gets an attribute by name.
377         /// Returns: A $(D GLAttribute) retrieved by name.
378         /// Throws: $(D OpenGLException) on error.
379         GLAttribute attrib(string name)
380         {
381             GLAttribute* a = name in _activeAttributes;
382             if (a is null)
383             {
384                 // no such attribute found, either it's really missing or the OpenGL driver discarded an unused uniform
385                 // create a fake disabled GLattribute to allow the show to proceed
386                 _activeAttributes[name] = new GLAttribute(_gl,name);
387                 return _activeAttributes[name];
388             }
389             return *a;
390         }
391 
392         /// Returns: Wrapped OpenGL resource handle.
393         GLuint handle() pure const nothrow
394         {
395             return _program;
396         }
397     }
398 
399     package
400     {
401         OpenGL _gl;
402     }
403 
404     private
405     {
406         GLuint _program; // OpenGL handle
407         bool _initialized;
408         GLUniform[string] _activeUniforms;
409         GLAttribute[string] _activeAttributes;
410     }
411 }
412 
413 
414 /// Represent an OpenGL program attribute. Owned by a GLProgram.
415 /// See_also: GLProgram.
416 final class GLAttribute
417 {
418     public
419     {
420         enum GLint fakeLocation = -1;
421 
422         this(OpenGL gl, string name, GLint location, GLenum type, GLsizei size)
423         {
424             _gl = gl;
425             _name = name;
426             _location = location;
427             _type = type;
428             _size = size;
429             _disabled = false;
430         }
431 
432         /// Creates a fake disabled attribute, designed to cope with attribute
433         /// that have been optimized out by the OpenGL driver, or those which do not exist.
434         this(OpenGL gl, string name)
435         {
436             _gl = gl;
437             _disabled = true;
438             _location = fakeLocation;
439             _type = GL_FLOAT; // whatever
440             _size = 1;
441             _gl._logger.warningf("Faking attribute '%s' which either does not exist in the shader program, or was discarded by the driver as unused", name);
442         }
443 
444     }
445 
446     GLint location()
447     {
448         return _location;
449     }
450 
451     string name() pure const nothrow
452     {
453         return _name;
454     }
455 
456     private
457     {
458         OpenGL _gl;
459         GLint _location;
460         GLenum _type;
461         GLsizei _size;
462         string _name;
463         bool _disabled;
464     }
465 }