1 module armos.graphics.shader;
2 
3 import derelict.opengl3.gl;
4 import armos.math.vector;
5 import armos.math.matrix;
6 import armos.graphics;
7 import armos.graphics.shader.source;
8 import colorize;
9 
10 /++
11 +/
12 class Shader {
13     public{
14         this(){
15             maximizeMaxGeometryOutputVertices;
16             _programID = glCreateProgram();
17         }
18 
19         ~this(){
20             glDeleteProgram(_programID);
21         }
22         
23         string log()const{
24             return this._log;
25         }
26 
27         Shader clearLog(){
28             _log = [];
29             return this;
30         }
31 
32         /++
33             Load the shader from shaderName
34         +/
35         Shader load(in string shaderName, in string[] paths = []){
36             import armos.utils.file;
37             string absVertPath = (shaderName ~ ".vert").absolutePath;
38             string absGeomPath = (shaderName ~ ".geom").absolutePath;
39             string absFragPath = (shaderName ~ ".frag").absolutePath;
40 
41             import std.file;
42             if(!absVertPath.exists) absVertPath = "";
43             if(!absGeomPath.exists) absGeomPath = "";
44             if(!absFragPath.exists) absFragPath = "";
45             loadFiles(absVertPath,
46                       absGeomPath,
47                       absFragPath,
48                       paths);
49             return this;
50         }
51 
52         /++
53             Load the shader from path
54         +/
55         Shader loadFiles(in string vertexShaderSourcePath,
56                          in string geometryShaderSourcePath,
57                          in string fragmentShaderSourcePath,
58                          in string[] paths = [])
59         {
60             loadSources((vertexShaderSourcePath   != "")?(Source.load(vertexShaderSourcePath)):   null,
61                         (geometryShaderSourcePath != "")?(Source.load(geometryShaderSourcePath)): null,
62                         (fragmentShaderSourcePath != "")?(Source.load(fragmentShaderSourcePath)): null,
63                         paths);
64             return this;
65         }
66         
67         ///
68         Shader loadSources(in string vertexShaderSourceText,
69                            in string geometryShaderSourceText,
70                            in string fragmentShaderSourceText,
71                            in string[] paths = [])
72         {
73             Source vertexShaderSource;
74             Source geometryShaderSource;
75             Source fragmentShaderSource;
76 
77             if(vertexShaderSourceText != ""){
78                 vertexShaderSource = new Source(vertexShaderSourceText, "main.vert", "");
79             }
80             if(geometryShaderSourceText != ""){
81                 geometryShaderSource = new Source(geometryShaderSourceText, "main.geom", "");
82             }
83             if(fragmentShaderSourceText != ""){
84                 fragmentShaderSource = new Source(fragmentShaderSourceText, "main.vert", "");
85             }
86 
87             loadSources(vertexShaderSource, geometryShaderSource, fragmentShaderSource, paths);
88 
89             return this;
90         }
91 
92         /++
93             Load the shader from sources 
94         +/
95         Shader loadSources(Source vertexShaderSource,
96                            Source geometryShaderSource,
97                            Source fragmentShaderSource,
98                            in string[] paths = [])
99         {
100             if(_programID){
101                 glDeleteProgram(_programID);
102                 _programID = glCreateProgram();
103                 _isLoaded = false;
104                 _attribNames.clear;
105             }
106 
107             addLog("#### Vertex Shader Source ####".color(mode.bold));
108             if(vertexShaderSource){
109                 addLog("Expanding vertex shader...");
110                 vertexShaderSource.expand(paths);
111                 addLog(("Expanded vertex shader (%d lines)".format(vertexShaderSource.numLines)).color(fg.green));
112                 addLog("Loading vertex shader...");
113                 bool isCompiled = loadShaderSource(vertexShaderSource.expanded, GL_VERTEX_SHADER);
114                 if(isCompiled){
115                     addLog("Finish loading vertex shader successfully.".color(fg.green).color(mode.bold));
116                 }else{
117                     addLog("Failed loading vertex shader".color(fg.red).color(mode.bold));
118                 }
119             }else{
120                 addLog("Skip vertex shader");
121             }
122             addLog("");
123 
124             addLog("#### Geometry Shader Source ####".color(mode.bold));
125             if(geometryShaderSource){
126                 addLog("Expanding geometry shader...");
127                 geometryShaderSource.expand(paths);
128                 addLog(("Expanded geometry shader (%d lines)".format(geometryShaderSource.numLines)).color(fg.green));
129                 addLog("Loading geometry shader..");
130                 bool isCompiled = loadShaderSource(geometryShaderSource.expanded, GL_GEOMETRY_SHADER);
131                 if(isCompiled){
132                     addLog("Finish loading geometry shader successfully.".color(fg.green).color(mode.bold));
133                 }else{
134                     addLog("Failed loading geometry shader".color(fg.red).color(mode.bold));
135                 }
136                 glProgramParameteri(_programID, GL_GEOMETRY_INPUT_TYPE, _geometryInput.primitiveMode);
137                 glProgramParameteri(_programID, GL_GEOMETRY_OUTPUT_TYPE, _geometryInput.primitiveMode);
138                 import std.conv:to;
139                 glProgramParameteri(_programID, GL_GEOMETRY_VERTICES_OUT, _maxGeometryOutputVertices.to!int);
140             }else{
141                 addLog("Skip geometry shader");
142             }
143 
144             addLog("");
145             
146             addLog("#### Fragment Shader Source ####".color(mode.bold));
147             if(fragmentShaderSource){
148                 addLog("Expanding fragment shader...");
149                 fragmentShaderSource.expand(paths);
150                 addLog(("Expanded fragment shader (%d lines)".format(fragmentShaderSource.numLines)).color(fg.green));
151                 addLog("Loading fragment shader...");
152                 bool isCompiled = loadShaderSource(fragmentShaderSource.expanded, GL_FRAGMENT_SHADER);
153                 if(isCompiled){
154                     addLog("Finish loading fragment  shader successfully.".color(fg.green).color(mode.bold));
155                 }else{
156                     addLog("Failed loading fragment  shader".color(fg.red).color(mode.bold));
157                 }
158             }else{
159                 addLog("Skip fragment shader");
160             }
161 
162             addLog("");
163 
164             addLog("#### Link Sources ####".color(mode.bold));
165             glLinkProgram(_programID);
166 
167             int isLinked;
168             glGetProgramiv(_programID, GL_LINK_STATUS, &isLinked);
169             if (isLinked == GL_FALSE) {
170                 addLog("Link error.".color(fg.red).color(mode.bold));
171                 addLog(logProgram(_programID));
172                 _isLoaded = false;
173             }else{
174                 addLog("Link success.".color(fg.green).color(mode.bold));
175                 _isLoaded = true;
176             }
177 
178             addLog("");
179 
180             return this;
181         }
182 
183         /++
184             Return gl program id.
185         +/
186         int id()const{return _programID;}
187 
188         /++
189             Begin adapted process
190         +/
191         Shader begin(){
192             int savedID;
193             glGetIntegerv(GL_CURRENT_PROGRAM, &savedID);
194             _savedIDs ~= savedID;
195             glUseProgram(_programID);
196             return this;
197         }
198 
199         /++
200             End adapted process
201         +/
202         Shader end(){
203             // TODO
204             glUseProgram(_savedIDs[$-1]);
205             if (_savedIDs.length == 0) {
206                 assert(0, "stack is empty");
207             }else{
208                 import std.range;
209                 _savedIDs.popBack;
210             }
211             return this;
212         }
213 
214         /++
215         +/
216         bool isLoaded()const{
217             return _isLoaded;
218         }
219 
220         /++
221         +/
222         int uniformLocation(in string name){
223             import std..string;
224             immutable location = glGetUniformLocation(_programID, name.toStringz);
225             // assert(location != -1, "Could not find uniform \"" ~ name ~ "\"");
226             return location;
227         }
228 
229         /++
230             Set vector to uniform.
231             example:
232             ----
233             auto v = ar.Vector!(float, 3)(1.0, 2.0, 3.0);
234         shader.setUniform("v", v);
235         ----
236         +/
237         Shader uniform(V)(in string name, V v)
238         if(isVector!(V) && V.dimention <= 4){
239             if(_isLoaded){
240                 begin;
241                 int location = uniformLocation(name);
242                 if(location != -1){
243                     mixin(glFunctionString!(typeof(v[0]), V.dimention).normal("glUniform"));
244                 }
245                 end;
246             }
247             return this;
248         }
249 
250         /++
251             Set matrix to uniform.
252             example:
253             ----
254             auto m = ar.Matrix!(float, 3, 3)(
255                     [0, 0, 0], 
256                     [0, 0, 0], 
257                     [0, 0, 0], 
258                     );
259         shader.setUniform("m", m);
260         ----
261         +/
262         Shader uniform(M)(in string name, M m)
263         if(isMatrix!(M) && M.rowSize<=4 && M.colSize<=4){
264             if(_isLoaded){
265                 begin;
266                 int location = uniformLocation(name);
267                 if(location != -1){
268                     mixin( glFunctionString!(typeof(m[0][0])[], m.rowSize, m.colSize).name("glUniform") ~ "(location, 1, GL_FALSE, m.array.ptr);" );
269                 }
270                 end;
271             }
272             return this;
273         }
274 
275         /++
276             Set bool as int to uniform.
277             example:
278             ----
279             // Set variables to glsl uniform named "v".
280             bool a = 1.0;
281             bool b = 2.0;
282             bool c = 3.0;
283             shader.setUniform("v", a, b, c);
284             ----
285         +/
286         Shader uniform(Args...)(in string name, Args v)if(0 < Args.length && Args.length <= 4 && is(Args[0]==bool)){
287             if(_isLoaded){
288                 begin;
289                 int location = uniformLocation(name);
290                 if(location != -1){
291                     mixin(glFunctionString!(int, v.length).normal("glUniform"));
292                 }
293                 end;
294             }
295             return this;
296         }
297 
298         /++
299             Set as an uniform.
300             example:
301             ----
302             // Set variables to glsl uniform named "v".
303             float a = 1.0;
304             float b = 2.0;
305             float c = 3.0;
306             shader.setUniform("v", a, b, c);
307             ----
308         +/
309         Shader uniform(Args...)(in string name, Args v)if(0 < Args.length && Args.length <= 4 && __traits(isArithmetic, Args[0]) && !is(Args[0]==bool)){
310             if(_isLoaded){
311                 begin;
312                 int location = uniformLocation(name);
313                 if(location != -1){
314                     mixin(glFunctionString!(typeof(v[0]), v.length).normal("glUniform"));
315                 }
316                 end;
317             }
318             return this;
319         }
320 
321         ///
322         Shader uniformArray(T)(in string name, T[] v)if(is(T==bool)){
323             if(_isLoaded){
324                 begin;
325                 int location = uniformLocation(name);
326                 if(location != -1){
327                     import std.conv:to;
328                     mixin(glFunctionString!(int[]).array("glUniform"));
329                 }
330                 end;
331             }
332             return this;
333         }
334 
335         ///
336         Shader uniformArray(T)(in string name, T[] v)if(__traits(isArithmetic, T) && !is(T==bool)){
337             if(_isLoaded){
338                 begin;
339                 int location = uniformLocation(name);
340                 if(location != -1){
341                     import std.conv:to;
342                     mixin(glFunctionString!(T[]).array("glUniform"));
343                 }
344                 end;
345             }
346             return this;
347         }
348 
349         /++
350         +/
351         Shader uniformTexture(in string name, Texture texture, int textureLocation){
352             import std..string;
353             if(_isLoaded){
354                 begin;scope(exit)end;
355                 glActiveTexture(GL_TEXTURE0 + textureLocation);
356                 texture.begin;
357                 uniform(name, textureLocation);
358             }
359             return this;
360         }
361 
362         /++
363         +/
364         int attrLocation(in string name)const{
365             import std..string;
366             immutable location = glGetAttribLocation(_programID, name.toStringz);
367             return location;
368         }
369 
370         /++
371             Set as an attribute.
372             example:
373             ----
374             // Set variables to glsl attribute named "v".
375             float a = 1.0;
376         float b = 2.0;
377         float c = 3.0;
378         shader.setAttrib("v", a, b, c);
379         ----
380         +/
381         Shader attr(Args...)(in string name, Args v)if(Args.length > 0 && __traits(isArithmetic, Args[0])){
382             if(_isLoaded){
383                 begin;{
384                     int location = attribLocation(name);
385                     if(location != -1){
386                         _attribNames[name] = true;
387                         mixin(glFunctionString!(typeof(v[0]), v.length)("glVertexAttrib"));
388                     }else{
389                         addLog("Could not find attribute \"" ~ name ~ "\"");
390                     }
391                 }end;
392             }
393             return this;
394         }
395 
396         /++
397             Set as an attribute.
398             example:
399             ----
400             // Set a array to glsl vec2 attribute named "coord2d".
401             float[] vertices = [
402             0.0,  0.8,
403             -0.8, -0.8,
404             0.8, -0.8,
405             ];
406         shader.setAttrib("coord2d", vertices);
407         ----
408         +/
409         Shader attr(Args...)(in string name, Args v)if(Args.length > 0 && !__traits(isArithmetic, Args[0])){
410             if(_isLoaded){
411                 begin;{
412                     int location = attribLocation(name);
413                     if(location != -1){
414                         int dim = attribDim(name);
415                         _attribNames[name] = true;
416                         glVertexAttribPointer(location, dim, GL_FLOAT, GL_FALSE, 0, v[0].ptr);
417                     }else{
418                         addLog("Could not find attribute \"" ~ name ~ "\"");
419                     }
420                 }end;
421             }
422             return this;
423         }
424 
425         /++
426             Set current selected buffer as an attribute.
427         +/
428         Shader attr(in string name){
429             if(_isLoaded){
430                 begin;{
431                     int location = attrLocation(name);
432                     if(location != -1){
433                         int dim = attribDim(name);
434                         _attribNames[name] = true;
435                         glVertexAttribPointer(location, dim, GL_FLOAT, GL_FALSE, 0, null);
436                     }else{
437                         addLog("Could not find attribute \"" ~ name ~ "\"");
438                     }
439                 }end;
440             }
441             return this;
442         }
443 
444         /++
445             Set vector as an attribute.
446             example:
447             ----
448             auto v = ar.Vector!(float, 3)(1.0, 2.0, 3.0);
449         shader.setAttrib("v", v);
450         ----
451         +/
452         Shader attr(V)(in string name, V v)if(isVector!(V) && V.dimention <= 4){
453             if(_isLoaded){
454                 begin;{
455                     int location = attribLocation(name);
456                     if(location != -1){
457                         _attribNames[name] = true;
458                         mixin(glFunctionString!(typeof(v[0]), v.elements.length)("glVertexAttrib"));
459                     }else{
460                         addLog("Could not find attribute \"" ~ name ~ "\"");
461                     }
462                 }end;
463             }
464             return this;
465         }
466 
467         /++
468         +/
469         Shader enableAttrib(in string name){
470             glEnableVertexAttribArray(attrLocation(name));
471             return this;
472         }
473 
474         /++
475         +/
476         Shader disableAttrib(in string name){
477             glDisableVertexAttribArray(attrLocation(name));
478             return this;
479         }
480         
481         ///
482         Shader enableAttribs(){
483             import std.algorithm;
484             _attribNames.keys.each!(attribName => enableAttrib(attribName));
485             return this;
486         }
487 
488         ///
489         Shader disableAttribs(){
490             import std.algorithm;
491             _attribNames.keys.each!(attribName => disableAttrib(attribName));
492             return this;
493         }
494         
495         string[] attribNames()const{
496             return _attribNames.keys;
497         }
498         
499         ///
500         size_t maxGeometryOutputVertices()const{return _maxGeometryOutputVertices;}
501         
502         ///
503         Shader maxGeometryOutputVertices(in size_t vertices){
504             _maxGeometryOutputVertices = vertices;
505             return this;
506         }
507         
508         ///
509         Shader maximizeMaxGeometryOutputVertices(){
510             import std.conv;
511             int maxGeometryOutputVerticesTemp;
512             glGetIntegerv(GL_MAX_GEOMETRY_OUTPUT_VERTICES, &maxGeometryOutputVerticesTemp);
513             _maxGeometryOutputVertices = maxGeometryOutputVerticesTemp;
514             return this;
515         }
516         
517         ///
518         PrimitiveMode geometryInput()const{
519             assert(_geometryInput.hasDefined, "Set geometryInput before loading shader");
520             return _geometryInput.primitiveMode;
521         }
522         
523         ///
524         Shader geometryInput(in PrimitiveMode p){_geometryInput.primitiveMode = p;return this;}
525         
526         ///
527         PrimitiveMode geometryOutput()const{
528             assert(_geometryInput.hasDefined, "Set geometryOutput before loading shader");
529             return _geometryOutput.primitiveMode;
530         }
531         
532         ///
533         Shader geometryOutput(in PrimitiveMode p){_geometryOutput.primitiveMode = p;return this;}
534     }//public
535 
536     private{
537         int _programID;
538         bool[string] _attribNames;
539         bool _isLoaded = false;
540         string _log;
541         int[] _savedIDs;
542 
543         //geometry shader parameters
544         size_t _maxGeometryOutputVertices;
545         MustDefinedPrimitiveMode _geometryInput;
546         MustDefinedPrimitiveMode _geometryOutput;
547 
548         void addLog(in string str){
549             this._log ~= (str ~ "\n");
550         }
551 
552         bool loadShaderFile(in string shaderPath, GLuint shaderType){
553             auto shaderSource = loadedSource(shaderPath);
554             return loadShaderSource(shaderSource, shaderType);
555         }
556         
557         bool loadShaderSource(in string shaderSource, GLuint shaderType){
558             int shaderID = glCreateShader(shaderType);
559             scope(exit) glDeleteShader(shaderID);
560 
561             bool isCompleted = compile(shaderID, shaderSource);
562             if(!isCompleted)return false;
563             glAttachShader(_programID, shaderID);
564             return true;
565         }
566 
567         bool compile(in int id, in string source){
568             const char* sourcePtr = source.ptr;
569             const int sourceLength = cast(int)source.length;
570 
571             glShaderSource(id, 1, &sourcePtr, &sourceLength);
572             glCompileShader(id);
573 
574             int isCompiled;
575             glGetShaderiv(id, GL_COMPILE_STATUS, &isCompiled);
576 
577             import colorize;
578             if (isCompiled == GL_FALSE) {
579                 addLog("compile error".color(fg.red));
580                 addLog(logShader(id));
581                 return false;
582             }else{
583                 addLog("compile success".color(fg.green));
584                 return true;
585             }
586         }
587 
588         string logShader(in int id)const{
589             int strLength;
590             glGetShaderiv(id, GL_INFO_LOG_LENGTH, &strLength);
591             char[] log = new char[strLength];
592             glGetShaderInfoLog(id, strLength, null, log.ptr);
593             import std..string; import std.conv;
594             return log.ptr.fromStringz.to!string.chomp;
595         }
596 
597         string logProgram(in int id)const{
598             int strLength;
599             glGetProgramiv(id, GL_INFO_LOG_LENGTH, &strLength);
600             char[] log = new char[strLength];
601             glGetProgramInfoLog(id, strLength, null, log.ptr);
602             import std..string; import std.conv;
603             return log.ptr.fromStringz.to!string.chomp;
604         }
605 
606         int attribDim(in string name)
607         out(dim){assert(dim>0);}
608         body{
609             int dim = 0;
610             if(_isLoaded){
611                 begin;scope(exit)end;
612                 int location = attrLocation(name);
613                 if(location != -1){
614                     int maxLength;
615                     glGetProgramiv(_programID, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &maxLength);
616 
617                     uint type = GL_ZERO;
618                     char[100] nameBuf;
619                     int l;
620                     int s;
621 
622                     glGetActiveAttrib(
623                         _programID, location, maxLength,
624                         &l, &s, &type, nameBuf.ptr 
625                     );
626 
627                     switch (type) {
628                         case GL_FLOAT:
629                             dim = 1;
630                             break;
631                         case GL_FLOAT_VEC2:
632                             dim = 2;
633                             break;
634                         case GL_FLOAT_VEC3:
635                             dim = 3;
636                             break;
637                         case GL_FLOAT_VEC4:
638                             dim = 4;
639                             break;
640                         default:
641                             dim = 0;
642                     }
643                 }
644             }
645             return dim;
646         }
647     }//private
648 }//class Shader
649 
650 private template glFunctionString(T, size_t DimC = 1, size_t DimR = 1){
651     import std.conv;
652 
653     public{
654         static if(DimR == 1){
655             string normal(in string functionString, in string variableName = "v"){
656                 return name(functionString) ~ "(location, " ~ args(variableName) ~ ");";
657             }
658 
659             string array(in string functionString, in string variableName = "v"){
660                 return name(functionString) ~ "(location, " ~ arrayArgs(variableName) ~ ");";
661             }
662         }
663 
664         string name(in string functionString){
665             return functionString ~ suffix;
666         }
667     }//public
668 
669     private{
670         string suffix(){
671             string type;
672             static if(is(T == float)){
673                 type = "f";
674             }else if(is(T == double)){
675                 type = "d";
676             }else if(is(T == int)){
677                 type = "i";
678             }
679 
680             static if(is(T == float[])){
681                 type = "fv";
682             }else if(is(T == double[])){
683                 type = "bv";
684             }else if(is(T == int[])){
685                 type = "iv";
686             }
687 
688             string str = "";
689             if(isMatrix){str ~= "Matrix";}
690             str ~= dim;
691             str ~= type;
692             return str;
693         }
694 
695         string dim(){
696             auto str = DimC.to!string;
697             static if(isMatrix){
698                 str ~= (DimC == DimR)?"":( "x" ~ DimR.to!string );
699             }
700             return str;
701         }
702 
703         string args(in string variableName){
704             string argsStr = variableName~"[0]";
705             for (int i = 1; i < DimC; i++) {
706                 argsStr ~= ", "~variableName~"[" ~ i.to!string~ "]";
707             }
708             return argsStr;
709         }
710 
711         string arrayArgs(in string variableName){
712             return  variableName ~ ".length.to!int, " ~ variableName ~ ".ptr";
713         }
714 
715         bool isMatrix(){
716             return (DimR > 1);
717         }
718 
719         // bool isVector(){
720         // 	return false;
721         // }
722     }//private
723 }
724 
725 private{
726     string loadedSource(in string path){
727         import armos.utils;
728         immutable absolutePath = absolutePath(path);
729         import std.file:exists, readText;
730         if(absolutePath.exists){
731             return readText(absolutePath);
732         }else{
733             return "";
734         }
735     }
736     
737     struct MustDefinedPrimitiveMode{
738         public{
739            PrimitiveMode primitiveMode()const{assert(_hasDefined);return _primitiveMode;}
740            void primitiveMode(in PrimitiveMode p){_primitiveMode = p;_hasDefined = true;}
741            bool hasDefined()const{return _hasDefined;}
742         }//public
743 
744         private{
745             bool _hasDefined = false;
746             PrimitiveMode _primitiveMode;
747         }//private
748     }//struct GeometryIO
749 }
750 
751 static unittest{
752     assert( glFunctionString!(float, 3).normal("glUniform") == "glUniform3f(location, v[0], v[1], v[2]);");
753     assert( glFunctionString!(float[], 3, 3).name("glUniform") == "glUniformMatrix3fv" );
754     assert( glFunctionString!(float[], 2, 3).name("glUniform") == "glUniformMatrix2x3fv" );
755     import std.stdio;
756     assert( glFunctionString!(float[]).array("glUniform") == "glUniform1fv(location, v.length.to!int, v.ptr);");
757 }
758 
759