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.core.log, 11 gfm.core.text, 12 gfm.math.vector, 13 gfm.math.matrix, 14 gfm.opengl.opengl, 15 gfm.opengl.shader, 16 gfm.opengl.uniform, 17 gfm.opengl.uniformblock; 18 19 /// OpenGL Program wrapper. 20 final class GLProgram 21 { 22 public 23 { 24 /// Creates an empty program. 25 /// Throws: $(D OpenGLException) on error. 26 this(OpenGL gl) 27 { 28 _gl = gl; 29 _program = glCreateProgram(); 30 if (_program == 0) 31 throw new OpenGLException("glCreateProgram failed"); 32 _initialized = true; 33 } 34 35 /// Creates a program from a set of compiled shaders. 36 /// Throws: $(D OpenGLException) on error. 37 this(OpenGL gl, GLShader[] shaders...) 38 { 39 this(gl); 40 attach(shaders); 41 link(); 42 } 43 44 /** 45 * Compiles N times the same GLSL source and link to a program. 46 * 47 * <p> 48 * The same input is compiled 1 to 5 times, each time prepended 49 * with a $(D #define) specific to a shader type. 50 * </p> 51 * $(UL 52 * $(LI $(D VERTEX_SHADER)) 53 * $(LI $(D FRAGMENT_SHADER)) 54 * $(LI $(D GEOMETRY_SHADER)) 55 * $(LI $(D TESS_CONTROL_SHADER)) 56 * $(LI $(D TESS_EVALUATION_SHADER)) 57 * ) 58 * <p> 59 * Each of these macros are alternatively set to 1 while the others are 60 * set to 0. If such a macro isn't used in any preprocessor directive 61 * of your source, this shader stage is considered unused.</p> 62 * 63 * <p>For conformance reasons, any #version directive on the first line will stay at the top.</p> 64 * 65 * Warning: <b>THIS FUNCTION REWRITES YOUR SHADER A BIT.</b> 66 * Expect slightly wrong lines in GLSL compiler's error messages. 67 * 68 * Example of a combined shader source: 69 * --- 70 * #version 110 71 * uniform vec4 color; 72 * 73 * #if VERTEX_SHADER 74 * 75 * void main() 76 * { 77 * gl_Vertex = ftransform(); 78 * } 79 * 80 * #elif FRAGMENT_SHADER 81 * 82 * void main() 83 * { 84 * gl_FragColor = color; 85 * } 86 * 87 * #endif 88 * --- 89 * 90 * Limitations: 91 * $(UL 92 * $(LI All of #preprocessor directives should not have whitespaces before the #.) 93 * $(LI sourceLines elements should be individual lines!) 94 * ) 95 * 96 * Throws: $(D OpenGLException) on error. 97 */ 98 this(OpenGL gl, string[] sourceLines) 99 { 100 _gl = gl; 101 bool present[5]; 102 enum string[5] defines = 103 [ 104 "VERTEX_SHADER", 105 "FRAGMENT_SHADER", 106 "GEOMETRY_SHADER", 107 "TESS_CONTROL_SHADER", 108 "TESS_EVALUATION_SHADER" 109 ]; 110 111 enum GLenum[5] 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 ]; 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(int 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 gl._logger.warning(message); 147 } 148 else 149 { 150 if (lineIndex != 0) 151 gl._logger.warning("For maximum compatibility, #version directive should be the first line of your shader."); 152 153 versionLine = 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 (int l, string line; sourceLines) 178 if (l != versionLine) 179 newSource ~= line; 180 181 shaders ~= new GLShader(_gl, shaderTypes[i], newSource); 182 } 183 } 184 this(gl, shaders); 185 } 186 187 /// Ditto, except with lines in a single string. 188 this(OpenGL gl, string wholeSource) 189 { 190 // split on end-of-lines 191 this(gl, splitLines(wholeSource)); 192 } 193 194 ~this() 195 { 196 close(); 197 } 198 199 /// Releases the OpenGL program resource. 200 void close() 201 { 202 if (_initialized) 203 { 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 string 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 = sanitizeUTF8(buffer.ptr, _gl._logger, "OpenGL uniform name"); 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 = sanitizeUTF8(buffer.ptr, _gl._logger, "OpenGL attribute name"); 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 string 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 sanitizeUTF8(log.ptr, _gl._logger, "shader link log"); 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 _gl._logger.warningf("Faking uniform variable '%s'", name); 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 throw new OpenGLException(format("Attribute %s is unknown", name)); 384 return *a; 385 } 386 387 /// Returns: Wrapped OpenGL resource handle. 388 GLuint handle() pure const nothrow 389 { 390 return _program; 391 } 392 } 393 394 private 395 { 396 OpenGL _gl; 397 GLuint _program; // OpenGL handle 398 bool _initialized; 399 GLUniform[string] _activeUniforms; 400 GLAttribute[string] _activeAttributes; 401 } 402 } 403 404 405 /// Represent an OpenGL program attribute. Owned by a GLProgram. 406 /// See_also: GLProgram. 407 final class GLAttribute 408 { 409 public 410 { 411 this(OpenGL gl, string name, GLint location, GLenum type, GLsizei size) 412 { 413 _gl = gl; 414 _name = name; 415 _location = location; 416 _type = type; 417 _size = size; 418 } 419 420 } 421 422 @property GLint location() { return _location; } // property, getter only 423 424 private 425 { 426 OpenGL _gl; 427 GLint _location; 428 GLenum _type; 429 GLsizei _size; 430 string _name; 431 } 432 }