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