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 }