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