1 module glamour.shader;
2 
3 private {
4     import glamour.gl : GLenum, GLuint, GLint, GLchar, GLboolean,
5                         GL_VERTEX_SHADER,
6 //                                 GL_TESS_CONTROL_SHADER, GL_TESS_EVALUATION_SHADER,
7                         GL_GEOMETRY_SHADER, GL_FRAGMENT_SHADER,
8                         GL_LINK_STATUS, GL_FALSE, GL_INFO_LOG_LENGTH,
9                         GL_COMPILE_STATUS, GL_TRUE,
10                         glCreateProgram, glCreateShader, glCompileShader,
11                         glLinkProgram, glGetShaderiv, glGetShaderInfoLog,
12                         glGetProgramInfoLog, glGetProgramiv, glShaderSource,
13                         glUseProgram, glAttachShader, glGetAttribLocation,
14                         glDeleteProgram, glDeleteShader, glGetFragDataLocation,
15                         glGetUniformLocation, glUniform1i, glUniform1f,
16                         glUniform2f, glUniform2fv, glUniform3fv,
17                         glUniform4fv, glUniformMatrix2fv, glUniformMatrix2x3fv,
18                         glUniformMatrix2x4fv, glUniformMatrix3fv, glUniformMatrix3x2fv,
19                         glUniformMatrix3x4fv, glUniformMatrix4fv, glUniformMatrix4x2fv,
20                         glUniformMatrix4x3fv, glUniform2iv, glUniform3iv, glUniform4iv;
21     import glamour.util : checkgl;
22 
23     import std.conv : to;
24     import std.file : readText;
25     import std.path : baseName, stripExtension;
26     import std..string : format, splitLines, toStringz, toLower, strip;
27     import std.array : join, split;
28     import std.algorithm : startsWith, endsWith;
29     import std.exception : enforceEx;
30     import std.typecons : Tuple;
31     
32     version(gl3n) {
33         import gl3n.util : is_vector, is_matrix, is_quaternion;
34     }
35 
36     debug import std.stdio : stderr;
37 }
38 
39 
40 GLenum to_opengl_shader(string s, string filename="<unknown>") {
41     switch(s) {
42         case "vertex": return GL_VERTEX_SHADER;
43         case "geometry": return GL_GEOMETRY_SHADER;
44         case "fragment": return GL_FRAGMENT_SHADER;
45         default: throw new ShaderException(format("Unknown shader, %s.", s), "load", filename);
46     }
47     assert(0);
48 }
49 
50 
51 // enum ctr_shader_type = ctRegex!(`^(\w+):`);
52 
53 /// This exception will be raised when
54 /// an error occurs while compiling or linking a shader.
55 class ShaderException : Exception {
56     /// The filename passed to the ctor.
57     string filename;
58     /// The process passed to the ctor. Will be one of "linking" or "compiling".
59     string process;
60     
61     /// Params:
62     /// infolog = Infolog returned from OpenGL.
63     /// process_ = Error occured while linking or compiling?
64     /// filename_ = Used to identify the shader.
65     this(string infolog, string process_, string filename_="<unknown>") {
66         filename = filename_;
67         process = process_;
68         
69         infolog ~= "\nFailed to " ~ process_ ~ " shader: " ~ filename_ ~ ". "; 
70         
71         super(infolog);
72     }
73 }
74 deprecated alias ShaderException ShaderError;
75 
76 /// Compiles an already created OpenGL shader.
77 /// Throws: ShaderException on failure.
78 /// Params:
79 /// shader = The OpenGL shader.
80 /// filename = Used to identify the shader, if an error occurs.
81 void compile_shader(GLuint shader, string filename="<unknown>") {
82     checkgl!glCompileShader(shader);
83 
84     GLint status;
85     checkgl!glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
86     if(status == GL_FALSE) {
87         GLint infolog_length;
88         checkgl!glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infolog_length);
89         
90         GLchar[] infolog = new GLchar[infolog_length+1];
91         checkgl!glGetShaderInfoLog(shader, infolog_length, null, infolog.ptr);
92         
93         throw new ShaderException(infolog.to!string(), "link", filename);
94     }
95 }
96 
97 /// Links an already created OpenGL program.
98 /// Throws: ShaderException on failure.
99 /// Params:
100 /// program = The OpenGL program.
101 /// filename = Used to identify the shader, if an error occurs.
102 void link_program(GLuint program, string filename="<unknown>") {
103     checkgl!glLinkProgram(program);
104 
105     GLint status;
106     checkgl!glGetProgramiv(program, GL_LINK_STATUS, &status);
107     if(status == GL_FALSE) {
108         GLint infolog_length;
109         checkgl!glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infolog_length);
110 
111         GLchar[] infolog = new GLchar[infolog_length + 1];
112         checkgl!glGetProgramInfoLog(program, infolog_length, null, infolog.ptr);
113         
114         throw new ShaderException(infolog.to!string(), "compile", filename);
115     }
116 }
117 
118 /// Stores each line of the shader, line and text.
119 alias Tuple!(size_t, "line", string, "text") Line; 
120 
121 /// Represents an OpenGL program with it's shaders.
122 /// The constructor must be used to avoid segmentation faults.
123 class Shader {
124     /// The OpenGL program.
125     GLuint program;
126     /// Alias this to program.
127     alias program this;
128     
129     private GLuint[] _shaders; 
130     /// Holds every shaders source.
131     Line[][string] shader_sources;
132     /// Holds the directives.
133     string[] directives;
134     
135     /// The shaders filename.
136     string filename;
137     
138     /// Uniform locations will be cached here.
139     GLint[string] uniform_locations;
140     /// Attrib locations will be cached here.
141     GLint[string] attrib_locations;
142     /// Frag-data locations will be cached here.
143     GLint[string] frag_locations;
144 
145     /// Loads the shaders directly from a file.
146     this(string file) {
147         this(stripExtension(baseName(file)), readText(file));
148     }
149     
150     /// Loads the shader from the source,
151     /// filename_ is stored in $(I filename) and will be used to identify the shader.
152     this(string filename_, string source) {
153         filename = filename_;
154         
155         program = checkgl!glCreateProgram();
156                
157         Line[]* current;
158         foreach(size_t line, string text; source.splitLines()) {
159             if(text.startsWith("#")) {
160                 directives ~= text;
161             } else {
162                 auto m = text.strip().split();
163 
164                 if(m.length >= 1 && m[0].endsWith(":")) {
165                     string type = toLower(m[0][0..$-1]);
166                     shader_sources[type] = null;
167                     current = &(shader_sources[type]);
168                 } else {
169                     if(current !is null) {
170                         *current ~= Line(line, text);
171                     }
172                 }
173             }
174         }
175         
176         if(!directives.length) {
177             // OSX only supports 3.2 forward contexts
178             version(OSX) {
179                 directives ~= "#version 150\n";
180             } else {
181                 directives ~= "#version 130\n";
182             }
183         }
184         
185         foreach(string type, Line[] lines; shader_sources) {
186             string shader_source = directives.join("\n") ~ "\n\n";
187             
188             foreach(Line line; lines) {
189                 shader_source ~= format("#line %d\n%s\n", line.line, line.text);
190             }
191             
192             GLenum shader_type = to_opengl_shader(type, filename);
193             GLuint shader = checkgl!glCreateShader(shader_type);
194             auto ssp = shader_source.ptr;
195             int ssl = cast(int)(shader_source.length);
196             checkgl!glShaderSource(shader, 1, &ssp, &ssl);
197 
198             compile_shader(shader, filename);
199 
200             _shaders ~= shader;
201             checkgl!glAttachShader(program, shader);
202         }
203         
204         link_program(program, filename);
205     }
206 
207     ~this() {
208         debug if(program != 0) stderr.writefln("OpenGL: Shader resources not released.");
209     }
210     
211     /// Deletes all shaders and the program.
212     void remove() {
213         foreach(GLuint shader; _shaders) {
214             checkgl!glDeleteShader(shader);
215         }
216         _shaders = [];
217         
218         checkgl!glDeleteProgram(program);
219         program = 0;
220     }
221     
222     /// Binds the program.
223     void bind() {
224         checkgl!glUseProgram(program);
225     }
226     
227     /// Unbinds the program.
228     void unbind() {
229         checkgl!glUseProgram(0);
230     }
231     
232     /// Queries an attrib location from OpenGL and caches it in $(I attrib_locations).
233     /// If the location was already queried the cache is returned.
234     GLint get_attrib_location(string name) {
235         if(auto loc = name in attrib_locations) {
236             return *loc;
237         }
238         
239         debug {
240             auto loc = checkgl!glGetAttribLocation(program, toStringz(name));
241             if(loc < 0) {
242                 stderr.writefln(`glGetAttribLocation returned a value < 0 for location: "%s"`, name);
243             }
244 
245             return attrib_locations[name] = loc;
246         } else {
247             return attrib_locations[name] = checkgl!glGetAttribLocation(program, toStringz(name));
248         }
249     }
250 
251     /// Queries an fragment-data location from OpenGL and caches it in $(I frag_locations).
252     /// If the location was already queried the cache is returned.
253     GLint get_frag_location(string name) {
254         if(auto loc = name in frag_locations) {
255             return *loc;
256         }
257 
258         debug {
259             auto loc = checkgl!glGetFragDataLocation(program, toStringz(name));
260             if(loc < 0) {
261                 stderr.writefln(`glGetFragDataLocation returned a value < 0 for location: "%s"`, name);
262             }
263 
264             return frag_locations[name] = loc;
265         } else {
266             return frag_locations[name] = checkgl!glGetFragDataLocation(program, toStringz(name));
267         }
268     }
269     
270     /// Queries an uniform location from OpenGL and caches it in $(I uniform_locations).
271     /// If the location was already queried the cache is returned.
272     GLint get_uniform_location(string name) {
273         if(auto loc = name in uniform_locations) {
274             return *loc;
275         }
276         
277         debug {
278             auto loc = checkgl!glGetUniformLocation(program, toStringz(name));
279             if(loc < 0) {
280                 stderr.writefln(`glGetUniformLocation returned a value < 0 for location: "%s"`, name);
281             }
282 
283             return uniform_locations[name] = loc;
284         } else {
285             return uniform_locations[name] = checkgl!glGetUniformLocation(program, toStringz(name));
286         }
287     }
288 
289     // gl3n integration
290     version(gl3n) {
291         /// If glamour gets compiled with version=gl3n support for
292         /// vectors, matrices and quaternions is added
293         void uniform(T)(string name, T value) if(is_vector!T) {
294             static if(is(T.vt : int)) {
295                 static if(T.dimension == 2) {
296                     checkgl!glUniform2iv(get_uniform_location(name), 1, value.value_ptr);
297                 } else static if(T.dimension == 3) {
298                     checkgl!glUniform3iv(get_uniform_location(name), 1, value.value_ptr);
299                 } else static if(T.dimension == 4) {
300                     checkgl!glUniform4iv(get_uniform_location(name), 1, value.value_ptr);
301                 } else static assert(false);
302             } else {
303                 static if(T.dimension == 2) {
304                     checkgl!glUniform2fv(get_uniform_location(name), 1, value.value_ptr);
305                 } else static if(T.dimension == 3) {
306                     checkgl!glUniform3fv(get_uniform_location(name), 1, value.value_ptr);
307                 } else static if(T.dimension == 4) {
308                     checkgl!glUniform4fv(get_uniform_location(name), 1, value.value_ptr);
309                 } else static assert(false);
310             }
311         }
312         
313         /// ditto
314         void uniform(S : string, T)(S name, T value) if(is_matrix!T) {
315             static if((T.rows == 2) && (T.cols == 2)) {
316                 checkgl!glUniformMatrix2fv(get_uniform_location(name), 1, GL_TRUE, value.value_ptr);
317             } else static if((T.rows == 3) && (T.cols == 3)) {
318                 checkgl!glUniformMatrix3fv(get_uniform_location(name), 1, GL_TRUE, value.value_ptr);
319             } else static if((T.rows == 4) && (T.cols == 4)) {
320                 checkgl!glUniformMatrix4fv(get_uniform_location(name), 1, GL_TRUE, value.value_ptr);
321             } else static if((T.rows == 2) && (T.cols == 3)) {
322                 checkgl!glUniformMatrix2x3fv(get_uniform_location(name), 1, GL_TRUE, value.value_ptr);
323             } else static if((T.rows == 3) && (T.cols == 2)) {
324                 checkgl!glUniformMatrix3x2fv(get_uniform_location(name), 1, GL_TRUE, value.value_ptr);
325             } else static if((T.rows == 2) && (T.cols == 4)) {
326                 checkgl!glUniformMatrix2x4fv(get_uniform_location(name), 1, GL_TRUE, value.value_ptr);
327             } else static if((T.rows == 4) && (T.cols == 2)) {
328                 checkgl!glUniformMatrix4x2fv(get_uniform_location(name), 1, GL_TRUE, value.value_ptr);
329             } else static if((T.rows == 3) && (T.cols == 4)) {
330                 checkgl!glUniformMatrix3x4fv(get_uniform_location(name), 1, GL_TRUE, value.value_ptr);
331             } else static if((T.rows == 4) && (T.cols == 3)) {
332                 checkgl!glUniformMatrix4x3fv(get_uniform_location(name), 1, GL_TRUE, value.value_ptr);
333             } else static assert(false, "Can not upload type " ~ T.stringof ~ " to GPU as uniform");
334         }
335         
336         /// ditto
337         void uniform(S : string, T)(S name, T value) if(is_quaternion!T) {
338             checkgl!glUniform4fv(get_uniform_location(name), 1, value.value_ptr);
339         } 
340     } else {
341         void uniform(S, T)(S name, T value) {
342             static assert(false, "you have to compile glamour with version=gl3n to use Shader.uniform");
343         }
344     }
345     
346     /// Sets a shader uniform. Consider the corresponding OpenGL for more information.
347     void uniform1i(string name, int value) {
348         checkgl!glUniform1i(get_uniform_location(name), value);
349     }
350 
351     /// ditto
352     void uniform1i(GLint name, int value) {
353         checkgl!glUniform1i(name, value);
354     }
355     
356     /// ditto
357     void uniform1f(string name, float value) {
358         checkgl!glUniform1f(get_uniform_location(name), value);
359     }
360 
361     /// ditto
362     void uniform1f(GLint name, float value) {
363         checkgl!glUniform1f(name, value);
364     }
365 
366     /// ditto
367     void uniform2f(string name, float value1, float value2) {
368         checkgl!glUniform2f(get_uniform_location(name), value1, value2);
369     }
370 
371     /// ditto
372     void uniform2f(GLint name, float value1, float value2) {
373         checkgl!glUniform2f(name, value1, value2);
374     }
375 
376     /// ditto
377     void uniform2fv(string name, const float[] value) {
378         checkgl!glUniform2fv(get_uniform_location(name), cast(int)(value.length/2), value.ptr);
379     }
380 
381     /// ditto
382     void uniform2fv(GLint name, const float[] value) {
383         checkgl!glUniform2fv(name, cast(int)(value.length/2), value.ptr);
384     }
385 
386     /// ditto
387     void uniform2fv(string name, const float[] value, int count) {
388         checkgl!glUniform2fv(get_uniform_location(name), count, value.ptr);
389     }
390     
391     /// ditto
392     void uniform2fv(GLint name, const float[] value, int count) {
393         checkgl!glUniform2fv(name, count, value.ptr);
394     }
395 
396     /// ditto
397     void uniform3fv(string name, const float[] value) {
398         checkgl!glUniform3fv(get_uniform_location(name), cast(int)(value.length/3), value.ptr);
399     }
400 
401     /// ditto
402     void uniform3fv(string name, const float[] value, int count) {
403         checkgl!glUniform3fv(get_uniform_location(name), count, value.ptr);
404     }
405 
406     /// ditto
407     void uniform3fv(GLint name, const float[] value, int count) {
408         checkgl!glUniform3fv(name, count, value.ptr);
409     }
410 
411     /// ditto
412     void uniform4fv(string name, const float[] value) {
413         checkgl!glUniform4fv(get_uniform_location(name), cast(int)(value.length/4), value.ptr);
414     }
415 
416     /// ditto
417     void uniform4fv(GLint name, const float[] value) {
418         checkgl!glUniform4fv(name, cast(int)(value.length/4), value.ptr);
419     }
420     
421     /// ditto
422     void uniform4fv(string name, const float[] value, int count) {
423         checkgl!glUniform4fv(get_uniform_location(name), count, value.ptr);
424     }
425 
426     /// ditto
427     void uniform4fv(GLint name, const float[] value, int count) {
428         checkgl!glUniform4fv(name, count, value.ptr);
429     }
430 
431     /// ditto
432     void uniform_matrix3fv(string name, const float[] value, GLboolean transpose=GL_TRUE) {
433         checkgl!glUniformMatrix3fv(get_uniform_location(name), cast(int)(value.length/9), transpose, value.ptr);
434     }
435 
436     /// ditto
437     void uniform_matrix3fv(GLint name, const float[] value, GLboolean transpose=GL_TRUE) {
438         checkgl!glUniformMatrix3fv(name, cast(int)(value.length/9), transpose, value.ptr);
439     }
440     
441     /// ditto
442     void uniform_matrix3fv(string name, const float[] value, GLboolean transpose=GL_TRUE, int count=1) {
443         checkgl!glUniformMatrix3fv(get_uniform_location(name), count, transpose, value.ptr);
444     }
445 
446     /// ditto
447     void uniform_matrix3fv(GLint name, const float[] value, GLboolean transpose=GL_TRUE, int count=1) {
448         checkgl!glUniformMatrix3fv(name, count, transpose, value.ptr);
449     }
450     
451     /// ditto
452     void uniform_matrix4fv(string name, const float[] value, GLboolean transpose=GL_TRUE) {
453         checkgl!glUniformMatrix4fv(get_uniform_location(name), cast(int)(value.length/16), transpose, value.ptr);
454     }
455 
456     /// ditto
457     void uniform_matrix4fv(GLint name, const float[] value, GLboolean transpose=GL_TRUE) {
458         checkgl!glUniformMatrix4fv(name, cast(int)(value.length/16), transpose, value.ptr);
459     }
460 
461     /// ditto
462     void uniform_matrix4fv(string name, const float[] value, GLboolean transpose=GL_TRUE, int count=1) {
463         checkgl!glUniformMatrix4fv(get_uniform_location(name), count, transpose, value.ptr);
464     }
465     
466     /// ditto
467     void uniform_matrix4fv(GLint name, const float[] value, GLboolean transpose=GL_TRUE, int count=1) {
468         checkgl!glUniformMatrix4fv(name, count, transpose, value.ptr);
469     }
470 }