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