1 module des.gl.shader;
2 
3 import des.gl.general;
4 import des.gl.texture;
5 
6 ///
7 class GLShaderException : DesGLException
8 {
9     ///
10     this( string msg, string file=__FILE__, size_t line=__LINE__ ) pure nothrow @safe
11     { super( msg, file, line ); }
12 }
13 
14 ///
15 class GLShader : DesObject
16 {
17     mixin DES;
18     mixin ClassLogger;
19 
20 protected:
21 
22     GLenum _type;
23     string _source;
24     uint _id;
25     bool _compiled;
26 
27 public:
28 
29     pure @property
30     {
31         nothrow const
32         {
33             ///
34             uint id() { return _id; }
35             /// get source
36             string source() { return _source; }
37             ///
38             GLenum type() { return _type; }
39             ///
40             bool compiled() { return _compiled; }
41         }
42 
43         /// set source
44         string source( string s ) { _source = s; return _source; }
45     }
46 
47     ///
48     this( GLenum tp, string src )
49     {
50         logger = new InstanceLogger(this);
51         _type = tp;
52         _source = src;
53     }
54 
55     /// glCreateShader, glShaderSource, glCompileShader
56     void make()
57     {
58         _id = checkGLCall!glCreateShader( _type );
59 
60         if( auto il = cast(InstanceLogger)logger )
61             il.instance = format( "%d", _id );
62 
63         logger.Debug( "[%s] with type [%s]", _id, _type ); 
64 
65         auto src = _source.toStringz;
66         checkGLCall!glShaderSource( _id, 1, &src, null );
67         checkGLCall!glCompileShader( _id );
68 
69         int res;
70         checkGLCall!glGetShaderiv( _id, GL_COMPILE_STATUS, &res );
71 
72         string fmtSource()
73         {
74             auto lines = source.splitLines();
75             foreach( i, ref line; lines )
76                 line = format( "% 3d ", i+1 ) ~ line;
77             return lines.join("\n");
78         }
79 
80         if( res == GL_FALSE )
81         {
82             int logLen;
83             checkGLCall!glGetShaderiv( _id, GL_INFO_LOG_LENGTH, &logLen );
84             if( logLen > 0 )
85             {
86                 auto chlog = new char[logLen];
87                 checkGLCall!glGetShaderInfoLog( _id, logLen, &logLen, chlog.ptr );
88                 throw new GLShaderException( "shader compile error: \n" ~ toDString(chlog.ptr) ~ "\n--- shader sources ---\n" ~ fmtSource() );
89             }
90         }
91 
92         _compiled = true;
93         logger.Debug( "pass" );
94     }
95 
96 protected:
97 
98     override void selfConstruct() { make(); }
99 
100     override void selfDestroy()
101     {
102         if( _compiled )
103             checkGLCall!glDeleteShader( _id );
104         logger.Debug( "pass" );
105     }
106 }
107 
108 ///
109 class GLVertShader : GLShader
110 { this( string src="" ) { super( GL_VERTEX_SHADER, src ); } }
111 
112 ///
113 class GLFragShader : GLShader
114 { this( string src="" ) { super( GL_FRAGMENT_SHADER, src ); } }
115 
116 ///
117 class GLGeomShader : GLShader
118 { this( string src="" ) { super( GL_GEOMETRY_SHADER, src ); } }
119 
120 /++ parse solid input string to different shaders
121 
122  Use [vert|vertex] for vertex shader,
123  [geom|geometry] for geometry shader,
124  [frag|fragment] for fragment shader
125 
126  Example:
127     //###vert
128     ... code of vertex shader ...
129     //###frag
130     ... cod of fragment shader ...
131 
132  +/
133 GLShader[] parseGLShaderSource( string src, string separator = "//###" )
134 {
135     GLShader[] ret;
136 
137     foreach( ln; src.splitLines() )
138     {
139         if( ln.startsWith(separator) )
140         {
141             auto str_type = ln.chompPrefix(separator).strip().toLower;
142             switch( str_type )
143             {
144                 case "vert": case "vertex":   ret ~= new GLVertShader; break;
145                 case "geom": case "geometry": ret ~= new GLGeomShader; break;
146                 case "frag": case "fragment": ret ~= new GLFragShader; break;
147                 default: throw new GLShaderException( "parse shader source: unknown section '" ~ str_type ~ "'" );
148             }
149         }
150         else
151         {
152             if( ret.length == 0 )
153             {
154                 if( ln.strip.length == 0 ) continue;
155                 throw new GLShaderException( "parse shader source: no section definition" );
156             }
157             ret[$-1].source = ret[$-1].source ~ ln ~ '\n';
158         }
159     }
160 
161     return ret;
162 }
163 
164 ///
165 class GLShaderProgram : DesObject
166 {
167     mixin DES;
168     mixin ClassLogger;
169 
170 protected:
171 
172     uint _id = 0;
173 
174     private static uint inUse = 0;
175     final @property
176     {
177         /// check this is current shader program
178         bool thisInUse() const { return inUse == _id; }
179 
180         /// glUseProgram, set this is current shader program or set zero (if u==false)
181         void thisInUse( bool u )
182         {
183             if( ( thisInUse && u ) || ( !thisInUse && !u ) ) return;
184             uint np = 0;
185             if( !thisInUse && u ) np = _id;
186             checkGLCall!glUseProgram( np );
187             inUse = np;
188         }
189     }
190 
191     ///
192     GLShader[] shaders;
193 
194 public:
195 
196     /// `create()`
197     this( GLShader[] shs )
198     {
199         logger = new InstanceLogger( this );
200         foreach( sh; shs )
201             enforce( sh !is null, new GLShaderException( "shader is null" ) );
202         shaders = registerChildEMM( shs, true );
203         create();
204     }
205 
206     ///
207     uint id() pure nothrow const @safe @property { return _id; }
208 
209     ///
210     final void use() { thisInUse = true; }
211 
212 protected:
213 
214     /// create program, attach shaders, bind attrib locations, link program
215     void create()
216     {
217         _id = checkGLCall!glCreateProgram();
218 
219         if( auto il = cast(InstanceLogger)logger )
220             il.instance = format( "%d", _id );
221 
222         logger.Debug( "program created" );
223 
224         attachShaders();
225 
226         bindAttribLocations();
227         bindFragDataLocations();
228 
229         link();
230 
231         logger.Debug( "pass" );
232     }
233 
234     /// makes shaders if are not compiled and attach their
235     final void attachShaders()
236     {
237         foreach( sh; shaders )
238         {
239             if( !sh.compiled ) sh.make();
240             checkGLCall!glAttachShader( _id, sh.id );
241         }
242         logger.Debug( "pass" );
243     }
244 
245     ///
246     final void detachShaders()
247     {
248         foreach( sh; shaders )
249             checkGLCall!glDetachShader( _id, sh.id );
250         logger.Debug( "pass" );
251     }
252 
253     /// check link status, throw exception if false
254     void check()
255     {
256         int res;
257         checkGLCall!glGetProgramiv( _id, GL_LINK_STATUS, &res );
258         if( res == GL_FALSE )
259         {
260             int logLen;
261             checkGLCall!glGetProgramiv( _id, GL_INFO_LOG_LENGTH, &logLen );
262             if( logLen > 0 )
263             {
264                 auto chlog = new char[logLen];
265                 checkGLCall!glGetProgramInfoLog( _id, logLen, &logLen, chlog.ptr );
266                 throw new GLShaderException( "program link error: \n" ~ chlog.idup );
267             }
268         }
269     }
270 
271     ///
272     uint[string] attribLocations() @property { return null; }
273 
274     /// uses result of `attribLocations()` call, affect after `link()` call
275     final void bindAttribLocations()
276     {
277         foreach( key, val; attribLocations )
278         {
279             checkGLCall!glBindAttribLocation( _id, val, key.toStringz );
280             logger.Debug( "attrib '%s' bind to location %d", key, val );
281         }
282         logger.Debug( "pass" );
283     }
284 
285     ///
286     uint[string] fragDataLocations() @property { return null; }
287 
288     /// uses result of `fragDataLocations()` call, affect after `link()` call
289     final void bindFragDataLocations()
290     {
291         foreach( key, val; fragDataLocations )
292         {
293             checkGLCall!glBindFragDataLocation( _id, val, key.toStringz );
294             logger.Debug( "frag data '%s' bind to location %d", key, val );
295         }
296         logger.Debug( "pass" );
297     }
298 
299     /// link program and check status
300     final void link()
301     {
302         checkGLCall!glLinkProgram( _id );
303         check();
304         logger.Debug( "pass" );
305     }
306 
307     override void selfConstruct() { create(); }
308 
309     override void preChildsDestroy()
310     {
311         thisInUse = false;
312         detachShaders();
313     }
314 
315     override void selfDestroy()
316     {
317         checkGLCall!glDeleteProgram( _id );
318         debug logger.Debug( "pass" );
319     }
320 }
321 
322 ///
323 class CommonGLShaderProgram : GLShaderProgram
324 {
325 public:
326     ///
327     this( GLShader[] shs ) { super(shs); }
328 
329     ///
330     int getAttribLocation( string name )
331     { 
332         auto ret = checkGLCall!glGetAttribLocation( _id, name.toStringz ); 
333         debug if( ret < 0 ) logger.warn( "bad attribute name: '%s'", name );
334         return ret;
335     }
336 
337     ///
338     int[] getAttribLocations( string[] names... )
339     { 
340         int[] ret;
341         foreach( name; names )
342             ret ~= getAttribLocation( name );
343         return ret;
344     }
345 
346     ///
347     int getUniformLocation( string name )
348     { return checkGLCall!glGetUniformLocation( _id, name.toStringz ); }
349 
350     ///
351     void setUniform(T)( int loc, in T[] vals... ) 
352         if( isAllowType!T || isAllowVector!T || isAllowMatrix!T || is( T == bool ) )
353     {
354         if( loc < 0 )
355         {
356             logger.error( "bad uniform location" );
357             return;
358         }
359 
360         use();
361 
362         enum fnc = "checkGLCall!glUniform";
363 
364         static if( isAllowMatrix!T )
365         {
366             enum args_str = "loc, cast(int)vals.length, GL_TRUE, cast(float*)vals.ptr";
367             static if( T.width == T.height )
368                 mixin( format( "%sMatrix%dfv( %s );", fnc, T.height, args_str ) );
369             else
370                 mixin( format( "%sMatrix%dx%dfv( %s );", fnc, T.height, T.width, args_str ) );
371         }
372         else static if( is( T == bool ) )
373         {
374             auto iv = to!(int[])(vals);
375             mixin( format( "%s1iv( loc, cast(int)vals.length, iv.ptr );", fnc ) );
376         }
377         else
378         {
379             static if( isAllowVector!T )
380             {
381                 alias X = T.datatype;
382                 enum sz = T.length;
383             }
384             else
385             {
386                 alias X = T;
387                 enum sz = 1;
388             }
389             enum pf = glPostfix!X;
390             enum cs = X.stringof;
391 
392             mixin( format( "%s%d%sv( loc, cast(int)vals.length, cast(%s*)vals.ptr );", fnc, sz, pf, cs ) );
393         }
394     }
395 
396     ///
397     void setUniform(T)( string name, in T[] vals... ) 
398         if( is( typeof( setUniform!T( 0, vals ) ) ) )
399     {
400         auto loc = getUniformLocation( name );
401         if( loc < 0 )
402         {
403             logger.error( "bad uniform name: '%s'", name );
404             return;
405         }
406         setUniform!T( loc, vals );
407     }
408 
409     /// set uniform and bind texture
410     void setTexture( int loc, GLTexture tex )
411     in{ assert( tex !is null ); } body
412     {
413         if( loc < 0 )
414         {
415             logger.error( "bad texture location" );
416             return;
417         }
418 
419         tex.bind();
420         setUniform!int( loc, tex.unit );
421     }
422 
423     /// ditto
424     void setTexture( string name, GLTexture tex )
425     in{ assert( tex !is null ); } body
426     {
427         auto loc = getUniformLocation( name );
428         if( loc < 0 )
429         {
430             logger.error( "bad texture name: '%s'", name );
431             return;
432         }
433         setTexture( loc, tex );
434     }
435 }
436 
437 private pure @property
438 {
439     bool isAllowType(T)()
440     {
441         return is( T == int ) ||
442                is( T == uint ) ||
443                is( T == float );
444     }
445 
446     bool isAllowVector(T)()
447     {
448         static if( !isStaticVector!T ) return false;
449         else return isAllowType!(T.datatype) && T.length <= 4;
450     }
451 
452     unittest
453     {
454         static assert( isAllowVector!vec2 );
455         static assert( isAllowVector!ivec4 );
456         static assert( isAllowVector!uivec3 );
457         static assert( !isAllowVector!dvec3 );
458         static assert( !isAllowVector!rvec3 );
459         static assert( !isAllowVector!(Vector!(5,float)) );
460     }
461 
462     bool isAllowMatrix(T)()
463     {
464         static if( !isStaticMatrix!T ) return false;
465         else return is( T.datatype == float ) &&
466             T.width >= 2 && T.width <= 4 &&
467             T.height >= 2 && T.height <= 4;
468     }
469 
470     unittest
471     {
472         static assert( isAllowMatrix!mat4 );
473         static assert( isAllowMatrix!mat2x3 );
474         static assert( !isAllowMatrix!rmat2x3 );
475         static assert( !isAllowMatrix!dmat2x3 );
476         static assert( !isAllowMatrix!(Matrix!(5,2,float)) );
477     }
478 }
479 
480 private string castArgsString(S,string data,T...)() @property
481 {
482     string ret = "";
483     foreach( i, type; T )
484         ret ~= format( "cast(%s)%s[%d],", 
485                 S.stringof, data, i );
486 
487     return ret[0 .. $-1];
488 }
489 
490 unittest
491 {
492     string getFloats(string data,T...)( in T vals )
493     { return castArgsString!(float,data,vals)(); }
494 
495     assert( getFloats!"v"( 1.0f, 2u, -3 ) == "cast(float)v[0],cast(float)v[1],cast(float)v[2]" );
496 }
497 
498 /// allows `float`, `int`, 'uint`
499 string glPostfix(T)() pure @property
500 {
501          static if( is( T == float ) ) return "f";
502     else static if( is( T == int ) )   return "i";
503     else static if( is( T == uint ) )  return "ui";
504     else static if( isStaticVector!T ) return glPostfix!(T.datatype);
505     else static if( isStaticMatrix!T ) return glPostfix!(T.datatype);
506     else return "";
507 }
508 
509 ///
510 unittest
511 {
512     assert( glPostfix!float  == "f" );
513     assert( glPostfix!int    == "i" );
514     assert( glPostfix!uint   == "ui");
515     assert( glPostfix!double == ""  );
516 }
517 
518 bool isAllConv(S,Args...)() pure @property
519 {
520     foreach( t; Args )
521         if( !is( t : S ) )
522             return false;
523     return true;
524 }