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 }