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