1 module gfmod.opengl.program; 2 3 import core.stdc..string; 4 5 import std.conv, 6 std.exception, 7 std..string, 8 std.regex, 9 std.typecons, 10 std.array, 11 std.algorithm; 12 13 import derelict.opengl3.gl3; 14 15 import gfmod.core.text, 16 // gfm.math.vector, 17 // gfm.math.matrix, 18 gfmod.opengl.opengl, 19 gfmod.opengl.shader, 20 gfmod.opengl.uniform, 21 gfmod.opengl.uniformblock; 22 23 /// OpenGL Program wrapper. 24 final class GLProgram 25 { 26 public 27 { 28 /// Creates an empty program. 29 /// Throws: $(D OpenGLException) on error. 30 this(OpenGL gl) @trusted 31 { 32 _gl = gl; 33 _program = glCreateProgram(); 34 if (_program == 0) 35 { 36 throw new OpenGLException("Failed to create a GL program failed"); 37 } 38 } 39 40 /// Creates a program from a set of compiled shaders. 41 /// Throws: $(D OpenGLException) on error. 42 this(OpenGL gl, GLShader[] shaders...) @safe 43 { 44 this(gl); 45 attach(shaders); 46 link(); 47 } 48 49 /** 50 * Compiles N times the same GLSL source and link to a program. 51 * 52 * <p> 53 * The same input is compiled 1 to 5 times, each time prepended 54 * with a $(D #define) specific to a shader type. 55 * </p> 56 * $(UL 57 * $(LI $(D VERTEX_SHADER)) 58 * $(LI $(D FRAGMENT_SHADER)) 59 * $(LI $(D GEOMETRY_SHADER)) 60 * $(LI $(D TESS_CONTROL_SHADER)) 61 * $(LI $(D TESS_EVALUATION_SHADER)) 62 * ) 63 * <p> 64 * Each of these macros are alternatively set to 1 while the others are 65 * set to 0. If such a macro isn't used in any preprocessor directive 66 * of your source, this shader stage is considered unused.</p> 67 * 68 * <p>For conformance reasons, any #version directive on the first line will stay at the top.</p> 69 * 70 * Warning: <b>THIS FUNCTION REWRITES YOUR SHADER A BIT.</b> 71 * Expect slightly wrong lines in GLSL compiler's error messages. 72 * 73 * Example of a combined shader source: 74 * --- 75 * #version 110 76 * uniform vec4 color; 77 * 78 * #if VERTEX_SHADER 79 * 80 * void main() 81 * { 82 * gl_Vertex = ftransform(); 83 * } 84 * 85 * #elif FRAGMENT_SHADER 86 * 87 * void main() 88 * { 89 * gl_FragColor = color; 90 * } 91 * 92 * #endif 93 * --- 94 * 95 * Limitations: 96 * $(UL 97 * $(LI All of #preprocessor directives should not have whitespaces before the #.) 98 * $(LI sourceLines elements should be individual lines!) 99 * ) 100 * 101 * Throws: $(D OpenGLException) on error. 102 */ 103 this(OpenGL gl, string source) @trusted 104 { 105 enum string[5] defines = 106 [ 107 "VERTEX_SHADER", 108 "FRAGMENT_SHADER", 109 "GEOMETRY_SHADER", 110 "TESS_CONTROL_SHADER", 111 "TESS_EVALUATION_SHADER" 112 ]; 113 enum GLenum[5] shaderTypes = 114 [ 115 GL_VERTEX_SHADER, 116 GL_FRAGMENT_SHADER, 117 GL_GEOMETRY_SHADER, 118 GL_TESS_CONTROL_SHADER, 119 GL_TESS_EVALUATION_SHADER 120 ]; 121 122 auto sourceLines = source.splitLines(); 123 _gl = gl; 124 bool[5] present; 125 126 // from GLSL spec: "Each number sign (#) can be preceded in its line only by 127 // spaces or horizontal tabs." 128 enum directiveRegexp = ctRegex!(r"^[ \t]*#"); 129 enum versionRegexp = ctRegex!(r"^[ \t]*#[ \t]*version"); 130 131 present[] = false; 132 int versionLine = -1; 133 134 // Scan source for #version and usage of shader macros in preprocessor lines 135 foreach(int lineIndex, line; sourceLines) if(line.match(directiveRegexp)) 136 { 137 foreach(i, define; defines) if(line.canFind(define)) 138 { 139 present[i] = true; 140 } 141 142 if(!line.match(versionRegexp)) { continue; } 143 144 if(versionLine != -1) 145 { 146 enum message = "Your shader program has several #version " 147 "directives, you are looking for problems."; 148 debug { throw new OpenGLException(message); } 149 else 150 { 151 gl._logger.warning(message); 152 continue; 153 } 154 } 155 156 if(lineIndex != 0) 157 { 158 gl._logger.warning("For maximum compatibility, #version directive " 159 "should be the first line of your shader."); 160 } 161 162 versionLine = lineIndex; 163 } 164 165 GLShader[] shaders; 166 167 foreach(i, define; defines) if (present[i]) 168 { 169 string[] newSource; 170 171 // add #version line 172 if(versionLine != -1) { newSource ~= sourceLines[versionLine]; } 173 174 // add each #define with the right value 175 foreach (j, define2; defines) if (present[j]) 176 { 177 newSource ~= "#define %s %d\n".format(define2, i == j ? 1 : 0); 178 ++_extraLines; 179 } 180 181 // add all lines except the #version one 182 foreach(l, line; sourceLines) if (l != versionLine) 183 { 184 newSource ~= line; 185 } 186 187 shaders ~= GLShader(_gl, shaderTypes[i], newSource); 188 } 189 this(gl, shaders); 190 } 191 192 /// Delete the GL program. Must be destroyed by the user. 193 ~this() @safe nothrow @nogc { close(); } 194 195 /// Releases the OpenGL program resource. 196 void close() @trusted nothrow @nogc 197 { 198 if(_program != 0) { glDeleteProgram(_program); } 199 } 200 201 /// Attaches OpenGL shaders to this program. 202 /// Throws: $(D OpenGLException) on error. 203 void attach(GLShader[] compiledShaders...) @trusted nothrow @nogc 204 { 205 foreach(shader; compiledShaders) 206 { 207 glAttachShader(_program, shader._shader); 208 glDeleteShader(shader._shader); 209 } 210 } 211 212 /// Links this OpenGL program. 213 /// Throws: $(D OpenGLException) on error. 214 void link() @trusted 215 { 216 glLinkProgram(_program); 217 _gl.runtimeCheck(); 218 GLint linkSuccess; 219 glGetProgramiv(_program, GL_LINK_STATUS, &linkSuccess); 220 if (GL_TRUE != linkSuccess) 221 { 222 string linkLog = getLinkLog(); 223 if(linkLog != null) { _gl._logger.errorf("%s", linkLog); } 224 throw new OpenGLException("Cannot link program"); 225 } 226 227 initUniforms(); 228 initAttribs(); 229 } 230 231 /// Uses this program for following draw calls. 232 /// Throws: $(D OpenGLException) on error. 233 void use() @trusted nothrow 234 { 235 assert(!_inUse, "Calling use() on a GLProgram that's already being used"); 236 glUseProgram(_program); 237 _gl.runtimeCheck(); 238 239 // upload uniform values then 240 // this allow setting uniform at anytime without binding the program 241 foreach(pair; _activeUniforms) { pair[1].use(); } 242 _inUse = true; 243 } 244 245 /// Unuses this program. 246 /// Throws: $(D OpenGLException) on error. 247 void unuse() @trusted nothrow @nogc 248 { 249 assert(_inUse, "Calling unuse() on a GLProgram that's not being used"); 250 _inUse = false; 251 foreach(pair; _activeUniforms) { pair[1].unuse(); } 252 glUseProgram(0); 253 } 254 255 /// Is the program currently being used for drawing? 256 bool inUse() @safe pure nothrow const @nogc 257 { 258 return _inUse; 259 } 260 261 /// Gets the linking report. 262 /// Returns: Log output of the GLSL linker. Can return null! 263 /// Throws: $(D OpenGLException) on error. 264 string getLinkLog() @trusted nothrow 265 { 266 GLint logLength; 267 glGetProgramiv(_program, GL_INFO_LOG_LENGTH, &logLength); 268 if (logLength <= 0) { return null; } 269 270 271 char[] log = new char[logLength]; 272 GLint dummy; 273 glGetProgramInfoLog(_program, logLength, &dummy, log.ptr); 274 if(_extraLines > 0) 275 { 276 log.assumeSafeAppend(); 277 log ~= "Extra lines added to the source code: %s " 278 .format(_extraLines).assumeWontThrow; 279 } 280 281 if(!log.sanitizeASCIIInPlace()) 282 { 283 _gl._logger.warning("Invalid (non-ASCII) character in GL shader link log") 284 .assumeWontThrow; 285 } 286 return log.assumeUnique; 287 } 288 289 /** Gets an attribute by name. 290 * 291 * Params: 292 * 293 * name = Name of the attribute to get. The program must have this attribute 294 * (check with hasAttrib()) 295 * 296 * Returns: A $(D GLAttribute) with specified name. 297 */ 298 GLAttribute attrib(string name) @safe pure nothrow const @nogc 299 { 300 auto found = _activeAttributes.find!(a => a[0] == name)(); 301 assert(!found.empty, 302 "Can't get an attrib that is not in the program. See hasAttrib()."); 303 return found.front[1]; 304 } 305 306 /// Determine if the program has an attribute with specified name 307 bool hasAttrib(string name) @safe pure nothrow const @nogc 308 { 309 return !_activeAttributes.find!(a => a[0] == name)().empty; 310 } 311 312 /// Returns: Wrapped OpenGL resource handle. 313 GLuint handle() @safe pure const nothrow @nogc 314 { 315 return _program; 316 } 317 } 318 319 package 320 { 321 /** Gets a uniform by name. 322 * 323 * Returns: A GLUniform with this name. This GLUniform might be created on demand if 324 * the name hasn't been found. So it might be a "fake" uniform. This 325 * feature has been added to avoid errors when the driver decides that 326 * a uniform is not used and removes it. 327 * See_also: GLUniform. 328 */ 329 GLUniform uniform(string name) @safe nothrow 330 { 331 auto found = _activeUniforms.find!(a => a[0] == name)(); 332 333 if(found.empty) 334 { 335 // no such variable found, either it's really missing or the OpenGL driver discarded an unused uniform 336 // create a fake disabled GLUniform to allow the show to proceed 337 _gl._logger.warningf("Faking uniform variable '%s'", name).assumeWontThrow; 338 _activeUniforms ~= tuple(name, new GLUniform(_gl, name)); 339 return _activeUniforms.back[1]; 340 } 341 return found.front[1]; 342 } 343 } 344 345 private 346 { 347 // Initialize _activeUniforms. Should only be called by link(). 348 void initUniforms() 349 { 350 // When getting uniform and attribute names, add some length because of stories like this: 351 // http://stackoverflow.com/questions/12555165/incorrect-value-from-glgetprogramivprogram-gl-active-uniform-max-length-outpa 352 enum SAFETY_SPACE = 128; 353 354 GLint uniformNameMaxLength; 355 glGetProgramiv(_program, GL_ACTIVE_UNIFORM_MAX_LENGTH, &uniformNameMaxLength); 356 357 GLchar[] buffer = new GLchar[uniformNameMaxLength + SAFETY_SPACE]; 358 359 GLint numActiveUniforms; 360 glGetProgramiv(_program, GL_ACTIVE_UNIFORMS, &numActiveUniforms); 361 362 // Get uniform block indices (if > 0, it's a block uniform) 363 GLuint[] uniformIndex; 364 GLint[] blockIndex; 365 uniformIndex.length = numActiveUniforms; 366 blockIndex.length = numActiveUniforms; 367 368 foreach(GLuint i; 0.. numActiveUniforms) { uniformIndex[i] = i; } 369 370 glGetActiveUniformsiv(_program, 371 cast(GLint)uniformIndex.length, 372 uniformIndex.ptr, 373 GL_UNIFORM_BLOCK_INDEX, 374 blockIndex.ptr); 375 _gl.runtimeCheck(); 376 377 // Get active uniform blocks 378 getUniformBlocks(_gl, this); 379 380 foreach(GLuint i; 0 .. numActiveUniforms) 381 { 382 if(blockIndex[i] >= 0) { continue; } 383 384 GLint size; 385 GLenum type; 386 GLsizei length; 387 glGetActiveUniform(_program, 388 i, 389 cast(GLint)(buffer.length), 390 &length, 391 &size, 392 &type, 393 buffer.ptr); 394 _gl.runtimeCheck(); 395 396 auto name = buffer[0 .. strlen(buffer.ptr)].dup; 397 if(!name.sanitizeASCIIInPlace()) 398 { 399 _gl._logger.warning("Invalid (non-ASCII) character in GL uniform name"); 400 } 401 402 auto nameStr = name.assumeUnique; 403 _activeUniforms ~= 404 tuple(nameStr, new GLUniform(_gl, _program, type, nameStr, size)); 405 } 406 } 407 408 // Initialize _activeAttributes. Should only be called by link(). 409 void initAttribs() 410 { 411 // When getting uniform and attribute names, add some length because of stories like this: 412 // http://stackoverflow.com/questions/12555165/incorrect-value-from-glgetprogramivprogram-gl-active-uniform-max-length-outpa 413 enum SAFETY_SPACE = 128; 414 415 GLint attribNameMaxLength; 416 glGetProgramiv(_program, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &attribNameMaxLength); 417 418 GLchar[] buffer = new GLchar[attribNameMaxLength + SAFETY_SPACE]; 419 420 GLint numActiveAttribs; 421 glGetProgramiv(_program, GL_ACTIVE_ATTRIBUTES, &numActiveAttribs); 422 423 foreach(GLuint i; 0 .. numActiveAttribs) 424 { 425 GLint size; 426 GLenum type; 427 GLsizei length; 428 glGetActiveAttrib(_program, i, cast(GLint)(buffer.length), 429 &length, &size, &type, buffer.ptr); 430 _gl.runtimeCheck(); 431 432 auto name = buffer[0 .. strlen(buffer.ptr)].dup; 433 if(!name.sanitizeASCIIInPlace()) 434 { 435 _gl._logger.warning("Invalid (non-ASCII) character in GL attribute name"); 436 } 437 438 GLint location = glGetAttribLocation(_program, buffer.ptr); 439 _gl.runtimeCheck(); 440 441 _activeAttributes ~= 442 tuple(name.assumeUnique, GLAttribute(name, location, type, size)); 443 } 444 } 445 446 // Is this program currently in use? 447 bool _inUse; 448 OpenGL _gl; 449 GLuint _program; // OpenGL handle 450 // The number of lines added to the source code when loading a program from 451 // a single source with both vertex and fragment shader. 452 size_t _extraLines; 453 Tuple!(string, GLUniform)[] _activeUniforms; 454 Tuple!(string, GLAttribute)[] _activeAttributes; 455 } 456 } 457 458 459 /// Represent an OpenGL program attribute. Owned by a GLProgram. 460 /// See_also: GLProgram. 461 struct GLAttribute 462 { 463 @safe pure nothrow: 464 public: 465 this(char[] name, GLint location, GLenum type, GLsizei size) 466 @safe pure nothrow 467 { 468 _name = name.dup; 469 _location = location; 470 _type = type; 471 _size = size; 472 } 473 474 GLint location() @safe pure nothrow const @nogc { return _location; } 475 476 477 private: 478 GLint _location; 479 GLenum _type; 480 GLsizei _size; 481 string _name; 482 }