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