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