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             foreach(shader; shaders)
188                 shader.destroy();
189         }
190 
191         /// Ditto, except with lines in a single string.
192         this(OpenGL gl, string wholeSource)
193         {
194             // split on end-of-lines
195             this(gl, splitLines(wholeSource));
196         }
197 
198         /// Releases the OpenGL program resource.
199         ~this()
200         {
201             if (_initialized)
202             {
203                 debug ensureNotInGC("GLProgram");
204                 glDeleteProgram(_program);
205                 _initialized = false;
206             }
207         }
208 
209         /// Attaches OpenGL shaders to this program.
210         /// Throws: $(D OpenGLException) on error.
211         void attach(GLShader[] compiledShaders...)
212         {
213             foreach(shader; compiledShaders)
214             {
215                 glAttachShader(_program, shader._shader);
216                 _gl.runtimeCheck();
217             }
218         }
219 
220         /// Links this OpenGL program.
221         /// Throws: $(D OpenGLException) on error.
222         void link()
223         {
224             glLinkProgram(_program);
225             _gl.runtimeCheck();
226             GLint res;
227             glGetProgramiv(_program, GL_LINK_STATUS, &res);
228             if (GL_TRUE != res)
229             {
230                 const(char)[] linkLog = getLinkLog();
231                 if (linkLog != null)
232                     _gl._logger.errorf("%s", linkLog);
233                 throw new OpenGLException("Cannot link program");
234             }
235 
236             // When getting uniform and attribute names, add some length because of stories like this:
237             // http://stackoverflow.com/questions/12555165/incorrect-value-from-glgetprogramivprogram-gl-active-uniform-max-length-outpa
238             enum SAFETY_SPACE = 128;
239 
240             // get active uniforms
241             {
242                 GLint uniformNameMaxLength;
243                 glGetProgramiv(_program, GL_ACTIVE_UNIFORM_MAX_LENGTH, &uniformNameMaxLength);
244 
245                 GLchar[] buffer = new GLchar[uniformNameMaxLength + SAFETY_SPACE];
246 
247                 GLint numActiveUniforms;
248                 glGetProgramiv(_program, GL_ACTIVE_UNIFORMS, &numActiveUniforms);
249 
250                 // get uniform block indices (if > 0, it's a block uniform)
251                 GLuint[] uniformIndex;
252                 GLint[] blockIndex;
253                 uniformIndex.length = numActiveUniforms;
254                 blockIndex.length = numActiveUniforms;
255 
256                 for (GLint i = 0; i < numActiveUniforms; ++i)
257                     uniformIndex[i] = cast(GLuint)i;
258 
259                 glGetActiveUniformsiv( _program,
260                                        cast(GLint)uniformIndex.length,
261                                        uniformIndex.ptr,
262                                        GL_UNIFORM_BLOCK_INDEX,
263                                        blockIndex.ptr);
264                 _gl.runtimeCheck();
265 
266                 // get active uniform blocks
267                 getUniformBlocks(_gl, this);
268 
269                 for (GLint i = 0; i < numActiveUniforms; ++i)
270                 {
271                     if(blockIndex[i] >= 0)
272                         continue;
273 
274                     GLint size;
275                     GLenum type;
276                     GLsizei length;
277                     glGetActiveUniform(_program,
278                                        cast(GLuint)i,
279                                        cast(GLint)(buffer.length),
280                                        &length,
281                                        &size,
282                                        &type,
283                                        buffer.ptr);
284                     _gl.runtimeCheck();
285                     string name = fromStringz(buffer.ptr).idup;
286                    _activeUniforms[name] = new GLUniform(_gl, _program, type, name, size);
287                 }
288             }
289 
290             // get active attributes
291             {
292                 GLint attribNameMaxLength;
293                 glGetProgramiv(_program, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &attribNameMaxLength);
294 
295                 GLchar[] buffer = new GLchar[attribNameMaxLength + SAFETY_SPACE];
296 
297                 GLint numActiveAttribs;
298                 glGetProgramiv(_program, GL_ACTIVE_ATTRIBUTES, &numActiveAttribs);
299 
300                 for (GLint i = 0; i < numActiveAttribs; ++i)
301                 {
302                     GLint size;
303                     GLenum type;
304                     GLsizei length;
305                     glGetActiveAttrib(_program, cast(GLuint)i, cast(GLint)(buffer.length), &length, &size, &type, buffer.ptr);
306                     _gl.runtimeCheck();
307                     string name = fromStringz(buffer.ptr).idup;
308                     GLint location = glGetAttribLocation(_program, buffer.ptr);
309                     _gl.runtimeCheck();
310 
311                     _activeAttributes[name] = new GLAttribute(_gl, name, location, type, size);
312                 }
313             }
314 
315         }
316 
317         /// Uses this program for following draw calls.
318         /// Throws: $(D OpenGLException) on error.
319         void use()
320         {
321             glUseProgram(_program);
322             _gl.runtimeCheck();
323 
324             // upload uniform values then
325             // this allow setting uniform at anytime without binding the program
326             foreach(uniform; _activeUniforms)
327                 uniform.use();
328         }
329 
330         /// Unuses this program.
331         /// Throws: $(D OpenGLException) on error.
332         void unuse()
333         {
334             foreach(uniform; _activeUniforms)
335                 uniform.unuse();
336             glUseProgram(0);
337             _gl.runtimeCheck();
338         }
339 
340         /// Gets the linking report.
341         /// Returns: Log output of the GLSL linker. Can return null!
342         /// Throws: $(D OpenGLException) on error.
343         const(char)[] getLinkLog()
344         {
345             GLint logLength;
346             glGetProgramiv(_program, GL_INFO_LOG_LENGTH, &logLength);
347             if (logLength <= 0) // " If program has no information log, a value of 0 is returned."
348                 return null;
349 
350             char[] log = new char[logLength + 1];
351             GLint dummy;
352             glGetProgramInfoLog(_program, logLength, &dummy, log.ptr);
353             _gl.runtimeCheck();
354             return fromStringz(log.ptr);
355         }
356 
357         /// Gets an uniform by name.
358         /// Returns: A GLUniform with this name. This GLUniform might be created on demand if
359         ///          the name hasn't been found. So it might be a "fake" uniform.
360         /// See_also: GLUniform.
361         GLUniform uniform(string name)
362         {
363             GLUniform* u = name in _activeUniforms;
364 
365             if (u is null)
366             {
367                 // no such variable found, either it's really missing or the OpenGL driver discarded an unused uniform
368                 // create a fake disabled GLUniform to allow the show to proceed
369                 _activeUniforms[name] = new GLUniform(_gl, name);
370                 return _activeUniforms[name];
371             }
372             return *u;
373         }
374 
375         /// Gets an attribute by name.
376         /// Returns: A $(D GLAttribute) retrieved by name.
377         /// Throws: $(D OpenGLException) on error.
378         GLAttribute attrib(string name)
379         {
380             GLAttribute* a = name in _activeAttributes;
381             if (a is null)
382             {
383                 // no such attribute found, either it's really missing or the OpenGL driver discarded an unused uniform
384                 // create a fake disabled GLattribute to allow the show to proceed
385                 _activeAttributes[name] = new GLAttribute(_gl,name);
386                 return _activeAttributes[name];
387             }
388             return *a;
389         }
390 
391         /// Returns: Wrapped OpenGL resource handle.
392         GLuint handle() pure const nothrow
393         {
394             return _program;
395         }
396     }
397 
398     package
399     {
400         OpenGL _gl;
401     }
402 
403     private
404     {
405         GLuint _program; // OpenGL handle
406         bool _initialized;
407         GLUniform[string] _activeUniforms;
408         GLAttribute[string] _activeAttributes;
409     }
410 }
411 
412 
413 /// Represent an OpenGL program attribute. Owned by a GLProgram.
414 /// See_also: GLProgram.
415 final class GLAttribute
416 {
417     public
418     {
419         enum GLint fakeLocation = -1;
420 
421         this(OpenGL gl, string name, GLint location, GLenum type, GLsizei size)
422         {
423             _gl = gl;
424             _name = name;
425             _location = location;
426             _type = type;
427             _size = size;
428             _disabled = false;
429         }
430 
431         /// Creates a fake disabled attribute, designed to cope with attribute
432         /// that have been optimized out by the OpenGL driver, or those which do not exist.
433         this(OpenGL gl, string name)
434         {
435             _gl = gl;
436             _disabled = true;
437             _location = fakeLocation;
438             _type = GL_FLOAT; // whatever
439             _size = 1;
440             _gl._logger.warningf("Faking attribute '%s' which either does not exist in the shader program, or was discarded by the driver as unused", name);
441         }
442 
443     }
444 
445     GLint location()
446     {
447         return _location;
448     }
449 
450     string name() pure const nothrow
451     {
452         return _name;
453     }
454 
455     private
456     {
457         OpenGL _gl;
458         GLint _location;
459         GLenum _type;
460         GLsizei _size;
461         string _name;
462         bool _disabled;
463     }
464 }