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.core.log,
11        gfm.core.text,
12        gfm.math.vector, 
13        gfm.math.matrix,
14        gfm.opengl.opengl, 
15        gfm.opengl.shader, 
16        gfm.opengl.uniform,
17        gfm.opengl.uniformblock;
18 
19 /// OpenGL Program wrapper.
20 final class GLProgram
21 {
22     public
23     {
24         /// Creates an empty program.
25         /// Throws: $(D OpenGLException) on error.
26         this(OpenGL gl)
27         {
28             _gl = gl;
29             _program = glCreateProgram();
30             if (_program == 0)
31                 throw new OpenGLException("glCreateProgram failed");
32             _initialized = true;
33         }
34 
35         /// Creates a program from a set of compiled shaders.
36         /// Throws: $(D OpenGLException) on error.
37         this(OpenGL gl, GLShader[] shaders...)
38         {
39             this(gl);
40             attach(shaders);
41             link();
42         }
43 
44         /**
45          * Compiles N times the same GLSL source and link to a program.
46          * 
47          * <p>
48          * The same input is compiled 1 to 5 times, each time prepended
49          * with a $(D #define) specific to a shader type.
50          * </p>
51          * $(UL
52          *    $(LI $(D VERTEX_SHADER))
53          *    $(LI $(D FRAGMENT_SHADER))
54          *    $(LI $(D GEOMETRY_SHADER))
55          *    $(LI $(D TESS_CONTROL_SHADER))
56          *    $(LI $(D TESS_EVALUATION_SHADER))
57          * )
58          * <p>
59          * Each of these macros are alternatively set to 1 while the others are
60          * set to 0. If such a macro isn't used in any preprocessor directive 
61          * of your source, this shader stage is considered unused.</p>
62          *
63          * <p>For conformance reasons, any #version directive on the first line will stay at the top.</p>
64          * 
65          * Warning: <b>THIS FUNCTION REWRITES YOUR SHADER A BIT.</b>
66          * Expect slightly wrong lines in GLSL compiler's error messages.
67          *
68          * Example of a combined shader source:
69          * ---
70          *      #version 110
71          *      uniform vec4 color;
72          *
73          *      #if VERTEX_SHADER
74          *
75          *      void main()
76          *      {
77          *          gl_Vertex = ftransform();
78          *      }
79          *      
80          *      #elif FRAGMENT_SHADER
81          *      
82          *      void main()
83          *      {
84          *          gl_FragColor = color;
85          *      }
86          *      
87          *      #endif
88          * ---
89          *
90          * Limitations:
91          * $(UL
92          *   $(LI All of #preprocessor directives should not have whitespaces before the #.)
93          *   $(LI sourceLines elements should be individual lines!)
94          * )
95          *
96          * Throws: $(D OpenGLException) on error.
97          */
98         this(OpenGL gl, string[] sourceLines)
99         {
100             _gl = gl;
101             bool present[5];
102             enum string[5] defines = 
103             [ 
104               "VERTEX_SHADER",
105               "FRAGMENT_SHADER",
106               "GEOMETRY_SHADER",
107               "TESS_CONTROL_SHADER",
108               "TESS_EVALUATION_SHADER"
109             ];
110 
111             enum GLenum[5] 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             ];
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(int 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                                 gl._logger.warning(message);
147                         }
148                         else
149                         {
150                             if (lineIndex != 0)
151                                 gl._logger.warning("For maximum compatibility, #version directive should be the first line of your shader.");
152 
153                             versionLine = 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 (int l, string line; sourceLines)
178                         if (l != versionLine)
179                             newSource ~= line;
180 
181                     shaders ~= new GLShader(_gl, shaderTypes[i], newSource);
182                 }
183             }
184             this(gl, shaders);
185         }
186 
187         /// Ditto, except with lines in a single string.
188         this(OpenGL gl, string wholeSource)
189         {
190             // split on end-of-lines
191             this(gl, splitLines(wholeSource));
192         }
193 
194         ~this()
195         {
196             close();
197         }
198 
199         /// Releases the OpenGL program resource.
200         void close()
201         {
202             if (_initialized)
203             {
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                 string 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 = sanitizeUTF8(buffer.ptr, _gl._logger, "OpenGL uniform name");
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 = sanitizeUTF8(buffer.ptr, _gl._logger, "OpenGL attribute name");
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         string 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 sanitizeUTF8(log.ptr, _gl._logger, "shader link log");
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                 _gl._logger.warningf("Faking uniform variable '%s'", name);
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                 throw new OpenGLException(format("Attribute %s is unknown", name));
384             return *a;
385         }
386 
387         /// Returns: Wrapped OpenGL resource handle.
388         GLuint handle() pure const nothrow
389         {
390             return _program;
391         }
392     }
393 
394     private
395     {
396         OpenGL _gl;
397         GLuint _program; // OpenGL handle
398         bool _initialized;
399         GLUniform[string] _activeUniforms;
400         GLAttribute[string] _activeAttributes;
401     }
402 }
403 
404 
405 /// Represent an OpenGL program attribute. Owned by a GLProgram.
406 /// See_also: GLProgram.
407 final class GLAttribute
408 {
409     public
410     {
411         this(OpenGL gl, string name, GLint location, GLenum type, GLsizei size)
412         {
413             _gl = gl;
414             _name = name;
415             _location = location;
416             _type = type;
417             _size = size;
418         }
419 
420     }
421 
422     @property GLint location() { return _location; } // property, getter only
423 
424     private
425     {
426         OpenGL _gl;
427         GLint _location;
428         GLenum _type;
429         GLsizei _size;
430         string _name;
431     }
432 }