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 deprecated("Use .destroy instead") void close(){} 209 210 /// Attaches OpenGL shaders to this program. 211 /// Throws: $(D OpenGLException) on error. 212 void attach(GLShader[] compiledShaders...) 213 { 214 foreach(shader; compiledShaders) 215 { 216 glAttachShader(_program, shader._shader); 217 _gl.runtimeCheck(); 218 } 219 } 220 221 /// Links this OpenGL program. 222 /// Throws: $(D OpenGLException) on error. 223 void link() 224 { 225 glLinkProgram(_program); 226 _gl.runtimeCheck(); 227 GLint res; 228 glGetProgramiv(_program, GL_LINK_STATUS, &res); 229 if (GL_TRUE != res) 230 { 231 const(char)[] linkLog = getLinkLog(); 232 if (linkLog != null) 233 _gl._logger.errorf("%s", linkLog); 234 throw new OpenGLException("Cannot link program"); 235 } 236 237 // When getting uniform and attribute names, add some length because of stories like this: 238 // http://stackoverflow.com/questions/12555165/incorrect-value-from-glgetprogramivprogram-gl-active-uniform-max-length-outpa 239 enum SAFETY_SPACE = 128; 240 241 // get active uniforms 242 { 243 GLint uniformNameMaxLength; 244 glGetProgramiv(_program, GL_ACTIVE_UNIFORM_MAX_LENGTH, &uniformNameMaxLength); 245 246 GLchar[] buffer = new GLchar[uniformNameMaxLength + SAFETY_SPACE]; 247 248 GLint numActiveUniforms; 249 glGetProgramiv(_program, GL_ACTIVE_UNIFORMS, &numActiveUniforms); 250 251 // get uniform block indices (if > 0, it's a block uniform) 252 GLuint[] uniformIndex; 253 GLint[] blockIndex; 254 uniformIndex.length = numActiveUniforms; 255 blockIndex.length = numActiveUniforms; 256 257 for (GLint i = 0; i < numActiveUniforms; ++i) 258 uniformIndex[i] = cast(GLuint)i; 259 260 glGetActiveUniformsiv( _program, 261 cast(GLint)uniformIndex.length, 262 uniformIndex.ptr, 263 GL_UNIFORM_BLOCK_INDEX, 264 blockIndex.ptr); 265 _gl.runtimeCheck(); 266 267 // get active uniform blocks 268 getUniformBlocks(_gl, this); 269 270 for (GLint i = 0; i < numActiveUniforms; ++i) 271 { 272 if(blockIndex[i] >= 0) 273 continue; 274 275 GLint size; 276 GLenum type; 277 GLsizei length; 278 glGetActiveUniform(_program, 279 cast(GLuint)i, 280 cast(GLint)(buffer.length), 281 &length, 282 &size, 283 &type, 284 buffer.ptr); 285 _gl.runtimeCheck(); 286 string name = fromStringz(buffer.ptr).idup; 287 _activeUniforms[name] = new GLUniform(_gl, _program, type, name, size); 288 } 289 } 290 291 // get active attributes 292 { 293 GLint attribNameMaxLength; 294 glGetProgramiv(_program, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &attribNameMaxLength); 295 296 GLchar[] buffer = new GLchar[attribNameMaxLength + SAFETY_SPACE]; 297 298 GLint numActiveAttribs; 299 glGetProgramiv(_program, GL_ACTIVE_ATTRIBUTES, &numActiveAttribs); 300 301 for (GLint i = 0; i < numActiveAttribs; ++i) 302 { 303 GLint size; 304 GLenum type; 305 GLsizei length; 306 glGetActiveAttrib(_program, cast(GLuint)i, cast(GLint)(buffer.length), &length, &size, &type, buffer.ptr); 307 _gl.runtimeCheck(); 308 string name = fromStringz(buffer.ptr).idup; 309 GLint location = glGetAttribLocation(_program, buffer.ptr); 310 _gl.runtimeCheck(); 311 312 _activeAttributes[name] = new GLAttribute(_gl, name, location, type, size); 313 } 314 } 315 316 } 317 318 /// Uses this program for following draw calls. 319 /// Throws: $(D OpenGLException) on error. 320 void use() 321 { 322 glUseProgram(_program); 323 _gl.runtimeCheck(); 324 325 // upload uniform values then 326 // this allow setting uniform at anytime without binding the program 327 foreach(uniform; _activeUniforms) 328 uniform.use(); 329 } 330 331 /// Unuses this program. 332 /// Throws: $(D OpenGLException) on error. 333 void unuse() 334 { 335 foreach(uniform; _activeUniforms) 336 uniform.unuse(); 337 glUseProgram(0); 338 _gl.runtimeCheck(); 339 } 340 341 /// Gets the linking report. 342 /// Returns: Log output of the GLSL linker. Can return null! 343 /// Throws: $(D OpenGLException) on error. 344 const(char)[] getLinkLog() 345 { 346 GLint logLength; 347 glGetProgramiv(_program, GL_INFO_LOG_LENGTH, &logLength); 348 if (logLength <= 0) // " If program has no information log, a value of 0 is returned." 349 return null; 350 351 char[] log = new char[logLength + 1]; 352 GLint dummy; 353 glGetProgramInfoLog(_program, logLength, &dummy, log.ptr); 354 _gl.runtimeCheck(); 355 return fromStringz(log.ptr); 356 } 357 358 /// Gets an uniform by name. 359 /// Returns: A GLUniform with this name. This GLUniform might be created on demand if 360 /// the name hasn't been found. So it might be a "fake" uniform. 361 /// See_also: GLUniform. 362 GLUniform uniform(string name) 363 { 364 GLUniform* u = name in _activeUniforms; 365 366 if (u is null) 367 { 368 // no such variable found, either it's really missing or the OpenGL driver discarded an unused uniform 369 // create a fake disabled GLUniform to allow the show to proceed 370 _activeUniforms[name] = new GLUniform(_gl, name); 371 return _activeUniforms[name]; 372 } 373 return *u; 374 } 375 376 /// Gets an attribute by name. 377 /// Returns: A $(D GLAttribute) retrieved by name. 378 /// Throws: $(D OpenGLException) on error. 379 GLAttribute attrib(string name) 380 { 381 GLAttribute* a = name in _activeAttributes; 382 if (a is null) 383 { 384 // no such attribute found, either it's really missing or the OpenGL driver discarded an unused uniform 385 // create a fake disabled GLattribute to allow the show to proceed 386 _activeAttributes[name] = new GLAttribute(_gl,name); 387 return _activeAttributes[name]; 388 } 389 return *a; 390 } 391 392 /// Returns: Wrapped OpenGL resource handle. 393 GLuint handle() pure const nothrow 394 { 395 return _program; 396 } 397 } 398 399 package 400 { 401 OpenGL _gl; 402 } 403 404 private 405 { 406 GLuint _program; // OpenGL handle 407 bool _initialized; 408 GLUniform[string] _activeUniforms; 409 GLAttribute[string] _activeAttributes; 410 } 411 } 412 413 414 /// Represent an OpenGL program attribute. Owned by a GLProgram. 415 /// See_also: GLProgram. 416 final class GLAttribute 417 { 418 public 419 { 420 enum GLint fakeLocation = -1; 421 422 this(OpenGL gl, string name, GLint location, GLenum type, GLsizei size) 423 { 424 _gl = gl; 425 _name = name; 426 _location = location; 427 _type = type; 428 _size = size; 429 _disabled = false; 430 } 431 432 /// Creates a fake disabled attribute, designed to cope with attribute 433 /// that have been optimized out by the OpenGL driver, or those which do not exist. 434 this(OpenGL gl, string name) 435 { 436 _gl = gl; 437 _disabled = true; 438 _location = fakeLocation; 439 _type = GL_FLOAT; // whatever 440 _size = 1; 441 _gl._logger.warningf("Faking attribute '%s' which either does not exist in the shader program, or was discarded by the driver as unused", name); 442 } 443 444 } 445 446 GLint location() 447 { 448 return _location; 449 } 450 451 string name() pure const nothrow 452 { 453 return _name; 454 } 455 456 private 457 { 458 OpenGL _gl; 459 GLint _location; 460 GLenum _type; 461 GLsizei _size; 462 string _name; 463 bool _disabled; 464 } 465 }