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