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