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