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 Shader clearLog(){ 28 _log = []; 29 return this; 30 } 31 32 /++ 33 Load the shader from shaderName 34 +/ 35 Shader load(in string shaderName, in string[] paths = []){ 36 import armos.utils.file; 37 string absVertPath = (shaderName ~ ".vert").absolutePath; 38 string absGeomPath = (shaderName ~ ".geom").absolutePath; 39 string absFragPath = (shaderName ~ ".frag").absolutePath; 40 41 import std.file; 42 if(!absVertPath.exists) absVertPath = ""; 43 if(!absGeomPath.exists) absGeomPath = ""; 44 if(!absFragPath.exists) absFragPath = ""; 45 loadFiles(absVertPath, 46 absGeomPath, 47 absFragPath, 48 paths); 49 return this; 50 } 51 52 /++ 53 Load the shader from path 54 +/ 55 Shader loadFiles(in string vertexShaderSourcePath, 56 in string geometryShaderSourcePath, 57 in string fragmentShaderSourcePath, 58 in string[] paths = []) 59 { 60 loadSources((vertexShaderSourcePath != "")?(Source.load(vertexShaderSourcePath)): null, 61 (geometryShaderSourcePath != "")?(Source.load(geometryShaderSourcePath)): null, 62 (fragmentShaderSourcePath != "")?(Source.load(fragmentShaderSourcePath)): null, 63 paths); 64 return this; 65 } 66 67 /// 68 Shader loadSources(in string vertexShaderSourceText, 69 in string geometryShaderSourceText, 70 in string fragmentShaderSourceText, 71 in string[] paths = []) 72 { 73 Source vertexShaderSource; 74 Source geometryShaderSource; 75 Source fragmentShaderSource; 76 77 if(vertexShaderSourceText != ""){ 78 vertexShaderSource = new Source(vertexShaderSourceText, "main.vert", ""); 79 } 80 if(geometryShaderSourceText != ""){ 81 geometryShaderSource = new Source(geometryShaderSourceText, "main.geom", ""); 82 } 83 if(fragmentShaderSourceText != ""){ 84 fragmentShaderSource = new Source(fragmentShaderSourceText, "main.vert", ""); 85 } 86 87 loadSources(vertexShaderSource, geometryShaderSource, fragmentShaderSource, paths); 88 89 return this; 90 } 91 92 /++ 93 Load the shader from sources 94 +/ 95 Shader loadSources(Source vertexShaderSource, 96 Source geometryShaderSource, 97 Source fragmentShaderSource, 98 in string[] paths = []) 99 { 100 if(_programID){ 101 glDeleteProgram(_programID); 102 _programID = glCreateProgram(); 103 _isLoaded = false; 104 _attribNames.clear; 105 } 106 107 addLog("#### Vertex Shader Source ####".color(mode.bold)); 108 if(vertexShaderSource){ 109 addLog("Expanding vertex shader..."); 110 vertexShaderSource.expand(paths); 111 addLog(("Expanded vertex shader (%d lines)".format(vertexShaderSource.numLines)).color(fg.green)); 112 addLog("Loading vertex shader..."); 113 bool isCompiled = loadShaderSource(vertexShaderSource.expanded, GL_VERTEX_SHADER); 114 if(isCompiled){ 115 addLog("Finish loading vertex shader successfully.".color(fg.green).color(mode.bold)); 116 }else{ 117 addLog("Failed loading vertex shader".color(fg.red).color(mode.bold)); 118 } 119 }else{ 120 addLog("Skip vertex shader"); 121 } 122 addLog(""); 123 124 addLog("#### Geometry Shader Source ####".color(mode.bold)); 125 if(geometryShaderSource){ 126 addLog("Expanding geometry shader..."); 127 geometryShaderSource.expand(paths); 128 addLog(("Expanded geometry shader (%d lines)".format(geometryShaderSource.numLines)).color(fg.green)); 129 addLog("Loading geometry shader.."); 130 bool isCompiled = loadShaderSource(geometryShaderSource.expanded, GL_GEOMETRY_SHADER); 131 if(isCompiled){ 132 addLog("Finish loading geometry shader successfully.".color(fg.green).color(mode.bold)); 133 }else{ 134 addLog("Failed loading geometry shader".color(fg.red).color(mode.bold)); 135 } 136 glProgramParameteri(_programID, GL_GEOMETRY_INPUT_TYPE, _geometryInput.primitiveMode); 137 glProgramParameteri(_programID, GL_GEOMETRY_OUTPUT_TYPE, _geometryInput.primitiveMode); 138 import std.conv:to; 139 glProgramParameteri(_programID, GL_GEOMETRY_VERTICES_OUT, _maxGeometryOutputVertices.to!int); 140 }else{ 141 addLog("Skip geometry shader"); 142 } 143 144 addLog(""); 145 146 addLog("#### Fragment Shader Source ####".color(mode.bold)); 147 if(fragmentShaderSource){ 148 addLog("Expanding fragment shader..."); 149 fragmentShaderSource.expand(paths); 150 addLog(("Expanded fragment shader (%d lines)".format(fragmentShaderSource.numLines)).color(fg.green)); 151 addLog("Loading fragment shader..."); 152 bool isCompiled = loadShaderSource(fragmentShaderSource.expanded, GL_FRAGMENT_SHADER); 153 if(isCompiled){ 154 addLog("Finish loading fragment shader successfully.".color(fg.green).color(mode.bold)); 155 }else{ 156 addLog("Failed loading fragment shader".color(fg.red).color(mode.bold)); 157 } 158 }else{ 159 addLog("Skip fragment shader"); 160 } 161 162 addLog(""); 163 164 addLog("#### Link Sources ####".color(mode.bold)); 165 glLinkProgram(_programID); 166 167 int isLinked; 168 glGetProgramiv(_programID, GL_LINK_STATUS, &isLinked); 169 if (isLinked == GL_FALSE) { 170 addLog("Link error.".color(fg.red).color(mode.bold)); 171 addLog(logProgram(_programID)); 172 _isLoaded = false; 173 }else{ 174 addLog("Link success.".color(fg.green).color(mode.bold)); 175 _isLoaded = true; 176 } 177 178 addLog(""); 179 180 return this; 181 } 182 183 /++ 184 Return gl program id. 185 +/ 186 int id()const{return _programID;} 187 188 /++ 189 Begin adapted process 190 +/ 191 Shader begin(){ 192 int savedID; 193 glGetIntegerv(GL_CURRENT_PROGRAM, &savedID); 194 _savedIDs ~= savedID; 195 glUseProgram(_programID); 196 return this; 197 } 198 199 /++ 200 End adapted process 201 +/ 202 Shader end(){ 203 // TODO 204 glUseProgram(_savedIDs[$-1]); 205 if (_savedIDs.length == 0) { 206 assert(0, "stack is empty"); 207 }else{ 208 import std.range; 209 _savedIDs.popBack; 210 } 211 return this; 212 } 213 214 /++ 215 +/ 216 bool isLoaded()const{ 217 return _isLoaded; 218 } 219 220 /++ 221 +/ 222 int uniformLocation(in string name){ 223 import std.string; 224 immutable location = glGetUniformLocation(_programID, name.toStringz); 225 // assert(location != -1, "Could not find uniform \"" ~ name ~ "\""); 226 return location; 227 } 228 229 /++ 230 Set vector to uniform. 231 example: 232 ---- 233 auto v = ar.Vector!(float, 3)(1.0, 2.0, 3.0); 234 shader.setUniform("v", v); 235 ---- 236 +/ 237 Shader uniform(V)(in string name, V v) 238 if(isVector!(V) && V.dimention <= 4){ 239 if(_isLoaded){ 240 begin; 241 int location = uniformLocation(name); 242 if(location != -1){ 243 mixin(glFunctionString!(typeof(v[0]), V.dimention).normal("glUniform")); 244 } 245 end; 246 } 247 return this; 248 } 249 250 /++ 251 Set matrix to uniform. 252 example: 253 ---- 254 auto m = ar.Matrix!(float, 3, 3)( 255 [0, 0, 0], 256 [0, 0, 0], 257 [0, 0, 0], 258 ); 259 shader.setUniform("m", m); 260 ---- 261 +/ 262 Shader uniform(M)(in string name, M m) 263 if(isMatrix!(M) && M.rowSize<=4 && M.colSize<=4){ 264 if(_isLoaded){ 265 begin; 266 int location = uniformLocation(name); 267 if(location != -1){ 268 mixin( glFunctionString!(typeof(m[0][0])[], m.rowSize, m.colSize).name("glUniform") ~ "(location, 1, GL_FALSE, m.array.ptr);" ); 269 } 270 end; 271 } 272 return this; 273 } 274 275 /++ 276 Set bool as int to uniform. 277 example: 278 ---- 279 // Set variables to glsl uniform named "v". 280 bool a = 1.0; 281 bool b = 2.0; 282 bool c = 3.0; 283 shader.setUniform("v", a, b, c); 284 ---- 285 +/ 286 Shader uniform(Args...)(in string name, Args v)if(0 < Args.length && Args.length <= 4 && is(Args[0]==bool)){ 287 if(_isLoaded){ 288 begin; 289 int location = uniformLocation(name); 290 if(location != -1){ 291 mixin(glFunctionString!(int, v.length).normal("glUniform")); 292 } 293 end; 294 } 295 return this; 296 } 297 298 /++ 299 Set as an uniform. 300 example: 301 ---- 302 // Set variables to glsl uniform named "v". 303 float a = 1.0; 304 float b = 2.0; 305 float c = 3.0; 306 shader.setUniform("v", a, b, c); 307 ---- 308 +/ 309 Shader uniform(Args...)(in string name, Args v)if(0 < Args.length && Args.length <= 4 && __traits(isArithmetic, Args[0]) && !is(Args[0]==bool)){ 310 if(_isLoaded){ 311 begin; 312 int location = uniformLocation(name); 313 if(location != -1){ 314 mixin(glFunctionString!(typeof(v[0]), v.length).normal("glUniform")); 315 } 316 end; 317 } 318 return this; 319 } 320 321 /// 322 Shader uniformArray(T)(in string name, T[] v)if(is(T==bool)){ 323 if(_isLoaded){ 324 begin; 325 int location = uniformLocation(name); 326 if(location != -1){ 327 import std.conv:to; 328 mixin(glFunctionString!(int[]).array("glUniform")); 329 } 330 end; 331 } 332 return this; 333 } 334 335 /// 336 Shader uniformArray(T)(in string name, T[] v)if(__traits(isArithmetic, T) && !is(T==bool)){ 337 if(_isLoaded){ 338 begin; 339 int location = uniformLocation(name); 340 if(location != -1){ 341 import std.conv:to; 342 mixin(glFunctionString!(T[]).array("glUniform")); 343 } 344 end; 345 } 346 return this; 347 } 348 349 /++ 350 +/ 351 Shader uniformTexture(in string name, Texture texture, int textureLocation){ 352 import std.string; 353 if(_isLoaded){ 354 begin;scope(exit)end; 355 glActiveTexture(GL_TEXTURE0 + textureLocation); 356 texture.begin; 357 uniform(name, textureLocation); 358 } 359 return this; 360 } 361 362 /++ 363 +/ 364 int attrLocation(in string name)const{ 365 import std.string; 366 immutable location = glGetAttribLocation(_programID, name.toStringz); 367 return location; 368 } 369 370 /++ 371 Set as an attribute. 372 example: 373 ---- 374 // Set variables to glsl attribute named "v". 375 float a = 1.0; 376 float b = 2.0; 377 float c = 3.0; 378 shader.setAttrib("v", a, b, c); 379 ---- 380 +/ 381 Shader attr(Args...)(in string name, Args v)if(Args.length > 0 && __traits(isArithmetic, Args[0])){ 382 if(_isLoaded){ 383 begin;{ 384 int location = attribLocation(name); 385 if(location != -1){ 386 _attribNames[name] = true; 387 mixin(glFunctionString!(typeof(v[0]), v.length)("glVertexAttrib")); 388 }else{ 389 addLog("Could not find attribute \"" ~ name ~ "\""); 390 } 391 }end; 392 } 393 return this; 394 } 395 396 /++ 397 Set as an attribute. 398 example: 399 ---- 400 // Set a array to glsl vec2 attribute named "coord2d". 401 float[] vertices = [ 402 0.0, 0.8, 403 -0.8, -0.8, 404 0.8, -0.8, 405 ]; 406 shader.setAttrib("coord2d", vertices); 407 ---- 408 +/ 409 Shader attr(Args...)(in string name, Args v)if(Args.length > 0 && !__traits(isArithmetic, Args[0])){ 410 if(_isLoaded){ 411 begin;{ 412 int location = attribLocation(name); 413 if(location != -1){ 414 int dim = attribDim(name); 415 _attribNames[name] = true; 416 glVertexAttribPointer(location, dim, GL_FLOAT, GL_FALSE, 0, v[0].ptr); 417 }else{ 418 addLog("Could not find attribute \"" ~ name ~ "\""); 419 } 420 }end; 421 } 422 return this; 423 } 424 425 /++ 426 Set current selected buffer as an attribute. 427 +/ 428 Shader attr(in string name){ 429 if(_isLoaded){ 430 begin;{ 431 int location = attrLocation(name); 432 if(location != -1){ 433 int dim = attribDim(name); 434 _attribNames[name] = true; 435 glVertexAttribPointer(location, dim, GL_FLOAT, GL_FALSE, 0, null); 436 }else{ 437 addLog("Could not find attribute \"" ~ name ~ "\""); 438 } 439 }end; 440 } 441 return this; 442 } 443 444 /++ 445 Set vector as an attribute. 446 example: 447 ---- 448 auto v = ar.Vector!(float, 3)(1.0, 2.0, 3.0); 449 shader.setAttrib("v", v); 450 ---- 451 +/ 452 Shader attr(V)(in string name, V v)if(isVector!(V) && V.dimention <= 4){ 453 if(_isLoaded){ 454 begin;{ 455 int location = attribLocation(name); 456 if(location != -1){ 457 _attribNames[name] = true; 458 mixin(glFunctionString!(typeof(v[0]), v.elements.length)("glVertexAttrib")); 459 }else{ 460 addLog("Could not find attribute \"" ~ name ~ "\""); 461 } 462 }end; 463 } 464 return this; 465 } 466 467 /++ 468 +/ 469 Shader enableAttrib(in string name){ 470 glEnableVertexAttribArray(attrLocation(name)); 471 return this; 472 } 473 474 /++ 475 +/ 476 Shader disableAttrib(in string name){ 477 glDisableVertexAttribArray(attrLocation(name)); 478 return this; 479 } 480 481 /// 482 Shader enableAttribs(){ 483 import std.algorithm; 484 _attribNames.keys.each!(attribName => enableAttrib(attribName)); 485 return this; 486 } 487 488 /// 489 Shader disableAttribs(){ 490 import std.algorithm; 491 _attribNames.keys.each!(attribName => disableAttrib(attribName)); 492 return this; 493 } 494 495 string[] attribNames()const{ 496 return _attribNames.keys; 497 } 498 499 /// 500 size_t maxGeometryOutputVertices()const{return _maxGeometryOutputVertices;} 501 502 /// 503 Shader maxGeometryOutputVertices(in size_t vertices){ 504 _maxGeometryOutputVertices = vertices; 505 return this; 506 } 507 508 /// 509 Shader maximizeMaxGeometryOutputVertices(){ 510 import std.conv; 511 int maxGeometryOutputVerticesTemp; 512 glGetIntegerv(GL_MAX_GEOMETRY_OUTPUT_VERTICES, &maxGeometryOutputVerticesTemp); 513 _maxGeometryOutputVertices = maxGeometryOutputVerticesTemp; 514 return this; 515 } 516 517 /// 518 PrimitiveMode geometryInput()const{ 519 assert(_geometryInput.hasDefined, "Set geometryInput before loading shader"); 520 return _geometryInput.primitiveMode; 521 } 522 523 /// 524 Shader geometryInput(in PrimitiveMode p){_geometryInput.primitiveMode = p;return this;} 525 526 /// 527 PrimitiveMode geometryOutput()const{ 528 assert(_geometryInput.hasDefined, "Set geometryOutput before loading shader"); 529 return _geometryOutput.primitiveMode; 530 } 531 532 /// 533 Shader geometryOutput(in PrimitiveMode p){_geometryOutput.primitiveMode = p;return this;} 534 }//public 535 536 private{ 537 int _programID; 538 bool[string] _attribNames; 539 bool _isLoaded = false; 540 string _log; 541 int[] _savedIDs; 542 543 //geometry shader parameters 544 size_t _maxGeometryOutputVertices; 545 MustDefinedPrimitiveMode _geometryInput; 546 MustDefinedPrimitiveMode _geometryOutput; 547 548 void addLog(in string str){ 549 this._log ~= (str ~ "\n"); 550 } 551 552 bool loadShaderFile(in string shaderPath, GLuint shaderType){ 553 auto shaderSource = loadedSource(shaderPath); 554 return loadShaderSource(shaderSource, shaderType); 555 } 556 557 bool loadShaderSource(in string shaderSource, GLuint shaderType){ 558 int shaderID = glCreateShader(shaderType); 559 scope(exit) glDeleteShader(shaderID); 560 561 bool isCompleted = compile(shaderID, shaderSource); 562 if(!isCompleted)return false; 563 glAttachShader(_programID, shaderID); 564 return true; 565 } 566 567 bool compile(in int id, in string source){ 568 const char* sourcePtr = source.ptr; 569 const int sourceLength = cast(int)source.length; 570 571 glShaderSource(id, 1, &sourcePtr, &sourceLength); 572 glCompileShader(id); 573 574 int isCompiled; 575 glGetShaderiv(id, GL_COMPILE_STATUS, &isCompiled); 576 577 import colorize; 578 if (isCompiled == GL_FALSE) { 579 addLog("compile error".color(fg.red)); 580 addLog(logShader(id)); 581 return false; 582 }else{ 583 addLog("compile success".color(fg.green)); 584 return true; 585 } 586 } 587 588 string logShader(in int id)const{ 589 int strLength; 590 glGetShaderiv(id, GL_INFO_LOG_LENGTH, &strLength); 591 char[] log = new char[strLength]; 592 glGetShaderInfoLog(id, strLength, null, log.ptr); 593 import std.string; import std.conv; 594 return log.ptr.fromStringz.to!string.chomp; 595 } 596 597 string logProgram(in int id)const{ 598 int strLength; 599 glGetProgramiv(id, GL_INFO_LOG_LENGTH, &strLength); 600 char[] log = new char[strLength]; 601 glGetProgramInfoLog(id, strLength, null, log.ptr); 602 import std.string; import std.conv; 603 return log.ptr.fromStringz.to!string.chomp; 604 } 605 606 int attribDim(in string name) 607 out(dim){assert(dim>0);} 608 body{ 609 int dim = 0; 610 if(_isLoaded){ 611 begin;scope(exit)end; 612 int location = attrLocation(name); 613 if(location != -1){ 614 int maxLength; 615 glGetProgramiv(_programID, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &maxLength); 616 617 uint type = GL_ZERO; 618 char[100] nameBuf; 619 int l; 620 int s; 621 622 glGetActiveAttrib( 623 _programID, location, maxLength, 624 &l, &s, &type, nameBuf.ptr 625 ); 626 627 switch (type) { 628 case GL_FLOAT: 629 dim = 1; 630 break; 631 case GL_FLOAT_VEC2: 632 dim = 2; 633 break; 634 case GL_FLOAT_VEC3: 635 dim = 3; 636 break; 637 case GL_FLOAT_VEC4: 638 dim = 4; 639 break; 640 default: 641 dim = 0; 642 } 643 } 644 } 645 return dim; 646 } 647 }//private 648 }//class Shader 649 650 private template glFunctionString(T, size_t DimC = 1, size_t DimR = 1){ 651 import std.conv; 652 653 public{ 654 static if(DimR == 1){ 655 string normal(in string functionString, in string variableName = "v"){ 656 return name(functionString) ~ "(location, " ~ args(variableName) ~ ");"; 657 } 658 659 string array(in string functionString, in string variableName = "v"){ 660 return name(functionString) ~ "(location, " ~ arrayArgs(variableName) ~ ");"; 661 } 662 } 663 664 string name(in string functionString){ 665 return functionString ~ suffix; 666 } 667 }//public 668 669 private{ 670 string suffix(){ 671 string type; 672 static if(is(T == float)){ 673 type = "f"; 674 }else if(is(T == double)){ 675 type = "d"; 676 }else if(is(T == int)){ 677 type = "i"; 678 } 679 680 static if(is(T == float[])){ 681 type = "fv"; 682 }else if(is(T == double[])){ 683 type = "bv"; 684 }else if(is(T == int[])){ 685 type = "iv"; 686 } 687 688 string str = ""; 689 if(isMatrix){str ~= "Matrix";} 690 str ~= dim; 691 str ~= type; 692 return str; 693 } 694 695 string dim(){ 696 auto str = DimC.to!string; 697 static if(isMatrix){ 698 str ~= (DimC == DimR)?"":( "x" ~ DimR.to!string ); 699 } 700 return str; 701 } 702 703 string args(in string variableName){ 704 string argsStr = variableName~"[0]"; 705 for (int i = 1; i < DimC; i++) { 706 argsStr ~= ", "~variableName~"[" ~ i.to!string~ "]"; 707 } 708 return argsStr; 709 } 710 711 string arrayArgs(in string variableName){ 712 return variableName ~ ".length.to!int, " ~ variableName ~ ".ptr"; 713 } 714 715 bool isMatrix(){ 716 return (DimR > 1); 717 } 718 719 // bool isVector(){ 720 // return false; 721 // } 722 }//private 723 } 724 725 private{ 726 string loadedSource(in string path){ 727 import armos.utils; 728 immutable absolutePath = absolutePath(path); 729 import std.file:exists, readText; 730 if(absolutePath.exists){ 731 return readText(absolutePath); 732 }else{ 733 return ""; 734 } 735 } 736 737 struct MustDefinedPrimitiveMode{ 738 public{ 739 PrimitiveMode primitiveMode()const{assert(_hasDefined);return _primitiveMode;} 740 void primitiveMode(in PrimitiveMode p){_primitiveMode = p;_hasDefined = true;} 741 bool hasDefined()const{return _hasDefined;} 742 }//public 743 744 private{ 745 bool _hasDefined = false; 746 PrimitiveMode _primitiveMode; 747 }//private 748 }//struct GeometryIO 749 } 750 751 static unittest{ 752 assert( glFunctionString!(float, 3).normal("glUniform") == "glUniform3f(location, v[0], v[1], v[2]);"); 753 assert( glFunctionString!(float[], 3, 3).name("glUniform") == "glUniformMatrix3fv" ); 754 assert( glFunctionString!(float[], 2, 3).name("glUniform") == "glUniformMatrix2x3fv" ); 755 import std.stdio; 756 assert( glFunctionString!(float[]).array("glUniform") == "glUniform1fv(location, v.length.to!int, v.ptr);"); 757 } 758 759